From f994b4dc2069984cd706e5800d384e94bcd1f666 Mon Sep 17 00:00:00 2001 From: R0ck Date: Sun, 25 Oct 2026 02:01:04 +0000 Subject: [PATCH 1/2] updater and headless: what verifying against a real release found Driven end to end with the published v0.2.0 tarball against the real v0.3.0 release: - Detect looked for radioserver beside the binary, which the tarball no longer ships, so every tarball install read as unpackaged. It looks for VARIANT now. - headless echoed the latest Status every 100 ms, dropping lines said between two looks and the last ones before exit; a failed check left nothing. It prints each new log line, and flushes before exiting. For #447. --- CHANGELOG.md | 9 ++++ cmd/meshbench/cmd_headless.go | 77 +++++++++++++++++++++++----- cmd/meshbench/cmd_headless_test.go | 12 +++++ internal/app/update/artefact.go | 7 ++- internal/app/update/artefact_test.go | 18 +++++++ 5 files changed, 110 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a38e02b4..79505b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,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. - **Adding a scheduled send no longer repeats the ones already said.** Any `schedule.add` made every one-shot send whose moment had passed go out again. 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..c88453ac 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, "VARIANT") || 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 From bded0a8cf360e18d4de4df527662a651d55b01c2 Mon Sep 17 00:00:00 2001 From: R0ck Date: Fri, 30 Oct 2026 16:54:51 +0000 Subject: [PATCH 2/2] update: detect the tarball by the existing variantFile constant rather than a second VARIANT literal --- internal/app/update/artefact.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/update/artefact.go b/internal/app/update/artefact.go index c88453ac..80e1f4c6 100644 --- a/internal/app/update/artefact.go +++ b/internal/app/update/artefact.go @@ -128,7 +128,7 @@ func Detect(goos, exe, appImage string) Artefact { // 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, "VARIANT") || besideBinary(exe, "radioserver")) { + if exe != "" && (besideBinary(exe, variantFile) || besideBinary(exe, "radioserver")) { return Tarball } return Loose