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 |