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
2 changes: 1 addition & 1 deletion internal/dev/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 8 additions & 7 deletions internal/dev/dev_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -147,22 +148,22 @@ func TestDevUploadsOnChangeAndFollowsTheLog(t *testing.T) {
name string
target string
watchFile bool
wantQuery string
wantQueries []string
wantUploaded string
wantRefusal string
}{
{
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",
},
Expand Down Expand Up @@ -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{
Expand All @@ -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) {
Expand Down
15 changes: 13 additions & 2 deletions internal/tail/tail.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
80 changes: 53 additions & 27 deletions internal/tail/tail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var sections = []dispatch.Section{
Commands: []dispatch.Command{
{Name: "upload", Arguments: "<imei|fleet_id> <file> ...", Summary: "Upload files or directories to a device or fleet", Run: files.Upload},
{Name: "download", Arguments: "<imei> <path>", Summary: "Download the code last uploaded to a device into <path>", Run: files.Download},
{Name: "dev", Arguments: "<imei|fleet_id> <file> ...", Summary: "Upload on every change, and tail", Run: dev.Dev},
{Name: "dev", Arguments: "<imei|fleet_id> <file> ...", Summary: "Upload on every change, and follow the log from the first upload", Run: dev.Dev},
},
},
{
Expand Down
Loading