blob: 9d011db55c4c072f73c5db6e5d389a2ef9baa5bd [file] [log] [blame]
Gilles Peskine15c2cbf2020-06-25 18:36:28 +02001#!/usr/bin/env python3
2
3"""Analyze the test outcomes from a full CI run.
4
5This script can also run on outcomes from a partial run, but the results are
6less likely to be useful.
7"""
8
9import argparse
10import sys
11import traceback
12
13class Results:
14 """Process analysis results."""
15
16 def __init__(self):
17 self.error_count = 0
18 self.warning_count = 0
19
20 @staticmethod
21 def log(fmt, *args, **kwargs):
22 sys.stderr.write((fmt + '\n').format(*args, **kwargs))
23
24 def error(self, fmt, *args, **kwargs):
25 self.log('Error: ' + fmt, *args, **kwargs)
26 self.error_count += 1
27
28 def warning(self, fmt, *args, **kwargs):
29 self.log('Warning: ' + fmt, *args, **kwargs)
30 self.warning_count += 1
31
32class TestCaseOutcomes:
33 """The outcomes of one test case across many configurations."""
34 # pylint: disable=too-few-public-methods
35
36 def __init__(self):
37 self.successes = []
38 self.failures = []
39
40 def hits(self):
41 """Return the number of times a test case has been run.
42
43 This includes passes and failures, but not skips.
44 """
45 return len(self.successes) + len(self.failures)
46
47def analyze_outcomes(outcomes):
48 """Run all analyses on the given outcome collection."""
49 results = Results()
50 return results
51
52def read_outcome_file(outcome_file):
53 """Parse an outcome file and return an outcome collection.
54
55An outcome collection is a dictionary mapping keys to TestCaseOutcomes objects.
56The keys are the test suite name and the test case description, separated
57by a semicolon.
58"""
59 outcomes = {}
60 with open(outcome_file, 'r', encoding='utf-8') as input_file:
61 for line in input_file:
62 (platform, config, suite, case, result, _cause) = line.split(';')
63 key = ';'.join([suite, case])
64 setup = ';'.join([platform, config])
65 if key not in outcomes:
66 outcomes[key] = TestCaseOutcomes()
67 if result == 'PASS':
68 outcomes[key].successes.append(setup)
69 elif result == 'FAIL':
70 outcomes[key].failures.append(setup)
71 return outcomes
72
73def analyze_outcome_file(outcome_file):
74 """Analyze the given outcome file."""
75 outcomes = read_outcome_file(outcome_file)
76 return analyze_outcomes(outcomes)
77
78def main():
79 try:
80 parser = argparse.ArgumentParser(description=__doc__)
81 parser.add_argument('outcomes', metavar='OUTCOMES.CSV',
82 help='Outcome file to analyze')
83 options = parser.parse_args()
84 results = analyze_outcome_file(options.outcomes)
85 if results.error_count > 0:
86 sys.exit(1)
87 except Exception: # pylint: disable=broad-except
88 # Print the backtrace and exit explicitly with our chosen status.
89 traceback.print_exc()
90 sys.exit(120)
91
92if __name__ == '__main__':
93 main()