| OLD | NEW |
| 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 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 106 status = 'FAIL' | 106 status = 'FAIL' |
| 107 if (re.search(r'GOOD.+completed successfully', status_raw) and | 107 if (re.search(r'GOOD.+completed successfully', status_raw) and |
| 108 not re.search(r'ABORT|ERROR|FAIL|TEST_NA', status_raw)): | 108 not re.search(r'ABORT|ERROR|FAIL|TEST_NA', status_raw)): |
| 109 status = 'PASS' | 109 status = 'PASS' |
| 110 | 110 |
| 111 perf = self._CollectPerf(testdir) | 111 perf = self._CollectPerf(testdir) |
| 112 | 112 |
| 113 if testdir.startswith(self._options.strip): | 113 if testdir.startswith(self._options.strip): |
| 114 testdir = testdir.replace(self._options.strip, '', 1) | 114 testdir = testdir.replace(self._options.strip, '', 1) |
| 115 | 115 |
| 116 self._results[testdir] = {'status': status, | 116 crashes = [] |
| 117 regex = re.compile('Received crash notification for ([-\w]+).+ (sig \d+)') |
| 118 for match in regex.finditer(status_raw): |
| 119 crashes.append('%s %s' % match.groups()) |
| 120 |
| 121 self._results[testdir] = {'crashes': crashes, |
| 122 'status': status, |
| 117 'perf': perf} | 123 'perf': perf} |
| 118 | 124 |
| 119 def _CollectResultsRec(self, resdir): | 125 def _CollectResultsRec(self, resdir): |
| 120 """Recursively collect results into the self._results dictionary. | 126 """Recursively collect results into the self._results dictionary. |
| 121 | 127 |
| 122 Args: | 128 Args: |
| 123 resdir: results/test directory to parse results from and recurse into. | 129 resdir: results/test directory to parse results from and recurse into. |
| 124 """ | 130 """ |
| 125 | 131 |
| 126 self._CollectResult(resdir) | 132 self._CollectResult(resdir) |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 169 results. | 175 results. |
| 170 """ | 176 """ |
| 171 tests = self._results.keys() | 177 tests = self._results.keys() |
| 172 tests.sort() | 178 tests.sort() |
| 173 | 179 |
| 174 tests_with_errors = [] | 180 tests_with_errors = [] |
| 175 | 181 |
| 176 width = self.GetTestColumnWidth() | 182 width = self.GetTestColumnWidth() |
| 177 line = ''.ljust(width + 5, '-') | 183 line = ''.ljust(width + 5, '-') |
| 178 | 184 |
| 185 crashes = {} |
| 179 tests_pass = 0 | 186 tests_pass = 0 |
| 180 print line | 187 print line |
| 181 for test in tests: | 188 for test in tests: |
| 182 # Emit the test/status entry first | 189 # Emit the test/status entry first |
| 183 test_entry = test.ljust(width) | 190 test_entry = test.ljust(width) |
| 184 result = self._results[test] | 191 result = self._results[test] |
| 185 status_entry = result['status'] | 192 status_entry = result['status'] |
| 186 if status_entry == 'PASS': | 193 if status_entry == 'PASS': |
| 187 color = Color.GREEN | 194 color = Color.GREEN |
| 188 tests_pass += 1 | 195 tests_pass += 1 |
| 189 else: | 196 else: |
| 190 color = Color.RED | 197 color = Color.RED |
| 191 tests_with_errors.append(test) | 198 tests_with_errors.append(test) |
| 192 | 199 |
| 193 status_entry = self._color.Color(color, status_entry) | 200 status_entry = self._color.Color(color, status_entry) |
| 194 print test_entry + status_entry | 201 print test_entry + status_entry |
| 195 | 202 |
| 196 # Emit the perf keyvals entries. There will be no entries if the | 203 # Emit the perf keyvals entries. There will be no entries if the |
| 197 # --no-perf option is specified. | 204 # --no-perf option is specified. |
| 198 perf = result['perf'] | 205 perf = result['perf'] |
| 199 perf_keys = perf.keys() | 206 perf_keys = perf.keys() |
| 200 perf_keys.sort() | 207 perf_keys.sort() |
| 201 | 208 |
| 202 for perf_key in perf_keys: | 209 for perf_key in perf_keys: |
| 203 perf_key_entry = perf_key.ljust(width - self._KEYVAL_INDENT) | 210 perf_key_entry = perf_key.ljust(width - self._KEYVAL_INDENT) |
| 204 perf_key_entry = perf_key_entry.rjust(width) | 211 perf_key_entry = perf_key_entry.rjust(width) |
| 205 perf_value_entry = self._color.Color(Color.BOLD, perf[perf_key]) | 212 perf_value_entry = self._color.Color(Color.BOLD, perf[perf_key]) |
| 206 print perf_key_entry + perf_value_entry | 213 print perf_key_entry + perf_value_entry |
| 207 | 214 |
| 215 # Ignore top-level entry, since it's just a combination of all the |
| 216 # individual results. |
| 217 if result['crashes'] and test != tests[0]: |
| 218 for crash in result['crashes']: |
| 219 if not crash in crashes: |
| 220 crashes[crash] = set([]) |
| 221 crashes[crash].add(test) |
| 222 |
| 208 print line | 223 print line |
| 209 | 224 |
| 210 total_tests = len(tests) | 225 total_tests = len(tests) |
| 211 percent_pass = 100 * tests_pass / total_tests | 226 percent_pass = 100 * tests_pass / total_tests |
| 212 pass_str = '%d/%d (%d%%)' % (tests_pass, total_tests, percent_pass) | 227 pass_str = '%d/%d (%d%%)' % (tests_pass, total_tests, percent_pass) |
| 213 print 'Total PASS: ' + self._color.Color(Color.BOLD, pass_str) | 228 print 'Total PASS: ' + self._color.Color(Color.BOLD, pass_str) |
| 214 | 229 |
| 230 if self._options.crash_detection: |
| 231 print '' |
| 232 if crashes: |
| 233 print self._color.Color(Color.RED, 'Crashes detected during testing:') |
| 234 print line |
| 235 |
| 236 for crash_name, crashed_tests in sorted(crashes.iteritems()): |
| 237 print self._color.Color(Color.RED, crash_name) |
| 238 for crashed_test in crashed_tests: |
| 239 print ' '*self._KEYVAL_INDENT + crashed_test |
| 240 |
| 241 print line |
| 242 print 'Total unique crashes: ' + self._color.Color(Color.BOLD, |
| 243 str(len(crashes))) |
| 244 else: |
| 245 print self._color.Color(Color.GREEN, |
| 246 'No crashes detected during testing.') |
| 247 |
| 215 # Print out the client debug information for failed tests. | 248 # Print out the client debug information for failed tests. |
| 216 if self._options.print_debug: | 249 if self._options.print_debug: |
| 217 for test in tests_with_errors: | 250 for test in tests_with_errors: |
| 218 debug_file_regex = os.path.join(self._options.strip, test, 'debug', | 251 debug_file_regex = os.path.join(self._options.strip, test, 'debug', |
| 219 '%s*.DEBUG' % os.path.basename(test)) | 252 '%s*.DEBUG' % os.path.basename(test)) |
| 220 for path in glob.glob(debug_file_regex): | 253 for path in glob.glob(debug_file_regex): |
| 221 try: | 254 try: |
| 222 fh = open(path) | 255 fh = open(path) |
| 223 print >> sys.stderr, ( | 256 print >> sys.stderr, ( |
| 224 '\n========== DEBUG FILE %s FOR TEST %s ==============\n' % ( | 257 '\n========== DEBUG FILE %s FOR TEST %s ==============\n' % ( |
| (...skipping 11 matching lines...) Expand all Loading... |
| 236 | 269 |
| 237 # Sometimes the builders exit before these buffers are flushed. | 270 # Sometimes the builders exit before these buffers are flushed. |
| 238 sys.stderr.flush() | 271 sys.stderr.flush() |
| 239 sys.stdout.flush() | 272 sys.stdout.flush() |
| 240 | 273 |
| 241 def Run(self): | 274 def Run(self): |
| 242 """Runs report generation.""" | 275 """Runs report generation.""" |
| 243 self._CollectResults() | 276 self._CollectResults() |
| 244 self._GenerateReportText() | 277 self._GenerateReportText() |
| 245 for v in self._results.itervalues(): | 278 for v in self._results.itervalues(): |
| 246 if v['status'] != 'PASS': | 279 if v['status'] != 'PASS' or (self._options.crash_detection |
| 280 and v['crashes']): |
| 247 sys.exit(1) | 281 sys.exit(1) |
| 248 | 282 |
| 249 | 283 |
| 250 def main(): | 284 def main(): |
| 251 usage = 'Usage: %prog [options] result-directories...' | 285 usage = 'Usage: %prog [options] result-directories...' |
| 252 parser = optparse.OptionParser(usage=usage) | 286 parser = optparse.OptionParser(usage=usage) |
| 253 parser.add_option('--color', dest='color', action='store_true', | 287 parser.add_option('--color', dest='color', action='store_true', |
| 254 default=_STDOUT_IS_TTY, | 288 default=_STDOUT_IS_TTY, |
| 255 help='Use color for text reports [default if TTY stdout]') | 289 help='Use color for text reports [default if TTY stdout]') |
| 256 parser.add_option('--no-color', dest='color', action='store_false', | 290 parser.add_option('--no-color', dest='color', action='store_false', |
| 257 help='Don\'t use color for text reports') | 291 help='Don\'t use color for text reports') |
| 292 parser.add_option('--no-crash-detection', dest='crash_detection', |
| 293 action='store_false', default=True, |
| 294 help='Don\'t report crashes or error out when detected') |
| 258 parser.add_option('--perf', dest='perf', action='store_true', | 295 parser.add_option('--perf', dest='perf', action='store_true', |
| 259 default=True, | 296 default=True, |
| 260 help='Include perf keyvals in the report [default]') | 297 help='Include perf keyvals in the report [default]') |
| 261 parser.add_option('--no-perf', dest='perf', action='store_false', | 298 parser.add_option('--no-perf', dest='perf', action='store_false', |
| 262 help='Don\'t include perf keyvals in the report') | 299 help='Don\'t include perf keyvals in the report') |
| 263 parser.add_option('--strip', dest='strip', type='string', action='store', | 300 parser.add_option('--strip', dest='strip', type='string', action='store', |
| 264 default='results.', | 301 default='results.', |
| 265 help='Strip a prefix from test directory names' | 302 help='Strip a prefix from test directory names' |
| 266 ' [default: \'%default\']') | 303 ' [default: \'%default\']') |
| 267 parser.add_option('--no-strip', dest='strip', const='', action='store_const', | 304 parser.add_option('--no-strip', dest='strip', const='', action='store_const', |
| 268 help='Don\'t strip a prefix from test directory names') | 305 help='Don\'t strip a prefix from test directory names') |
| 269 parser.add_option('--no-debug', dest='print_debug', action='store_false', | 306 parser.add_option('--no-debug', dest='print_debug', action='store_false', |
| 270 default=True, | 307 default=True, |
| 271 help='Do not print out the debug log when a test fails.') | 308 help='Don\'t print out the debug log when a test fails.') |
| 272 (options, args) = parser.parse_args() | 309 (options, args) = parser.parse_args() |
| 273 | 310 |
| 274 if not args: | 311 if not args: |
| 275 parser.print_help() | 312 parser.print_help() |
| 276 Die('no result directories provided') | 313 Die('no result directories provided') |
| 277 | 314 |
| 278 generator = ReportGenerator(options, args) | 315 generator = ReportGenerator(options, args) |
| 279 generator.Run() | 316 generator.Run() |
| 280 | 317 |
| 281 | 318 |
| 282 if __name__ == '__main__': | 319 if __name__ == '__main__': |
| 283 main() | 320 main() |
| OLD | NEW |