Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -40,21 +40,17 @@ type JSONProgress struct {
|
| 40 | 40 |
// If true, don't show xB/yB |
| 41 | 41 |
HideCounts bool `json:"hidecounts,omitempty"` |
| 42 | 42 |
Units string `json:"units,omitempty"` |
| 43 |
+ nowFunc func() time.Time |
|
| 44 |
+ winSize int |
|
| 43 | 45 |
} |
| 44 | 46 |
|
| 45 | 47 |
func (p *JSONProgress) String() string {
|
| 46 | 48 |
var ( |
| 47 |
- width = 200 |
|
| 49 |
+ width = p.width() |
|
| 48 | 50 |
pbBox string |
| 49 | 51 |
numbersBox string |
| 50 | 52 |
timeLeftBox string |
| 51 | 53 |
) |
| 52 |
- |
|
| 53 |
- ws, err := term.GetWinsize(p.terminalFd) |
|
| 54 |
- if err == nil {
|
|
| 55 |
- width = int(ws.Width) |
|
| 56 |
- } |
|
| 57 |
- |
|
| 58 | 54 |
if p.Current <= 0 && p.Total <= 0 {
|
| 59 | 55 |
return "" |
| 60 | 56 |
} |
| ... | ... |
@@ -103,7 +99,7 @@ func (p *JSONProgress) String() string {
|
| 103 | 103 |
} |
| 104 | 104 |
|
| 105 | 105 |
if p.Current > 0 && p.Start > 0 && percentage < 50 {
|
| 106 |
- fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0)) |
|
| 106 |
+ fromStart := p.now().Sub(time.Unix(p.Start, 0)) |
|
| 107 | 107 |
perEntry := fromStart / time.Duration(p.Current) |
| 108 | 108 |
left := time.Duration(p.Total-p.Current) * perEntry |
| 109 | 109 |
left = (left / time.Second) * time.Second |
| ... | ... |
@@ -115,6 +111,28 @@ func (p *JSONProgress) String() string {
|
| 115 | 115 |
return pbBox + numbersBox + timeLeftBox |
| 116 | 116 |
} |
| 117 | 117 |
|
| 118 |
+// shim for testing |
|
| 119 |
+func (p *JSONProgress) now() time.Time {
|
|
| 120 |
+ if p.nowFunc == nil {
|
|
| 121 |
+ p.nowFunc = func() time.Time {
|
|
| 122 |
+ return time.Now().UTC() |
|
| 123 |
+ } |
|
| 124 |
+ } |
|
| 125 |
+ return p.nowFunc() |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+// shim for testing |
|
| 129 |
+func (p *JSONProgress) width() int {
|
|
| 130 |
+ if p.winSize != 0 {
|
|
| 131 |
+ return p.winSize |
|
| 132 |
+ } |
|
| 133 |
+ ws, err := term.GetWinsize(p.terminalFd) |
|
| 134 |
+ if err == nil {
|
|
| 135 |
+ return int(ws.Width) |
|
| 136 |
+ } |
|
| 137 |
+ return 200 |
|
| 138 |
+} |
|
| 139 |
+ |
|
| 118 | 140 |
// JSONMessage defines a message struct. It describes |
| 119 | 141 |
// the created time, where it from, status, ID of the |
| 120 | 142 |
// message. It's used for docker events. |
| ... | ... |
@@ -15,84 +15,102 @@ import ( |
| 15 | 15 |
|
| 16 | 16 |
func TestError(t *testing.T) {
|
| 17 | 17 |
je := JSONError{404, "Not found"}
|
| 18 |
- if je.Error() != "Not found" {
|
|
| 19 |
- t.Fatalf("Expected 'Not found' got '%s'", je.Error())
|
|
| 20 |
- } |
|
| 18 |
+ assert.Assert(t, is.Error(&je, "Not found")) |
|
| 21 | 19 |
} |
| 22 | 20 |
|
| 23 |
-func TestProgress(t *testing.T) {
|
|
| 24 |
- termsz, err := term.GetWinsize(0) |
|
| 25 |
- if err != nil {
|
|
| 26 |
- // we can safely ignore the err here |
|
| 27 |
- termsz = nil |
|
| 28 |
- } |
|
| 29 |
- jp := JSONProgress{}
|
|
| 30 |
- if jp.String() != "" {
|
|
| 31 |
- t.Fatalf("Expected empty string, got '%s'", jp.String())
|
|
| 32 |
- } |
|
| 33 |
- |
|
| 34 |
- expected := " 1B" |
|
| 35 |
- jp2 := JSONProgress{Current: 1}
|
|
| 36 |
- if jp2.String() != expected {
|
|
| 37 |
- t.Fatalf("Expected %q, got %q", expected, jp2.String())
|
|
| 21 |
+func TestProgressString(t *testing.T) {
|
|
| 22 |
+ type expected struct {
|
|
| 23 |
+ short string |
|
| 24 |
+ long string |
|
| 38 | 25 |
} |
| 39 | 26 |
|
| 40 |
- expectedStart := "[==========> ] 20B/100B" |
|
| 41 |
- if termsz != nil && termsz.Width <= 110 {
|
|
| 42 |
- expectedStart = " 20B/100B" |
|
| 43 |
- } |
|
| 44 |
- jp3 := JSONProgress{Current: 20, Total: 100, Start: time.Now().Unix()}
|
|
| 45 |
- // Just look at the start of the string |
|
| 46 |
- // (the remaining time is really hard to test -_-) |
|
| 47 |
- if jp3.String()[:len(expectedStart)] != expectedStart {
|
|
| 48 |
- t.Fatalf("Expected to start with %q, got %q", expectedStart, jp3.String())
|
|
| 49 |
- } |
|
| 50 |
- |
|
| 51 |
- expected = "[=========================> ] 50B/100B" |
|
| 52 |
- if termsz != nil && termsz.Width <= 110 {
|
|
| 53 |
- expected = " 50B/100B" |
|
| 54 |
- } |
|
| 55 |
- jp4 := JSONProgress{Current: 50, Total: 100}
|
|
| 56 |
- if jp4.String() != expected {
|
|
| 57 |
- t.Fatalf("Expected %q, got %q", expected, jp4.String())
|
|
| 27 |
+ shortAndLong := func(short, long string) expected {
|
|
| 28 |
+ return expected{short: short, long: long}
|
|
| 58 | 29 |
} |
| 59 | 30 |
|
| 60 |
- // this number can't be negative gh#7136 |
|
| 61 |
- expected = "[==================================================>] 50B" |
|
| 62 |
- if termsz != nil && termsz.Width <= 110 {
|
|
| 63 |
- expected = " 50B" |
|
| 64 |
- } |
|
| 65 |
- jp5 := JSONProgress{Current: 50, Total: 40}
|
|
| 66 |
- if jp5.String() != expected {
|
|
| 67 |
- t.Fatalf("Expected %q, got %q", expected, jp5.String())
|
|
| 31 |
+ start := time.Date(2017, 12, 3, 15, 10, 1, 0, time.UTC) |
|
| 32 |
+ timeAfter := func(delta time.Duration) func() time.Time {
|
|
| 33 |
+ return func() time.Time {
|
|
| 34 |
+ return start.Add(delta) |
|
| 35 |
+ } |
|
| 68 | 36 |
} |
| 69 | 37 |
|
| 70 |
- expected = "[=========================> ] 50/100 units" |
|
| 71 |
- if termsz != nil && termsz.Width <= 110 {
|
|
| 72 |
- expected = " 50/100 units" |
|
| 73 |
- } |
|
| 74 |
- jp6 := JSONProgress{Current: 50, Total: 100, Units: "units"}
|
|
| 75 |
- if jp6.String() != expected {
|
|
| 76 |
- t.Fatalf("Expected %q, got %q", expected, jp6.String())
|
|
| 38 |
+ var testcases = []struct {
|
|
| 39 |
+ name string |
|
| 40 |
+ progress JSONProgress |
|
| 41 |
+ expected expected |
|
| 42 |
+ }{
|
|
| 43 |
+ {
|
|
| 44 |
+ name: "no progress", |
|
| 45 |
+ }, |
|
| 46 |
+ {
|
|
| 47 |
+ name: "progress 1", |
|
| 48 |
+ progress: JSONProgress{Current: 1},
|
|
| 49 |
+ expected: shortAndLong(" 1B", " 1B"),
|
|
| 50 |
+ }, |
|
| 51 |
+ {
|
|
| 52 |
+ name: "some progress with a start time", |
|
| 53 |
+ progress: JSONProgress{
|
|
| 54 |
+ Current: 20, |
|
| 55 |
+ Total: 100, |
|
| 56 |
+ Start: start.Unix(), |
|
| 57 |
+ nowFunc: timeAfter(time.Second), |
|
| 58 |
+ }, |
|
| 59 |
+ expected: shortAndLong( |
|
| 60 |
+ " 20B/100B 4s", |
|
| 61 |
+ "[==========> ] 20B/100B 4s", |
|
| 62 |
+ ), |
|
| 63 |
+ }, |
|
| 64 |
+ {
|
|
| 65 |
+ name: "some progress without a start time", |
|
| 66 |
+ progress: JSONProgress{Current: 50, Total: 100},
|
|
| 67 |
+ expected: shortAndLong( |
|
| 68 |
+ " 50B/100B", |
|
| 69 |
+ "[=========================> ] 50B/100B", |
|
| 70 |
+ ), |
|
| 71 |
+ }, |
|
| 72 |
+ {
|
|
| 73 |
+ name: "current more than total is not negative gh#7136", |
|
| 74 |
+ progress: JSONProgress{Current: 50, Total: 40},
|
|
| 75 |
+ expected: shortAndLong( |
|
| 76 |
+ " 50B", |
|
| 77 |
+ "[==================================================>] 50B", |
|
| 78 |
+ ), |
|
| 79 |
+ }, |
|
| 80 |
+ {
|
|
| 81 |
+ name: "with units", |
|
| 82 |
+ progress: JSONProgress{Current: 50, Total: 100, Units: "units"},
|
|
| 83 |
+ expected: shortAndLong( |
|
| 84 |
+ "50/100 units", |
|
| 85 |
+ "[=========================> ] 50/100 units", |
|
| 86 |
+ ), |
|
| 87 |
+ }, |
|
| 88 |
+ {
|
|
| 89 |
+ name: "current more than total with units is not negative ", |
|
| 90 |
+ progress: JSONProgress{Current: 50, Total: 40, Units: "units"},
|
|
| 91 |
+ expected: shortAndLong( |
|
| 92 |
+ "50 units", |
|
| 93 |
+ "[==================================================>] 50 units", |
|
| 94 |
+ ), |
|
| 95 |
+ }, |
|
| 96 |
+ {
|
|
| 97 |
+ name: "hide counts", |
|
| 98 |
+ progress: JSONProgress{Current: 50, Total: 100, HideCounts: true},
|
|
| 99 |
+ expected: shortAndLong( |
|
| 100 |
+ "", |
|
| 101 |
+ "[=========================> ] ", |
|
| 102 |
+ ), |
|
| 103 |
+ }, |
|
| 77 | 104 |
} |
| 78 | 105 |
|
| 79 |
- // this number can't be negative |
|
| 80 |
- expected = "[==================================================>] 50 units" |
|
| 81 |
- if termsz != nil && termsz.Width <= 110 {
|
|
| 82 |
- expected = " 50 units" |
|
| 83 |
- } |
|
| 84 |
- jp7 := JSONProgress{Current: 50, Total: 40, Units: "units"}
|
|
| 85 |
- if jp7.String() != expected {
|
|
| 86 |
- t.Fatalf("Expected %q, got %q", expected, jp7.String())
|
|
| 87 |
- } |
|
| 106 |
+ for _, testcase := range testcases {
|
|
| 107 |
+ t.Run(testcase.name, func(t *testing.T) {
|
|
| 108 |
+ testcase.progress.winSize = 100 |
|
| 109 |
+ assert.Equal(t, testcase.progress.String(), testcase.expected.short) |
|
| 88 | 110 |
|
| 89 |
- expected = "[=========================> ] " |
|
| 90 |
- if termsz != nil && termsz.Width <= 110 {
|
|
| 91 |
- expected = "" |
|
| 92 |
- } |
|
| 93 |
- jp8 := JSONProgress{Current: 50, Total: 100, HideCounts: true}
|
|
| 94 |
- if jp8.String() != expected {
|
|
| 95 |
- t.Fatalf("Expected %q, got %q", expected, jp8.String())
|
|
| 111 |
+ testcase.progress.winSize = 200 |
|
| 112 |
+ assert.Equal(t, testcase.progress.String(), testcase.expected.long) |
|
| 113 |
+ }) |
|
| 96 | 114 |
} |
| 97 | 115 |
} |
| 98 | 116 |
|
| ... | ... |
@@ -8,6 +8,8 @@ import ( |
| 8 | 8 |
"testing" |
| 9 | 9 |
|
| 10 | 10 |
"github.com/docker/docker/pkg/jsonmessage" |
| 11 |
+ "github.com/google/go-cmp/cmp" |
|
| 12 |
+ "github.com/google/go-cmp/cmp/cmpopts" |
|
| 11 | 13 |
"github.com/gotestyourself/gotestyourself/assert" |
| 12 | 14 |
is "github.com/gotestyourself/gotestyourself/assert/cmp" |
| 13 | 15 |
) |
| ... | ... |
@@ -58,30 +60,31 @@ func TestJsonProgressFormatterFormatProgress(t *testing.T) {
|
| 58 | 58 |
Total: 30, |
| 59 | 59 |
Start: 1, |
| 60 | 60 |
} |
| 61 |
- res := sf.formatProgress("id", "action", jsonProgress, &AuxFormatter{Writer: &bytes.Buffer{}})
|
|
| 61 |
+ aux := "aux message" |
|
| 62 |
+ res := sf.formatProgress("id", "action", jsonProgress, aux)
|
|
| 62 | 63 |
msg := &jsonmessage.JSONMessage{}
|
| 63 | 64 |
|
| 64 | 65 |
assert.NilError(t, json.Unmarshal(res, msg)) |
| 65 |
- assert.Check(t, is.Equal("id", msg.ID))
|
|
| 66 |
- assert.Check(t, is.Equal("action", msg.Status))
|
|
| 67 | 66 |
|
| 68 |
- // jsonProgress will always be in the format of: |
|
| 69 |
- // [=========================> ] 15B/30B 412910h51m30s |
|
| 70 |
- // The last entry '404933h7m11s' is the timeLeftBox. |
|
| 71 |
- // However, the timeLeftBox field may change as jsonProgress.String() depends on time.Now(). |
|
| 72 |
- // Therefore, we have to strip the timeLeftBox from the strings to do the comparison. |
|
| 73 |
- |
|
| 74 |
- // Compare the jsonProgress strings before the timeLeftBox |
|
| 75 |
- expectedProgress := "[=========================> ] 15B/30B" |
|
| 76 |
- // if terminal column is <= 110, expectedProgressShort is expected. |
|
| 77 |
- expectedProgressShort := " 15B/30B" |
|
| 78 |
- if !(strings.HasPrefix(msg.ProgressMessage, expectedProgress) || |
|
| 79 |
- strings.HasPrefix(msg.ProgressMessage, expectedProgressShort)) {
|
|
| 80 |
- t.Fatalf("ProgressMessage without the timeLeftBox must be %s or %s, got: %s",
|
|
| 81 |
- expectedProgress, expectedProgressShort, msg.ProgressMessage) |
|
| 67 |
+ rawAux := json.RawMessage(`"` + aux + `"`) |
|
| 68 |
+ expected := &jsonmessage.JSONMessage{
|
|
| 69 |
+ ID: "id", |
|
| 70 |
+ Status: "action", |
|
| 71 |
+ Aux: &rawAux, |
|
| 72 |
+ Progress: jsonProgress, |
|
| 82 | 73 |
} |
| 74 |
+ assert.DeepEqual(t, msg, expected, cmpJSONMessageOpt()) |
|
| 75 |
+} |
|
| 83 | 76 |
|
| 84 |
- assert.Check(t, is.DeepEqual(jsonProgress, msg.Progress)) |
|
| 77 |
+func cmpJSONMessageOpt() cmp.Option {
|
|
| 78 |
+ progressMessagePath := func(path cmp.Path) bool {
|
|
| 79 |
+ return path.String() == "ProgressMessage" |
|
| 80 |
+ } |
|
| 81 |
+ return cmp.Options{
|
|
| 82 |
+ cmpopts.IgnoreUnexported(jsonmessage.JSONProgress{}),
|
|
| 83 |
+ // Ignore deprecated property that is a derivative of Progress |
|
| 84 |
+ cmp.FilterPath(progressMessagePath, cmp.Ignore()), |
|
| 85 |
+ } |
|
| 85 | 86 |
} |
| 86 | 87 |
|
| 87 | 88 |
func TestJsonProgressFormatterFormatStatus(t *testing.T) {
|