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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « generate_test_report ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: generate_test_report.py
diff --git a/generate_test_report.py b/generate_test_report.py
new file mode 100755
index 0000000000000000000000000000000000000000..9235482cb9818f2493afbb97d30430933ee884a3
--- /dev/null
+++ b/generate_test_report.py
@@ -0,0 +1,282 @@
+#!/usr/bin/python
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+"""Parses and displays the contents of one or more autoserv result directories.
+
+This script parses the contents of one or more autoserv results folders and
+generates test reports.
+"""
+
+
+import glob
+import optparse
+import os
+import re
+import sys
+
+
+_STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
+
+
+class Color(object):
+ """Conditionally wraps text in ANSI color escape sequences."""
+ BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
+ BOLD = -1
+ COLOR_START = '\033[1;%dm'
+ BOLD_START = '\033[1m'
+ RESET = '\033[0m'
+
+ def __init__(self, enabled=True):
+ self._enabled = enabled
+
+ def Color(self, color, text):
+ """Returns text with conditionally added color escape sequences.
+
+ Args:
+ color: Text color -- one of the color constants defined in this class.
+ text: The text to color.
+
+ Returns:
+ If self._enabled is False, returns the original text. If it's True,
+ returns text with color escape sequences based on the value of color.
+ """
+ if not self._enabled:
+ return text
+ if color == self.BOLD:
+ start = self.BOLD_START
+ else:
+ start = self.COLOR_START % (color + 30)
+ return start + text + self.RESET
+
+
+def Die(message):
+ """Emits a red error message and halts execution.
+
+ Args:
+ message: The message to be emitted before exiting.
+ """
+ print Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message)
+ sys.exit(1)
+
+
+class ReportGenerator(object):
+ """Collects and displays data from autoserv results directories.
+
+ This class collects status and performance data from one or more autoserv
+ result directories and generates test reports.
+ """
+
+ _KEYVAL_INDENT = 2
+
+ def __init__(self, options, args):
+ self._options = options
+ self._args = args
+ self._color = Color(options.color)
+
+ def _CollectPerf(self, testdir):
+ """Parses keyval file under testdir.
+
+ If testdir contains a result folder, process the keyval file and return
+ a dictionary of perf keyval pairs.
+
+ Args:
+ testdir: The autoserv test result directory.
+
+ Returns:
+ If the perf option is disabled or the there's no keyval file under
+ testdir, returns an empty dictionary. Otherwise, returns a dictionary of
+ parsed keyvals. Duplicate keys are uniquified by their instance number.
+ """
+
+ perf = {}
+ if not self._options.perf:
+ return perf
+
+ keyval_file = os.path.join(testdir, 'results', 'keyval')
+ if not os.path.isfile(keyval_file):
+ return perf
+
+ instances = {}
+
+ for line in open(keyval_file):
+ match = re.search(r'^(.+){perf}=(.+)$', line)
+ if match:
+ key = match.group(1)
+ val = match.group(2)
+
+ # If the same key name was generated multiple times, uniquify all
+ # instances other than the first one by adding the instance count
+ # to the key name.
+ key_inst = key
+ instance = instances.get(key, 0)
+ if instance:
+ key_inst = '%s{%d}' % (key, instance)
+ instances[key] = instance + 1
+
+ perf[key_inst] = val
+
+ return perf
+
+ def _CollectResult(self, testdir):
+ """Adds results stored under testdir to the self._results dictionary.
+
+ If testdir contains 'status.log' or 'status' files, assume it's a test
+ result directory and add the results data to the self._results dictionary.
+ The test directory name is used as a key into the results dictionary.
+
+ Args:
+ testdir: The autoserv test result directory.
+ """
+
+ status_file = os.path.join(testdir, 'status.log')
+ if not os.path.isfile(status_file):
+ status_file = os.path.join(testdir, 'status')
+ if not os.path.isfile(status_file):
+ return
+
+ status_raw = open(status_file, 'r').read()
+ status = 'FAIL'
+ if (re.search(r'GOOD.+completed successfully', status_raw) and
+ not re.search(r'ABORT|ERROR|FAIL|TEST_NA', status_raw)):
+ status = 'PASS'
+
+ perf = self._CollectPerf(testdir)
+
+ if testdir.startswith(self._options.strip):
+ testdir = testdir.replace(self._options.strip, '', 1)
+
+ self._results[testdir] = {'status': status,
+ 'perf': perf}
+
+ def _CollectResultsRec(self, resdir):
+ """Recursively collect results into the self._results dictionary.
+
+ Args:
+ resdir: results/test directory to parse results from and recurse into.
+ """
+
+ self._CollectResult(resdir)
+ for testdir in glob.glob(os.path.join(resdir, '*')):
+ self._CollectResultsRec(testdir)
+
+ def _CollectResults(self):
+ """Parses results into the self._results dictionary.
+
+ Initializes a dictionary (self._results) with test folders as keys and
+ result data (status, perf keyvals) as values.
+ """
+ self._results = {}
+ for resdir in self._args:
+ if not os.path.isdir(resdir):
+ Die('\'%s\' does not exist' % resdir)
+ self._CollectResultsRec(resdir)
+
+ if not self._results:
+ Die('no test directories found')
+
+ def GetTestColumnWidth(self):
+ """Returns the test column width based on the test data.
+
+ Aligns the test results by formatting the test directory entry based on
+ the longest test directory or perf key string stored in the self._results
+ dictionary.
+
+ Returns:
+ The width for the test columnt.
+ """
+ width = len(max(self._results, key=len))
+ for result in self._results.values():
+ perf = result['perf']
+ if perf:
+ perf_key_width = len(max(perf, key=len))
+ width = max(width, perf_key_width + self._KEYVAL_INDENT)
+ return width + 1
+
+ def _GenerateReportText(self):
+ """Prints a result report to stdout.
+
+ Prints a result table to stdout. Each row of the table contains the test
+ result directory and the test result (PASS, FAIL). If the perf option is
+ enabled, each test entry is followed by perf keyval entries from the test
+ results.
+ """
+ tests = self._results.keys()
+ tests.sort()
+
+ width = self.GetTestColumnWidth()
+ line = ''.ljust(width + 5, '-')
+
+ tests_pass = 0
+ print line
+ for test in tests:
+ # Emit the test/status entry first
+ test_entry = test.ljust(width)
+ result = self._results[test]
+ status_entry = result['status']
+ if status_entry == 'PASS':
+ color = Color.GREEN
+ tests_pass += 1
+ else:
+ color = Color.RED
+ status_entry = self._color.Color(color, status_entry)
+ print test_entry + status_entry
+
+ # Emit the perf keyvals entries. There will be no entries if the
+ # --no-perf option is specified.
+ perf = result['perf']
+ perf_keys = perf.keys()
+ perf_keys.sort()
+
+ for perf_key in perf_keys:
+ perf_key_entry = perf_key.ljust(width - self._KEYVAL_INDENT)
+ perf_key_entry = perf_key_entry.rjust(width)
+ perf_value_entry = self._color.Color(Color.BOLD, perf[perf_key])
+ print perf_key_entry + perf_value_entry
+
+ print line
+
+ total_tests = len(tests)
+ percent_pass = 100 * tests_pass / total_tests
+ pass_str = '%d/%d (%d%%)' % (tests_pass, total_tests, percent_pass)
+ print 'Total PASS: ' + self._color.Color(Color.BOLD, pass_str)
+
+ def Run(self):
+ """Runs report generation."""
+ self._CollectResults()
+ self._GenerateReportText()
+
+
+def main():
+ usage = 'Usage: %prog [options] result-directories...'
+ parser = optparse.OptionParser(usage=usage)
+ parser.add_option('--color', dest='color', action='store_true',
+ default=_STDOUT_IS_TTY,
+ help='Use color for text reports [default if TTY stdout]')
+ parser.add_option('--no-color', dest='color', action='store_false',
+ help='Don\'t use color for text reports')
+ parser.add_option('--perf', dest='perf', action='store_true',
+ default=True,
+ help='Include perf keyvals in the report [default]')
+ parser.add_option('--no-perf', dest='perf', action='store_false',
+ help='Don\'t include perf keyvals in the report')
+ parser.add_option('--strip', dest='strip', type='string', action='store',
+ default='results.',
+ help='Strip a prefix from test directory names'
+ ' [default: \'%default\']')
+ parser.add_option('--no-strip', dest='strip', const='', action='store_const',
+ help='Don\'t strip a prefix from test directory names')
+ (options, args) = parser.parse_args()
+
+ if not args:
+ parser.print_help()
+ Die('no result directories provided')
+
+ generator = ReportGenerator(options, args)
+ generator.Run()
+
+
+if __name__ == '__main__':
+ main()
« 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