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}, }, }, {