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

Side by Side Diff: src/scripts/generate_test_report

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

Powered by Google App Engine
This is Rietveld 408576698