Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(160)

Side by Side Diff: utils_py/generate_test_report.py

Issue 6731024: Refactor generate_test_report for better library usage. (Closed) Base URL: ssh://gitrw.chromium.org:9222/crosutils.git@master
Patch Set: Speed up result collection. Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 6
7 """Parses and displays the contents of one or more autoserv result directories. 7 """Parses and displays the contents of one or more autoserv result directories.
8 8
9 This script parses the contents of one or more autoserv results folders and 9 This script parses the contents of one or more autoserv results folders and
10 generates test reports. 10 generates test reports.
(...skipping 10 matching lines...) Expand all
21 sys.path.append(constants.CROSUTILS_LIB_DIR) 21 sys.path.append(constants.CROSUTILS_LIB_DIR)
22 from cros_build_lib import Color, Die 22 from cros_build_lib import Color, Die
23 23
24 _STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() 24 _STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
25 25
26 # List of crashes which are okay to ignore. This list should almost always be 26 # List of crashes which are okay to ignore. This list should almost always be
27 # empty. If you add an entry, mark it with a TODO(<your name>) and the issue 27 # empty. If you add an entry, mark it with a TODO(<your name>) and the issue
28 # filed for the crash. 28 # filed for the crash.
29 _CRASH_WHITELIST = {} 29 _CRASH_WHITELIST = {}
30 30
31 class ReportGenerator(object):
32 """Collects and displays data from autoserv results directories.
33 31
34 This class collects status and performance data from one or more autoserv 32 class ResultCollector(object):
35 result directories and generates test reports. 33 """Collects status and performance data from an autoserv results directory."""
36 """
37 34
38 _KEYVAL_INDENT = 2 35 def __init__(self, collect_perf=True, strip_text=''):
36 """Initialize ResultsCollector class.
39 37
40 def __init__(self, options, args): 38 Args:
41 self._options = options 39 collect_perf: Should perf keyvals be collected?
42 self._args = args 40 strip_text: Prefix to strip from test directory names.
43 self._color = Color(options.color) 41 """
42 self._collect_perf = collect_perf
43 self._strip_text = strip_text
44 44
45 def _CollectPerf(self, testdir): 45 def _CollectPerf(self, testdir):
46 """Parses keyval file under testdir. 46 """Parses keyval file under testdir.
47 47
48 If testdir contains a result folder, process the keyval file and return 48 If testdir contains a result folder, process the keyval file and return
49 a dictionary of perf keyval pairs. 49 a dictionary of perf keyval pairs.
50 50
51 Args: 51 Args:
52 testdir: The autoserv test result directory. 52 testdir: The autoserv test result directory.
53 53
54 Returns: 54 Returns:
55 If the perf option is disabled or the there's no keyval file under 55 If the perf option is disabled or the there's no keyval file under
56 testdir, returns an empty dictionary. Otherwise, returns a dictionary of 56 testdir, returns an empty dictionary. Otherwise, returns a dictionary of
57 parsed keyvals. Duplicate keys are uniquified by their instance number. 57 parsed keyvals. Duplicate keys are uniquified by their instance number.
58 """ 58 """
59 59
60 perf = {} 60 perf = {}
61 if not self._options.perf: 61 if not self._collect_perf:
62 return perf 62 return perf
63 63
64 keyval_file = os.path.join(testdir, 'results', 'keyval') 64 keyval_file = os.path.join(testdir, 'results', 'keyval')
65 if not os.path.isfile(keyval_file): 65 if not os.path.isfile(keyval_file):
66 return perf 66 return perf
67 67
68 instances = {} 68 instances = {}
69 69
70 for line in open(keyval_file): 70 for line in open(keyval_file):
71 match = re.search(r'^(.+){perf}=(.+)$', line) 71 match = re.search(r'^(.+){perf}=(.+)$', line)
72 if match: 72 if match:
73 key = match.group(1) 73 key = match.group(1)
74 val = match.group(2) 74 val = match.group(2)
75 75
76 # If the same key name was generated multiple times, uniquify all 76 # If the same key name was generated multiple times, uniquify all
77 # instances other than the first one by adding the instance count 77 # instances other than the first one by adding the instance count
78 # to the key name. 78 # to the key name.
79 key_inst = key 79 key_inst = key
80 instance = instances.get(key, 0) 80 instance = instances.get(key, 0)
81 if instance: 81 if instance:
82 key_inst = '%s{%d}' % (key, instance) 82 key_inst = '%s{%d}' % (key, instance)
83 instances[key] = instance + 1 83 instances[key] = instance + 1
84 84
85 perf[key_inst] = val 85 perf[key_inst] = val
86 86
87 return perf 87 return perf
88 88
89 def _CollectResult(self, testdir): 89 def _CollectResult(self, testdir, results):
90 """Adds results stored under testdir to the self._results dictionary. 90 """Adds results stored under testdir to the self._results dictionary.
91 91
92 If testdir contains 'status.log' or 'status' files, assume it's a test 92 If testdir contains 'status.log' or 'status' files, assume it's a test
93 result directory and add the results data to the self._results dictionary. 93 result directory and add the results data to the self._results dictionary.
94 The test directory name is used as a key into the results dictionary. 94 The test directory name is used as a key into the results dictionary.
95 95
96 Args: 96 Args:
97 testdir: The autoserv test result directory. 97 testdir: The autoserv test result directory.
98 results: Results dictionary to store results in.
98 """ 99 """
99 100
100 status_file = os.path.join(testdir, 'status.log') 101 status_file = os.path.join(testdir, 'status.log')
101 if not os.path.isfile(status_file): 102 if not os.path.isfile(status_file):
102 status_file = os.path.join(testdir, 'status') 103 status_file = os.path.join(testdir, 'status')
103 if not os.path.isfile(status_file): 104 if not os.path.isfile(status_file):
104 return 105 return
105 106
106 # Remove false positives that are missing a debug dir.
107 if not os.path.exists(os.path.join(testdir, 'debug')):
108 return
sosa 2011/03/31 20:03:34 Why move this into the caller but not the above?
DaleCurtis 2011/03/31 20:17:43 Just checking for the debug directory prunes every
109
110 status_raw = open(status_file, 'r').read() 107 status_raw = open(status_file, 'r').read()
111 status = 'FAIL' 108 status = 'FAIL'
112 if (re.search(r'GOOD.+completed successfully', status_raw) and 109 if (re.search(r'GOOD.+completed successfully', status_raw) and
113 not re.search(r'ABORT|ERROR|FAIL|TEST_NA', status_raw)): 110 not re.search(r'ABORT|ERROR|FAIL|TEST_NA', status_raw)):
114 status = 'PASS' 111 status = 'PASS'
115 112
116 perf = self._CollectPerf(testdir) 113 perf = self._CollectPerf(testdir)
117 114
118 if testdir.startswith(self._options.strip): 115 if testdir.startswith(self._strip_text):
119 testdir = testdir.replace(self._options.strip, '', 1) 116 testdir = testdir.replace(self._strip_text, '', 1)
120 117
121 crashes = [] 118 crashes = []
122 regex = re.compile('Received crash notification for ([-\w]+).+ (sig \d+)') 119 regex = re.compile('Received crash notification for ([-\w]+).+ (sig \d+)')
123 for match in regex.finditer(status_raw): 120 for match in regex.finditer(status_raw):
124 if (match.group(1) in _CRASH_WHITELIST and 121 if (match.group(1) in _CRASH_WHITELIST and
125 match.group(2) in _CRASH_WHITELIST[match.group(1)]): 122 match.group(2) in _CRASH_WHITELIST[match.group(1)]):
126 continue 123 continue
127 crashes.append('%s %s' % match.groups()) 124 crashes.append('%s %s' % match.groups())
128 125
129 self._results[testdir] = {'crashes': crashes, 126 results[testdir] = {'crashes': crashes, 'status': status, 'perf': perf}
130 'status': status,
131 'perf': perf}
132 127
133 def _CollectResultsRec(self, resdir): 128 def CollectResults(self, resdir):
134 """Recursively collect results into the self._results dictionary. 129 """Recursively collect results into a dictionary.
135 130
136 Args: 131 Args:
137 resdir: results/test directory to parse results from and recurse into. 132 resdir: results/test directory to parse results from and recurse into.
133
134 Returns:
135 Dictionary of results.
138 """ 136 """
137 results = {}
138 self._CollectResult(resdir, results)
139 for testdir in glob.glob(os.path.join(resdir, '*')):
140 # Remove false positives that are missing a debug dir.
141 if not os.path.exists(os.path.join(testdir, 'debug')):
142 continue
139 143
140 self._CollectResult(resdir) 144 results.update(self.CollectResults(testdir))
141 for testdir in glob.glob(os.path.join(resdir, '*')): 145 return results
142 self._CollectResultsRec(testdir) 146
147
148 class ReportGenerator(object):
149 """Collects and displays data from autoserv results directories.
150
151 This class collects status and performance data from one or more autoserv
152 result directories and generates test reports.
153 """
154
155 _KEYVAL_INDENT = 2
156
157 def __init__(self, options, args):
158 self._options = options
159 self._args = args
160 self._color = Color(options.color)
143 161
144 def _CollectResults(self): 162 def _CollectResults(self):
145 """Parses results into the self._results dictionary. 163 """Parses results into the self._results dictionary.
146 164
147 Initializes a dictionary (self._results) with test folders as keys and 165 Initializes a dictionary (self._results) with test folders as keys and
148 result data (status, perf keyvals) as values. 166 result data (status, perf keyvals) as values.
149 """ 167 """
150 self._results = {} 168 self._results = {}
169 collector = ResultCollector(self._options.perf, self._options.strip)
151 for resdir in self._args: 170 for resdir in self._args:
152 if not os.path.isdir(resdir): 171 if not os.path.isdir(resdir):
153 Die('\'%s\' does not exist' % resdir) 172 Die('\'%s\' does not exist' % resdir)
154 self._CollectResultsRec(resdir) 173 self._results.update(collector.CollectResults(resdir))
155 174
156 if not self._results: 175 if not self._results:
157 Die('no test directories found') 176 Die('no test directories found')
158 177
159 def GetTestColumnWidth(self): 178 def _GetTestColumnWidth(self):
160 """Returns the test column width based on the test data. 179 """Returns the test column width based on the test data.
161 180
162 Aligns the test results by formatting the test directory entry based on 181 Aligns the test results by formatting the test directory entry based on
163 the longest test directory or perf key string stored in the self._results 182 the longest test directory or perf key string stored in the self._results
164 dictionary. 183 dictionary.
165 184
166 Returns: 185 Returns:
167 The width for the test columnt. 186 The width for the test columnt.
168 """ 187 """
169 width = len(max(self._results, key=len)) 188 width = len(max(self._results, key=len))
(...skipping 10 matching lines...) Expand all
180 Prints a result table to stdout. Each row of the table contains the test 199 Prints a result table to stdout. Each row of the table contains the test
181 result directory and the test result (PASS, FAIL). If the perf option is 200 result directory and the test result (PASS, FAIL). If the perf option is
182 enabled, each test entry is followed by perf keyval entries from the test 201 enabled, each test entry is followed by perf keyval entries from the test
183 results. 202 results.
184 """ 203 """
185 tests = self._results.keys() 204 tests = self._results.keys()
186 tests.sort() 205 tests.sort()
187 206
188 tests_with_errors = [] 207 tests_with_errors = []
189 208
190 width = self.GetTestColumnWidth() 209 width = self._GetTestColumnWidth()
191 line = ''.ljust(width + 5, '-') 210 line = ''.ljust(width + 5, '-')
192 211
193 crashes = {} 212 crashes = {}
194 tests_pass = 0 213 tests_pass = 0
195 print line 214 print line
196 for test in tests: 215 for test in tests:
197 # Emit the test/status entry first 216 # Emit the test/status entry first
198 test_entry = test.ljust(width) 217 test_entry = test.ljust(width)
199 result = self._results[test] 218 result = self._results[test]
200 status_entry = result['status'] 219 status_entry = result['status']
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
250 print 'Total unique crashes: ' + self._color.Color(Color.BOLD, 269 print 'Total unique crashes: ' + self._color.Color(Color.BOLD,
251 str(len(crashes))) 270 str(len(crashes)))
252 else: 271 else:
253 print self._color.Color(Color.GREEN, 272 print self._color.Color(Color.GREEN,
254 'No crashes detected during testing.') 273 'No crashes detected during testing.')
255 274
256 # Print out error log for failed tests. 275 # Print out error log for failed tests.
257 if self._options.print_debug: 276 if self._options.print_debug:
258 for test in tests_with_errors: 277 for test in tests_with_errors:
259 debug_file_regex = os.path.join(self._options.strip, test, 'debug', 278 debug_file_regex = os.path.join(self._options.strip, test, 'debug',
260 '%s*.ERROR' % os.path.basename(test)) 279 '%s*.ERROR' % os.path.basename(test))
261 for path in glob.glob(debug_file_regex): 280 for path in glob.glob(debug_file_regex):
262 try: 281 try:
263 fh = open(path) 282 fh = open(path)
264 print >> sys.stderr, ( 283 print >> sys.stderr, (
265 '\n========== ERROR FILE %s FOR TEST %s ==============\n' % ( 284 '\n========== ERROR FILE %s FOR TEST %s ==============\n' % (
266 path, test)) 285 path, test))
267 out = fh.read() 286 out = fh.read()
268 while out: 287 while out:
269 print >> sys.stderr, out 288 print >> sys.stderr, out
270 out = fh.read() 289 out = fh.read()
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
319 if not args: 338 if not args:
320 parser.print_help() 339 parser.print_help()
321 Die('no result directories provided') 340 Die('no result directories provided')
322 341
323 generator = ReportGenerator(options, args) 342 generator = ReportGenerator(options, args)
324 generator.Run() 343 generator.Run()
325 344
326 345
327 if __name__ == '__main__': 346 if __name__ == '__main__':
328 main() 347 main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698