From d852f6e0e8b5d3bae348e5fd77fd0612b40fb57b Mon Sep 17 00:00:00 2001 From: Raj Nakarja Date: Mon, 19 Oct 2026 13:23:02 +0200 Subject: [PATCH] Make dev print only logs that arrive after its first upload dev followed the log through tail, which first prints the newest 100 earlier logs. Those are noise after an upload, and a script that waits for a line could match one left from a previous run. dev now takes the newest log only as the point to follow from. tail is unchanged. --- internal/dev/dev.go | 2 +- internal/dev/dev_test.go | 15 +++---- internal/tail/tail.go | 15 ++++++- internal/tail/tail_test.go | 80 +++++++++++++++++++++++++------------- main.go | 2 +- 5 files changed, 76 insertions(+), 38 deletions(-) diff --git a/internal/dev/dev.go b/internal/dev/dev.go index 8ba515c..48d4b10 100644 --- a/internal/dev/dev.go +++ b/internal/dev/dev.go @@ -68,7 +68,7 @@ func Dev(invocation api.Invocation, arguments []string) error { tailEnded := make(chan error, 1) go func() { - tailEnded <- tail.Tail(invocation, tailArguments) + tailEnded <- tail.Follow(invocation, tailArguments, true) }() // Upload again once the watched paths have changed and then stayed still for one poll diff --git a/internal/dev/dev_test.go b/internal/dev/dev_test.go index bd27608..ae65daf 100644 --- a/internal/dev/dev_test.go +++ b/internal/dev/dev_test.go @@ -6,6 +6,7 @@ import ( "net/http" "os" "path/filepath" + "slices" "strings" "sync" "testing" @@ -147,7 +148,7 @@ func TestDevUploadsOnChangeAndFollowsTheLog(t *testing.T) { name string target string watchFile bool - wantQuery string + wantQueries []string wantUploaded string wantRefusal string }{ @@ -155,14 +156,14 @@ func TestDevUploadsOnChangeAndFollowsTheLog(t *testing.T) { name: "a file to a device", target: "354820091234567", watchFile: true, - wantQuery: "after=2&imei=354820091234567", + wantQueries: []string{"imei=354820091234567&last=1", "after=2&imei=354820091234567"}, wantUploaded: "Uploaded 1 file to device 354820091234567. It arrives at its next check-in.\n", wantRefusal: "does not exist", }, { name: "a directory to a fleet", target: "3", - wantQuery: "after=2", + wantQueries: []string{"last=1", "after=2"}, wantUploaded: "Uploaded 1 file to 2 devices in fleet 3. It arrives at each device's next check-in.\n", wantRefusal: "there are no files to upload", }, @@ -226,8 +227,8 @@ func TestDevUploadsOnChangeAndFollowsTheLog(t *testing.T) { server.mutex.Lock() defer server.mutex.Unlock() - if len(server.queries) < 2 || server.queries[1] != test.wantQuery { - t.Errorf("log queries were %q, want the follow to ask %q", server.queries, test.wantQuery) + if len(server.queries) < 2 || !slices.Equal(server.queries[:2], test.wantQueries) { + t.Errorf("log queries were %q, want them to start %q", server.queries, test.wantQueries) } wantBodies := []string{ @@ -248,8 +249,8 @@ func TestDevUploadsOnChangeAndFollowsTheLog(t *testing.T) { t.Errorf("output was:\n%s\nwant %q three times", output, test.wantUploaded) } - if !strings.Contains(output, "[boiler] log 1\n") || !strings.Contains(output, "[boiler] log 2\n") { - t.Errorf("output was:\n%s\nwant the two stored logs", output) + if strings.Contains(output, "[boiler] log") { + t.Errorf("output was:\n%s\nwant none of the logs stored before the first upload", output) } if !strings.HasPrefix(output, test.wantUploaded) { diff --git a/internal/tail/tail.go b/internal/tail/tail.go index b3de5fd..6bfac13 100644 --- a/internal/tail/tail.go +++ b/internal/tail/tail.go @@ -14,6 +14,11 @@ import ( ) func Tail(invocation api.Invocation, arguments []string) error { + return Follow(invocation, arguments, false) +} + +// Takes tail's arguments. fromNow leaves out every log stored before the follow starts +func Follow(invocation api.Invocation, arguments []string, fromNow bool) error { positionals := []string{} count := int64(0) offset := int64(0) @@ -98,14 +103,20 @@ func Tail(invocation api.Invocation, arguments []string) error { // The newest logs, then follow query.Set("last", "100") + if fromNow { + query.Set("last", "1") + } + answer, err := api.FetchLogs(invocation, fleetID, query) if err != nil { return err } - for _, entry := range answer.Logs { - printLog(invocation.Out, entry) + if !fromNow { + for _, entry := range answer.Logs { + printLog(invocation.Out, entry) + } } query.Del("last") diff --git a/internal/tail/tail_test.go b/internal/tail/tail_test.go index 37d47cd..267a076 100644 --- a/internal/tail/tail_test.go +++ b/internal/tail/tail_test.go @@ -240,41 +240,67 @@ func TestTailPrintsEachLogSafely(t *testing.T) { } func TestTailFollows(t *testing.T) { - logs := testLogs(7) - server := &testLogServer{ - logs: logs[:5], - follow: []*api.LogsAnswer{ - {Logs: logs[5:6]}, - nil, - {Logs: []api.LogEntry{}, Next: 6}, - {Logs: logs[6:7]}, + tests := []struct { + name string + follow func(invocation api.Invocation, arguments []string) error + wantTexts []string + wantNewest string + }{ + { + name: "after the newest logs", + follow: Tail, + wantTexts: []string{"log 2", "log 4", "log 6", "log 7"}, + wantNewest: "imei=222222222222222&last=100", + }, + { + name: "from now", + follow: func(invocation api.Invocation, arguments []string) error { + return Follow(invocation, arguments, true) + }, + wantTexts: []string{"log 6", "log 7"}, + wantNewest: "imei=222222222222222&last=1", }, } - invocation, out := apitest.LoggedInInvocation(t, server) - err := Tail(invocation, []string{"3", "222222222222222"}) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + logs := testLogs(7) + server := &testLogServer{ + logs: logs[:5], + follow: []*api.LogsAnswer{ + {Logs: logs[5:6]}, + nil, + {Logs: []api.LogEntry{}, Next: 6}, + {Logs: logs[6:7]}, + }, + } + invocation, out := apitest.LoggedInInvocation(t, server) - if err == nil || err.Error() != "no such fleet" { - t.Fatalf("error = %v, want the server's refusal that ends the follow", err) - } + err := test.follow(invocation, []string{"3", "222222222222222"}) - texts := printedTexts(out.String()) + if err == nil || err.Error() != "no such fleet" { + t.Fatalf("error = %v, want the server's refusal that ends the follow", err) + } - if !slices.Equal(texts, []string{"log 2", "log 4", "log 6", "log 7"}) { - t.Errorf("printed %q, want the newest logs then the ones that arrived while following", texts) - } + texts := printedTexts(out.String()) - wantQueries := []string{ - "imei=222222222222222&last=100", - "after=4&imei=222222222222222", - "after=6&imei=222222222222222", - "after=6&imei=222222222222222", - "after=6&imei=222222222222222", - "after=7&imei=222222222222222", - } + if !slices.Equal(texts, test.wantTexts) { + t.Errorf("printed %q, want %q", texts, test.wantTexts) + } - if !slices.Equal(server.queries, wantQueries) { - t.Errorf("asked %q, want %q", server.queries, wantQueries) + wantQueries := []string{ + test.wantNewest, + "after=4&imei=222222222222222", + "after=6&imei=222222222222222", + "after=6&imei=222222222222222", + "after=6&imei=222222222222222", + "after=7&imei=222222222222222", + } + + if !slices.Equal(server.queries, wantQueries) { + t.Errorf("asked %q, want %q", server.queries, wantQueries) + } + }) } } diff --git a/main.go b/main.go index f7d624c..b6b5d97 100644 --- a/main.go +++ b/main.go @@ -53,7 +53,7 @@ var sections = []dispatch.Section{ Commands: []dispatch.Command{ {Name: "upload", Arguments: " ...", Summary: "Upload files or directories to a device or fleet", Run: files.Upload}, {Name: "download", Arguments: " ", Summary: "Download the code last uploaded to a device into ", Run: files.Download}, - {Name: "dev", Arguments: " ...", Summary: "Upload on every change, and tail", Run: dev.Dev}, + {Name: "dev", Arguments: " ...", Summary: "Upload on every change, and follow the log from the first upload", Run: dev.Dev}, }, }, {