diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0c4f75..8e29171f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/cmd/meshbench/cmd_headless.go b/cmd/meshbench/cmd_headless.go index b272be1a..2bd9db8d 100644 --- a/cmd/meshbench/cmd_headless.go +++ b/cmd/meshbench/cmd_headless.go @@ -5,6 +5,8 @@ import ( "flag" "fmt" "os" + "strconv" + "strings" "time" "github.com/MeshBench/meshbench/internal/app/session" @@ -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") @@ -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:]) +} diff --git a/cmd/meshbench/cmd_headless_test.go b/cmd/meshbench/cmd_headless_test.go index 8dff306f..9b71f98b 100644 --- a/cmd/meshbench/cmd_headless_test.go +++ b/cmd/meshbench/cmd_headless_test.go @@ -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) + } +} diff --git a/internal/app/update/artefact.go b/internal/app/update/artefact.go index cd79da64..80e1f4c6 100644 --- a/internal/app/update/artefact.go +++ b/internal/app/update/artefact.go @@ -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 diff --git a/internal/app/update/artefact_test.go b/internal/app/update/artefact_test.go index 50ef9519..2692dbb5 100644 --- a/internal/app/update/artefact_test.go +++ b/internal/app/update/artefact_test.go @@ -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