github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/core/test_results.go (about) 1 package core 2 3 import ( 4 "bytes" 5 "sort" 6 "strings" 7 "time" 8 ) 9 10 // TestResults describes a set of test results for a test target. 11 type TestResults struct { 12 NumTests int // Total number of test cases in the test target. 13 Passed int // Number of tests that passed outright. 14 Failed int // Number of tests that failed. 15 ExpectedFailures int // Number of tests that were expected to fail (counts as a pass, but displayed differently) 16 Skipped int // Number of tests skipped (also count as passes) 17 Flakes int // Number of failed attempts to run the test 18 Failures []TestFailure 19 Passes []string 20 Output string // Stdout / stderr from the test. 21 Cached bool // True if the test results were retrieved from cache 22 TimedOut bool // True if the test failed because we timed it out. 23 Duration time.Duration // Length of time this test took 24 } 25 26 // TestFailure represents information about a test failure. 27 type TestFailure struct { 28 Name string // Name of failed test 29 Type string // Type of failure, eg. type of exception raised 30 Traceback string // Traceback 31 Stdout string // Standard output during test 32 Stderr string // Standard error during test 33 } 34 35 // Aggregate aggregates the given results into this one. 36 func (results *TestResults) Aggregate(r *TestResults) { 37 results.NumTests += r.NumTests 38 results.Passed += r.Passed 39 results.Failed += r.Failed 40 results.ExpectedFailures += r.ExpectedFailures 41 results.Skipped += r.Skipped 42 results.Flakes += r.Flakes 43 results.Failures = append(results.Failures, r.Failures...) 44 results.Passes = append(results.Passes, r.Passes...) 45 results.Duration += r.Duration 46 // Output can't really be aggregated sensibly. 47 } 48 49 // A LineCoverage represents a single line of coverage, which can be in one of several states. 50 // Note that Please doesn't support sub-line coverage at present. 51 type LineCoverage uint8 52 53 // Constants representing the states that a single line can be in for coverage. 54 const ( 55 NotExecutable LineCoverage = iota // Line isn't executable (eg. comment, blank) 56 Unreachable LineCoverage = iota // Line is executable but we've determined it can't be reached. So far not used. 57 Uncovered LineCoverage = iota // Line is executable but isn't covered. 58 Covered LineCoverage = iota // Line is executable and covered. 59 ) 60 61 var lineCoverageOutput = [...]rune{'N', 'X', 'U', 'C'} // Corresponds to ordering of enum. 62 63 // TestCoverage implements a pretty simple coverage format; we record one int for each line 64 // stating what its coverage is. 65 type TestCoverage struct { 66 Tests map[BuildLabel]map[string][]LineCoverage 67 Files map[string][]LineCoverage 68 } 69 70 // Aggregate aggregates results from that coverage object into this one. 71 func (coverage *TestCoverage) Aggregate(cov *TestCoverage) { 72 if coverage.Tests == nil { 73 coverage.Tests = map[BuildLabel]map[string][]LineCoverage{} 74 } 75 if coverage.Files == nil { 76 coverage.Files = map[string][]LineCoverage{} 77 } 78 79 // Assume that tests are independent (will currently always be the case). 80 for label, c := range cov.Tests { 81 coverage.Tests[label] = c 82 } 83 // Files are more complex since multiple tests can cover the same file. 84 // We take the best result for each line from each test. 85 for filename, c := range cov.Files { 86 coverage.Files[filename] = MergeCoverageLines(coverage.Files[filename], c) 87 } 88 } 89 90 // MergeCoverageLines merges two sets of coverage results together, taking 91 // the superset of the two results. 92 func MergeCoverageLines(existing, coverage []LineCoverage) []LineCoverage { 93 ret := make([]LineCoverage, len(existing)) 94 copy(ret, existing) 95 for i, line := range coverage { 96 if i >= len(ret) { 97 ret = append(ret, line) 98 } else if coverage[i] > ret[i] { 99 ret[i] = coverage[i] 100 } 101 } 102 return ret 103 } 104 105 // OrderedFiles returns an ordered slice of all the files we have coverage information for. 106 func (coverage *TestCoverage) OrderedFiles() []string { 107 files := []string{} 108 for file := range coverage.Files { 109 if strings.HasPrefix(file, RepoRoot) { 110 file = strings.TrimLeft(file[len(RepoRoot):], "/") 111 } 112 files = append(files, file) 113 } 114 sort.Strings(files) 115 return files 116 } 117 118 // NewTestCoverage constructs and returns a new TestCoverage instance. 119 func NewTestCoverage() TestCoverage { 120 return TestCoverage{ 121 Tests: map[BuildLabel]map[string][]LineCoverage{}, 122 Files: map[string][]LineCoverage{}, 123 } 124 } 125 126 // TestCoverageString produces a string representation of coverage for serialising to file so we don't 127 // expose the internal enum values (ordering is important so we may want to insert 128 // new ones later). This format happens to be the same as the one Phabricator uses, 129 // which is mildly useful to us since we want to integrate with it anyway. See 130 // https://secure.phabricator.com/book/phabricator/article/arcanist_coverage/ 131 // for more detail of how it works. 132 func TestCoverageString(lines []LineCoverage) string { 133 var buffer bytes.Buffer 134 for _, line := range lines { 135 buffer.WriteRune(lineCoverageOutput[line]) 136 } 137 return buffer.String() 138 }