github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/spyglass/lenses/junit/lens_test.go (about) 1 /* 2 Copyright 2020 The Kubernetes 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 junit provides a junit viewer for Spyglass 18 package junit 19 20 import ( 21 "bytes" 22 "context" 23 "html/template" 24 "io" 25 "strings" 26 "testing" 27 28 "github.com/GoogleCloudPlatform/testgrid/metadata/junit" 29 "github.com/google/go-cmp/cmp" 30 utilpointer "k8s.io/utils/pointer" 31 32 "sigs.k8s.io/prow/pkg/spyglass/api" 33 "sigs.k8s.io/prow/pkg/spyglass/lenses" 34 ) 35 36 const ( 37 fakeCanonicalLink = "linknotfound.io/404" 38 ) 39 40 // FakeArtifact implements lenses.Artifact. 41 // This is pretty much copy/pasted from prow/spyglass/lenses/lenses_test.go, if 42 // another package needs to reuse, should think about refactor this into it's 43 // own package 44 type FakeArtifact struct { 45 path string 46 content []byte 47 sizeLimit int64 48 } 49 50 func (fa *FakeArtifact) JobPath() string { 51 return fa.path 52 } 53 54 func (fa *FakeArtifact) Size() (int64, error) { 55 return int64(len(fa.content)), nil 56 } 57 58 func (fa *FakeArtifact) CanonicalLink() string { 59 return fakeCanonicalLink 60 } 61 62 func (fa *FakeArtifact) Metadata() (map[string]string, error) { 63 return nil, nil 64 } 65 66 func (fa *FakeArtifact) UpdateMetadata(map[string]string) error { 67 return nil 68 } 69 70 func (fa *FakeArtifact) ReadAt(b []byte, off int64) (int, error) { 71 r := bytes.NewReader(fa.content) 72 return r.ReadAt(b, off) 73 } 74 75 func (fa *FakeArtifact) ReadAll() ([]byte, error) { 76 size, err := fa.Size() 77 if err != nil { 78 return nil, err 79 } 80 if size > fa.sizeLimit { 81 return nil, lenses.ErrFileTooLarge 82 } 83 r := bytes.NewReader(fa.content) 84 return io.ReadAll(r) 85 } 86 87 func (fa *FakeArtifact) ReadTail(n int64) ([]byte, error) { 88 return nil, nil 89 } 90 91 func (fa *FakeArtifact) UseContext(ctx context.Context) error { 92 return nil 93 } 94 95 func (fa *FakeArtifact) ReadAtMost(n int64) ([]byte, error) { 96 return nil, nil 97 } 98 99 func TestGetJvd(t *testing.T) { 100 emptySkipped := junit.Skipped{} 101 failures := []junit.Failure{ 102 { 103 Type: "failure", 104 Message: "failure message 0", 105 Value: " failure value 0 ", 106 }, 107 { 108 Type: "failure", 109 Message: "failure message 1", 110 Value: " failure value 1 ", 111 }, 112 } 113 errors := []junit.Errored{ 114 { 115 Type: "error", 116 Message: "error message 0", 117 Value: " error value 0 ", 118 }, 119 { 120 Type: "error", 121 Message: "error message 1", 122 Value: " error value 1 ", 123 }, 124 } 125 126 tests := []struct { 127 name string 128 rawResults [][]byte 129 exp JVD 130 }{ 131 { 132 "Failed", 133 [][]byte{ 134 []byte(` 135 <testsuites> 136 <testsuite> 137 <testcase classname="fake_class_0" name="fake_test_0"> 138 <failure message="failure message 0" type="failure"> failure value 0 </failure> 139 </testcase> 140 </testsuite> 141 </testsuites> 142 `), 143 }, 144 JVD{ 145 NumTests: 1, 146 Passed: nil, 147 Failed: []TestResult{ 148 { 149 Junit: []JunitResult{ 150 { 151 junit.Result{ 152 Name: "fake_test_0", 153 ClassName: "fake_class_0", 154 Failure: &failures[0], 155 }, 156 }, 157 }, 158 Link: "linknotfound.io/404", 159 }, 160 }, 161 Skipped: nil, 162 Flaky: nil, 163 }, 164 }, { 165 "Errored", 166 [][]byte{ 167 []byte(` 168 <testsuites> 169 <testsuite> 170 <testcase classname="fake_class_0" name="fake_test_0"> 171 <error message="error message 0" type="error"> error value 0 </error> 172 </testcase> 173 </testsuite> 174 </testsuites> 175 `), 176 }, 177 JVD{ 178 NumTests: 1, 179 Passed: nil, 180 Failed: []TestResult{ 181 { 182 Junit: []JunitResult{ 183 { 184 junit.Result{ 185 Name: "fake_test_0", 186 ClassName: "fake_class_0", 187 Errored: &errors[0], 188 }, 189 }, 190 }, 191 Link: "linknotfound.io/404", 192 }, 193 }, 194 Skipped: nil, 195 Flaky: nil, 196 }, 197 }, { 198 "Passed", 199 [][]byte{ 200 []byte(` 201 <testsuites> 202 <testsuite> 203 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 204 </testsuite> 205 </testsuites> 206 `), 207 }, 208 JVD{ 209 NumTests: 1, 210 Passed: []TestResult{ 211 { 212 Junit: []JunitResult{ 213 { 214 junit.Result{ 215 Name: "fake_test_0", 216 ClassName: "fake_class_0", 217 Failure: nil, 218 }, 219 }, 220 }, 221 Link: "linknotfound.io/404", 222 }, 223 }, 224 Failed: nil, 225 Skipped: nil, 226 Flaky: nil, 227 }, 228 }, { 229 "Skipped", 230 [][]byte{ 231 []byte(` 232 <testsuites> 233 <testsuite> 234 <testcase classname="fake_class_0" name="fake_test_0"> 235 <skipped/> 236 </testcase> 237 </testsuite> 238 </testsuites> 239 `), 240 }, 241 JVD{ 242 NumTests: 1, 243 Passed: nil, 244 Failed: nil, 245 Skipped: []TestResult{ 246 { 247 Junit: []JunitResult{ 248 { 249 junit.Result{ 250 Name: "fake_test_0", 251 ClassName: "fake_class_0", 252 Failure: nil, 253 Skipped: &emptySkipped, 254 }, 255 }, 256 }, 257 Link: "linknotfound.io/404", 258 }, 259 }, 260 Flaky: nil, 261 }, 262 }, { 263 "Failed and Skipped", 264 [][]byte{ 265 []byte(` 266 <testsuites> 267 <testsuite> 268 <testcase classname="fake_class_0" name="fake_test_0"> 269 <failure message="failure message 0" type="failure"> failure value 0 </failure> 270 </testcase> 271 <testcase classname="fake_class_0" name="fake_test_0"> 272 <skipped/> 273 </testcase> 274 </testsuite> 275 </testsuites> 276 `), 277 }, 278 JVD{ 279 NumTests: 1, 280 Passed: nil, 281 Failed: []TestResult{ 282 { 283 Junit: []JunitResult{ 284 { 285 junit.Result{ 286 Name: "fake_test_0", 287 ClassName: "fake_class_0", 288 Failure: &failures[0], 289 }, 290 }, 291 { 292 junit.Result{ 293 Name: "fake_test_0", 294 ClassName: "fake_class_0", 295 Failure: nil, 296 Skipped: &emptySkipped, 297 }, 298 }, 299 }, 300 Link: "linknotfound.io/404", 301 }, 302 }, 303 Skipped: []TestResult{ 304 { 305 Junit: []JunitResult{ 306 { 307 junit.Result{ 308 Name: "fake_test_0", 309 ClassName: "fake_class_0", 310 Failure: &failures[0], 311 }, 312 }, 313 { 314 junit.Result{ 315 Name: "fake_test_0", 316 ClassName: "fake_class_0", 317 Failure: nil, 318 Skipped: &emptySkipped, 319 }, 320 }, 321 }, 322 Link: "linknotfound.io/404", 323 }, 324 }, 325 Flaky: nil, 326 }, 327 }, { 328 "Multiple tests in same file", 329 [][]byte{ 330 []byte(` 331 <testsuites> 332 <testsuite> 333 <testcase classname="fake_class_0" name="fake_test_0"> 334 <failure message="failure message 0" type="failure"> failure value 0 </failure> 335 </testcase> 336 <testcase classname="fake_class_1" name="fake_test_0"></testcase> 337 </testsuite> 338 </testsuites> 339 `), 340 }, 341 JVD{ 342 NumTests: 2, 343 Passed: []TestResult{ 344 { 345 Junit: []JunitResult{ 346 { 347 junit.Result{ 348 Name: "fake_test_0", 349 ClassName: "fake_class_1", 350 Failure: nil, 351 }, 352 }, 353 }, 354 Link: "linknotfound.io/404", 355 }, 356 }, 357 Failed: []TestResult{ 358 { 359 Junit: []JunitResult{ 360 { 361 junit.Result{ 362 Name: "fake_test_0", 363 ClassName: "fake_class_0", 364 Failure: &failures[0], 365 }, 366 }, 367 }, 368 Link: "linknotfound.io/404", 369 }, 370 }, 371 Skipped: nil, 372 Flaky: nil, 373 }, 374 }, { 375 "Multiple tests in different files", 376 [][]byte{ 377 []byte(` 378 <testsuites> 379 <testsuite> 380 <testcase classname="fake_class_0" name="fake_test_0"> 381 <failure message="failure message 0" type="failure"> failure value 0 </failure> 382 </testcase> 383 </testsuite> 384 </testsuites> 385 `), 386 []byte(` 387 <testsuites> 388 <testsuite> 389 <testcase classname="fake_class_1" name="fake_test_0"></testcase> 390 </testsuite> 391 </testsuites> 392 `), 393 }, 394 JVD{ 395 NumTests: 2, 396 Passed: []TestResult{ 397 { 398 Junit: []JunitResult{ 399 { 400 junit.Result{ 401 Name: "fake_test_0", 402 ClassName: "fake_class_1", 403 Failure: nil, 404 }, 405 }, 406 }, 407 Link: "linknotfound.io/404", 408 }, 409 }, 410 Failed: []TestResult{ 411 { 412 Junit: []JunitResult{ 413 { 414 junit.Result{ 415 Name: "fake_test_0", 416 ClassName: "fake_class_0", 417 Failure: &failures[0], 418 }, 419 }, 420 }, 421 Link: "linknotfound.io/404", 422 }, 423 }, 424 Skipped: nil, 425 Flaky: nil, 426 }, 427 }, { 428 "Fail multiple times in same file", 429 [][]byte{ 430 []byte(` 431 <testsuites> 432 <testsuite> 433 <testcase classname="fake_class_0" name="fake_test_0"> 434 <failure message="failure message 0" type="failure"> failure value 0 </failure> 435 </testcase> 436 </testsuite> 437 <testsuite> 438 <testcase classname="fake_class_0" name="fake_test_0"> 439 <failure message="failure message 1" type="failure"> failure value 1 </failure> 440 </testcase> 441 </testsuite> 442 </testsuites> 443 `), 444 }, 445 JVD{ 446 NumTests: 1, 447 Passed: nil, 448 Failed: []TestResult{ 449 { 450 Junit: []JunitResult{ 451 { 452 junit.Result{ 453 Name: "fake_test_0", 454 ClassName: "fake_class_0", 455 Failure: &failures[0], 456 }, 457 }, 458 { 459 junit.Result{ 460 Name: "fake_test_0", 461 ClassName: "fake_class_0", 462 Failure: &failures[1], 463 }, 464 }, 465 }, 466 Link: "linknotfound.io/404", 467 }, 468 }, 469 Skipped: nil, 470 Flaky: nil, 471 }, 472 }, { 473 "Passed multiple times in same file", 474 [][]byte{ 475 []byte(` 476 <testsuites> 477 <testsuite> 478 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 479 </testsuite> 480 <testsuite> 481 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 482 </testsuite> 483 </testsuites> 484 `), 485 }, 486 JVD{ 487 NumTests: 1, 488 Passed: []TestResult{ 489 { 490 Junit: []JunitResult{ 491 { 492 junit.Result{ 493 Name: "fake_test_0", 494 ClassName: "fake_class_0", 495 Failure: nil, 496 }, 497 }, 498 { 499 junit.Result{ 500 Name: "fake_test_0", 501 ClassName: "fake_class_0", 502 Failure: nil, 503 }, 504 }, 505 }, 506 Link: "linknotfound.io/404", 507 }, 508 }, 509 Failed: nil, 510 Skipped: nil, 511 Flaky: nil, 512 }, 513 }, { 514 // This is the case where `go test --count=N`, where N>1 515 "Passed multiple times in same suite", 516 [][]byte{ 517 []byte(` 518 <testsuites> 519 <testsuite> 520 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 521 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 522 </testsuite> 523 </testsuites> 524 `), 525 }, 526 JVD{ 527 NumTests: 1, 528 Passed: []TestResult{ 529 { 530 Junit: []JunitResult{ 531 { 532 junit.Result{ 533 Name: "fake_test_0", 534 ClassName: "fake_class_0", 535 Failure: nil, 536 }, 537 }, 538 { 539 junit.Result{ 540 Name: "fake_test_0", 541 ClassName: "fake_class_0", 542 Failure: nil, 543 }, 544 }, 545 }, 546 Link: "linknotfound.io/404", 547 }, 548 }, 549 Failed: nil, 550 Skipped: nil, 551 Flaky: nil, 552 }, 553 }, { 554 "Failed then pass in same file (flaky)", 555 [][]byte{ 556 []byte(` 557 <testsuites> 558 <testsuite> 559 <testcase classname="fake_class_0" name="fake_test_0"> 560 <failure message="failure message 0" type="failure"> failure value 0 </failure> 561 </testcase> 562 </testsuite> 563 <testsuite> 564 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 565 </testsuite> 566 </testsuites> 567 `), 568 }, 569 JVD{ 570 NumTests: 1, 571 Passed: nil, 572 Failed: nil, 573 Skipped: nil, 574 Flaky: []TestResult{ 575 { 576 Junit: []JunitResult{ 577 { 578 junit.Result{ 579 Name: "fake_test_0", 580 ClassName: "fake_class_0", 581 Failure: &failures[0], 582 }, 583 }, 584 { 585 junit.Result{ 586 Name: "fake_test_0", 587 ClassName: "fake_class_0", 588 Failure: nil, 589 }, 590 }, 591 }, 592 Link: "linknotfound.io/404", 593 }, 594 }, 595 }, 596 }, { 597 "Pass then fail in same file (flaky)", 598 [][]byte{ 599 []byte(` 600 <testsuites> 601 <testsuite> 602 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 603 </testsuite> 604 <testsuite> 605 <testcase classname="fake_class_0" name="fake_test_0"> 606 <failure message="failure message 0" type="failure"> failure value 0 </failure> 607 </testcase> 608 </testsuite> 609 </testsuites> 610 `), 611 }, 612 JVD{ 613 NumTests: 1, 614 Passed: nil, 615 Failed: nil, 616 Skipped: nil, 617 Flaky: []TestResult{ 618 { 619 Junit: []JunitResult{ 620 { 621 junit.Result{ 622 Name: "fake_test_0", 623 ClassName: "fake_class_0", 624 Failure: nil, 625 }, 626 }, 627 { 628 junit.Result{ 629 Name: "fake_test_0", 630 ClassName: "fake_class_0", 631 Failure: &failures[0], 632 }, 633 }, 634 }, 635 Link: "linknotfound.io/404", 636 }, 637 }, 638 }, 639 }, { 640 "Fail multiple times then pass in same file (flaky)", 641 [][]byte{ 642 []byte(` 643 <testsuites> 644 <testsuite> 645 <testcase classname="fake_class_0" name="fake_test_0"> 646 <failure message="failure message 0" type="failure"> failure value 0 </failure> 647 </testcase> 648 </testsuite> 649 <testsuite> 650 <testcase classname="fake_class_0" name="fake_test_0"> 651 <failure message="failure message 1" type="failure"> failure value 1 </failure> 652 </testcase> 653 </testsuite> 654 <testsuite> 655 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 656 </testsuite> 657 </testsuites> 658 `), 659 }, 660 JVD{ 661 NumTests: 1, 662 Passed: nil, 663 Failed: nil, 664 Skipped: nil, 665 Flaky: []TestResult{ 666 { 667 Junit: []JunitResult{ 668 { 669 junit.Result{ 670 Name: "fake_test_0", 671 ClassName: "fake_class_0", 672 Failure: &failures[0], 673 }, 674 }, 675 { 676 junit.Result{ 677 Name: "fake_test_0", 678 ClassName: "fake_class_0", 679 Failure: &failures[1], 680 }, 681 }, 682 { 683 junit.Result{ 684 Name: "fake_test_0", 685 ClassName: "fake_class_0", 686 Failure: nil, 687 }, 688 }, 689 }, 690 Link: "linknotfound.io/404", 691 }, 692 }, 693 }, 694 }, { 695 "Same test in different file", 696 [][]byte{ 697 []byte(` 698 <testsuites> 699 <testsuite> 700 <testcase classname="fake_class_0" name="fake_test_0"> 701 <failure message="failure message 0" type="failure"> failure value 0 </failure> 702 </testcase> 703 </testsuite> 704 </testsuites> 705 `), 706 []byte(` 707 <testsuites> 708 <testsuite> 709 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 710 </testsuite> 711 </testsuites> 712 `), 713 }, 714 JVD{ 715 NumTests: 2, 716 Passed: []TestResult{ 717 { 718 Junit: []JunitResult{ 719 { 720 junit.Result{ 721 Name: "fake_test_0", 722 ClassName: "fake_class_0", 723 Failure: nil, 724 }, 725 }, 726 }, 727 Link: "linknotfound.io/404", 728 }, 729 }, 730 Failed: []TestResult{ 731 { 732 Junit: []JunitResult{ 733 { 734 junit.Result{ 735 Name: "fake_test_0", 736 ClassName: "fake_class_0", 737 Failure: &failures[0], 738 }, 739 }, 740 }, 741 Link: "linknotfound.io/404", 742 }, 743 }, 744 Skipped: nil, 745 Flaky: nil, 746 }, 747 }, { 748 "Sequence of test cases in the artifact file is reflected in the lens", 749 [][]byte{ 750 []byte(` 751 <testsuites> 752 <testsuite> 753 <testcase classname="fake_class_0" name="fake_test_0"></testcase> 754 </testsuite> 755 <testsuite> 756 <testcase classname="fake_class_1" name="fake_test_1"></testcase> 757 </testsuite> 758 <testsuite> 759 <testcase classname="fake_class_2" name="fake_test_2"></testcase> 760 </testsuite> 761 <testsuite> 762 <testcase classname="fake_class_3" name="fake_test_3"></testcase> 763 </testsuite> 764 <testsuite> 765 <testcase classname="fake_class_4" name="fake_test_4"></testcase> 766 </testsuite> 767 </testsuites> 768 `), 769 }, 770 JVD{ 771 NumTests: 5, 772 Passed: []TestResult{ 773 { 774 Junit: []JunitResult{ 775 { 776 junit.Result{ 777 Name: "fake_test_0", 778 ClassName: "fake_class_0", 779 Failure: nil, 780 }, 781 }, 782 }, 783 Link: "linknotfound.io/404", 784 }, 785 { 786 Junit: []JunitResult{ 787 { 788 junit.Result{ 789 Name: "fake_test_1", 790 ClassName: "fake_class_1", 791 Failure: nil, 792 }, 793 }, 794 }, 795 Link: "linknotfound.io/404", 796 }, 797 { 798 Junit: []JunitResult{ 799 { 800 junit.Result{ 801 Name: "fake_test_2", 802 ClassName: "fake_class_2", 803 Failure: nil, 804 }, 805 }, 806 }, 807 Link: "linknotfound.io/404", 808 }, 809 { 810 Junit: []JunitResult{ 811 { 812 junit.Result{ 813 Name: "fake_test_3", 814 ClassName: "fake_class_3", 815 Failure: nil, 816 }, 817 }, 818 }, 819 Link: "linknotfound.io/404", 820 }, 821 { 822 Junit: []JunitResult{ 823 { 824 junit.Result{ 825 Name: "fake_test_4", 826 ClassName: "fake_class_4", 827 Failure: nil, 828 }, 829 }, 830 }, 831 Link: "linknotfound.io/404", 832 }, 833 }, 834 Failed: nil, 835 Skipped: nil, 836 Flaky: nil, 837 }, 838 }, 839 } 840 841 for _, tt := range tests { 842 t.Run(tt.name, func(t *testing.T) { 843 artifacts := make([]api.Artifact, 0) 844 for _, rr := range tt.rawResults { 845 artifacts = append(artifacts, &FakeArtifact{ 846 path: "log.txt", 847 content: rr, 848 sizeLimit: 500e6, 849 }) 850 } 851 l := Lens{} 852 got := l.getJvd(artifacts) 853 if diff := cmp.Diff(tt.exp, got); diff != "" { 854 t.Fatalf("JVD mismatch, want(-), got(+): \n%s", diff) 855 } 856 }) 857 } 858 } 859 860 func TestTemplate(t *testing.T) { 861 t.Parallel() 862 testCases := []struct { 863 name string 864 input JVD 865 expectedSubstrings []string 866 }{ 867 { 868 name: "Both stdout and stderr get rendered when there is one test", 869 input: JVD{NumTests: 1, Failed: []TestResult{{ 870 Junit: []JunitResult{{ 871 Result: junit.Result{ 872 Output: utilpointer.String("output"), 873 Error: utilpointer.String("error"), 874 }, 875 }}, 876 }}}, 877 expectedSubstrings: []string{ 878 `<a href="#" class="open-stdout-stderr">open stdout<i class="material-icons" `, 879 `<a href="#" class="open-stdout-stderr">open stderr<i class="material-icons"`, 880 `output`, 881 `error`, 882 }, 883 }, 884 { 885 name: "Both stdout and stderr get rendered when there are multiple tests", 886 input: JVD{NumTests: 1, Failed: []TestResult{{ 887 Junit: []JunitResult{ 888 { 889 Result: junit.Result{ 890 Output: utilpointer.String("output"), 891 Error: utilpointer.String("error"), 892 }, 893 }, 894 { 895 Result: junit.Result{ 896 Output: utilpointer.String("output"), 897 Error: utilpointer.String("error"), 898 }, 899 }, 900 }, 901 }}}, 902 expectedSubstrings: []string{ 903 `<a href="#" class="open-stdout-stderr">open stdout<i class="material-icons" `, 904 `<a href="#" class="open-stdout-stderr">open stderr<i class="material-icons"`, 905 `<td class="mdl-data-table__cell--non-numeric test-name">Run #0`, 906 `<td class="mdl-data-table__cell--non-numeric test-name">Run #1`, 907 `output`, 908 `error`, 909 }, 910 }, 911 { 912 name: "Both stdout and stderr get rendered for flaky tests", 913 input: JVD{NumTests: 1, Flaky: []TestResult{{ 914 Junit: []JunitResult{{ 915 Result: junit.Result{ 916 Output: utilpointer.String("output"), 917 Error: utilpointer.String("error"), 918 }, 919 }}, 920 }}}, 921 expectedSubstrings: []string{ 922 `<a href="#" class="open-stdout-stderr">open stdout<i class="material-icons" `, 923 `<a href="#" class="open-stdout-stderr">open stderr<i class="material-icons"`, 924 `output`, 925 `error`, 926 }, 927 }, 928 } 929 930 tmpl, err := template.ParseFiles("template.html") 931 if err != nil { 932 t.Fatalf("failed to parse template: %v", err) 933 } 934 for _, tc := range testCases { 935 t.Run(tc.name, func(t *testing.T) { 936 var buf bytes.Buffer 937 if err := tmpl.ExecuteTemplate(&buf, "body", tc.input); err != nil { 938 t.Fatalf("failed to execute template: %v", err) 939 } 940 result := buf.String() 941 942 for _, substring := range tc.expectedSubstrings { 943 if !strings.Contains(result, substring) { 944 t.Errorf("expected to find substring '%s' in rendered template '%s', wasn't the case", substring, result) 945 } 946 } 947 }) 948 } 949 }