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  }