github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/roachtest/java_helpers.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package main 12 13 import ( 14 "context" 15 "encoding/xml" 16 "fmt" 17 "regexp" 18 "strings" 19 ) 20 21 var issueRegexp = regexp.MustCompile(`See: https://github.com/cockroachdb/cockroach/issues/(\d+)`) 22 23 type status int 24 25 const ( 26 pass status = iota 27 fail 28 skip 29 ) 30 31 // extractFailureFromJUnitXML parses an XML report to find all failed tests. The 32 // return values are: 33 // - slice of all test names. 34 // - slice of status for each test. 35 // - map from name of a failed test to a github issue that explains the failure, 36 // if the error message contained a reference to an issue. 37 // - error if there was a problem parsing the XML. 38 func extractFailureFromJUnitXML(contents []byte) ([]string, []status, map[string]string, error) { 39 type Failure struct { 40 Message string `xml:"message,attr"` 41 } 42 type Error struct { 43 Message string `xml:"message,attr"` 44 } 45 type TestCase struct { 46 Name string `xml:"name,attr"` 47 ClassName string `xml:"classname,attr"` 48 Failure Failure `xml:"failure,omitempty"` 49 Error Error `xml:"error,omitempty"` 50 Skipped *struct{} `xml:"skipped,omitempty"` 51 } 52 type TestSuite struct { 53 XMLName xml.Name `xml:"testsuite"` 54 TestCases []TestCase `xml:"testcase"` 55 } 56 type TestSuites struct { 57 XMLName xml.Name `xml:"testsuites"` 58 TestSuites []TestSuite `xml:"testsuite"` 59 } 60 61 var testSuite TestSuite 62 _ = testSuite.XMLName 63 var testSuites TestSuites 64 _ = testSuites.XMLName 65 66 var tests []string 67 var testStatuses []status 68 var failedTestToIssue = make(map[string]string) 69 processTestSuite := func(testSuite TestSuite) { 70 for _, testCase := range testSuite.TestCases { 71 testName := fmt.Sprintf("%s.%s", testCase.ClassName, testCase.Name) 72 testPassed := len(testCase.Failure.Message) == 0 && len(testCase.Error.Message) == 0 73 tests = append(tests, testName) 74 if testCase.Skipped != nil { 75 testStatuses = append(testStatuses, skip) 76 } else if testPassed { 77 testStatuses = append(testStatuses, pass) 78 } else { 79 testStatuses = append(testStatuses, fail) 80 message := testCase.Failure.Message 81 if len(message) == 0 { 82 message = testCase.Error.Message 83 } 84 85 issue := "unknown" 86 match := issueRegexp.FindStringSubmatch(message) 87 if match != nil { 88 issue = match[1] 89 } 90 failedTestToIssue[testName] = issue 91 } 92 } 93 } 94 95 // First, we try to parse the XML with an assumption that there are multiple 96 // test suites in contents. 97 if err := xml.Unmarshal(contents, &testSuites); err == nil { 98 // The parsing was successful, so we process each test suite. 99 for _, testSuite := range testSuites.TestSuites { 100 processTestSuite(testSuite) 101 } 102 } else { 103 // The parsing wasn't successful, so now we try to parse the XML with an 104 // assumption that there is a single test suite. 105 if err := xml.Unmarshal(contents, &testSuite); err != nil { 106 return nil, nil, nil, err 107 } 108 processTestSuite(testSuite) 109 } 110 111 return tests, testStatuses, failedTestToIssue, nil 112 } 113 114 // parseJUnitXML parses testOutputInJUnitXMLFormat and updates the receiver 115 // accordingly. 116 func (r *ormTestsResults) parseJUnitXML( 117 t *test, expectedFailures, ignorelist blacklist, testOutputInJUnitXMLFormat []byte, 118 ) { 119 tests, statuses, issueHints, err := extractFailureFromJUnitXML(testOutputInJUnitXMLFormat) 120 if err != nil { 121 t.Fatal(err) 122 } 123 for testName, issue := range issueHints { 124 r.allIssueHints[testName] = issue 125 } 126 for i, test := range tests { 127 // There is at least a single test that's run twice, so if we already 128 // have a result, skip it. 129 if _, alreadyTested := r.results[test]; alreadyTested { 130 continue 131 } 132 r.allTests = append(r.allTests, test) 133 ignoredIssue, expectedIgnored := ignorelist[test] 134 issue, expectedFailure := expectedFailures[test] 135 if len(issue) == 0 || issue == "unknown" { 136 issue = issueHints[test] 137 } 138 status := statuses[i] 139 switch { 140 case expectedIgnored: 141 r.results[test] = fmt.Sprintf("--- IGNORE: %s due to %s (expected)", test, ignoredIssue) 142 r.ignoredCount++ 143 case status == skip: 144 r.results[test] = fmt.Sprintf("--- SKIP: %s", test) 145 r.skipCount++ 146 case status == pass && !expectedFailure: 147 r.results[test] = fmt.Sprintf("--- PASS: %s (expected)", test) 148 r.passExpectedCount++ 149 case status == pass && expectedFailure: 150 r.results[test] = fmt.Sprintf("--- PASS: %s - %s (unexpected)", 151 test, maybeAddGithubLink(issue), 152 ) 153 r.passUnexpectedCount++ 154 case status == fail && expectedFailure: 155 r.results[test] = fmt.Sprintf("--- FAIL: %s - %s (expected)", 156 test, maybeAddGithubLink(issue), 157 ) 158 r.failExpectedCount++ 159 r.currentFailures = append(r.currentFailures, test) 160 case status == fail && !expectedFailure: 161 r.results[test] = fmt.Sprintf("--- FAIL: %s - %s (unexpected)", 162 test, maybeAddGithubLink(issue)) 163 r.failUnexpectedCount++ 164 r.currentFailures = append(r.currentFailures, test) 165 } 166 r.runTests[test] = struct{}{} 167 } 168 } 169 170 // parseAndSummarizeJavaORMTestsResults parses the test output of running a 171 // test suite for some Java ORM against cockroach and summarizes it. If an 172 // unexpected result is observed (for example, a test unexpectedly failed or 173 // passed), a new blacklist is populated. 174 func parseAndSummarizeJavaORMTestsResults( 175 ctx context.Context, 176 t *test, 177 c *cluster, 178 node nodeListOption, 179 ormName string, 180 testOutput []byte, 181 blacklistName string, 182 expectedFailures blacklist, 183 ignorelist blacklist, 184 version string, 185 latestTag string, 186 ) { 187 results := newORMTestsResults() 188 filesRaw := strings.Split(string(testOutput), "\n") 189 190 // There is always at least one entry that's just space characters, remove 191 // it. 192 var files []string 193 for _, f := range filesRaw { 194 file := strings.TrimSpace(f) 195 if len(file) > 0 { 196 files = append(files, file) 197 } 198 } 199 for i, file := range files { 200 t.l.Printf("Parsing %d of %d: %s\n", i+1, len(files), file) 201 fileOutput, err := repeatRunWithBuffer( 202 ctx, 203 c, 204 t.l, 205 node, 206 fmt.Sprintf("fetching results file %s", file), 207 fmt.Sprintf("cat %s", file), 208 ) 209 if err != nil { 210 t.Fatal(err) 211 } 212 213 results.parseJUnitXML(t, expectedFailures, ignorelist, fileOutput) 214 } 215 216 results.summarizeAll( 217 t, ormName, blacklistName, expectedFailures, version, latestTag, 218 ) 219 }