github.com/moby/docker@v26.1.3+incompatible/pkg/jsonmessage/jsonmessage_test.go (about) 1 package jsonmessage // import "github.com/docker/docker/pkg/jsonmessage" 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/moby/term" 11 "gotest.tools/v3/assert" 12 is "gotest.tools/v3/assert/cmp" 13 ) 14 15 func TestError(t *testing.T) { 16 je := JSONError{404, "Not found"} 17 assert.Assert(t, is.Error(&je, "Not found")) 18 } 19 20 func TestProgressString(t *testing.T) { 21 type expected struct { 22 short string 23 long string 24 } 25 26 shortAndLong := func(short, long string) expected { 27 return expected{short: short, long: long} 28 } 29 30 start := time.Date(2017, 12, 3, 15, 10, 1, 0, time.UTC) 31 timeAfter := func(delta time.Duration) func() time.Time { 32 return func() time.Time { 33 return start.Add(delta) 34 } 35 } 36 37 testcases := []struct { 38 name string 39 progress JSONProgress 40 expected expected 41 }{ 42 { 43 name: "no progress", 44 }, 45 { 46 name: "progress 1", 47 progress: JSONProgress{Current: 1}, 48 expected: shortAndLong(" 1B", " 1B"), 49 }, 50 { 51 name: "some progress with a start time", 52 progress: JSONProgress{ 53 Current: 20, 54 Total: 100, 55 Start: start.Unix(), 56 nowFunc: timeAfter(time.Second), 57 }, 58 expected: shortAndLong( 59 " 20B/100B 4s", 60 "[==========> ] 20B/100B 4s", 61 ), 62 }, 63 { 64 name: "some progress without a start time", 65 progress: JSONProgress{Current: 50, Total: 100}, 66 expected: shortAndLong( 67 " 50B/100B", 68 "[=========================> ] 50B/100B", 69 ), 70 }, 71 { 72 name: "current more than total is not negative gh#7136", 73 progress: JSONProgress{Current: 50, Total: 40}, 74 expected: shortAndLong( 75 " 50B", 76 "[==================================================>] 50B", 77 ), 78 }, 79 { 80 name: "with units", 81 progress: JSONProgress{Current: 50, Total: 100, Units: "units"}, 82 expected: shortAndLong( 83 "50/100 units", 84 "[=========================> ] 50/100 units", 85 ), 86 }, 87 { 88 name: "current more than total with units is not negative ", 89 progress: JSONProgress{Current: 50, Total: 40, Units: "units"}, 90 expected: shortAndLong( 91 "50 units", 92 "[==================================================>] 50 units", 93 ), 94 }, 95 { 96 name: "hide counts", 97 progress: JSONProgress{Current: 50, Total: 100, HideCounts: true}, 98 expected: shortAndLong( 99 "", 100 "[=========================> ] ", 101 ), 102 }, 103 } 104 105 for _, testcase := range testcases { 106 t.Run(testcase.name, func(t *testing.T) { 107 testcase.progress.winSize = 100 108 assert.Equal(t, testcase.progress.String(), testcase.expected.short) 109 110 testcase.progress.winSize = 200 111 assert.Equal(t, testcase.progress.String(), testcase.expected.long) 112 }) 113 } 114 } 115 116 func TestJSONMessageDisplay(t *testing.T) { 117 now := time.Now() 118 messages := map[JSONMessage][]string{ 119 // Empty 120 {}: {"\n", "\n"}, 121 // Status 122 { 123 Status: "status", 124 }: { 125 "status\n", 126 "status\n", 127 }, 128 // General 129 { 130 Time: now.Unix(), 131 ID: "ID", 132 From: "From", 133 Status: "status", 134 }: { 135 fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(RFC3339NanoFixed)), 136 fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(RFC3339NanoFixed)), 137 }, 138 // General, with nano precision time 139 { 140 TimeNano: now.UnixNano(), 141 ID: "ID", 142 From: "From", 143 Status: "status", 144 }: { 145 fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)), 146 fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)), 147 }, 148 // General, with both times Nano is preferred 149 { 150 Time: now.Unix(), 151 TimeNano: now.UnixNano(), 152 ID: "ID", 153 From: "From", 154 Status: "status", 155 }: { 156 fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)), 157 fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)), 158 }, 159 // Stream over status 160 { 161 Status: "status", 162 Stream: "stream", 163 }: { 164 "stream", 165 "stream", 166 }, 167 // With progress message 168 { 169 Status: "status", 170 ProgressMessage: "progressMessage", 171 }: { 172 "status progressMessage", 173 "status progressMessage", 174 }, 175 // With progress, stream empty 176 { 177 Status: "status", 178 Stream: "", 179 Progress: &JSONProgress{Current: 1}, 180 }: { 181 "", 182 fmt.Sprintf("%c[2K\rstatus 1B\r", 27), 183 }, 184 } 185 186 // The tests :) 187 for jsonMessage, expectedMessages := range messages { 188 // Without terminal 189 data := bytes.NewBuffer([]byte{}) 190 if err := jsonMessage.Display(data, false); err != nil { 191 t.Fatal(err) 192 } 193 if data.String() != expectedMessages[0] { 194 t.Fatalf("Expected %q,got %q", expectedMessages[0], data.String()) 195 } 196 // With terminal 197 data = bytes.NewBuffer([]byte{}) 198 if err := jsonMessage.Display(data, true); err != nil { 199 t.Fatal(err) 200 } 201 if data.String() != expectedMessages[1] { 202 t.Fatalf("\nExpected %q\n got %q", expectedMessages[1], data.String()) 203 } 204 } 205 } 206 207 // Test JSONMessage with an Error. It will return an error with the text as error, not the meaning of the HTTP code. 208 func TestJSONMessageDisplayWithJSONError(t *testing.T) { 209 data := bytes.NewBuffer([]byte{}) 210 jsonMessage := JSONMessage{Error: &JSONError{404, "Can't find it"}} 211 212 err := jsonMessage.Display(data, true) 213 if err == nil || err.Error() != "Can't find it" { 214 t.Fatalf("Expected a JSONError 404, got %q", err) 215 } 216 217 jsonMessage = JSONMessage{Error: &JSONError{401, "Anything"}} 218 err = jsonMessage.Display(data, true) 219 assert.Check(t, is.Error(err, "Anything")) 220 } 221 222 func TestDisplayJSONMessagesStreamInvalidJSON(t *testing.T) { 223 var inFd uintptr 224 data := bytes.NewBuffer([]byte{}) 225 reader := strings.NewReader("This is not a 'valid' JSON []") 226 inFd, _ = term.GetFdInfo(reader) 227 228 exp := "invalid character " 229 if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err == nil || !strings.HasPrefix(err.Error(), exp) { 230 t.Fatalf("Expected error (%s...), got %q", exp, err) 231 } 232 } 233 234 func TestDisplayJSONMessagesStream(t *testing.T) { 235 var inFd uintptr 236 237 messages := map[string][]string{ 238 // empty string 239 "": { 240 "", 241 "", 242 }, 243 // Without progress & ID 244 `{ "status": "status" }`: { 245 "status\n", 246 "status\n", 247 }, 248 // Without progress, with ID 249 `{ "id": "ID","status": "status" }`: { 250 "ID: status\n", 251 "ID: status\n", 252 }, 253 // With progress 254 `{ "id": "ID", "status": "status", "progress": "ProgressMessage" }`: { 255 "ID: status ProgressMessage", 256 fmt.Sprintf("\n%c[%dAID: status ProgressMessage%c[%dB", 27, 1, 27, 1), 257 }, 258 // With progressDetail 259 `{ "id": "ID", "status": "status", "progressDetail": { "Current": 1} }`: { 260 "", // progressbar is disabled in non-terminal 261 fmt.Sprintf("\n%c[%dA%c[2K\rID: status 1B\r%c[%dB", 27, 1, 27, 27, 1), 262 }, 263 } 264 265 // Use $TERM which is unlikely to exist, forcing DisplayJSONMessageStream to 266 // (hopefully) use &noTermInfo. 267 t.Setenv("TERM", "xyzzy-non-existent-terminfo") 268 269 for jsonMessage, expectedMessages := range messages { 270 data := bytes.NewBuffer([]byte{}) 271 reader := strings.NewReader(jsonMessage) 272 inFd, _ = term.GetFdInfo(reader) 273 274 // Without terminal 275 if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err != nil { 276 t.Fatal(err) 277 } 278 if data.String() != expectedMessages[0] { 279 t.Fatalf("Expected an %q, got %q", expectedMessages[0], data.String()) 280 } 281 282 // With terminal 283 data = bytes.NewBuffer([]byte{}) 284 reader = strings.NewReader(jsonMessage) 285 if err := DisplayJSONMessagesStream(reader, data, inFd, true, nil); err != nil { 286 t.Fatal(err) 287 } 288 if data.String() != expectedMessages[1] { 289 t.Fatalf("\nExpected %q\n got %q", expectedMessages[1], data.String()) 290 } 291 } 292 }