diff --git a/pkg/jsonmessage/jsonmessage.go b/pkg/jsonmessage/jsonmessage.go index 368f128949..b9f40d3ef1 100644 --- a/pkg/jsonmessage/jsonmessage.go +++ b/pkg/jsonmessage/jsonmessage.go @@ -40,21 +40,17 @@ type JSONProgress struct { // If true, don't show xB/yB HideCounts bool `json:"hidecounts,omitempty"` Units string `json:"units,omitempty"` + nowFunc func() time.Time + winSize int } func (p *JSONProgress) String() string { var ( - width = 200 + width = p.width() pbBox string numbersBox string timeLeftBox string ) - - ws, err := term.GetWinsize(p.terminalFd) - if err == nil { - width = int(ws.Width) - } - if p.Current <= 0 && p.Total <= 0 { return "" } @@ -103,7 +99,7 @@ func (p *JSONProgress) String() string { } if p.Current > 0 && p.Start > 0 && percentage < 50 { - fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0)) + fromStart := p.now().Sub(time.Unix(p.Start, 0)) perEntry := fromStart / time.Duration(p.Current) left := time.Duration(p.Total-p.Current) * perEntry left = (left / time.Second) * time.Second @@ -115,6 +111,28 @@ func (p *JSONProgress) String() string { return pbBox + numbersBox + timeLeftBox } +// shim for testing +func (p *JSONProgress) now() time.Time { + if p.nowFunc == nil { + p.nowFunc = func() time.Time { + return time.Now().UTC() + } + } + return p.nowFunc() +} + +// shim for testing +func (p *JSONProgress) width() int { + if p.winSize != 0 { + return p.winSize + } + ws, err := term.GetWinsize(p.terminalFd) + if err == nil { + return int(ws.Width) + } + return 200 +} + // JSONMessage defines a message struct. It describes // the created time, where it from, status, ID of the // message. It's used for docker events. diff --git a/pkg/jsonmessage/jsonmessage_test.go b/pkg/jsonmessage/jsonmessage_test.go index ab1d879c5a..f9ead207ca 100644 --- a/pkg/jsonmessage/jsonmessage_test.go +++ b/pkg/jsonmessage/jsonmessage_test.go @@ -15,84 +15,102 @@ import ( func TestError(t *testing.T) { je := JSONError{404, "Not found"} - if je.Error() != "Not found" { - t.Fatalf("Expected 'Not found' got '%s'", je.Error()) - } + assert.Assert(t, is.Error(&je, "Not found")) } -func TestProgress(t *testing.T) { - termsz, err := term.GetWinsize(0) - if err != nil { - // we can safely ignore the err here - termsz = nil - } - jp := JSONProgress{} - if jp.String() != "" { - t.Fatalf("Expected empty string, got '%s'", jp.String()) +func TestProgressString(t *testing.T) { + type expected struct { + short string + long string } - expected := " 1B" - jp2 := JSONProgress{Current: 1} - if jp2.String() != expected { - t.Fatalf("Expected %q, got %q", expected, jp2.String()) + shortAndLong := func(short, long string) expected { + return expected{short: short, long: long} } - expectedStart := "[==========> ] 20B/100B" - if termsz != nil && termsz.Width <= 110 { - expectedStart = " 20B/100B" - } - jp3 := JSONProgress{Current: 20, Total: 100, Start: time.Now().Unix()} - // Just look at the start of the string - // (the remaining time is really hard to test -_-) - if jp3.String()[:len(expectedStart)] != expectedStart { - t.Fatalf("Expected to start with %q, got %q", expectedStart, jp3.String()) + 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) + } } - expected = "[=========================> ] 50B/100B" - if termsz != nil && termsz.Width <= 110 { - expected = " 50B/100B" - } - jp4 := JSONProgress{Current: 50, Total: 100} - if jp4.String() != expected { - t.Fatalf("Expected %q, got %q", expected, jp4.String()) + var 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( + "", + "[=========================> ] ", + ), + }, } - // this number can't be negative gh#7136 - expected = "[==================================================>] 50B" - if termsz != nil && termsz.Width <= 110 { - expected = " 50B" - } - jp5 := JSONProgress{Current: 50, Total: 40} - if jp5.String() != expected { - t.Fatalf("Expected %q, got %q", expected, jp5.String()) - } + 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) - expected = "[=========================> ] 50/100 units" - if termsz != nil && termsz.Width <= 110 { - expected = " 50/100 units" - } - jp6 := JSONProgress{Current: 50, Total: 100, Units: "units"} - if jp6.String() != expected { - t.Fatalf("Expected %q, got %q", expected, jp6.String()) - } - - // this number can't be negative - expected = "[==================================================>] 50 units" - if termsz != nil && termsz.Width <= 110 { - expected = " 50 units" - } - jp7 := JSONProgress{Current: 50, Total: 40, Units: "units"} - if jp7.String() != expected { - t.Fatalf("Expected %q, got %q", expected, jp7.String()) - } - - expected = "[=========================> ] " - if termsz != nil && termsz.Width <= 110 { - expected = "" - } - jp8 := JSONProgress{Current: 50, Total: 100, HideCounts: true} - if jp8.String() != expected { - t.Fatalf("Expected %q, got %q", expected, jp8.String()) + testcase.progress.winSize = 200 + assert.Equal(t, testcase.progress.String(), testcase.expected.long) + }) } } diff --git a/pkg/streamformatter/streamformatter_test.go b/pkg/streamformatter/streamformatter_test.go index e655f9140a..172d568bda 100644 --- a/pkg/streamformatter/streamformatter_test.go +++ b/pkg/streamformatter/streamformatter_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/docker/docker/pkg/jsonmessage" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/gotestyourself/gotestyourself/assert" is "github.com/gotestyourself/gotestyourself/assert/cmp" ) @@ -58,30 +60,31 @@ func TestJsonProgressFormatterFormatProgress(t *testing.T) { Total: 30, Start: 1, } - res := sf.formatProgress("id", "action", jsonProgress, &AuxFormatter{Writer: &bytes.Buffer{}}) + aux := "aux message" + res := sf.formatProgress("id", "action", jsonProgress, aux) msg := &jsonmessage.JSONMessage{} assert.NilError(t, json.Unmarshal(res, msg)) - assert.Check(t, is.Equal("id", msg.ID)) - assert.Check(t, is.Equal("action", msg.Status)) - // jsonProgress will always be in the format of: - // [=========================> ] 15B/30B 412910h51m30s - // The last entry '404933h7m11s' is the timeLeftBox. - // However, the timeLeftBox field may change as jsonProgress.String() depends on time.Now(). - // Therefore, we have to strip the timeLeftBox from the strings to do the comparison. - - // Compare the jsonProgress strings before the timeLeftBox - expectedProgress := "[=========================> ] 15B/30B" - // if terminal column is <= 110, expectedProgressShort is expected. - expectedProgressShort := " 15B/30B" - if !(strings.HasPrefix(msg.ProgressMessage, expectedProgress) || - strings.HasPrefix(msg.ProgressMessage, expectedProgressShort)) { - t.Fatalf("ProgressMessage without the timeLeftBox must be %s or %s, got: %s", - expectedProgress, expectedProgressShort, msg.ProgressMessage) + rawAux := json.RawMessage(`"` + aux + `"`) + expected := &jsonmessage.JSONMessage{ + ID: "id", + Status: "action", + Aux: &rawAux, + Progress: jsonProgress, } + assert.DeepEqual(t, msg, expected, cmpJSONMessageOpt()) +} - assert.Check(t, is.DeepEqual(jsonProgress, msg.Progress)) +func cmpJSONMessageOpt() cmp.Option { + progressMessagePath := func(path cmp.Path) bool { + return path.String() == "ProgressMessage" + } + return cmp.Options{ + cmpopts.IgnoreUnexported(jsonmessage.JSONProgress{}), + // Ignore deprecated property that is a derivative of Progress + cmp.FilterPath(progressMessagePath, cmp.Ignore()), + } } func TestJsonProgressFormatterFormatStatus(t *testing.T) {