github.com/GoogleCloudPlatform/testgrid@v0.0.174/pkg/updater/inflate_test.go (about) 1 /* 2 Copyright 2020 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 updater 18 19 import ( 20 "context" 21 "flag" 22 "reflect" 23 "testing" 24 "time" 25 26 "github.com/google/go-cmp/cmp" 27 "google.golang.org/protobuf/testing/protocmp" 28 29 "cloud.google.com/go/storage" 30 statepb "github.com/GoogleCloudPlatform/testgrid/pb/state" 31 statuspb "github.com/GoogleCloudPlatform/testgrid/pb/test_status" 32 "github.com/GoogleCloudPlatform/testgrid/util/gcs" 33 ) 34 35 // TODO(fejta): rename everything to InflatedColumn 36 type inflatedColumn = InflatedColumn 37 38 // TODO(fejta): rename everything to Cell 39 type cell = Cell 40 41 func blank(n int) []string { 42 var out []string 43 for i := 0; i < n; i++ { 44 out = append(out, "") 45 } 46 return out 47 } 48 49 var benchPath gcs.Path 50 51 func init() { 52 flag.Var(&benchPath, "bench-path", "Path to ./foo/local-state or gs://bucket/grid/test-group") 53 } 54 55 func BenchmarkInflateGrid(b *testing.B) { 56 ctx := context.Background() 57 if benchPath.Object() == "" { 58 b.Skip("No grid-path specified") 59 } 60 var storageClient *storage.Client 61 if benchPath.Bucket() != "" { 62 s, err := gcs.ClientWithCreds(context.Background()) 63 if err != nil { 64 b.Fatalf("Cannot create GCS client: %v", err) 65 } 66 storageClient = s 67 } 68 client := gcs.NewClient(storageClient) 69 grid, _, err := gcs.DownloadGrid(ctx, client, benchPath) 70 if err != nil { 71 b.Fatalf("Failed to download %s: %v", benchPath, err) 72 } 73 latest := time.Now().Add(time.Hour) 74 earliest := time.Unix(0, 0) 75 b.ResetTimer() 76 b.RunParallel(func(pb *testing.PB) { 77 for pb.Next() { 78 InflateGrid(ctx, grid, earliest, latest) 79 } 80 }) 81 } 82 83 func TestInflateGrid(t *testing.T) { 84 var hours []time.Time 85 when := time.Now().Round(time.Hour) 86 for i := 0; i < 24; i++ { 87 hours = append(hours, when) 88 when = when.Add(time.Hour) 89 } 90 91 millis := func(t time.Time) float64 { 92 return float64(t.Unix() * 1000) 93 } 94 95 cases := []struct { 96 name string 97 ctx context.Context 98 grid *statepb.Grid 99 earliest time.Time 100 latest time.Time 101 expected []inflatedColumn 102 wantIssues map[string][]string 103 err bool 104 }{ 105 { 106 name: "basically works", 107 grid: &statepb.Grid{}, 108 }, 109 { 110 name: "preserve column data", 111 ctx: func() context.Context { 112 ctx, cancel := context.WithCancel(context.Background()) 113 cancel() 114 return ctx 115 }(), 116 grid: &statepb.Grid{ 117 Columns: []*statepb.Column{ 118 { 119 Build: "build", 120 Hint: "xyzpdq", 121 Name: "name", 122 Started: 5, 123 Extra: []string{"extra", "fun"}, 124 HotlistIds: "hot topic", 125 }, 126 { 127 Build: "second build", // Also becomes Hint 128 Name: "second name", 129 Started: 10, 130 Extra: []string{"more", "gooder"}, 131 HotlistIds: "hot pocket", 132 }, 133 }, 134 }, 135 err: true, 136 }, 137 { 138 name: "preserve column data", 139 grid: &statepb.Grid{ 140 Columns: []*statepb.Column{ 141 { 142 Build: "build", 143 Hint: "xyzpdq", 144 Name: "name", 145 Started: 5, 146 Extra: []string{"extra", "fun"}, 147 HotlistIds: "hot topic", 148 }, 149 { 150 Build: "second build", // Also becomes Hint 151 Name: "second name", 152 Started: 10, 153 Extra: []string{"more", "gooder"}, 154 HotlistIds: "hot pocket", 155 }, 156 }, 157 }, 158 latest: hours[23], 159 expected: []inflatedColumn{ 160 { 161 Column: &statepb.Column{ 162 Build: "build", 163 Hint: "xyzpdq", 164 Name: "name", 165 Started: 5, 166 Extra: []string{"extra", "fun"}, 167 HotlistIds: "hot topic", 168 }, 169 Cells: map[string]cell{}, 170 }, 171 { 172 Column: &statepb.Column{ 173 Build: "second build", 174 Hint: "second build", 175 Name: "second name", 176 Started: 10, 177 Extra: []string{"more", "gooder"}, 178 HotlistIds: "hot pocket", 179 }, 180 Cells: map[string]cell{}, 181 }, 182 }, 183 }, 184 { 185 name: "preserve row data", 186 grid: &statepb.Grid{ 187 Columns: []*statepb.Column{ 188 { 189 Build: "b1", 190 Name: "n1", 191 Started: 1, 192 }, 193 { 194 Build: "b2", 195 Name: "n2", 196 Started: 2, 197 }, 198 }, 199 Rows: []*statepb.Row{ 200 { 201 Name: "name", 202 Results: []int32{ 203 int32(statuspb.TestStatus_FAIL), 2, 204 }, 205 CellIds: []string{"this", "that"}, 206 Messages: []string{"important", "notice"}, 207 Icons: []string{"I1", "I2"}, 208 Metric: []string{"this", "that"}, 209 UserProperty: []string{"hello", "there"}, 210 Metrics: []*statepb.Metric{ 211 { 212 Indices: []int32{0, 2}, // both columns 213 Values: []float64{0.1, 0.2}, 214 }, 215 { 216 Name: "override", 217 Indices: []int32{1, 1}, // only second 218 Values: []float64{1.1}, 219 }, 220 }, 221 Issues: []string{"fun", "times"}, 222 }, 223 { 224 Name: "second", 225 Results: []int32{ 226 int32(statuspb.TestStatus_PASS), 2, 227 }, 228 CellIds: blank(2), 229 Messages: blank(2), 230 Icons: blank(2), 231 Metric: blank(2), 232 UserProperty: blank(2), 233 }, 234 { 235 Name: "sparse", 236 Results: []int32{ 237 int32(statuspb.TestStatus_NO_RESULT), 1, 238 int32(statuspb.TestStatus_FLAKY), 1, 239 }, 240 CellIds: []string{"that-sparse"}, 241 Messages: []string{"notice-sparse"}, 242 Icons: []string{"I2-sparse"}, 243 UserProperty: []string{"there-sparse"}, 244 }, 245 { 246 Name: "issued", 247 Issues: []string{"three", "4"}, 248 }, 249 }, 250 }, 251 latest: hours[23], 252 expected: []inflatedColumn{ 253 { 254 Column: &statepb.Column{ 255 Build: "b1", 256 Hint: "b1", 257 Name: "n1", 258 Started: 1, 259 }, 260 Cells: map[string]cell{ 261 "name": { 262 Result: statuspb.TestStatus_FAIL, 263 CellID: "this", 264 Message: "important", 265 Icon: "I1", 266 Metrics: map[string]float64{ 267 "this": 0.1, 268 }, 269 UserProperty: "hello", 270 }, 271 "second": { 272 Result: statuspb.TestStatus_PASS, 273 }, 274 "sparse": {}, 275 }, 276 }, 277 { 278 Column: &statepb.Column{ 279 Build: "b2", 280 Hint: "b2", 281 Name: "n2", 282 Started: 2, 283 }, 284 Cells: map[string]cell{ 285 "name": { 286 Result: statuspb.TestStatus_FAIL, 287 CellID: "that", 288 Message: "notice", 289 Icon: "I2", 290 Metrics: map[string]float64{ 291 "this": 0.2, 292 "override": 1.1, 293 }, 294 UserProperty: "there", 295 }, 296 "second": { 297 Result: statuspb.TestStatus_PASS, 298 }, 299 "sparse": { 300 Result: statuspb.TestStatus_FLAKY, 301 CellID: "that-sparse", 302 Message: "notice-sparse", 303 Icon: "I2-sparse", 304 UserProperty: "there-sparse", 305 }, 306 }, 307 }, 308 }, 309 wantIssues: map[string][]string{ 310 "issued": {"three", "4"}, 311 "name": {"fun", "times"}, 312 }, 313 }, 314 { 315 name: "drop latest columns", 316 grid: &statepb.Grid{ 317 Columns: []*statepb.Column{ 318 { 319 Build: "latest1", 320 Started: millis(hours[23]), 321 }, 322 { 323 Build: "latest2", 324 Started: millis(hours[20]) + 1000, 325 }, 326 { 327 Build: "keep1", 328 Started: millis(hours[20]) + 999, 329 }, 330 { 331 Build: "keep2", 332 Started: millis(hours[10]), 333 }, 334 }, 335 Rows: []*statepb.Row{ 336 { 337 Name: "hello", 338 CellIds: blank(4), 339 Messages: blank(4), 340 Icons: blank(4), 341 Results: []int32{ 342 int32(statuspb.TestStatus_RUNNING), 1, 343 int32(statuspb.TestStatus_PASS), 1, 344 int32(statuspb.TestStatus_FAIL), 1, 345 int32(statuspb.TestStatus_FLAKY), 1, 346 }, 347 }, 348 { 349 Name: "world", 350 CellIds: blank(4), 351 Messages: blank(4), 352 Icons: blank(4), 353 Results: []int32{ 354 int32(statuspb.TestStatus_PASS_WITH_SKIPS), 4, 355 }, 356 }, 357 }, 358 }, 359 latest: hours[20], 360 expected: []inflatedColumn{ 361 { 362 Column: &statepb.Column{ 363 Build: "keep1", 364 Hint: "keep1", 365 Started: millis(hours[20]) + 999, 366 }, 367 Cells: map[string]cell{ 368 "hello": {Result: statuspb.TestStatus_FAIL}, 369 "world": {Result: statuspb.TestStatus_PASS_WITH_SKIPS}, 370 }, 371 }, 372 { 373 Column: &statepb.Column{ 374 Build: "keep2", 375 Hint: "keep2", 376 Started: millis(hours[10]), 377 }, 378 Cells: map[string]cell{ 379 "hello": {Result: statuspb.TestStatus_FLAKY}, 380 "world": {Result: statuspb.TestStatus_PASS_WITH_SKIPS}, 381 }, 382 }, 383 }, 384 }, 385 { 386 name: "unsorted", // drop old and new 387 grid: &statepb.Grid{ 388 Columns: []*statepb.Column{ 389 { 390 Build: "current1", 391 Started: millis(hours[20]), 392 }, 393 { 394 Build: "old1", 395 Started: millis(hours[10]) - 1, 396 }, 397 { 398 Build: "new1", 399 Started: millis(hours[22]), 400 }, 401 { 402 Build: "current3", 403 Started: millis(hours[19]), 404 }, 405 { 406 Build: "new2", 407 Started: millis(hours[23]), 408 }, 409 { 410 Build: "old2", 411 Started: millis(hours[0]), 412 }, 413 { 414 Build: "current2", 415 Started: millis(hours[10]), 416 }, 417 }, 418 Rows: []*statepb.Row{ 419 { 420 Name: "hello", 421 CellIds: blank(7), 422 Messages: blank(7), 423 Icons: blank(7), 424 Results: []int32{ 425 int32(statuspb.TestStatus_RUNNING), 1, 426 int32(statuspb.TestStatus_PASS), 2, 427 int32(statuspb.TestStatus_FAIL), 1, 428 int32(statuspb.TestStatus_PASS), 2, 429 int32(statuspb.TestStatus_FLAKY), 1, 430 }, 431 }, 432 { 433 Name: "world", 434 CellIds: blank(7), 435 Messages: blank(7), 436 Icons: blank(7), 437 Results: []int32{ 438 int32(statuspb.TestStatus_PASS_WITH_SKIPS), 7, 439 }, 440 }, 441 }, 442 }, 443 latest: hours[21], 444 earliest: hours[10], 445 expected: []inflatedColumn{ 446 { 447 Column: &statepb.Column{ 448 Build: "current1", 449 Hint: "current1", 450 Started: millis(hours[20]), 451 }, 452 Cells: map[string]cell{ 453 "hello": {Result: statuspb.TestStatus_RUNNING}, 454 "world": {Result: statuspb.TestStatus_PASS_WITH_SKIPS}, 455 }, 456 }, 457 { 458 Column: &statepb.Column{ 459 Build: "current3", 460 Hint: "current3", 461 Started: millis(hours[19]), 462 }, 463 Cells: map[string]cell{ 464 "hello": {Result: statuspb.TestStatus_FAIL}, 465 "world": {Result: statuspb.TestStatus_PASS_WITH_SKIPS}, 466 }, 467 }, 468 { 469 Column: &statepb.Column{ 470 Build: "current2", 471 Hint: "current2", 472 Started: millis(hours[10]), 473 }, 474 Cells: map[string]cell{ 475 "hello": {Result: statuspb.TestStatus_FLAKY}, 476 "world": {Result: statuspb.TestStatus_PASS_WITH_SKIPS}, 477 }, 478 }, 479 }, 480 }, 481 { 482 name: "drop old columns", 483 grid: &statepb.Grid{ 484 Columns: []*statepb.Column{ 485 { 486 Build: "current1", 487 Started: millis(hours[20]), 488 }, 489 { 490 Build: "current2", 491 Started: millis(hours[10]), 492 }, 493 { 494 Build: "old1", 495 Started: millis(hours[10]) - 1, 496 }, 497 { 498 Build: "old2", 499 Started: millis(hours[0]), 500 }, 501 }, 502 Rows: []*statepb.Row{ 503 { 504 Name: "hello", 505 CellIds: blank(4), 506 Messages: blank(4), 507 Icons: blank(4), 508 Results: []int32{ 509 int32(statuspb.TestStatus_RUNNING), 1, 510 int32(statuspb.TestStatus_PASS), 1, 511 int32(statuspb.TestStatus_FAIL), 1, 512 int32(statuspb.TestStatus_FLAKY), 1, 513 }, 514 }, 515 { 516 Name: "world", 517 CellIds: blank(4), 518 Messages: blank(4), 519 Icons: blank(4), 520 Results: []int32{ 521 int32(statuspb.TestStatus_PASS_WITH_SKIPS), 4, 522 }, 523 }, 524 }, 525 }, 526 latest: hours[23], 527 earliest: hours[10], 528 expected: []inflatedColumn{ 529 { 530 Column: &statepb.Column{ 531 Build: "current1", 532 Hint: "current1", 533 Started: millis(hours[20]), 534 }, 535 Cells: map[string]cell{ 536 "hello": {Result: statuspb.TestStatus_RUNNING}, 537 "world": {Result: statuspb.TestStatus_PASS_WITH_SKIPS}, 538 }, 539 }, 540 { 541 Column: &statepb.Column{ 542 Build: "current2", 543 Hint: "current2", 544 Started: millis(hours[10]), 545 }, 546 Cells: map[string]cell{ 547 "hello": {Result: statuspb.TestStatus_PASS}, 548 "world": {Result: statuspb.TestStatus_PASS_WITH_SKIPS}, 549 }, 550 }, 551 }, 552 }, 553 { 554 name: "keep newest old column when none newer", 555 grid: &statepb.Grid{ 556 Columns: []*statepb.Column{ 557 { 558 Build: "drop-latest1", 559 Started: millis(hours[23]), 560 }, 561 { 562 Build: "keep-old1", 563 Started: millis(hours[10]) - 1, 564 }, 565 { 566 Build: "drop-old2", 567 Started: millis(hours[0]), 568 }, 569 }, 570 Rows: []*statepb.Row{ 571 { 572 Name: "hello", 573 CellIds: blank(4), 574 Messages: blank(4), 575 Icons: blank(4), 576 Results: []int32{ 577 int32(statuspb.TestStatus_RUNNING), 1, 578 int32(statuspb.TestStatus_FAIL), 1, 579 int32(statuspb.TestStatus_FLAKY), 1, 580 }, 581 }, 582 { 583 Name: "world", 584 CellIds: blank(4), 585 Messages: blank(4), 586 Icons: blank(4), 587 Results: []int32{ 588 int32(statuspb.TestStatus_PASS_WITH_SKIPS), 3, 589 }, 590 }, 591 }, 592 }, 593 latest: hours[20], 594 earliest: hours[10], 595 expected: []inflatedColumn{ 596 { 597 Column: &statepb.Column{ 598 Build: "keep-old1", 599 Hint: "keep-old1", 600 Started: millis(hours[10]) - 1, 601 }, 602 Cells: map[string]cell{ 603 "hello": {Result: statuspb.TestStatus_FAIL}, 604 "world": {Result: statuspb.TestStatus_PASS_WITH_SKIPS}, 605 }, 606 }, 607 }, 608 }, 609 } 610 611 for _, tc := range cases { 612 t.Run(tc.name, func(t *testing.T) { 613 if tc.wantIssues == nil { 614 tc.wantIssues = map[string][]string{} 615 } 616 if tc.ctx == nil { 617 tc.ctx = context.Background() 618 } 619 actual, issues, err := InflateGrid(tc.ctx, tc.grid, tc.earliest, tc.latest) 620 switch { 621 case err != nil: 622 if !tc.err { 623 t.Errorf("InflatedGrid() got unexpected error: %v", err) 624 } 625 case tc.err: 626 t.Error("InflateGrid() failed to return an error") 627 default: 628 if diff := cmp.Diff(tc.expected, actual, cmp.AllowUnexported(inflatedColumn{}, cell{}), protocmp.Transform()); diff != "" { 629 t.Errorf("InflateGrid() got unexpected diff (-want +got):\n%s", diff) 630 } 631 if diff := cmp.Diff(tc.wantIssues, issues); diff != "" { 632 t.Errorf("InflateGrid() got unexpected issue diff (-want +got):\n%s", diff) 633 } 634 } 635 }) 636 637 } 638 } 639 640 func TestInflateRow(t *testing.T) { 641 cases := []struct { 642 name string 643 row *statepb.Row 644 expected []cell 645 }{ 646 { 647 name: "basically works", 648 }, 649 { 650 name: "preserve cell ids", 651 row: &statepb.Row{ 652 CellIds: []string{"cell-a", "cell-b", "cell-d"}, 653 Icons: blank(3), 654 Messages: blank(3), 655 Results: []int32{ 656 int32(statuspb.TestStatus_PASS), 2, 657 int32(statuspb.TestStatus_NO_RESULT), 1, 658 int32(statuspb.TestStatus_PASS), 1, 659 int32(statuspb.TestStatus_NO_RESULT), 1, 660 }, 661 }, 662 expected: []cell{ 663 { 664 Result: statuspb.TestStatus_PASS, 665 CellID: "cell-a", 666 }, 667 { 668 Result: statuspb.TestStatus_PASS, 669 CellID: "cell-b", 670 }, 671 { 672 Result: statuspb.TestStatus_NO_RESULT, 673 }, 674 { 675 Result: statuspb.TestStatus_PASS, 676 CellID: "cell-d", 677 }, 678 { 679 Result: statuspb.TestStatus_NO_RESULT, 680 }, 681 }, 682 }, 683 { 684 name: "only finished columns contain icons and messages", 685 row: &statepb.Row{ 686 CellIds: blank(8), 687 Icons: []string{ 688 "F1", "~1", "~2", 689 }, 690 Messages: []string{ 691 "fail", "flake-first", "flake-second", 692 }, 693 Results: []int32{ 694 int32(statuspb.TestStatus_NO_RESULT), 2, 695 int32(statuspb.TestStatus_FAIL), 1, 696 int32(statuspb.TestStatus_NO_RESULT), 2, 697 int32(statuspb.TestStatus_FLAKY), 2, 698 int32(statuspb.TestStatus_NO_RESULT), 1, 699 }, 700 }, 701 expected: []cell{ 702 {}, 703 {}, 704 { 705 Result: statuspb.TestStatus_FAIL, 706 Icon: "F1", 707 Message: "fail", 708 }, 709 {}, 710 {}, 711 { 712 Result: statuspb.TestStatus_FLAKY, 713 Icon: "~1", 714 Message: "flake-first", 715 }, 716 { 717 Result: statuspb.TestStatus_FLAKY, 718 Icon: "~2", 719 Message: "flake-second", 720 }, 721 {}, 722 }, 723 }, 724 { 725 name: "find metric name from row when missing", 726 row: &statepb.Row{ 727 CellIds: blank(1), 728 Icons: blank(1), 729 Messages: blank(1), 730 Results: []int32{ 731 int32(statuspb.TestStatus_PASS), 1, 732 }, 733 Metric: []string{"found-it"}, 734 Metrics: []*statepb.Metric{ 735 { 736 Indices: []int32{0, 1}, 737 Values: []float64{7}, 738 }, 739 }, 740 }, 741 expected: []cell{ 742 { 743 Result: statuspb.TestStatus_PASS, 744 Metrics: map[string]float64{ 745 "found-it": 7, 746 }, 747 }, 748 }, 749 }, 750 { 751 name: "prioritize local metric name", 752 row: &statepb.Row{ 753 CellIds: blank(1), 754 Icons: blank(1), 755 Messages: blank(1), 756 Results: []int32{ 757 int32(statuspb.TestStatus_PASS), 1, 758 }, 759 Metric: []string{"ignore-this"}, 760 Metrics: []*statepb.Metric{ 761 { 762 Name: "oh yeah", 763 Indices: []int32{0, 1}, 764 Values: []float64{7}, 765 }, 766 }, 767 }, 768 expected: []cell{ 769 { 770 Result: statuspb.TestStatus_PASS, 771 Metrics: map[string]float64{ 772 "oh yeah": 7, 773 }, 774 }, 775 }, 776 }, 777 } 778 779 for _, tc := range cases { 780 t.Run(tc.name, func(t *testing.T) { 781 var actual []cell 782 nextCell := inflateRow(tc.row) 783 for c := nextCell(); c != nil; c = nextCell() { 784 actual = append(actual, *c) 785 } 786 787 if diff := cmp.Diff(actual, tc.expected, cmp.AllowUnexported(cell{}), protocmp.Transform()); diff != "" { 788 t.Errorf("inflateRow() got unexpected diff (-have, +want):\n%s", diff) 789 } 790 }) 791 } 792 } 793 794 func TestInflateMetric(t *testing.T) { 795 point := func(v float64) *float64 { 796 return &v 797 } 798 cases := []struct { 799 name string 800 indices []int32 801 values []float64 802 expected []*float64 803 }{ 804 { 805 name: "basically works", 806 }, 807 { 808 name: "documented example with both values and holes works", 809 indices: []int32{0, 2, 6, 4}, 810 values: []float64{0.1, 0.2, 6.1, 6.2, 6.3, 6.4}, 811 expected: []*float64{ 812 point(0.1), 813 point(0.2), 814 nil, 815 nil, 816 nil, 817 nil, 818 point(6.1), 819 point(6.2), 820 point(6.3), 821 point(6.4), 822 }, 823 }, 824 } 825 826 for _, tc := range cases { 827 t.Run(tc.name, func(t *testing.T) { 828 var actual []*float64 829 metric := statepb.Metric{ 830 Name: tc.name, 831 Indices: tc.indices, 832 Values: tc.values, 833 } 834 nextMetric := inflateMetric(&metric) 835 for val, ok := nextMetric(); ok; val, ok = nextMetric() { 836 actual = append(actual, val) 837 } 838 839 if diff := cmp.Diff(tc.expected, actual); diff != "" { 840 t.Errorf("inflateMetric(%v) got unexpected diff (-want +got):\n%s", metric, diff) 841 } 842 }) 843 } 844 } 845 846 func TestInflateResults(t *testing.T) { 847 cases := []struct { 848 name string 849 results []int32 850 expected []statuspb.TestStatus 851 }{ 852 { 853 name: "basically works", 854 }, 855 { 856 name: "first documented example with multiple values works", 857 results: []int32{ 858 int32(statuspb.TestStatus_NO_RESULT), 3, 859 int32(statuspb.TestStatus_PASS), 4, 860 }, 861 expected: []statuspb.TestStatus{ 862 statuspb.TestStatus_NO_RESULT, 863 statuspb.TestStatus_NO_RESULT, 864 statuspb.TestStatus_NO_RESULT, 865 statuspb.TestStatus_PASS, 866 statuspb.TestStatus_PASS, 867 statuspb.TestStatus_PASS, 868 statuspb.TestStatus_PASS, 869 }, 870 }, 871 { 872 name: "first item is the type", 873 results: []int32{ 874 int32(statuspb.TestStatus_RUNNING), 1, // RUNNING == 4 875 }, 876 expected: []statuspb.TestStatus{ 877 statuspb.TestStatus_RUNNING, 878 }, 879 }, 880 { 881 name: "second item is the number of repetitions", 882 results: []int32{ 883 int32(statuspb.TestStatus_PASS), 4, // Running == 1 884 }, 885 expected: []statuspb.TestStatus{ 886 statuspb.TestStatus_PASS, 887 statuspb.TestStatus_PASS, 888 statuspb.TestStatus_PASS, 889 statuspb.TestStatus_PASS, 890 }, 891 }, 892 } 893 894 for _, tc := range cases { 895 t.Run(tc.name, func(t *testing.T) { 896 nextResult := inflateResults(tc.results) 897 var actual []statuspb.TestStatus 898 for cur := nextResult(); cur != nil; cur = nextResult() { 899 actual = append(actual, *cur) 900 } 901 if !reflect.DeepEqual(actual, tc.expected) { 902 t.Errorf("inflateResults(%v) got %v, want %v", tc.results, actual, tc.expected) 903 } 904 }) 905 } 906 }