Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ had changed in them - which is the gap this file exists to close.

### Fixed

- **A Linux tarball install knows it is one.** The updater recognised the
tarball by a `radioserver` beside the binary, which no tarball has shipped
since the chip became a library, so every tarball install reported itself
as unpackaged and got a generic instruction instead of its own. It now looks
for `VARIANT`.
- **Headless echoes every status line.** It printed the latest status line at
each look, so several lines said close together lost all but the last, and
a line said just before exit was lost. A failed update check left nothing in
the log at all.
- **The compact macOS build carries the chip model and Renode's support
files.** Both were added after the compact image was cut, so a compact Mac
build could start no emulated board at all, and after fetching Renode still
Expand Down
77 changes: 65 additions & 12 deletions cmd/meshbench/cmd_headless.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"time"

"github.com/MeshBench/meshbench/internal/app/session"
Expand Down Expand Up @@ -70,7 +72,17 @@ func runHeadless(ctx context.Context, args []string) error {
defer func() { _ = srv.Close() }()

if !*quiet {
go echoStatus(ctx, st)
echoed := make(chan struct{})
go func() {
echoStatus(ctx, st)
close(echoed)
}()
// The last lines said are the ones most worth reading, so the echo
// is let finish before the process goes.
defer func() {
cancel()
<-echoed
}()
}
fmt.Fprintln(os.Stderr, "meshbench", version.Detail(), "headless")

Expand Down Expand Up @@ -139,25 +151,66 @@ func runHeadless(ctx context.Context, args []string) error {
// because the snapshot never blocks a verb - and a log that could delay the
// thing it is logging is worse than no log.
//
// By the sentence, not by the sequence number. Status is the last line said
// and stays said; the sequence moves on every publish, which for a playing run
// is every tick. Keying on it printed "running to 2.0 s" seventeen times for
// one command.
// By the line, not by the sequence number: the sequence moves on every
// publish, which for a playing run is every tick, and keying on it printed
// "running to 2.0 s" seventeen times. Each line of the log is printed once.
func echoStatus(ctx context.Context, st *state.Store) {
t := time.NewTicker(100 * time.Millisecond)
defer t.Stop()
last := ""
// Every line said, not the latest one at each look. Echoing Status alone
// dropped all but the last of several lines said between two looks, and a
// line said just before the process exited - which is how a failed
// update check left nothing at all in the log (#447).
var last string
echo := func() {
s := st.Snapshot()
if s == nil {
return
}
lines := s.FullLog
from := 0
if last != "" {
from = len(lines)
for i := len(lines) - 1; i >= 0; i-- {
if lines[i] == last {
from = i + 1
break
}
}
}
for _, l := range lines[from:] {
fmt.Fprintln(os.Stderr, echoLine(l))
}
if len(lines) > 0 {
last = lines[len(lines)-1]
}
}
for {
select {
case <-ctx.Done():
echo()
return
case <-t.C:
s := st.Snapshot()
if s == nil || s.Status == "" || s.Status == last {
continue
}
last = s.Status
fmt.Fprintf(os.Stderr, "%8.2fs %s\n", float64(s.NowMs)/1000, s.Status)
echo()
}
}
}

// echoLine turns a log line, "15:04:05.000 t= 1.234s message", into the
// shape headless has always echoed: the simulated time, then the message.
func echoLine(l string) string {
i := strings.Index(l, "t=")
if i < 0 {
return l
}
rest := strings.TrimLeft(l[i+2:], " ")
j := strings.Index(rest, "s ")
if j < 0 {
return l
}
secs, err := strconv.ParseFloat(rest[:j], 64)
if err != nil {
return l
}
return fmt.Sprintf("%8.2fs %s", secs, rest[j+3:])
}
12 changes: 12 additions & 0 deletions cmd/meshbench/cmd_headless_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,15 @@ func TestHeadlessPlayStartsFirmwareBeforeTheClock(t *testing.T) {
t.Fatalf("the clock said \"playing\" before firmware was reported running:\n%s", out)
}
}

// A log line is echoed as headless always has: simulated time, then the
// message (#447).
func TestTheEchoKeepsItsShape(t *testing.T) {
got := echoLine("01:57:49.123 t= 12.345s could not find out whether a newer release exists")
if got != " 12.35s could not find out whether a newer release exists" {
t.Errorf("echoed %q", got)
}
if got := echoLine("not a log line"); got != "not a log line" {
t.Errorf("an unexpected line was changed: %q", got)
}
}
7 changes: 6 additions & 1 deletion internal/app/update/artefact.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ func Detect(goos, exe, appImage string) Artefact {
if strings.HasPrefix(filepath.ToSlash(exe), "/usr/") {
return Deb
}
if exe != "" && besideBinary(exe, "radioserver") {
// The tarball is told by what packaging puts beside the binary: VARIANT,
// which every packaged tree carries and a build from source does not.
// This looked for radioserver, which the tarball stopped shipping when the
// chip became a library, so every tarball install read as unpackaged and
// was handed the generic instruction rather than its own (#447).
if exe != "" && (besideBinary(exe, variantFile) || besideBinary(exe, "radioserver")) {
return Tarball
}
return Loose
Expand Down
18 changes: 18 additions & 0 deletions internal/app/update/artefact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ func TestTheBundleIsDeducedFromWhereTheBinaryIs(t *testing.T) {
}
}

// An unpacked Linux tarball is told from a build from source by the VARIANT
// file packaging puts beside the binary. It used to look for radioserver,
// which the tarball no longer ships, so a real 0.2.0 tarball install reported
// itself as unpackaged and got the generic instruction (#447).
func TestAnUnpackedTarballIsToldFromABuildFromSource(t *testing.T) {
dir := t.TempDir()
exe := filepath.Join(dir, "meshbench")
if got := update.Detect("linux", exe, ""); got != update.Loose {
t.Errorf("with nothing beside it, Detect = %q, want %q", got, update.Loose)
}
if err := os.WriteFile(filepath.Join(dir, "VARIANT"), []byte("compact\n"), 0o600); err != nil {
t.Fatal(err)
}
if got := update.Detect("linux", exe, ""); got != update.Tarball {
t.Errorf("with VARIANT beside it, Detect = %q, want %q", got, update.Tarball)
}
}

// The two Windows bundles are the same files in the same layout once they are
// unpacked, and they are updated differently, so the one thing that tells them
// apart is the note the installer lays down. It has to be looked for on disk
Expand Down