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

Side by Side Diff: generate_test_report.py

Issue 2951003: Make generate_test_reports a symlink to the py file so it can be imported. (Closed) Base URL: ssh://git@chromiumos-git//crosutils.git
Patch Set: Created 10 years, 5 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
« no previous file with comments | « generate_test_report ('k') | 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
(Empty)
1 #!/usr/bin/python
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
4 # found in the LICENSE file.
5
6
7 """Parses and displays the contents of one or more autoserv result directories.
8
9 This script parses the contents of one or more autoserv results folders and
10 generates test reports.
11 """
12
13
14 import glob
15 import optparse
16 import os
17 import re
18 import sys
19
20
21 _STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
22
23
24 class Color(object):
25 """Conditionally wraps text in ANSI color escape sequences."""
26 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
27 BOLD = -1
28 COLOR_START = '\033[1;%dm'
29 BOLD_START = '\033[1m'
30 RESET = '\033[0m'
31
32 def __init__(self, enabled=True):
33 self._enabled = enabled
34
35 def Color(self, color, text):
36 """Returns text with conditionally added color escape sequences.
37
38 Args:
39 color: Text color -- one of the color constants defined in this class.
40 text: The text to color.
41
42 Returns:
43 If self._enabled is False, returns the original text. If it's True,
44 returns text with color escape sequences based on the value of color.
45 """
46 if not self._enabled:
47 return text
48 if color == self.BOLD:
49 start = self.BOLD_START
50 else:
51 start = self.COLOR_START % (color + 30)
52 return start + text + self.RESET
53
54
55 def Die(message):
56 """Emits a red error message and halts execution.
57
58 Args:
59 message: The message to be emitted before exiting.
60 """
61 print Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message)
62 sys.exit(1)
63
64
65 class ReportGenerator(object):
66 """Collects and displays data from autoserv results directories.
67
68 This class collects status and performance data from one or more autoserv
69 result directories and generates test reports.
70 """
71
72 _KEYVAL_INDENT = 2
73
74 def __init__(self, options, args):
75 self._options = options
76 self._args = args
77 self._color = Color(options.color)
78
79 def _CollectPerf(self, testdir):
80 """Parses keyval file under testdir.
81
82 If testdir contains a result folder, process the keyval file and return
83 a dictionary of perf keyval pairs.
84
85 Args:
86 testdir: The autoserv test result directory.
87
88 Returns:
89 If the perf option is disabled or the there's no keyval file under
90 testdir, returns an empty dictionary. Otherwise, returns a dictionary of
91 parsed keyvals. Duplicate keys are uniquified by their instance number.
92 """
93
94 perf = {}
95 if not self._options.perf:
96 return perf
97
98 keyval_file = os.path.join(testdir, 'results', 'keyval')
99 if not os.path.isfile(keyval_file):
100 return perf
101
102 instances = {}
103
104 for line in open(keyval_file):
105 match = re.search(r'^(.+){perf}=(.+)$', line)
106 if match:
107 key = match.group(1)
108 val = match.group(2)
109
110 # If the same key name was generated multiple times, uniquify all
111 # instances other than the first one by adding the instance count
112 # to the key name.
113 key_inst = key
114 instance = instances.get(key, 0)
115 if instance:
116 key_inst = '%s{%d}' % (key, instance)
117 instances[key] = instance + 1
118
119 perf[key_inst] = val
120
121 return perf
122
123 def _CollectResult(self, testdir):
124 """Adds results stored under testdir to the self._results dictionary.
125
126 If testdir contains 'status.log' or 'status' files, assume it's a test
127 result directory and add the results data to the self._results dictionary.
128 The test directory name is used as a key into the results dictionary.
129
130 Args:
131 testdir: The autoserv test result directory.
132 """
133
134 status_file = os.path.join(testdir, 'status.log')
135 if not os.path.isfile(status_file):
136 status_file = os.path.join(testdir, 'status')
137 if not os.path.isfile(status_file):
138 return
139
140 status_raw = open(status_file, 'r').read()
141 status = 'FAIL'
142 if (re.search(r'GOOD.+completed successfully', status_raw) and
143 not re.search(r'ABORT|ERROR|FAIL|TEST_NA', status_raw)):
144 status = 'PASS'
145
146 perf = self._CollectPerf(testdir)
147
148 if testdir.startswith(self._options.strip):
149 testdir = testdir.replace(self._options.strip, '', 1)
150
151 self._results[testdir] = {'status': status,
152 'perf': perf}
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
165 def _CollectResults(self):
166 """Parses results into the self._results dictionary.
167
168 Initializes a dictionary (self._results) with test folders as keys and
169 result data (status, perf keyvals) as values.
170 """
171 self._results = {}
172 for resdir in self._args:
173 if not os.path.isdir(resdir):
174 Die('\'%s\' does not exist' % resdir)
175 self._CollectResultsRec(resdir)
176
177 if not self._results:
178 Die('no test directories found')
179
180 def GetTestColumnWidth(self):
181 """Returns the test column width based on the test data.
182
183 Aligns the test results by formatting the test directory entry based on
184 the longest test directory or perf key string stored in the self._results
185 dictionary.
186
187 Returns:
188 The width for the test columnt.
189 """
190 width = len(max(self._results, key=len))
191 for result in self._results.values():
192 perf = result['perf']
193 if perf:
194 perf_key_width = len(max(perf, key=len))
195 width = max(width, perf_key_width + self._KEYVAL_INDENT)
196 return width + 1
197
198 def _GenerateReportText(self):
199 """Prints a result report to stdout.
200
201 Prints a result table to stdout. Each row of the table contains the test
202 result directory and the test result (PASS, FAIL). If the perf option is
203 enabled, each test entry is followed by perf keyval entries from the test
204 results.
205 """
206 tests = self._results.keys()
207 tests.sort()
208
209 width = self.GetTestColumnWidth()
210 line = ''.ljust(width + 5, '-')
211
212 tests_pass = 0
213 print line
214 for test in tests:
215 # Emit the test/status entry first
216 test_entry = test.ljust(width)
217 result = self._results[test]
218 status_entry = result['status']
219 if status_entry == 'PASS':
220 color = Color.GREEN
221 tests_pass += 1
222 else:
223 color = Color.RED
224 status_entry = self._color.Color(color, status_entry)
225 print test_entry + status_entry
226
227 # Emit the perf keyvals entries. There will be no entries if the
228 # --no-perf option is specified.
229 perf = result['perf']
230 perf_keys = perf.keys()
231 perf_keys.sort()
232
233 for perf_key in perf_keys:
234 perf_key_entry = perf_key.ljust(width - self._KEYVAL_INDENT)
235 perf_key_entry = perf_key_entry.rjust(width)
236 perf_value_entry = self._color.Color(Color.BOLD, perf[perf_key])
237 print perf_key_entry + perf_value_entry
238
239 print line
240
241 total_tests = len(tests)
242 percent_pass = 100 * tests_pass / total_tests
243 pass_str = '%d/%d (%d%%)' % (tests_pass, total_tests, percent_pass)
244 print 'Total PASS: ' + self._color.Color(Color.BOLD, pass_str)
245
246 def Run(self):
247 """Runs report generation."""
248 self._CollectResults()
249 self._GenerateReportText()
250
251
252 def main():
253 usage = 'Usage: %prog [options] result-directories...'
254 parser = optparse.OptionParser(usage=usage)
255 parser.add_option('--color', dest='color', action='store_true',
256 default=_STDOUT_IS_TTY,
257 help='Use color for text reports [default if TTY stdout]')
258 parser.add_option('--no-color', dest='color', action='store_false',
259 help='Don\'t use color for text reports')
260 parser.add_option('--perf', dest='perf', action='store_true',
261 default=True,
262 help='Include perf keyvals in the report [default]')
263 parser.add_option('--no-perf', dest='perf', action='store_false',
264 help='Don\'t include perf keyvals in the report')
265 parser.add_option('--strip', dest='strip', type='string', action='store',
266 default='results.',
267 help='Strip a prefix from test directory names'
268 ' [default: \'%default\']')
269 parser.add_option('--no-strip', dest='strip', const='', action='store_const',
270 help='Don\'t strip a prefix from test directory names')
271 (options, args) = parser.parse_args()
272
273 if not args:
274 parser.print_help()
275 Die('no result directories provided')
276
277 generator = ReportGenerator(options, args)
278 generator.Run()
279
280
281 if __name__ == '__main__':
282 main()
OLDNEW
« no previous file with comments | « generate_test_report ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698