github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/distribution/xfer/transfer_test.go (about) 1 package xfer // import "github.com/docker/docker/distribution/xfer" 2 3 import ( 4 "sync/atomic" 5 "testing" 6 "time" 7 8 "github.com/docker/docker/pkg/progress" 9 ) 10 11 func TestTransfer(t *testing.T) { 12 makeXferFunc := func(id string) doFunc { 13 return func(progressChan chan<- progress.Progress, start <-chan struct{}, _ chan<- struct{}) transfer { 14 select { 15 case <-start: 16 default: 17 t.Errorf("%s: transfer function not started even though concurrency limit not reached", id) 18 } 19 20 xfer := newTransfer() 21 go func() { 22 for i := 0; i <= 10; i++ { 23 progressChan <- progress.Progress{ID: id, Action: "testing", Current: int64(i), Total: 10} 24 time.Sleep(10 * time.Millisecond) 25 } 26 close(progressChan) 27 }() 28 return xfer 29 } 30 } 31 32 tm := newTransferManager(5) 33 progressChan := make(chan progress.Progress) 34 progressDone := make(chan struct{}) 35 receivedProgress := make(map[string]int64) 36 37 go func() { 38 for p := range progressChan { 39 val, present := receivedProgress[p.ID] 40 if present && p.Current <= val { 41 t.Errorf("%s: got unexpected progress value: %d (expected <= %d)", p.ID, p.Current, val) 42 } 43 receivedProgress[p.ID] = p.Current 44 } 45 close(progressDone) 46 }() 47 48 // Start a few transfers 49 ids := []string{"id1", "id2", "id3"} 50 xfers := make([]transfer, len(ids)) 51 watchers := make([]*watcher, len(ids)) 52 for i, id := range ids { 53 xfers[i], watchers[i] = tm.transfer(id, makeXferFunc(id), progress.ChanOutput(progressChan)) 54 } 55 56 for i, xfer := range xfers { 57 <-xfer.done() 58 xfer.release(watchers[i]) 59 } 60 close(progressChan) 61 <-progressDone 62 63 for _, id := range ids { 64 if receivedProgress[id] != 10 { 65 t.Fatalf("final progress value %d instead of 10", receivedProgress[id]) 66 } 67 } 68 } 69 70 func TestConcurrencyLimit(t *testing.T) { 71 const concurrencyLimit = 3 72 var runningJobs int32 73 74 makeXferFunc := func(id string) doFunc { 75 return func(progressChan chan<- progress.Progress, start <-chan struct{}, _ chan<- struct{}) transfer { 76 xfer := newTransfer() 77 go func() { 78 <-start 79 totalJobs := atomic.AddInt32(&runningJobs, 1) 80 if int(totalJobs) > concurrencyLimit { 81 t.Errorf("%s: too many jobs running (%d > %d)", id, totalJobs, concurrencyLimit) 82 } 83 for i := 0; i <= 10; i++ { 84 progressChan <- progress.Progress{ID: id, Action: "testing", Current: int64(i), Total: 10} 85 time.Sleep(10 * time.Millisecond) 86 } 87 atomic.AddInt32(&runningJobs, -1) 88 close(progressChan) 89 }() 90 return xfer 91 } 92 } 93 94 tm := newTransferManager(concurrencyLimit) 95 progressChan := make(chan progress.Progress) 96 progressDone := make(chan struct{}) 97 receivedProgress := make(map[string]int64) 98 99 go func() { 100 for p := range progressChan { 101 receivedProgress[p.ID] = p.Current 102 } 103 close(progressDone) 104 }() 105 106 // Start more transfers than the concurrency limit 107 ids := []string{"id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8"} 108 xfers := make([]transfer, len(ids)) 109 watchers := make([]*watcher, len(ids)) 110 for i, id := range ids { 111 xfers[i], watchers[i] = tm.transfer(id, makeXferFunc(id), progress.ChanOutput(progressChan)) 112 } 113 114 for i, xfer := range xfers { 115 <-xfer.done() 116 xfer.release(watchers[i]) 117 } 118 close(progressChan) 119 <-progressDone 120 121 for _, id := range ids { 122 if receivedProgress[id] != 10 { 123 t.Fatalf("final progress value %d instead of 10", receivedProgress[id]) 124 } 125 } 126 } 127 128 func TestInactiveJobs(t *testing.T) { 129 const concurrencyLimit = 3 130 var runningJobs int32 131 testDone := make(chan struct{}) 132 133 makeXferFunc := func(id string) doFunc { 134 return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) transfer { 135 xfer := newTransfer() 136 go func() { 137 <-start 138 totalJobs := atomic.AddInt32(&runningJobs, 1) 139 if int(totalJobs) > concurrencyLimit { 140 t.Errorf("%s: too many jobs running (%d > %d)", id, totalJobs, concurrencyLimit) 141 } 142 for i := 0; i <= 10; i++ { 143 progressChan <- progress.Progress{ID: id, Action: "testing", Current: int64(i), Total: 10} 144 time.Sleep(10 * time.Millisecond) 145 } 146 atomic.AddInt32(&runningJobs, -1) 147 close(inactive) 148 <-testDone 149 close(progressChan) 150 }() 151 return xfer 152 } 153 } 154 155 tm := newTransferManager(concurrencyLimit) 156 progressChan := make(chan progress.Progress) 157 progressDone := make(chan struct{}) 158 receivedProgress := make(map[string]int64) 159 160 go func() { 161 for p := range progressChan { 162 receivedProgress[p.ID] = p.Current 163 } 164 close(progressDone) 165 }() 166 167 // Start more transfers than the concurrency limit 168 ids := []string{"id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8"} 169 xfers := make([]transfer, len(ids)) 170 watchers := make([]*watcher, len(ids)) 171 for i, id := range ids { 172 xfers[i], watchers[i] = tm.transfer(id, makeXferFunc(id), progress.ChanOutput(progressChan)) 173 } 174 175 close(testDone) 176 for i, xfer := range xfers { 177 <-xfer.done() 178 xfer.release(watchers[i]) 179 } 180 close(progressChan) 181 <-progressDone 182 183 for _, id := range ids { 184 if receivedProgress[id] != 10 { 185 t.Fatalf("final progress value %d instead of 10", receivedProgress[id]) 186 } 187 } 188 } 189 190 func TestWatchRelease(t *testing.T) { 191 ready := make(chan struct{}) 192 193 makeXferFunc := func(id string) doFunc { 194 return func(progressChan chan<- progress.Progress, start <-chan struct{}, _ chan<- struct{}) transfer { 195 xfer := newTransfer() 196 go func() { 197 defer func() { 198 close(progressChan) 199 }() 200 <-ready 201 for i := int64(0); ; i++ { 202 select { 203 case <-time.After(10 * time.Millisecond): 204 case <-xfer.context().Done(): 205 return 206 } 207 progressChan <- progress.Progress{ID: id, Action: "testing", Current: i, Total: 10} 208 } 209 }() 210 return xfer 211 } 212 } 213 214 tm := newTransferManager(5) 215 216 type watcherInfo struct { 217 watcher *watcher 218 progressChan chan progress.Progress 219 progressDone chan struct{} 220 receivedFirstProgress chan struct{} 221 } 222 223 progressConsumer := func(w watcherInfo) { 224 first := true 225 for range w.progressChan { 226 if first { 227 close(w.receivedFirstProgress) 228 } 229 first = false 230 } 231 close(w.progressDone) 232 } 233 234 // Start a transfer 235 watchers := make([]watcherInfo, 5) 236 var xfer transfer 237 watchers[0].progressChan = make(chan progress.Progress) 238 watchers[0].progressDone = make(chan struct{}) 239 watchers[0].receivedFirstProgress = make(chan struct{}) 240 xfer, watchers[0].watcher = tm.transfer("id1", makeXferFunc("id1"), progress.ChanOutput(watchers[0].progressChan)) 241 go progressConsumer(watchers[0]) 242 243 // Give it multiple watchers 244 for i := 1; i != len(watchers); i++ { 245 watchers[i].progressChan = make(chan progress.Progress) 246 watchers[i].progressDone = make(chan struct{}) 247 watchers[i].receivedFirstProgress = make(chan struct{}) 248 watchers[i].watcher = xfer.watch(progress.ChanOutput(watchers[i].progressChan)) 249 go progressConsumer(watchers[i]) 250 } 251 252 // Now that the watchers are set up, allow the transfer goroutine to 253 // proceed. 254 close(ready) 255 256 // Confirm that each watcher gets progress output. 257 for _, w := range watchers { 258 <-w.receivedFirstProgress 259 } 260 261 // Release one watcher every 5ms 262 for _, w := range watchers { 263 xfer.release(w.watcher) 264 <-time.After(5 * time.Millisecond) 265 } 266 267 // Now that all watchers have been released, Released() should 268 // return a closed channel. 269 <-xfer.released() 270 271 // Done() should return a closed channel because the xfer func returned 272 // due to cancellation. 273 <-xfer.done() 274 275 for _, w := range watchers { 276 close(w.progressChan) 277 <-w.progressDone 278 } 279 } 280 281 func TestWatchFinishedTransfer(t *testing.T) { 282 makeXferFunc := func(id string) doFunc { 283 return func(progressChan chan<- progress.Progress, _ <-chan struct{}, _ chan<- struct{}) transfer { 284 xfer := newTransfer() 285 go func() { 286 // Finish immediately 287 close(progressChan) 288 }() 289 return xfer 290 } 291 } 292 293 tm := newTransferManager(5) 294 295 // Start a transfer 296 watchers := make([]*watcher, 3) 297 var xfer transfer 298 xfer, watchers[0] = tm.transfer("id1", makeXferFunc("id1"), progress.ChanOutput(make(chan progress.Progress))) 299 300 // Give it a watcher immediately 301 watchers[1] = xfer.watch(progress.ChanOutput(make(chan progress.Progress))) 302 303 // Wait for the transfer to complete 304 <-xfer.done() 305 306 // Set up another watcher 307 watchers[2] = xfer.watch(progress.ChanOutput(make(chan progress.Progress))) 308 309 // Release the watchers 310 for _, w := range watchers { 311 xfer.release(w) 312 } 313 314 // Now that all watchers have been released, Released() should 315 // return a closed channel. 316 <-xfer.released() 317 } 318 319 func TestDuplicateTransfer(t *testing.T) { 320 ready := make(chan struct{}) 321 322 var xferFuncCalls int32 323 324 makeXferFunc := func(id string) doFunc { 325 return func(progressChan chan<- progress.Progress, _ <-chan struct{}, _ chan<- struct{}) transfer { 326 atomic.AddInt32(&xferFuncCalls, 1) 327 xfer := newTransfer() 328 go func() { 329 defer func() { 330 close(progressChan) 331 }() 332 <-ready 333 for i := int64(0); ; i++ { 334 select { 335 case <-time.After(10 * time.Millisecond): 336 case <-xfer.context().Done(): 337 return 338 } 339 progressChan <- progress.Progress{ID: id, Action: "testing", Current: i, Total: 10} 340 } 341 }() 342 return xfer 343 } 344 } 345 346 tm := newTransferManager(5) 347 348 type transferInfo struct { 349 xfer transfer 350 watcher *watcher 351 progressChan chan progress.Progress 352 progressDone chan struct{} 353 receivedFirstProgress chan struct{} 354 } 355 356 progressConsumer := func(t transferInfo) { 357 first := true 358 for range t.progressChan { 359 if first { 360 close(t.receivedFirstProgress) 361 } 362 first = false 363 } 364 close(t.progressDone) 365 } 366 367 // Try to start multiple transfers with the same ID 368 transfers := make([]transferInfo, 5) 369 for i := range transfers { 370 t := &transfers[i] 371 t.progressChan = make(chan progress.Progress) 372 t.progressDone = make(chan struct{}) 373 t.receivedFirstProgress = make(chan struct{}) 374 t.xfer, t.watcher = tm.transfer("id1", makeXferFunc("id1"), progress.ChanOutput(t.progressChan)) 375 go progressConsumer(*t) 376 } 377 378 // Allow the transfer goroutine to proceed. 379 close(ready) 380 381 // Confirm that each watcher gets progress output. 382 for _, t := range transfers { 383 <-t.receivedFirstProgress 384 } 385 386 // Confirm that the transfer function was called exactly once. 387 if xferFuncCalls != 1 { 388 t.Fatal("transfer function wasn't called exactly once") 389 } 390 391 // Release one watcher every 5ms 392 for _, t := range transfers { 393 t.xfer.release(t.watcher) 394 <-time.After(5 * time.Millisecond) 395 } 396 397 for _, t := range transfers { 398 // Now that all watchers have been released, Released() should 399 // return a closed channel. 400 <-t.xfer.released() 401 // Done() should return a closed channel because the xfer func returned 402 // due to cancellation. 403 <-t.xfer.done() 404 } 405 406 for _, t := range transfers { 407 close(t.progressChan) 408 <-t.progressDone 409 } 410 }