github.com/GoogleCloudPlatform/testgrid@v0.0.174/util/queue/queue_test.go (about) 1 /* 2 Copyright 2021 The TestGrid Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package queue 18 19 import ( 20 "container/heap" 21 "context" 22 "sync" 23 "testing" 24 "time" 25 26 "github.com/google/go-cmp/cmp" 27 "github.com/sirupsen/logrus" 28 "google.golang.org/protobuf/testing/protocmp" 29 ) 30 31 func TestInit(t *testing.T) { 32 now := time.Now() 33 log := logrus.WithField("test", "TestInit") 34 cases := []struct { 35 name string 36 q *Queue 37 names []string 38 when time.Time 39 40 next []string 41 }{ 42 { 43 name: "add", 44 q: &Queue{}, 45 names: []string{ 46 "hi", 47 }, 48 when: now, 49 50 next: []string{"hi"}, 51 }, 52 { 53 name: "remove", 54 q: func() *Queue { 55 var q Queue 56 q.Init(log, []string{"drop", "keep"}, now) 57 return &q 58 }(), 59 names: []string{ 60 "keep", 61 "add", 62 }, 63 when: now.Add(-time.Minute), 64 next: []string{ 65 "add", 66 "keep", 67 }, 68 }, 69 } 70 71 for _, tc := range cases { 72 t.Run(tc.name, func(t *testing.T) { 73 tc.q.Init(log, tc.names, tc.when) 74 75 var got []string 76 for range tc.next { 77 got = append(got, heap.Pop(&tc.q.queue).(*item).name) 78 } 79 if diff := cmp.Diff(tc.next, got, protocmp.Transform()); diff != "" { 80 t.Errorf("FixAll() got unexpected diff (-want +got):\n%s", diff) 81 } 82 }) 83 } 84 } 85 86 func TestFixAll(t *testing.T) { 87 log := logrus.WithField("test", "TestFixAll") 88 now := time.Now() 89 cases := []struct { 90 name string 91 q *Queue 92 fixes map[string]time.Time 93 later bool 94 95 next []string 96 err bool 97 }{ 98 { 99 name: "empty", 100 q: &Queue{}, 101 }, 102 { 103 name: "later", 104 q: func() *Queue { 105 var q Queue 106 q.Init(log, []string{ 107 "first-now-second", 108 "second-now-fifth", 109 "third", 110 "fourth-now-first", 111 "fifth-now-fourth", 112 }, now) 113 return &q 114 }(), 115 fixes: map[string]time.Time{ 116 "fourth-now-first": now.Add(-2 * time.Minute), 117 "first-now-second": now.Add(-time.Minute), 118 "second-now-fifth": now.Add(2 * time.Minute), 119 "fifth-now-fourth": now.Add(time.Minute), 120 }, 121 later: true, 122 123 next: []string{ 124 "fourth-now-first", 125 "first-now-second", 126 "third", 127 "fifth-now-fourth", 128 "second-now-fifth", 129 }, 130 }, 131 { 132 name: "reduce", 133 q: func() *Queue { 134 var q Queue 135 q.Init(log, []string{ 136 "first-now-second", 137 "second-ignored-becomes-fifth", 138 "third-becomes-fourth", 139 "fourth-now-first", 140 "fifth-ignored-becomes-fourth", 141 }, now) 142 return &q 143 }(), 144 fixes: map[string]time.Time{ 145 "fourth-now-first": now.Add(-2 * time.Minute), 146 "first-now-second": now.Add(-time.Minute), 147 "second-ignored-becomes-fifth": now.Add(2 * time.Minute), // noop 148 "fifth-ignored-becomes-fourth": now.Add(time.Minute), // noop 149 }, 150 later: true, 151 152 next: []string{ 153 "fourth-now-first", 154 "first-now-second", 155 "third-becomes-fourth", 156 "fifth-ignored-becomes-fourth", 157 "second-ignored-becomes-fifth", 158 }, 159 }, 160 } 161 162 for _, tc := range cases { 163 t.Run(tc.name, func(t *testing.T) { 164 if err := tc.q.FixAll(tc.fixes, tc.later); (err != nil) != tc.err { 165 t.Errorf("FixAll() got unexpected error %v, wanted err=%t", err, tc.err) 166 } 167 var got []string 168 for range tc.next { 169 got = append(got, heap.Pop(&tc.q.queue).(*item).name) 170 } 171 if diff := cmp.Diff(tc.next, got, protocmp.Transform()); diff != "" { 172 t.Errorf("FixAll() got unexpected diff (-want +got):\n%s", diff) 173 } 174 }) 175 } 176 } 177 178 func TestFix(t *testing.T) { 179 now := time.Now() 180 log := logrus.WithField("test", "TestFix") 181 cases := []struct { 182 name string 183 184 q *Queue 185 fix string 186 when time.Time 187 later bool 188 189 next []string 190 err bool 191 }{ 192 { 193 name: "missing", 194 fix: "missing", 195 q: &Queue{}, 196 err: true, 197 }, 198 { 199 name: "later", 200 fix: "basic", 201 q: func() *Queue { 202 var q Queue 203 q.Init(log, []string{ 204 "basic", 205 "was-later-now-first", 206 }, now) 207 return &q 208 }(), 209 when: now.Add(time.Minute), 210 later: true, 211 next: []string{ 212 "was-later-now-first", 213 "basic", 214 }, 215 }, 216 { 217 name: "ignore later", 218 fix: "basic", 219 q: func() *Queue { 220 var q Queue 221 q.Init(log, []string{ 222 "basic", 223 "was-later-still-later", 224 }, now) 225 return &q 226 }(), 227 when: now.Add(time.Minute), 228 next: []string{ 229 "basic", 230 "was-later-still-later", 231 }, 232 }, 233 { 234 name: "reduce", 235 fix: "basic", 236 q: func() *Queue { 237 var q Queue 238 q.Init(log, []string{ 239 "was-earlier-now-later", 240 "basic", 241 }, now) 242 return &q 243 }(), 244 when: now.Add(-time.Minute), 245 next: []string{ 246 "basic", 247 "was-earlier-now-later", 248 }, 249 }, 250 } 251 252 for _, tc := range cases { 253 t.Run(tc.name, func(t *testing.T) { 254 if err := tc.q.Fix(tc.fix, tc.when, tc.later); (err != nil) != tc.err { 255 t.Errorf("Fix() got unexpected error %v, wanted err=%t", err, tc.err) 256 } 257 var got []string 258 for range tc.next { 259 got = append(got, heap.Pop(&tc.q.queue).(*item).name) 260 } 261 if diff := cmp.Diff(tc.next, got, protocmp.Transform()); diff != "" { 262 t.Errorf("Fix() got unexpected diff (-want +got):\n%s", diff) 263 } 264 }) 265 } 266 } 267 268 func TestStatus(t *testing.T) { 269 log := logrus.WithField("test", "TestStatus") 270 pstr := func(s string) *string { return &s } 271 now := time.Now() 272 cases := []struct { 273 name string 274 q *Queue 275 276 depth int 277 next *string 278 when time.Time 279 }{ 280 { 281 name: "empty", 282 q: &Queue{}, 283 }, 284 { 285 name: "single", 286 q: func() *Queue { 287 var q Queue 288 q.Init(log, []string{"hi"}, now) 289 return &q 290 }(), 291 depth: 1, 292 next: pstr("hi"), 293 when: now, 294 }, 295 { 296 name: "multi", 297 q: func() *Queue { 298 var q Queue 299 q.Init(log, []string{ 300 "hi", 301 "middle", 302 "there", 303 }, now) 304 q.Fix("middle", now.Add(-time.Minute), true) 305 return &q 306 }(), 307 depth: 3, 308 next: pstr("middle"), 309 when: now.Add(-time.Minute), 310 }, 311 } 312 313 for _, tc := range cases { 314 t.Run(tc.name, func(t *testing.T) { 315 depth, next, when := tc.q.Status() 316 if want, got := tc.depth, depth; want != got { 317 t.Errorf("Status() wanted depth %d, got %d", want, got) 318 } 319 if diff := cmp.Diff(tc.next, next, protocmp.Transform()); diff != "" { 320 t.Errorf("Status() got unexpected next diff (-want +got):\n%s", diff) 321 } 322 if !when.Equal(tc.when) { 323 t.Errorf("Status() wanted when %v, got %v", tc.when, when) 324 } 325 }) 326 } 327 } 328 329 func TestSend(t *testing.T) { 330 log := logrus.WithField("test", "TestSend") 331 cases := []struct { 332 name string 333 q *Queue 334 receivers func(context.Context, *testing.T) (context.Context, chan<- string, func() []string) 335 freq time.Duration 336 337 want []string 338 }{ 339 { 340 name: "empty", 341 q: &Queue{log: log}, 342 receivers: func(ctx context.Context, t *testing.T) (context.Context, chan<- string, func() []string) { 343 ch := make(chan string) 344 go func() { 345 for { 346 select { 347 case name := <-ch: 348 t.Errorf("Send() receiver got unexpected group: %v", name) 349 return 350 case <-ctx.Done(): 351 return 352 } 353 } 354 }() 355 356 return ctx, ch, func() []string { return nil } 357 }, 358 }, 359 { 360 name: "empty loop", 361 q: &Queue{log: log}, 362 receivers: func(ctx context.Context, t *testing.T) (context.Context, chan<- string, func() []string) { 363 ch := make(chan string) 364 ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) 365 go func() { 366 for { 367 select { 368 case name := <-ch: 369 t.Errorf("Send() receiver got unexpected name: %q", name) 370 return 371 case <-ctx.Done(): 372 cancel() 373 return 374 } 375 } 376 }() 377 378 return ctx, ch, func() []string { return nil } 379 }, 380 freq: time.Microsecond, 381 }, 382 { 383 name: "single", 384 q: func() *Queue { 385 var q Queue 386 q.Init(log, []string{"hi"}, time.Now()) 387 return &q 388 }(), 389 receivers: func(ctx context.Context, t *testing.T) (context.Context, chan<- string, func() []string) { 390 ch := make(chan string) 391 var wg sync.WaitGroup 392 wg.Add(1) 393 var got []string 394 go func() { 395 defer wg.Done() 396 for { 397 select { 398 case name := <-ch: 399 got = append(got, name) 400 return 401 case <-ctx.Done(): 402 return 403 } 404 } 405 }() 406 407 return ctx, ch, func() []string { 408 wg.Wait() 409 return got 410 } 411 }, 412 want: []string{"hi"}, 413 }, 414 { 415 name: "single loop", 416 q: func() *Queue { 417 var q Queue 418 q.Init(log, []string{"hi"}, time.Now()) 419 return &q 420 }(), 421 receivers: func(ctx context.Context, _ *testing.T) (context.Context, chan<- string, func() []string) { 422 ch := make(chan string) 423 var wg sync.WaitGroup 424 wg.Add(1) 425 var got []string 426 ctx, cancel := context.WithCancel(ctx) 427 go func() { 428 defer wg.Done() 429 for { 430 select { 431 case name := <-ch: 432 got = append(got, name) 433 if len(got) == 3 { 434 cancel() 435 } 436 case <-ctx.Done(): 437 cancel() 438 return 439 } 440 } 441 }() 442 443 return ctx, ch, func() []string { 444 wg.Wait() 445 return got 446 } 447 }, 448 freq: time.Microsecond, 449 want: []string{ 450 "hi", 451 "hi", 452 "hi", 453 }, 454 }, 455 { 456 name: "multi", 457 q: func() *Queue { 458 var q Queue 459 q.Init(log, []string{ 460 "hi", 461 "there", 462 }, time.Now()) 463 return &q 464 }(), 465 receivers: func(ctx context.Context, _ *testing.T) (context.Context, chan<- string, func() []string) { 466 ch := make(chan string) 467 var wg sync.WaitGroup 468 wg.Add(1) 469 var got []string 470 go func() { 471 defer wg.Done() 472 for { 473 select { 474 case name := <-ch: 475 got = append(got, name) 476 if len(got) == 2 { 477 return 478 } 479 case <-ctx.Done(): 480 return 481 } 482 } 483 }() 484 485 return ctx, ch, func() []string { 486 wg.Wait() 487 return got 488 } 489 }, 490 want: []string{ 491 "hi", 492 "there", 493 }, 494 }, 495 { 496 name: "multi loop", 497 q: func() *Queue { 498 var q Queue 499 q.Init(log, []string{"hi", "there"}, time.Now()) 500 return &q 501 }(), 502 receivers: func(ctx context.Context, _ *testing.T) (context.Context, chan<- string, func() []string) { 503 ch := make(chan string) 504 var wg sync.WaitGroup 505 wg.Add(1) 506 var got []string 507 ctx, cancel := context.WithCancel(ctx) 508 go func() { 509 defer wg.Done() 510 for { 511 select { 512 case name := <-ch: 513 got = append(got, name) 514 if len(got) == 6 { 515 cancel() 516 } 517 case <-ctx.Done(): 518 cancel() 519 return 520 } 521 } 522 }() 523 524 return ctx, ch, func() []string { 525 wg.Wait() 526 return got 527 } 528 }, 529 freq: time.Microsecond, 530 want: []string{ 531 "hi", 532 "there", 533 "hi", 534 "there", 535 "hi", 536 "there", 537 }, 538 }, 539 } 540 541 for _, tc := range cases { 542 t.Run(tc.name, func(t *testing.T) { 543 544 ctx, cancel := context.WithCancel(context.Background()) 545 defer cancel() 546 547 ctx, channel, get := tc.receivers(ctx, t) 548 if err := tc.q.Send(ctx, channel, tc.freq); err != ctx.Err() { 549 t.Errorf("Send() returned unexpected error: want %v, got %v", ctx.Err(), err) 550 } 551 got := get() 552 if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { 553 t.Errorf("Send() got unexpected diff (-want +got):\n%s", diff) 554 } 555 }) 556 } 557 } 558 559 func TestPriorityQueue(t *testing.T) { 560 cases := []struct { 561 name string 562 items []*item 563 want []string 564 }{ 565 { 566 name: "basic", 567 }, 568 { 569 name: "single", 570 items: []*item{ 571 { 572 name: "hi", 573 }, 574 }, 575 want: []string{"hi"}, 576 }, 577 { 578 name: "desc", 579 items: []*item{ 580 { 581 name: "young", 582 when: time.Now(), 583 }, 584 { 585 name: "old", 586 when: time.Now().Add(-time.Hour), 587 }, 588 }, 589 want: []string{"old", "young"}, 590 }, 591 { 592 name: "asc", 593 items: []*item{ 594 { 595 name: "old", 596 when: time.Now().Add(-time.Hour), 597 }, 598 { 599 name: "young", 600 when: time.Now(), 601 }, 602 }, 603 want: []string{"old", "young"}, 604 }, 605 } 606 607 for _, tc := range cases { 608 t.Run(tc.name, func(t *testing.T) { 609 pq := priorityQueue(tc.items) 610 heap.Init(&pq) 611 var got []string 612 for i, w := range tc.want { 613 g := pq.peek().name 614 if diff := cmp.Diff(w, g, protocmp.Transform()); diff != "" { 615 t.Errorf("%d peek() got unexpected diff (-want +got):\n%s", i, diff) 616 } 617 got = append(got, heap.Pop(&pq).(*item).name) 618 } 619 if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { 620 t.Errorf("priorityQueue() got unexpected diff (-want +got):\n%s", diff) 621 } 622 }) 623 } 624 } 625 626 func TestSleep(t *testing.T) { 627 cases := []struct { 628 name string 629 sleep time.Duration 630 rouseAfter time.Duration // if specified, rouse after this time 631 wantRouse bool 632 }{ 633 { 634 name: "basic", 635 sleep: 100 * time.Millisecond, 636 wantRouse: false, 637 }, 638 { 639 name: "rouse during sleep", 640 sleep: 1 * time.Minute, 641 rouseAfter: 100 * time.Millisecond, 642 wantRouse: true, 643 }, 644 { 645 name: "rouse after sleep", 646 sleep: 100 * time.Millisecond, 647 rouseAfter: 1 * time.Second, 648 wantRouse: false, 649 }, 650 } 651 for _, tc := range cases { 652 t.Run(tc.name, func(t *testing.T) { 653 var q Queue 654 q.Init(logrus.WithField("name", tc.name), []string{"hi", "there"}, time.Now()) 655 656 var slept, roused bool 657 var lock sync.Mutex 658 go func(rouseAfter time.Duration) { 659 if rouseAfter == 0 { 660 return 661 } 662 time.Sleep(rouseAfter) 663 q.rouse() 664 lock.Lock() 665 if !slept { 666 roused = true 667 } 668 lock.Unlock() 669 }(tc.rouseAfter) 670 671 q.sleep(tc.sleep) 672 lock.Lock() 673 slept = true 674 lock.Unlock() 675 676 if tc.wantRouse != roused { 677 t.Errorf("sleep() roused incorrectly (with sleep %q and rouse after %q): want rouse = %t, got %t", tc.sleep, tc.rouseAfter, tc.wantRouse, roused) 678 } 679 }) 680 } 681 }