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  }