| 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. |
| 11 """ | 11 """ |
| 12 | 12 |
| 13 | 13 |
| 14 import glob | 14 import glob |
| 15 import optparse | 15 import optparse |
| 16 import os | 16 import os |
| 17 import re | 17 import re |
| 18 import sys | 18 import sys |
| 19 | 19 |
| 20 | 20 |
| 21 _STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() |
| 22 |
| 23 |
| 21 class Color(object): | 24 class Color(object): |
| 22 """Conditionally wraps text in ANSI color escape sequences.""" | 25 """Conditionally wraps text in ANSI color escape sequences.""" |
| 23 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) | 26 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) |
| 24 BOLD = -1 | 27 BOLD = -1 |
| 25 COLOR_START = '\033[1;%dm' | 28 COLOR_START = '\033[1;%dm' |
| 26 BOLD_START = '\033[1m' | 29 BOLD_START = '\033[1m' |
| 27 RESET = '\033[0m' | 30 RESET = '\033[0m' |
| 28 | 31 |
| 29 def __init__(self, enabled=True): | 32 def __init__(self, enabled=True): |
| 30 self._enabled = enabled | 33 self._enabled = enabled |
| (...skipping 17 matching lines...) Expand all Loading... |
| 48 start = self.COLOR_START % (color + 30) | 51 start = self.COLOR_START % (color + 30) |
| 49 return start + text + self.RESET | 52 return start + text + self.RESET |
| 50 | 53 |
| 51 | 54 |
| 52 def Die(message): | 55 def Die(message): |
| 53 """Emits a red error message and halts execution. | 56 """Emits a red error message and halts execution. |
| 54 | 57 |
| 55 Args: | 58 Args: |
| 56 message: The message to be emitted before exiting. | 59 message: The message to be emitted before exiting. |
| 57 """ | 60 """ |
| 58 print Color().Color(Color.RED, '\nERROR: ' + message) | 61 print Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message) |
| 59 sys.exit(1) | 62 sys.exit(1) |
| 60 | 63 |
| 61 | 64 |
| 62 class ReportGenerator(object): | 65 class ReportGenerator(object): |
| 63 """Collects and displays data from autoserv results directories. | 66 """Collects and displays data from autoserv results directories. |
| 64 | 67 |
| 65 This class collects status and performance data from one or more autoserv | 68 This class collects status and performance data from one or more autoserv |
| 66 result directories and generates test reports. | 69 result directories and generates test reports. |
| 67 """ | 70 """ |
| 68 | 71 |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 141 status = 'PASS' | 144 status = 'PASS' |
| 142 | 145 |
| 143 perf = self._CollectPerf(testdir) | 146 perf = self._CollectPerf(testdir) |
| 144 | 147 |
| 145 if testdir.startswith(self._options.strip): | 148 if testdir.startswith(self._options.strip): |
| 146 testdir = testdir.replace(self._options.strip, '', 1) | 149 testdir = testdir.replace(self._options.strip, '', 1) |
| 147 | 150 |
| 148 self._results[testdir] = {'status': status, | 151 self._results[testdir] = {'status': status, |
| 149 'perf': perf} | 152 'perf': perf} |
| 150 | 153 |
| 154 def _CollectResultsRec(self, resdir): |
| 155 """Recursively collect results into the self._results dictionary. |
| 156 |
| 157 Args: |
| 158 resdir: results/test directory to parse results from and recurse into. |
| 159 """ |
| 160 |
| 161 self._CollectResult(resdir) |
| 162 for testdir in glob.glob(os.path.join(resdir, '*')): |
| 163 self._CollectResultsRec(testdir) |
| 164 |
| 151 def _CollectResults(self): | 165 def _CollectResults(self): |
| 152 """Parses results into the self._results dictionary. | 166 """Parses results into the self._results dictionary. |
| 153 | 167 |
| 154 Initializes a dictionary (self._results) with test folders as keys and | 168 Initializes a dictionary (self._results) with test folders as keys and |
| 155 result data (status, perf keyvals) as values. | 169 result data (status, perf keyvals) as values. |
| 156 """ | 170 """ |
| 157 self._results = {} | 171 self._results = {} |
| 158 for resdir in self._args: | 172 for resdir in self._args: |
| 159 if not os.path.isdir(resdir): | 173 if not os.path.isdir(resdir): |
| 160 Die('\'%s\' does not exist' % resdir) | 174 Die('\'%s\' does not exist' % resdir) |
| 161 | 175 self._CollectResultsRec(resdir) |
| 162 # Check the top level result directory, in case the control file or | |
| 163 # autoserv have signalled failures. Then check subdirectories. | |
| 164 self._CollectResult(resdir) | |
| 165 for testdir in glob.glob(os.path.join(resdir, '*')): | |
| 166 self._CollectResult(testdir) | |
| 167 | 176 |
| 168 if not self._results: | 177 if not self._results: |
| 169 Die('no test directories found') | 178 Die('no test directories found') |
| 170 | 179 |
| 171 def GetTestColumnWidth(self): | 180 def GetTestColumnWidth(self): |
| 172 """Returns the test column width based on the test data. | 181 """Returns the test column width based on the test data. |
| 173 | 182 |
| 174 Aligns the test results by formatting the test directory entry based on | 183 Aligns the test results by formatting the test directory entry based on |
| 175 the longest test directory or perf key string stored in the self._results | 184 the longest test directory or perf key string stored in the self._results |
| 176 dictionary. | 185 dictionary. |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 228 print perf_key_entry + perf_value_entry | 237 print perf_key_entry + perf_value_entry |
| 229 | 238 |
| 230 print line | 239 print line |
| 231 | 240 |
| 232 total_tests = len(tests) | 241 total_tests = len(tests) |
| 233 percent_pass = 100 * tests_pass / total_tests | 242 percent_pass = 100 * tests_pass / total_tests |
| 234 pass_str = '%d/%d (%d%%)' % (tests_pass, total_tests, percent_pass) | 243 pass_str = '%d/%d (%d%%)' % (tests_pass, total_tests, percent_pass) |
| 235 print 'Total PASS: ' + self._color.Color(Color.BOLD, pass_str) | 244 print 'Total PASS: ' + self._color.Color(Color.BOLD, pass_str) |
| 236 | 245 |
| 237 def Run(self): | 246 def Run(self): |
| 238 """Run report generation.""" | 247 """Runs report generation.""" |
| 239 self._CollectResults() | 248 self._CollectResults() |
| 240 self._GenerateReportText() | 249 self._GenerateReportText() |
| 241 | 250 |
| 242 | 251 |
| 243 def main(): | 252 def main(): |
| 244 usage = 'Usage: %prog [options] result-directories...' | 253 usage = 'Usage: %prog [options] result-directories...' |
| 245 parser = optparse.OptionParser(usage=usage) | 254 parser = optparse.OptionParser(usage=usage) |
| 246 parser.add_option('--color', dest='color', action='store_true', | 255 parser.add_option('--color', dest='color', action='store_true', |
| 247 default=True, | 256 default=_STDOUT_IS_TTY, |
| 248 help='Use color for text reports [default]') | 257 help='Use color for text reports [default if TTY stdout]') |
| 249 parser.add_option('--no-color', dest='color', action='store_false', | 258 parser.add_option('--no-color', dest='color', action='store_false', |
| 250 help='Don\'t use color for text reports') | 259 help='Don\'t use color for text reports') |
| 251 parser.add_option('--perf', dest='perf', action='store_true', | 260 parser.add_option('--perf', dest='perf', action='store_true', |
| 252 default=True, | 261 default=True, |
| 253 help='Include perf keyvals in the report [default]') | 262 help='Include perf keyvals in the report [default]') |
| 254 parser.add_option('--no-perf', dest='perf', action='store_false', | 263 parser.add_option('--no-perf', dest='perf', action='store_false', |
| 255 help='Don\'t include perf keyvals in the report') | 264 help='Don\'t include perf keyvals in the report') |
| 256 parser.add_option('--strip', dest='strip', type='string', action='store', | 265 parser.add_option('--strip', dest='strip', type='string', action='store', |
| 257 default='results.', | 266 default='results.', |
| 258 help='Strip a prefix from test directory names' | 267 help='Strip a prefix from test directory names' |
| 259 ' [default: \'%default\']') | 268 ' [default: \'%default\']') |
| 260 parser.add_option('--no-strip', dest='strip', const='', action='store_const', | 269 parser.add_option('--no-strip', dest='strip', const='', action='store_const', |
| 261 help='Don\'t strip a prefix from test directory names') | 270 help='Don\'t strip a prefix from test directory names') |
| 262 (options, args) = parser.parse_args() | 271 (options, args) = parser.parse_args() |
| 263 | 272 |
| 264 if not args: | 273 if not args: |
| 265 parser.print_help() | 274 parser.print_help() |
| 266 Die('no result directories provided') | 275 Die('no result directories provided') |
| 267 | 276 |
| 268 generator = ReportGenerator(options, args) | 277 generator = ReportGenerator(options, args) |
| 269 generator.Run() | 278 generator.Run() |
| 270 | 279 |
| 271 | 280 |
| 272 if __name__ == '__main__': | 281 if __name__ == '__main__': |
| 273 main() | 282 main() |
| OLD | NEW |