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  }