github.com/GoogleCloudPlatform/testgrid@v0.0.174/pkg/updater/gcs_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 "fmt" 21 "reflect" 22 "testing" 23 "time" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/sirupsen/logrus" 27 "google.golang.org/protobuf/testing/protocmp" 28 core "k8s.io/api/core/v1" 29 30 "github.com/GoogleCloudPlatform/testgrid/metadata" 31 "github.com/GoogleCloudPlatform/testgrid/metadata/junit" 32 configpb "github.com/GoogleCloudPlatform/testgrid/pb/config" 33 statepb "github.com/GoogleCloudPlatform/testgrid/pb/state" 34 statuspb "github.com/GoogleCloudPlatform/testgrid/pb/test_status" 35 "github.com/GoogleCloudPlatform/testgrid/util/gcs" 36 ) 37 38 func TestMergeCells(t *testing.T) { 39 cases := []struct { 40 name string 41 flaky bool 42 cells []Cell 43 expected Cell 44 }{ 45 { 46 name: "basically works", 47 cells: []Cell{ 48 { 49 Result: statuspb.TestStatus_TOOL_FAIL, 50 CellID: "random", 51 Icon: "religious", 52 Message: "empty", 53 Metrics: map[string]float64{ 54 "answer": 42, 55 "question": 1, 56 }, 57 }, 58 }, 59 expected: Cell{ 60 Result: statuspb.TestStatus_TOOL_FAIL, 61 CellID: "random", 62 Icon: "religious", 63 Message: "empty", 64 Metrics: map[string]float64{ 65 "answer": 42, 66 "question": 1, 67 }, 68 }, 69 }, 70 { 71 name: "passes work and take highest filled message", 72 cells: []Cell{ 73 { 74 Result: statuspb.TestStatus_PASS, 75 Icon: "drop", 76 }, 77 { 78 Result: statuspb.TestStatus_BUILD_PASSED, 79 Message: "woah", 80 }, 81 { 82 Result: statuspb.TestStatus_PASS_WITH_ERRORS, // highest but empty 83 }, 84 { 85 Result: statuspb.TestStatus_PASS_WITH_SKIPS, // highest with message 86 Message: "already got one", 87 }, 88 { 89 Result: statuspb.TestStatus_PASS, 90 Message: "there", 91 }, 92 }, 93 expected: Cell{ 94 Result: statuspb.TestStatus_PASS_WITH_ERRORS, 95 Message: "5/5 runs passed: already got one", 96 Icon: "5/5", 97 }, 98 }, 99 { 100 name: "merge metrics", 101 cells: []Cell{ 102 { 103 Result: statuspb.TestStatus_PASS, 104 Metrics: map[string]float64{ 105 "common": 1, 106 "first": 1, 107 }, 108 }, 109 { 110 Result: statuspb.TestStatus_PASS, 111 Metrics: map[string]float64{ 112 "common": 2, 113 "second": 2, 114 }, 115 }, 116 { 117 Result: statuspb.TestStatus_PASS, 118 Metrics: map[string]float64{ 119 "common": 108, // total 111 120 "third": 3, 121 }, 122 }, 123 }, 124 expected: Cell{ 125 Result: statuspb.TestStatus_PASS, 126 Message: "3/3 runs passed", 127 Icon: "3/3", 128 Metrics: map[string]float64{ 129 "common": 37, 130 "first": 1, 131 "second": 2, 132 "third": 3, 133 }, 134 }, 135 }, 136 { 137 name: "issues", 138 cells: []Cell{ 139 { 140 Result: statuspb.TestStatus_PASS, 141 Issues: []string{"a", "b", "common"}, 142 }, 143 { 144 Result: statuspb.TestStatus_PASS, 145 Issues: []string{"common", "c"}, 146 }, 147 { 148 Result: statuspb.TestStatus_PASS, 149 Issues: []string{"common", "d"}, 150 }, 151 }, 152 expected: Cell{ 153 Result: statuspb.TestStatus_PASS, 154 Message: "3/3 runs passed", 155 Icon: "3/3", 156 Issues: []string{ 157 "a", 158 "b", 159 "c", 160 "common", 161 "d", 162 }, 163 }, 164 }, 165 { 166 name: "failures take highest failure and highest failure message", 167 cells: []Cell{ 168 { 169 Result: statuspb.TestStatus_TIMED_OUT, 170 Message: "agonizingly slow", 171 Icon: "drop", 172 }, 173 { 174 Result: statuspb.TestStatus_CATEGORIZED_FAIL, //highest with message 175 Icon: "drop", 176 Message: "categorically wrong", 177 }, 178 { 179 Result: statuspb.TestStatus_BUILD_FAIL, // highest 180 Icon: "drop", 181 }, 182 }, 183 expected: Cell{ 184 Result: statuspb.TestStatus_BUILD_FAIL, 185 Icon: "0/3", 186 Message: "0/3 runs passed: categorically wrong", 187 }, 188 }, 189 { 190 name: "mix of passes and failures flake upon request", 191 flaky: true, 192 cells: []Cell{ 193 { 194 Result: statuspb.TestStatus_PASS, 195 Message: "yay", 196 }, 197 { 198 Result: statuspb.TestStatus_FAIL, 199 Message: "boom", 200 }, 201 }, 202 expected: Cell{ 203 Result: statuspb.TestStatus_FLAKY, 204 Icon: "1/2", 205 Message: "1/2 runs passed: boom", 206 }, 207 }, 208 { 209 name: "mix of passes and failures will fail upon request", 210 cells: []Cell{ 211 { 212 Result: statuspb.TestStatus_PASS, 213 Message: "yay", 214 }, 215 { 216 Result: statuspb.TestStatus_TOOL_FAIL, 217 Message: "boom", 218 }, 219 { 220 Result: statuspb.TestStatus_FAIL, // highest result.GTE 221 Message: "bang", 222 }, 223 { 224 Result: statuspb.TestStatus_BUILD_FAIL, 225 Message: "missing ;", 226 }, 227 }, 228 expected: Cell{ 229 Result: statuspb.TestStatus_FAIL, 230 Icon: "1/4", 231 Message: "1/4 runs passed: bang", 232 }, 233 }, 234 } 235 236 for _, tc := range cases { 237 t.Run(tc.name, func(t *testing.T) { 238 got := MergeCells(tc.flaky, tc.cells...) 239 if diff := cmp.Diff(tc.expected, got); diff != "" { 240 t.Errorf("MergeCells() got unexpected diff (-want +got):\n%s", diff) 241 } 242 }) 243 } 244 } 245 246 func TestSplitCells(t *testing.T) { 247 const cellName = "foo" 248 cases := []struct { 249 name string 250 cells []Cell 251 expected map[string]Cell 252 }{ 253 { 254 name: "basically works", 255 }, 256 { 257 name: "single item returns that item", 258 cells: []Cell{{Message: "hi"}}, 259 expected: map[string]Cell{ 260 "foo": {Message: "hi"}, 261 }, 262 }, 263 { 264 name: "multiple items have [1] starting from second", 265 cells: []Cell{ 266 {Message: "first"}, 267 {Message: "second"}, 268 {Message: "third"}, 269 }, 270 expected: map[string]Cell{ 271 "foo": {Message: "first"}, 272 "foo [1]": {Message: "second"}, 273 "foo [2]": {Message: "third"}, 274 }, 275 }, 276 { 277 name: "many items eventually truncate", 278 cells: func() []Cell { 279 var out []Cell 280 for i := 0; i < maxDuplicates*2; i++ { 281 out = append(out, Cell{Icon: fmt.Sprintf("row %d", i)}) 282 } 283 return out 284 }(), 285 expected: func() map[string]Cell { 286 out := map[string]Cell{} 287 out[cellName] = Cell{Icon: "row 0"} 288 for i := 1; i < maxDuplicates; i++ { 289 name := fmt.Sprintf("%s [%d]", cellName, i) 290 out[name] = Cell{Icon: fmt.Sprintf("row %d", i)} 291 } 292 out[cellName+" [overflow]"] = overflowCell 293 return out 294 }(), 295 }, 296 } 297 298 for _, tc := range cases { 299 t.Run(tc.name, func(t *testing.T) { 300 actual := SplitCells(cellName, tc.cells...) 301 if diff := cmp.Diff(actual, tc.expected); diff != "" { 302 t.Errorf("SplitCells() got unexpected diff (-have, +want):\n%s", diff) 303 } 304 }) 305 } 306 } 307 308 func TestConvertResult(t *testing.T) { 309 pint := func(v int64) *int64 { 310 return &v 311 } 312 pstr := func(s string) *string { 313 return &s 314 } 315 yes := true 316 now := time.Now().Unix() 317 cases := []struct { 318 name string 319 nameCfg nameConfig 320 id string 321 headers []string 322 result gcsResult 323 opt groupOptions 324 expected InflatedColumn 325 }{ 326 { 327 name: "basically works", 328 expected: InflatedColumn{ 329 Column: &statepb.Column{}, 330 Cells: map[string]Cell{ 331 "." + overallRow: { 332 Result: statuspb.TestStatus_FAIL, 333 Icon: "T", 334 Message: "Build did not complete within 24 hours", 335 }, 336 }, 337 }, 338 }, 339 { 340 name: "pass dynamic email list", 341 result: gcsResult{ 342 finished: gcs.Finished{ 343 Finished: metadata.Finished{ 344 Metadata: metadata.Metadata{ 345 EmailListKey: []string{"world"}, 346 }, 347 }, 348 }, 349 }, 350 expected: InflatedColumn{ 351 Column: &statepb.Column{ 352 EmailAddresses: []string{"world"}, 353 }, 354 Cells: map[string]Cell{ 355 "." + overallRow: { 356 Result: statuspb.TestStatus_FAIL, 357 Icon: "T", 358 Message: "Build did not complete within 24 hours", 359 }, 360 }, 361 }, 362 }, 363 { 364 name: "pass dynamic email list as list of interaces", 365 result: gcsResult{ 366 finished: gcs.Finished{ 367 Finished: metadata.Finished{ 368 Metadata: metadata.Metadata{ 369 EmailListKey: []interface{}{"world"}, 370 }, 371 }, 372 }, 373 }, 374 expected: InflatedColumn{ 375 Column: &statepb.Column{ 376 EmailAddresses: []string{"world"}, 377 }, 378 Cells: map[string]Cell{ 379 "." + overallRow: { 380 Result: statuspb.TestStatus_FAIL, 381 Icon: "T", 382 Message: "Build did not complete within 24 hours", 383 }, 384 }, 385 }, 386 }, 387 { 388 name: "multiple dynamic email list", 389 result: gcsResult{ 390 finished: gcs.Finished{ 391 Finished: metadata.Finished{ 392 Metadata: metadata.Metadata{ 393 EmailListKey: []string{"world", "olam"}, 394 }, 395 }, 396 }, 397 }, 398 expected: InflatedColumn{ 399 Column: &statepb.Column{ 400 EmailAddresses: []string{"world", "olam"}, 401 }, 402 Cells: map[string]Cell{ 403 "." + overallRow: { 404 Result: statuspb.TestStatus_FAIL, 405 Icon: "T", 406 Message: "Build did not complete within 24 hours", 407 }, 408 }, 409 }, 410 }, 411 { 412 name: "not a list", 413 result: gcsResult{ 414 finished: gcs.Finished{ 415 Finished: metadata.Finished{ 416 Metadata: metadata.Metadata{ 417 EmailListKey: "olam", 418 }, 419 }, 420 }, 421 }, 422 expected: InflatedColumn{ 423 Column: &statepb.Column{ 424 EmailAddresses: []string{}, 425 }, 426 Cells: map[string]Cell{ 427 "." + overallRow: { 428 Result: statuspb.TestStatus_FAIL, 429 Icon: "T", 430 Message: "Build did not complete within 24 hours", 431 }, 432 }, 433 }, 434 }, 435 { 436 name: "invalid email list", 437 result: gcsResult{ 438 finished: gcs.Finished{ 439 Finished: metadata.Finished{ 440 Metadata: metadata.Metadata{ 441 EmailListKey: []interface{}{1}, 442 }, 443 }, 444 }, 445 }, 446 expected: InflatedColumn{ 447 Column: &statepb.Column{ 448 EmailAddresses: []string{}, 449 }, 450 Cells: map[string]Cell{ 451 "." + overallRow: { 452 Result: statuspb.TestStatus_FAIL, 453 Icon: "T", 454 Message: "Build did not complete within 24 hours", 455 }, 456 }, 457 }, 458 }, 459 { 460 name: "correct column information", 461 headers: []string{"Commit", "hello", "spam", "do not have this one"}, 462 id: "hello", 463 result: gcsResult{ 464 started: gcs.Started{ 465 Started: metadata.Started{ 466 Timestamp: 300, 467 }, 468 }, 469 finished: gcs.Finished{ 470 Finished: metadata.Finished{ 471 Metadata: metadata.Metadata{ 472 "hello": "world", 473 "spam": "eggs", 474 metadata.JobVersion: "1.2.3", 475 }, 476 }, 477 }, 478 }, 479 expected: InflatedColumn{ 480 Column: &statepb.Column{ 481 Build: "hello", 482 Hint: "hello", 483 Started: 300 * 1000, 484 Extra: []string{ 485 "1.2.3", 486 "world", 487 "eggs", 488 "missing", 489 }, 490 }, 491 Cells: map[string]Cell{ 492 "." + overallRow: { 493 Result: statuspb.TestStatus_FAIL, 494 Icon: "T", 495 Message: "Build did not complete within 24 hours", 496 }, 497 }, 498 }, 499 }, 500 { 501 name: "running results do not have missing column headers", 502 headers: []string{"Commit", "hello", "spam", "do not have this one"}, 503 id: "hello", 504 result: gcsResult{ 505 started: gcs.Started{ 506 Started: metadata.Started{ 507 Timestamp: now, 508 }, 509 }, 510 finished: gcs.Finished{ 511 Finished: metadata.Finished{ 512 Metadata: metadata.Metadata{ 513 "hello": "world", 514 "spam": "eggs", 515 metadata.JobVersion: "1.2.3", 516 }, 517 }, 518 }, 519 }, 520 expected: InflatedColumn{ 521 Column: &statepb.Column{ 522 Build: "hello", 523 Hint: "hello", 524 Started: float64(now * 1000), 525 Extra: []string{ 526 "1.2.3", 527 "world", 528 "eggs", 529 "", // not missing 530 }, 531 }, 532 Cells: map[string]Cell{ 533 "." + overallRow: { 534 Result: statuspb.TestStatus_RUNNING, 535 Icon: "R", 536 Message: "Build still running...", 537 }, 538 }, 539 }, 540 }, 541 { 542 name: "add overall when multiJob", 543 id: "build", 544 nameCfg: nameConfig{ 545 format: "%s.%s", 546 parts: []string{jobName, testsName}, 547 multiJob: true, 548 }, 549 result: gcsResult{ 550 started: gcs.Started{ 551 Started: metadata.Started{ 552 Timestamp: now, 553 }, 554 }, 555 finished: gcs.Finished{ 556 Finished: metadata.Finished{ 557 Timestamp: pint(now + 1), 558 }, 559 }, 560 suites: []gcs.SuitesMeta{ 561 { 562 Suites: &junit.Suites{ 563 Suites: []junit.Suite{ 564 { 565 Name: "this", 566 Results: []junit.Result{ 567 { 568 Name: "that", 569 }, 570 }, 571 }, 572 }, 573 }, 574 }, 575 }, 576 job: "job-name", 577 }, 578 expected: InflatedColumn{ 579 Column: &statepb.Column{ 580 Started: float64(now * 1000), 581 Build: "build", 582 Hint: "build", 583 }, 584 Cells: map[string]Cell{ 585 overallRow: { 586 Result: statuspb.TestStatus_FAIL, 587 Icon: "F", 588 Message: "Build failed outside of test results", 589 Metrics: setElapsed(nil, 1), 590 CellID: "job-name/build", 591 }, 592 "job-name.Overall": { 593 Result: statuspb.TestStatus_FAIL, 594 Icon: "F", 595 Message: "Build failed outside of test results", 596 Metrics: setElapsed(nil, 1), 597 CellID: "job-name/build", 598 }, 599 "job-name.this.that": { 600 Result: statuspb.TestStatus_PASS, 601 CellID: "job-name/build", 602 }, 603 }, 604 }, 605 }, 606 { 607 name: "include job name upon request", 608 nameCfg: nameConfig{ 609 format: "%s.%s", 610 parts: []string{jobName, testsName}, 611 }, 612 result: gcsResult{ 613 started: gcs.Started{ 614 Started: metadata.Started{ 615 Timestamp: now, 616 }, 617 }, 618 finished: gcs.Finished{ 619 Finished: metadata.Finished{ 620 Timestamp: pint(now + 1), 621 }, 622 }, 623 suites: []gcs.SuitesMeta{ 624 { 625 Suites: &junit.Suites{ 626 Suites: []junit.Suite{ 627 { 628 Name: "this", 629 Results: []junit.Result{ 630 { 631 Name: "that", 632 }, 633 }, 634 }, 635 }, 636 }, 637 }, 638 }, 639 job: "job-name", 640 }, 641 expected: InflatedColumn{ 642 Column: &statepb.Column{ 643 Started: float64(now * 1000), 644 }, 645 Cells: map[string]Cell{ 646 "job-name." + overallRow: { 647 Result: statuspb.TestStatus_FAIL, 648 Icon: "F", 649 Message: "Build failed outside of test results", 650 Metrics: setElapsed(nil, 1), 651 }, 652 "job-name.this.that": { 653 Result: statuspb.TestStatus_PASS, 654 }, 655 }, 656 }, 657 }, 658 { 659 name: "failing job with only passing results has a failing overall message", 660 nameCfg: nameConfig{ 661 format: "%s", 662 parts: []string{testsName}, 663 }, 664 result: gcsResult{ 665 started: gcs.Started{ 666 Started: metadata.Started{ 667 Timestamp: now, 668 }, 669 }, 670 finished: gcs.Finished{ 671 Finished: metadata.Finished{ 672 Timestamp: pint(now + 1), 673 }, 674 }, 675 suites: []gcs.SuitesMeta{ 676 { 677 Suites: &junit.Suites{ 678 Suites: []junit.Suite{ 679 { 680 Name: "this", 681 Results: []junit.Result{ 682 { 683 Name: "that", 684 }, 685 }, 686 }, 687 }, 688 }, 689 }, 690 }, 691 }, 692 expected: InflatedColumn{ 693 Column: &statepb.Column{ 694 Started: float64(now * 1000), 695 }, 696 Cells: map[string]Cell{ 697 "." + overallRow: { 698 Result: statuspb.TestStatus_FAIL, 699 Icon: "F", 700 Message: "Build failed outside of test results", 701 Metrics: setElapsed(nil, 1), 702 }, 703 "this.that": { 704 Result: statuspb.TestStatus_PASS, 705 }, 706 }, 707 }, 708 }, 709 { 710 name: "result fields parsed properly", 711 nameCfg: nameConfig{ 712 format: "%s", 713 parts: []string{testsName}, 714 }, 715 result: gcsResult{ 716 started: gcs.Started{ 717 Started: metadata.Started{ 718 Timestamp: now, 719 }, 720 }, 721 finished: gcs.Finished{ 722 Finished: metadata.Finished{ 723 Timestamp: pint(now + 1), 724 }, 725 }, 726 suites: []gcs.SuitesMeta{ 727 { 728 Suites: &junit.Suites{ 729 Suites: []junit.Suite{ 730 { 731 Results: []junit.Result{ 732 { 733 Name: "elapsed", 734 Time: 5, 735 }, 736 { 737 Name: "failed no message", 738 Failure: &junit.Failure{Value: *pstr("")}, 739 }, 740 { 741 Name: "failed", 742 Failure: &junit.Failure{Value: *pstr("boom")}, 743 }, 744 { 745 Name: "failed other message", 746 Failure: &junit.Failure{Value: *pstr("")}, 747 Output: pstr("irrelevant message"), 748 }, 749 { 750 Name: "errored no message", 751 Failure: &junit.Failure{Value: *pstr("")}, 752 }, 753 { 754 Name: "errored", 755 Failure: &junit.Failure{Value: *pstr("oh no")}, 756 }, 757 { 758 Name: "invisible skip", 759 Skipped: &junit.Skipped{Value: *pstr("")}, 760 }, 761 { 762 Name: "visible skip", 763 Skipped: &junit.Skipped{Value: *pstr("tl;dr")}, 764 }, 765 { 766 Name: "stderr message", 767 Error: pstr("ouch"), 768 }, 769 { 770 Name: "stdout message", 771 Output: pstr("bellybutton"), 772 }, 773 }, 774 }, 775 }, 776 }, 777 }, 778 }, 779 }, 780 expected: InflatedColumn{ 781 Column: &statepb.Column{ 782 Started: float64(now * 1000), 783 }, 784 Cells: map[string]Cell{ 785 "." + overallRow: { 786 Result: statuspb.TestStatus_FAIL, 787 Metrics: setElapsed(nil, 1), 788 }, 789 "elapsed": { 790 Result: statuspb.TestStatus_PASS, 791 Metrics: setElapsed(nil, 5), 792 }, 793 "failed no message": { 794 Result: statuspb.TestStatus_FAIL, 795 }, 796 "failed": { 797 Message: "boom", 798 Result: statuspb.TestStatus_FAIL, 799 Icon: "F", 800 }, 801 "failed other message": { 802 Message: "irrelevant message", 803 Result: statuspb.TestStatus_FAIL, 804 Icon: "F", 805 }, 806 "errored no message": { 807 Result: statuspb.TestStatus_FAIL, 808 }, 809 "errored": { 810 Message: "oh no", 811 Result: statuspb.TestStatus_FAIL, 812 Icon: "F", 813 }, 814 // no invisible skip 815 "visible skip": { 816 Result: statuspb.TestStatus_PASS_WITH_SKIPS, 817 Message: "tl;dr", 818 Icon: "S", 819 }, 820 "stderr message": { 821 Message: "ouch", 822 Result: statuspb.TestStatus_PASS, 823 }, 824 "stdout message": { 825 Message: "bellybutton", 826 Result: statuspb.TestStatus_PASS, 827 }, 828 }, 829 }, 830 }, 831 { 832 name: "metricKey", 833 nameCfg: nameConfig{ 834 format: "%s", 835 parts: []string{testsName}, 836 }, 837 opt: groupOptions{ 838 metricKey: "food", 839 }, 840 result: gcsResult{ 841 started: gcs.Started{ 842 Started: metadata.Started{ 843 Timestamp: now, 844 }, 845 }, 846 finished: gcs.Finished{ 847 Finished: metadata.Finished{ 848 Timestamp: pint(now + 1), 849 }, 850 }, 851 suites: []gcs.SuitesMeta{ 852 { 853 Suites: &junit.Suites{ 854 Suites: []junit.Suite{ 855 { 856 Results: []junit.Result{ 857 { 858 Name: "no properties", 859 }, 860 { 861 Name: "missing property", 862 Properties: &junit.Properties{ 863 PropertyList: []junit.Property{ 864 {"random", "thing"}, 865 }, 866 }, 867 }, 868 { 869 Name: "not a number", 870 Properties: &junit.Properties{ 871 PropertyList: []junit.Property{ 872 {"food", "tasty"}, 873 }, 874 }, 875 }, 876 { 877 Name: "short number", 878 Properties: &junit.Properties{ 879 PropertyList: []junit.Property{ 880 {"food", "123"}, 881 }, 882 }, 883 }, 884 { 885 Name: "large number", 886 Properties: &junit.Properties{ 887 PropertyList: []junit.Property{ 888 {"food", "123456789"}, 889 }, 890 }, 891 }, 892 { 893 Name: "many digits", 894 Properties: &junit.Properties{ 895 PropertyList: []junit.Property{ 896 {"food", "1.567890"}, 897 }, 898 }, 899 }, 900 { 901 Name: "multiple values", 902 Properties: &junit.Properties{ 903 PropertyList: []junit.Property{ 904 {"food", "1"}, 905 {"food", "2"}, 906 {"food", "3"}, 907 {"food", "4"}, 908 }, 909 }, 910 }, 911 { 912 Name: "preceds failure message", 913 Properties: &junit.Properties{ 914 PropertyList: []junit.Property{ 915 {"food", "1"}, 916 }, 917 }, 918 Failure: &junit.Failure{Value: *pstr("boom")}, 919 }, 920 { 921 Name: "preceds skip message", 922 Properties: &junit.Properties{ 923 PropertyList: []junit.Property{ 924 {"food", "1"}, 925 }, 926 }, 927 Skipped: &junit.Skipped{Value: *pstr("tl;dr")}, 928 }, 929 }, 930 }, 931 }, 932 }, 933 }, 934 }, 935 }, 936 expected: InflatedColumn{ 937 Column: &statepb.Column{ 938 Started: float64(now * 1000), 939 }, 940 Cells: map[string]Cell{ 941 "." + overallRow: { 942 Result: statuspb.TestStatus_FAIL, 943 Metrics: setElapsed(nil, 1), 944 }, 945 "no properties": { 946 Result: statuspb.TestStatus_PASS, 947 }, 948 "missing property": { 949 Result: statuspb.TestStatus_PASS, 950 }, 951 "not a number": { 952 Result: statuspb.TestStatus_PASS, 953 }, 954 "short number": { 955 Result: statuspb.TestStatus_PASS, 956 Icon: "123", 957 Metrics: map[string]float64{ 958 "food": 123, 959 }, 960 }, 961 "large number": { 962 Result: statuspb.TestStatus_PASS, 963 Icon: "1.235e+08", 964 Metrics: map[string]float64{ 965 "food": 123456789, 966 }, 967 }, 968 "many digits": { 969 Result: statuspb.TestStatus_PASS, 970 Icon: "1.568", 971 Metrics: map[string]float64{ 972 "food": 1.567890, 973 }, 974 }, 975 "multiple values": { 976 Result: statuspb.TestStatus_PASS, 977 Icon: "2.5", 978 Metrics: map[string]float64{ 979 "food": 2.5, 980 }, 981 }, 982 "preceds failure message": { 983 Result: statuspb.TestStatus_FAIL, 984 Message: "boom", 985 Icon: "1", 986 Metrics: map[string]float64{ 987 "food": 1, 988 }, 989 }, 990 "preceds skip message": { 991 Result: statuspb.TestStatus_PASS_WITH_SKIPS, 992 Message: "tl;dr", 993 Icon: "1", 994 Metrics: map[string]float64{ 995 "food": 1, 996 }, 997 }, 998 }, 999 }, 1000 }, 1001 { 1002 name: "annotations", 1003 nameCfg: nameConfig{ 1004 format: "%s", 1005 parts: []string{testsName}, 1006 }, 1007 opt: groupOptions{ 1008 annotations: []*configpb.TestGroup_TestAnnotation{ 1009 { 1010 ShortText: "steak", 1011 ShortTextMessageSource: &configpb.TestGroup_TestAnnotation_PropertyName{ 1012 PropertyName: "fries", 1013 }, 1014 }, 1015 }, 1016 }, 1017 result: gcsResult{ 1018 started: gcs.Started{ 1019 Started: metadata.Started{ 1020 Timestamp: now, 1021 }, 1022 }, 1023 finished: gcs.Finished{ 1024 Finished: metadata.Finished{ 1025 Timestamp: pint(now + 1), 1026 Passed: &yes, 1027 }, 1028 }, 1029 suites: []gcs.SuitesMeta{ 1030 { 1031 Suites: &junit.Suites{ 1032 Suites: []junit.Suite{ 1033 { 1034 Results: []junit.Result{ 1035 { 1036 Name: "no properties", 1037 }, 1038 { 1039 Name: "missing property", 1040 Properties: &junit.Properties{ 1041 PropertyList: []junit.Property{ 1042 {"random", "thing"}, 1043 }, 1044 }, 1045 }, 1046 { 1047 Name: "present", 1048 Properties: &junit.Properties{ 1049 PropertyList: []junit.Property{ 1050 {"fries", "irrelevant"}, 1051 }, 1052 }, 1053 }, 1054 { 1055 Name: "empty", 1056 Properties: &junit.Properties{ 1057 PropertyList: []junit.Property{ 1058 {"fries", ""}, 1059 }, 1060 }, 1061 }, 1062 { 1063 Name: "multiple", 1064 Properties: &junit.Properties{ 1065 PropertyList: []junit.Property{ 1066 {"fries", "shoestring"}, 1067 {"fries", "curly"}, 1068 }, 1069 }, 1070 }, 1071 { 1072 Name: "annotation over failure", 1073 Failure: &junit.Failure{Value: *pstr("boom")}, 1074 Properties: &junit.Properties{ 1075 PropertyList: []junit.Property{ 1076 {"fries", "irrelevant"}, 1077 }, 1078 }, 1079 }, 1080 { 1081 Name: "annotation over error", 1082 Errored: &junit.Errored{Value: *pstr("boom")}, 1083 Properties: &junit.Properties{ 1084 PropertyList: []junit.Property{ 1085 {"fries", "irrelevant"}, 1086 }, 1087 }, 1088 }, 1089 { 1090 Name: "annotation over skip", 1091 Skipped: &junit.Skipped{Value: *pstr("boom")}, 1092 Properties: &junit.Properties{ 1093 PropertyList: []junit.Property{ 1094 {"fries", "irrelevant"}, 1095 }, 1096 }, 1097 }, 1098 }, 1099 }, 1100 }, 1101 }, 1102 }, 1103 }, 1104 }, 1105 expected: InflatedColumn{ 1106 Column: &statepb.Column{ 1107 Started: float64(now * 1000), 1108 }, 1109 Cells: map[string]Cell{ 1110 "." + overallRow: { 1111 Result: statuspb.TestStatus_PASS, 1112 Metrics: setElapsed(nil, 1), 1113 }, 1114 "no properties": { 1115 Result: statuspb.TestStatus_PASS, 1116 }, 1117 "missing property": { 1118 Result: statuspb.TestStatus_PASS, 1119 }, 1120 "present": { 1121 Result: statuspb.TestStatus_PASS, 1122 Icon: "steak", 1123 }, 1124 "empty": { 1125 Result: statuspb.TestStatus_PASS, 1126 Icon: "steak", 1127 }, 1128 "multiple": { 1129 Result: statuspb.TestStatus_PASS, 1130 Icon: "steak", 1131 }, 1132 "annotation over failure": { 1133 Result: statuspb.TestStatus_FAIL, 1134 Icon: "steak", 1135 Message: "boom", 1136 }, 1137 "annotation over error": { 1138 Result: statuspb.TestStatus_FAIL, 1139 Icon: "steak", 1140 Message: "boom", 1141 }, 1142 "annotation over skip": { 1143 Result: statuspb.TestStatus_PASS_WITH_SKIPS, 1144 Icon: "steak", 1145 Message: "boom", 1146 }, 1147 }, 1148 }, 1149 }, 1150 { 1151 name: "userKey", 1152 nameCfg: nameConfig{ 1153 format: "%s", 1154 parts: []string{testsName}, 1155 }, 1156 opt: groupOptions{ 1157 userKey: "fries", 1158 }, 1159 result: gcsResult{ 1160 started: gcs.Started{ 1161 Started: metadata.Started{ 1162 Timestamp: now, 1163 }, 1164 }, 1165 finished: gcs.Finished{ 1166 Finished: metadata.Finished{ 1167 Timestamp: pint(now + 1), 1168 Passed: &yes, 1169 }, 1170 }, 1171 suites: []gcs.SuitesMeta{ 1172 { 1173 Suites: &junit.Suites{ 1174 Suites: []junit.Suite{ 1175 { 1176 Results: []junit.Result{ 1177 { 1178 Name: "no properties", 1179 }, 1180 { 1181 Name: "missing property", 1182 Properties: &junit.Properties{ 1183 PropertyList: []junit.Property{ 1184 {"random", "thing"}, 1185 }, 1186 }, 1187 }, 1188 { 1189 Name: "present", 1190 Properties: &junit.Properties{ 1191 PropertyList: []junit.Property{ 1192 {"fries", "curly"}, 1193 }, 1194 }, 1195 }, 1196 { 1197 Name: "choose first", 1198 Properties: &junit.Properties{ 1199 PropertyList: []junit.Property{ 1200 {"fries", "shoestring"}, 1201 {"fries", "curly"}, 1202 }, 1203 }, 1204 }, 1205 }, 1206 }, 1207 }, 1208 }, 1209 }, 1210 }, 1211 }, 1212 expected: InflatedColumn{ 1213 Column: &statepb.Column{ 1214 Started: float64(now * 1000), 1215 }, 1216 Cells: map[string]Cell{ 1217 "." + overallRow: { 1218 Result: statuspb.TestStatus_PASS, 1219 Metrics: setElapsed(nil, 1), 1220 }, 1221 "no properties": { 1222 Result: statuspb.TestStatus_PASS, 1223 }, 1224 "missing property": { 1225 Result: statuspb.TestStatus_PASS, 1226 }, 1227 "present": { 1228 Result: statuspb.TestStatus_PASS, 1229 UserProperty: "curly", 1230 }, 1231 "choose first": { 1232 Result: statuspb.TestStatus_PASS, 1233 UserProperty: "shoestring", 1234 }, 1235 }, 1236 }, 1237 }, 1238 { 1239 name: "names formatted correctly", 1240 nameCfg: nameConfig{ 1241 format: "%s - %s [%s] (%s)", 1242 parts: []string{testsName, "extra", "part", "property"}, 1243 }, 1244 result: gcsResult{ 1245 started: gcs.Started{ 1246 Started: metadata.Started{ 1247 Timestamp: now, 1248 }, 1249 }, 1250 finished: gcs.Finished{ 1251 Finished: metadata.Finished{ 1252 Timestamp: pint(now + 1), 1253 Passed: &yes, 1254 }, 1255 }, 1256 suites: []gcs.SuitesMeta{ 1257 { 1258 Suites: &junit.Suites{ 1259 Suites: []junit.Suite{ 1260 { 1261 Results: []junit.Result{ 1262 { 1263 Name: "elapsed", 1264 Properties: &junit.Properties{ 1265 PropertyList: []junit.Property{ 1266 {"property", "good-property"}, 1267 {"ignore", "me"}, 1268 }, 1269 }, 1270 }, 1271 }, 1272 }, 1273 }, 1274 }, 1275 Metadata: map[string]string{ 1276 "extra": "first", 1277 "part": "second", 1278 }, 1279 }, 1280 { 1281 Suites: &junit.Suites{ 1282 Suites: []junit.Suite{ 1283 { 1284 Results: []junit.Result{ 1285 { 1286 Name: "other", 1287 Properties: &junit.Properties{ 1288 PropertyList: []junit.Property{ 1289 // "property" missing 1290 {"ignore", "me"}, 1291 }, 1292 }, 1293 }, 1294 }, 1295 }, 1296 }, 1297 }, 1298 Metadata: map[string]string{ 1299 "extra": "hey", 1300 // part missing 1301 }, 1302 }, 1303 }, 1304 }, 1305 expected: InflatedColumn{ 1306 Column: &statepb.Column{ 1307 Started: float64(now * 1000), 1308 }, 1309 Cells: map[string]Cell{ 1310 "." + overallRow: { 1311 Result: statuspb.TestStatus_PASS, 1312 Metrics: setElapsed(nil, 1), 1313 }, 1314 "elapsed - first [second] (good-property)": { 1315 Result: statuspb.TestStatus_PASS, 1316 }, 1317 "other - hey [] ()": { 1318 Result: statuspb.TestStatus_PASS, 1319 }, 1320 }, 1321 }, 1322 }, 1323 { 1324 name: "duplicate row names can be merged", 1325 nameCfg: nameConfig{ 1326 format: "%s - %s", 1327 parts: []string{testsName, "extra"}, 1328 }, 1329 opt: groupOptions{ 1330 merge: true, 1331 }, 1332 result: gcsResult{ 1333 started: gcs.Started{ 1334 Started: metadata.Started{ 1335 Timestamp: now, 1336 }, 1337 }, 1338 finished: gcs.Finished{ 1339 Finished: metadata.Finished{ 1340 Timestamp: pint(now + 1), 1341 Passed: &yes, 1342 }, 1343 }, 1344 suites: []gcs.SuitesMeta{ 1345 { 1346 Suites: &junit.Suites{ 1347 Suites: []junit.Suite{ 1348 { 1349 Results: []junit.Result{ 1350 { 1351 Name: "same", 1352 Time: 1, 1353 }, 1354 { 1355 Name: "same", 1356 Time: 2, 1357 }, 1358 }, 1359 }, 1360 }, 1361 }, 1362 Metadata: map[string]string{ 1363 "extra": "same", 1364 }, 1365 }, 1366 { 1367 Suites: &junit.Suites{ 1368 Suites: []junit.Suite{ 1369 { 1370 Results: []junit.Result{ 1371 { 1372 Name: "same", 1373 Time: 3, 1374 Failure: &junit.Failure{Value: *pstr("ugh")}, 1375 }, 1376 }, 1377 }, 1378 }, 1379 }, 1380 Metadata: map[string]string{ 1381 "extra": "same", 1382 }, 1383 }, 1384 }, 1385 }, 1386 expected: InflatedColumn{ 1387 Column: &statepb.Column{ 1388 Started: float64(now * 1000), 1389 }, 1390 Cells: map[string]Cell{ 1391 "." + overallRow: { 1392 Result: statuspb.TestStatus_PASS, 1393 Metrics: setElapsed(nil, 1), 1394 }, 1395 "same - same": { 1396 Result: statuspb.TestStatus_FLAKY, 1397 Icon: "2/3", 1398 Message: "2/3 runs passed: ugh", 1399 Metrics: setElapsed(nil, 2), // mean 1400 }, 1401 }, 1402 }, 1403 }, 1404 { 1405 name: "duplicate row names can be disambiguated", 1406 nameCfg: nameConfig{ 1407 format: "%s - %s", 1408 parts: []string{testsName, "extra"}, 1409 }, 1410 result: gcsResult{ 1411 started: gcs.Started{ 1412 Started: metadata.Started{ 1413 Timestamp: now, 1414 }, 1415 }, 1416 finished: gcs.Finished{ 1417 Finished: metadata.Finished{ 1418 Timestamp: pint(now + 1), 1419 Passed: &yes, 1420 }, 1421 }, 1422 suites: []gcs.SuitesMeta{ 1423 { 1424 Suites: &junit.Suites{ 1425 Suites: []junit.Suite{ 1426 { 1427 Results: []junit.Result{ 1428 { 1429 Name: "same", 1430 Time: 1, 1431 }, 1432 { 1433 Name: "same", 1434 Time: 2, 1435 }, 1436 }, 1437 }, 1438 }, 1439 }, 1440 Metadata: map[string]string{ 1441 "extra": "same", 1442 }, 1443 }, 1444 { 1445 Suites: &junit.Suites{ 1446 Suites: []junit.Suite{ 1447 { 1448 Results: []junit.Result{ 1449 { 1450 Name: "same", 1451 Time: 3, 1452 }, 1453 }, 1454 }, 1455 }, 1456 }, 1457 Metadata: map[string]string{ 1458 "extra": "same", 1459 }, 1460 }, 1461 }, 1462 }, 1463 expected: InflatedColumn{ 1464 Column: &statepb.Column{ 1465 Started: float64(now * 1000), 1466 }, 1467 Cells: map[string]Cell{ 1468 "." + overallRow: { 1469 Result: statuspb.TestStatus_PASS, 1470 Metrics: setElapsed(nil, 1), 1471 }, 1472 "same - same": { 1473 Result: statuspb.TestStatus_PASS, 1474 Metrics: setElapsed(nil, 1), 1475 }, 1476 "same - same [1]": { 1477 Result: statuspb.TestStatus_PASS, 1478 Metrics: setElapsed(nil, 2), 1479 }, 1480 "same - same [2]": { 1481 Result: statuspb.TestStatus_PASS, 1482 Metrics: setElapsed(nil, 3), 1483 }, 1484 }, 1485 }, 1486 }, 1487 { 1488 name: "excessively duplicated rows overflows", 1489 nameCfg: nameConfig{ 1490 format: "%s", 1491 parts: []string{testsName}, 1492 }, 1493 result: gcsResult{ 1494 started: gcs.Started{ 1495 Started: metadata.Started{ 1496 Timestamp: now, 1497 }, 1498 }, 1499 finished: gcs.Finished{ 1500 Finished: metadata.Finished{ 1501 Timestamp: pint(now + 1), 1502 Passed: &yes, 1503 }, 1504 }, 1505 suites: []gcs.SuitesMeta{ 1506 { 1507 Suites: &junit.Suites{ 1508 Suites: []junit.Suite{ 1509 { 1510 Results: func() []junit.Result { 1511 var out []junit.Result 1512 under := junit.Result{Name: "under"} 1513 max := junit.Result{Name: "max"} 1514 over := junit.Result{Name: "over"} 1515 for i := 0; i < maxDuplicates; i++ { 1516 under.Time = float64(i) 1517 max.Time = float64(i) 1518 over.Time = float64(i) 1519 out = append(out, under, max, over) 1520 } 1521 max.Time = maxDuplicates 1522 over.Time = maxDuplicates 1523 out = append(out, max, over) 1524 over.Time++ 1525 out = append(out, over) 1526 return out 1527 }(), 1528 }, 1529 }, 1530 }, 1531 Metadata: map[string]string{ 1532 "extra": "same", 1533 }, 1534 }, 1535 }, 1536 }, 1537 expected: InflatedColumn{ 1538 Column: &statepb.Column{ 1539 Started: float64(now * 1000), 1540 }, 1541 Cells: func() map[string]Cell { 1542 out := map[string]Cell{ 1543 "." + overallRow: { 1544 Result: statuspb.TestStatus_PASS, 1545 Metrics: setElapsed(nil, 1), 1546 }, 1547 } 1548 under := Cell{Result: statuspb.TestStatus_PASS} 1549 max := Cell{Result: statuspb.TestStatus_PASS} 1550 over := Cell{Result: statuspb.TestStatus_PASS} 1551 out["under"] = under 1552 out["max"] = max 1553 out["over"] = over 1554 for i := 1; i < maxDuplicates; i++ { 1555 t := float64(i) 1556 under.Metrics = setElapsed(nil, t) 1557 out[fmt.Sprintf("under [%d]", i)] = under 1558 max.Metrics = setElapsed(nil, t) 1559 out[fmt.Sprintf("max [%d]", i)] = max 1560 over.Metrics = setElapsed(nil, t) 1561 out[fmt.Sprintf("over [%d]", i)] = over 1562 } 1563 max.Metrics = setElapsed(nil, maxDuplicates) 1564 out[`max [overflow]`] = overflowCell 1565 out[`over [overflow]`] = overflowCell 1566 return out 1567 }(), 1568 }, 1569 }, 1570 { 1571 name: "can add missing podInfo", 1572 opt: groupOptions{ 1573 analyzeProwJob: true, 1574 }, 1575 result: gcsResult{ 1576 started: gcs.Started{ 1577 Started: metadata.Started{ 1578 Timestamp: now, 1579 }, 1580 }, 1581 finished: gcs.Finished{ 1582 Finished: metadata.Finished{ 1583 Timestamp: pint(now + 1), 1584 Passed: &yes, 1585 }, 1586 }, 1587 }, 1588 expected: InflatedColumn{ 1589 Column: &statepb.Column{ 1590 Started: float64(now * 1000), 1591 }, 1592 Cells: map[string]Cell{ 1593 "." + overallRow: { 1594 Result: statuspb.TestStatus_PASS, 1595 Metrics: setElapsed(nil, 1), 1596 }, 1597 "." + podInfoRow: podInfoMissingCell, 1598 }, 1599 }, 1600 }, 1601 { 1602 name: "can add interesting pod info", 1603 opt: groupOptions{ 1604 analyzeProwJob: true, 1605 }, 1606 result: gcsResult{ 1607 started: gcs.Started{ 1608 Started: metadata.Started{ 1609 Timestamp: now, 1610 }, 1611 }, 1612 finished: gcs.Finished{ 1613 Finished: metadata.Finished{ 1614 Timestamp: pint(now + 1), 1615 Passed: &yes, 1616 }, 1617 }, 1618 podInfo: gcs.PodInfo{ 1619 Pod: &core.Pod{ 1620 Status: core.PodStatus{Phase: core.PodSucceeded}, 1621 }, 1622 }, 1623 }, 1624 expected: InflatedColumn{ 1625 Column: &statepb.Column{ 1626 Started: float64(now * 1000), 1627 }, 1628 Cells: map[string]Cell{ 1629 "." + overallRow: { 1630 Result: statuspb.TestStatus_PASS, 1631 Metrics: setElapsed(nil, 1), 1632 }, 1633 "." + podInfoRow: podInfoPassCell, 1634 }, 1635 }, 1636 }, 1637 { 1638 name: "do not add missing podinfo when still running", 1639 opt: groupOptions{ 1640 analyzeProwJob: true, 1641 }, 1642 result: gcsResult{ 1643 started: gcs.Started{ 1644 Started: metadata.Started{ 1645 Timestamp: now, 1646 }, 1647 }, 1648 }, 1649 expected: InflatedColumn{ 1650 Column: &statepb.Column{ 1651 Started: float64(now * 1000), 1652 }, 1653 Cells: map[string]Cell{ 1654 "." + overallRow: { 1655 Result: statuspb.TestStatus_RUNNING, 1656 Icon: "R", 1657 Message: "Build still running...", 1658 }, 1659 }, 1660 }, 1661 }, 1662 { 1663 name: "add intersting podinfo even still running", 1664 opt: groupOptions{ 1665 analyzeProwJob: true, 1666 }, 1667 result: gcsResult{ 1668 started: gcs.Started{ 1669 Started: metadata.Started{ 1670 Timestamp: now, 1671 }, 1672 }, 1673 podInfo: gcs.PodInfo{ 1674 Pod: &core.Pod{ 1675 Status: core.PodStatus{Phase: core.PodSucceeded}, 1676 }, 1677 }, 1678 }, 1679 expected: InflatedColumn{ 1680 Column: &statepb.Column{ 1681 Started: float64(now * 1000), 1682 }, 1683 Cells: map[string]Cell{ 1684 "." + overallRow: { 1685 Result: statuspb.TestStatus_RUNNING, 1686 Icon: "R", 1687 Message: "Build still running...", 1688 }, 1689 "." + podInfoRow: podInfoPassCell, 1690 }, 1691 }, 1692 }, 1693 { 1694 name: "addCellID", 1695 nameCfg: nameConfig{ 1696 format: "%s", 1697 parts: []string{testsName}, 1698 }, 1699 opt: groupOptions{ 1700 addCellID: true, 1701 }, 1702 result: gcsResult{ 1703 started: gcs.Started{ 1704 Started: metadata.Started{ 1705 Timestamp: now, 1706 }, 1707 }, 1708 finished: gcs.Finished{ 1709 Finished: metadata.Finished{ 1710 Timestamp: pint(now + 1), 1711 Passed: &yes, 1712 }, 1713 }, 1714 suites: []gcs.SuitesMeta{ 1715 { 1716 Suites: &junit.Suites{ 1717 Suites: []junit.Suite{ 1718 { 1719 Name: "this", 1720 Results: []junit.Result{ 1721 { 1722 Name: "that", 1723 }, 1724 }, 1725 }, 1726 }, 1727 }, 1728 }, 1729 }, 1730 }, 1731 id: "McLovin", 1732 expected: InflatedColumn{ 1733 Column: &statepb.Column{ 1734 Started: float64(now * 1000), 1735 Build: "McLovin", 1736 Hint: "McLovin", 1737 }, 1738 Cells: map[string]Cell{ 1739 "." + overallRow: { 1740 Result: statuspb.TestStatus_PASS, 1741 Metrics: setElapsed(nil, 1), 1742 CellID: "McLovin", 1743 }, 1744 "this.that": { 1745 Result: statuspb.TestStatus_PASS, 1746 CellID: "McLovin", 1747 }, 1748 }, 1749 }, 1750 }, 1751 { 1752 name: "override build key", 1753 nameCfg: nameConfig{ 1754 format: "%s", 1755 parts: []string{testsName}, 1756 }, 1757 opt: groupOptions{ 1758 buildKey: "my-build-id", 1759 }, 1760 result: gcsResult{ 1761 started: gcs.Started{ 1762 Started: metadata.Started{ 1763 Timestamp: now, 1764 }, 1765 }, 1766 finished: gcs.Finished{ 1767 Finished: metadata.Finished{ 1768 Metadata: metadata.Metadata{ 1769 "my-build-id": "id-1", 1770 }, 1771 Timestamp: pint(now + 1), 1772 Passed: &yes, 1773 }, 1774 }, 1775 }, 1776 id: "McLovin", 1777 expected: InflatedColumn{ 1778 Column: &statepb.Column{ 1779 Started: float64(now * 1000), 1780 Build: "id-1", 1781 Hint: "McLovin", 1782 }, 1783 Cells: map[string]Cell{ 1784 "." + overallRow: { 1785 Result: statuspb.TestStatus_PASS, 1786 Metrics: setElapsed(nil, 1), 1787 }, 1788 }, 1789 }, 1790 }, 1791 { 1792 name: "override build key missing", 1793 nameCfg: nameConfig{ 1794 format: "%s", 1795 parts: []string{testsName}, 1796 }, 1797 opt: groupOptions{ 1798 buildKey: "my-build-id", 1799 }, 1800 result: gcsResult{ 1801 started: gcs.Started{ 1802 Started: metadata.Started{ 1803 Timestamp: now, 1804 }, 1805 }, 1806 finished: gcs.Finished{ 1807 Finished: metadata.Finished{ 1808 Metadata: metadata.Metadata{ 1809 "something": "hello", 1810 }, 1811 Timestamp: pint(now + 1), 1812 Passed: &yes, 1813 }, 1814 }, 1815 }, 1816 id: "McLovin", 1817 expected: InflatedColumn{ 1818 Column: &statepb.Column{ 1819 Started: float64(now * 1000), 1820 Build: "", 1821 Hint: "McLovin", 1822 }, 1823 Cells: map[string]Cell{ 1824 "." + overallRow: { 1825 Result: statuspb.TestStatus_PASS, 1826 Metrics: setElapsed(nil, 1), 1827 }, 1828 }, 1829 }, 1830 }, 1831 { 1832 name: "Ginkgo V2 skipped with message", 1833 nameCfg: nameConfig{ 1834 format: "%s", 1835 parts: []string{testsName}, 1836 }, 1837 result: gcsResult{ 1838 started: gcs.Started{ 1839 Started: metadata.Started{ 1840 Timestamp: now, 1841 }, 1842 }, 1843 finished: gcs.Finished{ 1844 Finished: metadata.Finished{ 1845 Timestamp: pint(now + 1), 1846 Passed: &yes, 1847 }, 1848 }, 1849 suites: []gcs.SuitesMeta{ 1850 { 1851 Suites: &junit.Suites{ 1852 Suites: []junit.Suite{ 1853 { 1854 Results: []junit.Result{ 1855 { 1856 Name: "visible skip non-default msg", 1857 Skipped: &junit.Skipped{Message: *pstr("non-default message")}, 1858 }, 1859 { 1860 Name: "invisible skip default msg", 1861 Skipped: &junit.Skipped{Message: *pstr("skipped")}, 1862 }, 1863 { 1864 Name: "invisible skip msg empty", 1865 Skipped: &junit.Skipped{Message: *pstr("")}, 1866 }, 1867 }, 1868 }, 1869 }, 1870 }, 1871 }, 1872 }, 1873 }, 1874 expected: InflatedColumn{ 1875 Column: &statepb.Column{ 1876 Started: float64(now * 1000), 1877 }, 1878 Cells: map[string]Cell{ 1879 "." + overallRow: { 1880 Result: statuspb.TestStatus_PASS, 1881 Metrics: setElapsed(nil, 1), 1882 }, 1883 "visible skip non-default msg": { 1884 Result: statuspb.TestStatus_PASS_WITH_SKIPS, 1885 Message: "non-default message", 1886 Icon: "S", 1887 }, 1888 }, 1889 }, 1890 }, 1891 { 1892 name: "ignore_skip works", 1893 opt: groupOptions{ 1894 ignoreSkip: true, 1895 }, 1896 result: gcsResult{ 1897 started: gcs.Started{ 1898 Started: metadata.Started{ 1899 Timestamp: now, 1900 }, 1901 }, 1902 finished: gcs.Finished{ 1903 Finished: metadata.Finished{ 1904 Timestamp: pint(now + 1), 1905 Passed: &yes, 1906 }, 1907 }, 1908 suites: []gcs.SuitesMeta{ 1909 { 1910 Suites: &junit.Suites{ 1911 Suites: []junit.Suite{ 1912 { 1913 Results: []junit.Result{ 1914 { 1915 Name: "visible skip non-default msg", 1916 Skipped: &junit.Skipped{Message: *pstr("non-default message")}, 1917 }, 1918 }, 1919 }, 1920 }, 1921 }, 1922 }, 1923 }, 1924 }, 1925 expected: InflatedColumn{ 1926 Column: &statepb.Column{ 1927 Started: float64(now * 1000), 1928 }, 1929 Cells: map[string]Cell{ 1930 "." + overallRow: { 1931 Result: statuspb.TestStatus_PASS, 1932 Metrics: setElapsed(nil, 1), 1933 }, 1934 }, 1935 }, 1936 }, 1937 } 1938 for _, tc := range cases { 1939 t.Run(tc.name, func(t *testing.T) { 1940 log := logrus.WithField("test name", tc.name) 1941 actual := convertResult(log, tc.nameCfg, tc.id, tc.headers, tc.result, tc.opt) 1942 if diff := cmp.Diff(tc.expected, actual, protocmp.Transform()); diff != "" { 1943 t.Errorf("convertResult() got unexpected diff (-want +got):\n%s", diff) 1944 } 1945 }) 1946 } 1947 } 1948 1949 func TestIgnoreStatus(t *testing.T) { 1950 cases := []struct { 1951 opt groupOptions 1952 status statuspb.TestStatus 1953 want bool 1954 }{ 1955 { 1956 opt: groupOptions{}, 1957 status: statuspb.TestStatus_NO_RESULT, 1958 want: true, 1959 }, 1960 { 1961 opt: groupOptions{}, 1962 status: statuspb.TestStatus_PASS, 1963 want: false, 1964 }, 1965 { 1966 opt: groupOptions{}, 1967 status: statuspb.TestStatus_PASS_WITH_SKIPS, 1968 want: false, 1969 }, 1970 { 1971 opt: groupOptions{}, 1972 status: statuspb.TestStatus_RUNNING, 1973 want: false, 1974 }, 1975 { 1976 opt: groupOptions{ignoreSkip: true}, 1977 status: statuspb.TestStatus_PASS, 1978 want: false, 1979 }, 1980 { 1981 opt: groupOptions{ignoreSkip: true}, 1982 status: statuspb.TestStatus_PASS_WITH_SKIPS, 1983 want: true, 1984 }, 1985 { 1986 opt: groupOptions{ignoreSkip: true}, 1987 status: statuspb.TestStatus_RUNNING, 1988 want: false, 1989 }, 1990 } 1991 for _, tc := range cases { 1992 t.Run(fmt.Sprintf("ignoreStatus(%+v, %v)", tc.opt, tc.status), func(t *testing.T) { 1993 if got := ignoreStatus(tc.opt, tc.status); got != tc.want { 1994 t.Errorf("ignoreStatus(%v, %v) got %v, want %v", tc.opt, tc.status, got, tc.want) 1995 } 1996 }) 1997 } 1998 } 1999 2000 func TestPodInfoCell(t *testing.T) { 2001 now := time.Now().Add(-time.Second) 2002 2003 cases := []struct { 2004 name string 2005 result gcsResult 2006 expected Cell 2007 }{ 2008 { 2009 name: "basically works", 2010 result: gcsResult{ 2011 started: gcs.Started{ 2012 Started: metadata.Started{ 2013 Timestamp: now.Unix(), 2014 }, 2015 }, 2016 }, 2017 expected: podInfoMissingCell, 2018 }, 2019 { 2020 name: "wait for pod from running pod", 2021 result: gcsResult{ 2022 finished: gcs.Finished{ 2023 Finished: metadata.Finished{ 2024 Timestamp: func() *int64 { 2025 when := now.Unix() 2026 return &when 2027 }(), 2028 }, 2029 }, 2030 }, 2031 expected: podInfoMissingCell, 2032 }, 2033 { 2034 name: "wait for pod info from finished pod", 2035 result: gcsResult{ 2036 started: gcs.Started{ 2037 Started: metadata.Started{ 2038 Timestamp: now.Add(-24 * time.Hour).Unix(), 2039 }, 2040 }, 2041 }, 2042 expected: func() Cell { 2043 c := podInfoMissingCell 2044 c.Result = statuspb.TestStatus_PASS_WITH_SKIPS 2045 return c 2046 }(), 2047 }, 2048 { 2049 name: "finished pod without podinfo", 2050 result: gcsResult{ 2051 finished: gcs.Finished{ 2052 Finished: metadata.Finished{ 2053 Timestamp: func() *int64 { 2054 when := now.Add(-time.Hour).Unix() 2055 return &when 2056 }(), 2057 }, 2058 }, 2059 }, 2060 expected: func() Cell { 2061 c := podInfoMissingCell 2062 c.Result = statuspb.TestStatus_PASS_WITH_SKIPS 2063 return c 2064 }(), 2065 }, 2066 { 2067 name: "passing pod", 2068 result: gcsResult{ 2069 podInfo: podInfoSuccessPodInfo, 2070 }, 2071 expected: podInfoPassCell, 2072 }, 2073 { 2074 name: "no pod utils", 2075 result: gcsResult{ 2076 podInfo: gcs.PodInfo{Pod: &core.Pod{}}, 2077 }, 2078 expected: Cell{ 2079 Message: gcs.NoPodUtils, 2080 Icon: "E", 2081 Result: statuspb.TestStatus_PASS, 2082 }, 2083 }, 2084 { 2085 name: "pod failure", 2086 result: gcsResult{ 2087 podInfo: gcs.PodInfo{ 2088 Pod: &core.Pod{ 2089 Status: core.PodStatus{ 2090 Conditions: []core.PodCondition{ 2091 { 2092 Type: core.PodScheduled, 2093 Status: core.ConditionFalse, 2094 Message: "hi there", 2095 }, 2096 }, 2097 }, 2098 }, 2099 }, 2100 }, 2101 expected: Cell{ 2102 Message: "pod did not schedule: hi there", 2103 Icon: "F", 2104 Result: statuspb.TestStatus_FAIL, 2105 }, 2106 }, 2107 } 2108 2109 for _, tc := range cases { 2110 t.Run(tc.name, func(t *testing.T) { 2111 actual := podInfoCell(tc.result) 2112 if diff := cmp.Diff(actual, tc.expected); diff != "" { 2113 t.Errorf("podInfoCell(%v) got unexpected diff (-have, +want):\n%s", tc.result, diff) 2114 } 2115 }) 2116 } 2117 } 2118 2119 func TestOverallCell(t *testing.T) { 2120 pint := func(v int64) *int64 { 2121 return &v 2122 } 2123 yes := true 2124 var no bool 2125 cases := []struct { 2126 name string 2127 result gcsResult 2128 expected Cell 2129 }{ 2130 { 2131 name: "result timed out", 2132 result: gcsResult{ 2133 started: gcs.Started{ 2134 Started: metadata.Started{ 2135 Timestamp: time.Now().Add(-25 * time.Hour).Unix(), 2136 }, 2137 }, 2138 }, 2139 expected: Cell{ 2140 Result: statuspb.TestStatus_FAIL, 2141 Message: "Build did not complete within 24 hours", 2142 Icon: "T", 2143 }, 2144 }, 2145 { 2146 name: "missing results fail", 2147 result: gcsResult{ 2148 started: gcs.Started{ 2149 Started: metadata.Started{ 2150 Timestamp: 100, 2151 }, 2152 }, 2153 finished: gcs.Finished{ 2154 Finished: metadata.Finished{ 2155 Timestamp: pint(250), 2156 Passed: &yes, 2157 }, 2158 }, 2159 malformed: []string{ 2160 "podinfo.json", 2161 }, 2162 }, 2163 expected: Cell{ 2164 Result: statuspb.TestStatus_FAIL, 2165 Message: "Malformed artifacts: podinfo.json", 2166 Icon: "E", 2167 }, 2168 }, 2169 { 2170 name: "passed result passes", 2171 result: gcsResult{ 2172 started: gcs.Started{ 2173 Started: metadata.Started{ 2174 Timestamp: 100, 2175 }, 2176 }, 2177 finished: gcs.Finished{ 2178 Finished: metadata.Finished{ 2179 Timestamp: pint(250), 2180 Passed: &yes, 2181 }, 2182 }, 2183 }, 2184 expected: Cell{ 2185 Result: statuspb.TestStatus_PASS, 2186 Metrics: setElapsed(nil, 150), 2187 }, 2188 }, 2189 { 2190 name: "failed via deprecated result", 2191 result: gcsResult{ 2192 started: gcs.Started{ 2193 Started: metadata.Started{ 2194 Timestamp: 100, 2195 }, 2196 }, 2197 finished: gcs.Finished{ 2198 Finished: metadata.Finished{ 2199 Timestamp: pint(250), 2200 Result: "BOOM", 2201 }, 2202 }, 2203 }, 2204 expected: Cell{ 2205 Result: statuspb.TestStatus_FAIL, 2206 Icon: "E", 2207 Message: `finished.json missing "passed": false`, 2208 Metrics: setElapsed(nil, 150), 2209 }, 2210 }, 2211 { 2212 name: "passed via deprecated result", 2213 result: gcsResult{ 2214 started: gcs.Started{ 2215 Started: metadata.Started{ 2216 Timestamp: 100, 2217 }, 2218 }, 2219 finished: gcs.Finished{ 2220 Finished: metadata.Finished{ 2221 Timestamp: pint(250), 2222 Result: "SUCCESS", 2223 }, 2224 }, 2225 }, 2226 expected: Cell{ 2227 Result: statuspb.TestStatus_PASS, 2228 Icon: "E", 2229 Message: `finished.json missing "passed": true`, 2230 Metrics: setElapsed(nil, 150), 2231 }, 2232 }, 2233 { 2234 name: "failed result fails", 2235 result: gcsResult{ 2236 started: gcs.Started{ 2237 Started: metadata.Started{ 2238 Timestamp: 100, 2239 }, 2240 }, 2241 finished: gcs.Finished{ 2242 Finished: metadata.Finished{ 2243 Timestamp: pint(250), 2244 Passed: &no, 2245 }, 2246 }, 2247 }, 2248 expected: Cell{ 2249 Result: statuspb.TestStatus_FAIL, 2250 Metrics: setElapsed(nil, 150), 2251 }, 2252 }, 2253 { 2254 name: "missing passed field is a failure", 2255 result: gcsResult{ 2256 started: gcs.Started{ 2257 Started: metadata.Started{ 2258 Timestamp: 100, 2259 }, 2260 }, 2261 finished: gcs.Finished{ 2262 Finished: metadata.Finished{ 2263 Timestamp: pint(250), 2264 }, 2265 }, 2266 }, 2267 expected: Cell{ 2268 Result: statuspb.TestStatus_FAIL, 2269 Metrics: setElapsed(nil, 150), 2270 }, 2271 }, 2272 } 2273 2274 for _, tc := range cases { 2275 t.Run(tc.name, func(t *testing.T) { 2276 actual := overallCell(tc.result) 2277 if diff := cmp.Diff(actual, tc.expected); diff != "" { 2278 t.Errorf("overallCell(%v) got unexpected diff:\n%s", tc.result, diff) 2279 } 2280 }) 2281 } 2282 } 2283 2284 func TestSetElapsed(t *testing.T) { 2285 cases := []struct { 2286 name string 2287 metrics map[string]float64 2288 seconds float64 2289 expected map[string]float64 2290 }{ 2291 { 2292 name: "nil map works", 2293 seconds: 10, 2294 expected: map[string]float64{ 2295 ElapsedKey: 10 / 60.0, 2296 }, 2297 }, 2298 { 2299 name: "existing keys preserved", 2300 metrics: map[string]float64{ 2301 "hello": 7, 2302 }, 2303 seconds: 5, 2304 expected: map[string]float64{ 2305 "hello": 7, 2306 ElapsedKey: 5 / 60.0, 2307 }, 2308 }, 2309 { 2310 name: "override existing value", 2311 metrics: map[string]float64{ 2312 ElapsedKey: 3 / 60.0, 2313 }, 2314 seconds: 10, 2315 expected: map[string]float64{ 2316 ElapsedKey: 10 / 60.0, 2317 }, 2318 }, 2319 } 2320 2321 for _, tc := range cases { 2322 t.Run(tc.name, func(t *testing.T) { 2323 actual := setElapsed(tc.metrics, tc.seconds) 2324 if !reflect.DeepEqual(actual, tc.expected) { 2325 t.Errorf("setElapsed(%v, %v) got %v, want %v", tc.metrics, tc.seconds, actual, tc.expected) 2326 } 2327 }) 2328 } 2329 } 2330 2331 func TestFlattenResults(t *testing.T) { 2332 pstr := func(s string) *string { 2333 return &s 2334 } 2335 cases := []struct { 2336 name string 2337 suites []junit.Suite 2338 expected []junit.Result 2339 }{ 2340 { 2341 name: "basically works", 2342 }, 2343 { 2344 name: "results from multiple suites", 2345 suites: []junit.Suite{ 2346 { 2347 Name: "suite1", 2348 Results: []junit.Result{ 2349 { 2350 Name: "resultA", 2351 Output: pstr("hello"), 2352 }, 2353 { 2354 Name: "resultB", 2355 Error: pstr("bonk"), 2356 }, 2357 }, 2358 }, 2359 { 2360 Name: "suite-two", 2361 Results: []junit.Result{ 2362 { 2363 Name: "resultX", 2364 }, 2365 }, 2366 }, 2367 }, 2368 expected: []junit.Result{ 2369 { 2370 Name: "suite1.resultA", 2371 Output: pstr("hello"), 2372 }, 2373 { 2374 Name: "suite1.resultB", 2375 Error: pstr("bonk"), 2376 }, 2377 { 2378 Name: "suite-two.resultX", 2379 }, 2380 }, 2381 }, 2382 { 2383 name: "find results deeply nested in suites", 2384 suites: []junit.Suite{ 2385 { 2386 Name: "must", 2387 Suites: []junit.Suite{ 2388 { 2389 Name: "go", 2390 Suites: []junit.Suite{ 2391 { 2392 Name: "deeper", 2393 Results: []junit.Result{ 2394 { 2395 Name: "leaf", 2396 Skipped: &junit.Skipped{Value: *pstr("first")}, 2397 }, 2398 }, 2399 }, 2400 }, 2401 Results: []junit.Result{ 2402 { 2403 Name: "branch", 2404 Skipped: &junit.Skipped{Value: *pstr("second")}, 2405 }, 2406 }, 2407 }, 2408 }, 2409 Results: []junit.Result{ 2410 { 2411 Name: "trunk", 2412 Skipped: &junit.Skipped{Value: *pstr("third")}, 2413 }, 2414 }, 2415 }, 2416 }, 2417 expected: []junit.Result{ 2418 { 2419 Name: "must.go.deeper.leaf", 2420 Skipped: &junit.Skipped{Value: *pstr("first")}, 2421 }, 2422 { 2423 Name: "must.go.branch", 2424 Skipped: &junit.Skipped{Value: *pstr("second")}, 2425 }, 2426 { 2427 Name: "must.trunk", 2428 Skipped: &junit.Skipped{Value: *pstr("third")}, 2429 }, 2430 }, 2431 }, 2432 } 2433 2434 for _, tc := range cases { 2435 t.Run(tc.name, func(t *testing.T) { 2436 actual := flattenResults(tc.suites...) 2437 if !reflect.DeepEqual(actual, tc.expected) { 2438 t.Errorf("flattenResults(%v) got %v, want %v", tc.suites, actual, tc.expected) 2439 } 2440 }) 2441 } 2442 } 2443 2444 func TestDotName(t *testing.T) { 2445 cases := []struct { 2446 name string 2447 left string 2448 right string 2449 expected string 2450 }{ 2451 { 2452 name: "basically works", 2453 }, 2454 { 2455 name: "left.right", 2456 left: "left", 2457 right: "right", 2458 expected: "left.right", 2459 }, 2460 { 2461 name: "only left", 2462 left: "left", 2463 expected: "left", 2464 }, 2465 { 2466 name: "only right", 2467 right: "right", 2468 expected: "right", 2469 }, 2470 } 2471 2472 for _, tc := range cases { 2473 t.Run(tc.name, func(t *testing.T) { 2474 if actual := dotName(tc.left, tc.right); actual != tc.expected { 2475 t.Errorf("dotName(%q, %q) got %q, want %q", tc.left, tc.right, actual, tc.expected) 2476 } 2477 }) 2478 } 2479 }