| package jsonmessage // import "github.com/docker/docker/pkg/jsonmessage" |
| |
| import ( |
| "bytes" |
| "fmt" |
| "strings" |
| "testing" |
| "time" |
| |
| "github.com/moby/term" |
| "gotest.tools/v3/assert" |
| is "gotest.tools/v3/assert/cmp" |
| ) |
| |
| func TestError(t *testing.T) { |
| je := JSONError{404, "Not found"} |
| assert.Assert(t, is.Error(&je, "Not found")) |
| } |
| |
| func TestProgressString(t *testing.T) { |
| type expected struct { |
| short string |
| long string |
| } |
| |
| shortAndLong := func(short, long string) expected { |
| return expected{short: short, long: long} |
| } |
| |
| start := time.Date(2017, 12, 3, 15, 10, 1, 0, time.UTC) |
| timeAfter := func(delta time.Duration) func() time.Time { |
| return func() time.Time { |
| return start.Add(delta) |
| } |
| } |
| |
| testcases := []struct { |
| name string |
| progress JSONProgress |
| expected expected |
| }{ |
| { |
| name: "no progress", |
| }, |
| { |
| name: "progress 1", |
| progress: JSONProgress{Current: 1}, |
| expected: shortAndLong(" 1B", " 1B"), |
| }, |
| { |
| name: "some progress with a start time", |
| progress: JSONProgress{ |
| Current: 20, |
| Total: 100, |
| Start: start.Unix(), |
| nowFunc: timeAfter(time.Second), |
| }, |
| expected: shortAndLong( |
| " 20B/100B 4s", |
| "[==========> ] 20B/100B 4s", |
| ), |
| }, |
| { |
| name: "some progress without a start time", |
| progress: JSONProgress{Current: 50, Total: 100}, |
| expected: shortAndLong( |
| " 50B/100B", |
| "[=========================> ] 50B/100B", |
| ), |
| }, |
| { |
| name: "current more than total is not negative gh#7136", |
| progress: JSONProgress{Current: 50, Total: 40}, |
| expected: shortAndLong( |
| " 50B", |
| "[==================================================>] 50B", |
| ), |
| }, |
| { |
| name: "with units", |
| progress: JSONProgress{Current: 50, Total: 100, Units: "units"}, |
| expected: shortAndLong( |
| "50/100 units", |
| "[=========================> ] 50/100 units", |
| ), |
| }, |
| { |
| name: "current more than total with units is not negative ", |
| progress: JSONProgress{Current: 50, Total: 40, Units: "units"}, |
| expected: shortAndLong( |
| "50 units", |
| "[==================================================>] 50 units", |
| ), |
| }, |
| { |
| name: "hide counts", |
| progress: JSONProgress{Current: 50, Total: 100, HideCounts: true}, |
| expected: shortAndLong( |
| "", |
| "[=========================> ] ", |
| ), |
| }, |
| } |
| |
| for _, testcase := range testcases { |
| t.Run(testcase.name, func(t *testing.T) { |
| testcase.progress.winSize = 100 |
| assert.Equal(t, testcase.progress.String(), testcase.expected.short) |
| |
| testcase.progress.winSize = 200 |
| assert.Equal(t, testcase.progress.String(), testcase.expected.long) |
| }) |
| } |
| } |
| |
| func TestJSONMessageDisplay(t *testing.T) { |
| now := time.Now() |
| messages := map[JSONMessage][]string{ |
| // Empty |
| {}: {"\n", "\n"}, |
| // Status |
| { |
| Status: "status", |
| }: { |
| "status\n", |
| "status\n", |
| }, |
| // General |
| { |
| Time: now.Unix(), |
| ID: "ID", |
| From: "From", |
| Status: "status", |
| }: { |
| fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(RFC3339NanoFixed)), |
| fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(RFC3339NanoFixed)), |
| }, |
| // General, with nano precision time |
| { |
| TimeNano: now.UnixNano(), |
| ID: "ID", |
| From: "From", |
| Status: "status", |
| }: { |
| fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)), |
| fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)), |
| }, |
| // General, with both times Nano is preferred |
| { |
| Time: now.Unix(), |
| TimeNano: now.UnixNano(), |
| ID: "ID", |
| From: "From", |
| Status: "status", |
| }: { |
| fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)), |
| fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)), |
| }, |
| // Stream over status |
| { |
| Status: "status", |
| Stream: "stream", |
| }: { |
| "stream", |
| "stream", |
| }, |
| // With progress message |
| { |
| Status: "status", |
| ProgressMessage: "progressMessage", |
| }: { |
| "status progressMessage", |
| "status progressMessage", |
| }, |
| // With progress, stream empty |
| { |
| Status: "status", |
| Stream: "", |
| Progress: &JSONProgress{Current: 1}, |
| }: { |
| "", |
| fmt.Sprintf("%c[2K\rstatus 1B\r", 27), |
| }, |
| } |
| |
| // The tests :) |
| for jsonMessage, expectedMessages := range messages { |
| // Without terminal |
| data := bytes.NewBuffer([]byte{}) |
| if err := jsonMessage.Display(data, false); err != nil { |
| t.Fatal(err) |
| } |
| if data.String() != expectedMessages[0] { |
| t.Fatalf("Expected %q,got %q", expectedMessages[0], data.String()) |
| } |
| // With terminal |
| data = bytes.NewBuffer([]byte{}) |
| if err := jsonMessage.Display(data, true); err != nil { |
| t.Fatal(err) |
| } |
| if data.String() != expectedMessages[1] { |
| t.Fatalf("\nExpected %q\n got %q", expectedMessages[1], data.String()) |
| } |
| } |
| } |
| |
| // Test JSONMessage with an Error. It will return an error with the text as error, not the meaning of the HTTP code. |
| func TestJSONMessageDisplayWithJSONError(t *testing.T) { |
| data := bytes.NewBuffer([]byte{}) |
| jsonMessage := JSONMessage{Error: &JSONError{404, "Can't find it"}} |
| |
| err := jsonMessage.Display(data, true) |
| if err == nil || err.Error() != "Can't find it" { |
| t.Fatalf("Expected a JSONError 404, got %q", err) |
| } |
| |
| jsonMessage = JSONMessage{Error: &JSONError{401, "Anything"}} |
| err = jsonMessage.Display(data, true) |
| assert.Check(t, is.Error(err, "Anything")) |
| } |
| |
| func TestDisplayJSONMessagesStreamInvalidJSON(t *testing.T) { |
| var inFd uintptr |
| data := bytes.NewBuffer([]byte{}) |
| reader := strings.NewReader("This is not a 'valid' JSON []") |
| inFd, _ = term.GetFdInfo(reader) |
| |
| exp := "invalid character " |
| if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err == nil || !strings.HasPrefix(err.Error(), exp) { |
| t.Fatalf("Expected error (%s...), got %q", exp, err) |
| } |
| } |
| |
| func TestDisplayJSONMessagesStream(t *testing.T) { |
| var inFd uintptr |
| |
| messages := map[string][]string{ |
| // empty string |
| "": { |
| "", |
| "", |
| }, |
| // Without progress & ID |
| `{ "status": "status" }`: { |
| "status\n", |
| "status\n", |
| }, |
| // Without progress, with ID |
| `{ "id": "ID","status": "status" }`: { |
| "ID: status\n", |
| "ID: status\n", |
| }, |
| // With progress |
| `{ "id": "ID", "status": "status", "progress": "ProgressMessage" }`: { |
| "ID: status ProgressMessage", |
| fmt.Sprintf("\n%c[%dAID: status ProgressMessage%c[%dB", 27, 1, 27, 1), |
| }, |
| // With progressDetail |
| `{ "id": "ID", "status": "status", "progressDetail": { "Current": 1} }`: { |
| "", // progressbar is disabled in non-terminal |
| fmt.Sprintf("\n%c[%dA%c[2K\rID: status 1B\r%c[%dB", 27, 1, 27, 27, 1), |
| }, |
| } |
| |
| // Use $TERM which is unlikely to exist, forcing DisplayJSONMessageStream to |
| // (hopefully) use &noTermInfo. |
| t.Setenv("TERM", "xyzzy-non-existent-terminfo") |
| |
| for jsonMessage, expectedMessages := range messages { |
| data := bytes.NewBuffer([]byte{}) |
| reader := strings.NewReader(jsonMessage) |
| inFd, _ = term.GetFdInfo(reader) |
| |
| // Without terminal |
| if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err != nil { |
| t.Fatal(err) |
| } |
| if data.String() != expectedMessages[0] { |
| t.Fatalf("Expected an %q, got %q", expectedMessages[0], data.String()) |
| } |
| |
| // With terminal |
| data = bytes.NewBuffer([]byte{}) |
| reader = strings.NewReader(jsonMessage) |
| if err := DisplayJSONMessagesStream(reader, data, inFd, true, nil); err != nil { |
| t.Fatal(err) |
| } |
| if data.String() != expectedMessages[1] { |
| t.Fatalf("\nExpected %q\n got %q", expectedMessages[1], data.String()) |
| } |
| } |
| } |