blob: 76bd27beaba5f9121233ca5e7f754f0f6fdc5bdd [file] [log] [blame]
#!/usr/bin/python -u
#
# Copyright (c) 2011 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.
#
# Site extension of the default parser. Generates a JSON report of test results.
#
# This site parser is used to generate a JSON report of test failures, crashes,
# and the associated logs for later consumption by an Email generator.
#
# The parser uses the test report generator which comes bundled with the Chrome
# OS source tree in order to maintain consistency. As well as not having to keep
# track of any secondary failure white lists.
#
# The path to the Chrome OS source tree is defined in global_config under the
# CROS section as 'source_tree'.
#
# Existing parse behavior is kept completely intact. If the site parser is not
# configured it will print a debug message and exit after default parser is
# called.
#
import os, json, sys
import common
from autotest_lib.tko import parse, utils as tko_utils
from autotest_lib.tko.parsers import version_0
from autotest_lib.client.common_lib import global_config, utils
# Name of the report file to produce upon completion.
_JSON_REPORT_FILE = 'results.json'
# Number of log lines to include from error log with each test results.
_ERROR_LOG_LIMIT = 10
# Status information is generally more useful than error log, so provide a lot.
_STATUS_LOG_LIMIT = 50
def parse_reason(path):
"""Process status.log or status and return a test-name: reason dict."""
status_log = os.path.join(path, 'status.log')
if not os.path.exists(status_log):
status_log = os.path.join(path, 'status')
if not os.path.exists(status_log):
return
reasons = {}
last_test = None
for line in open(status_log).readlines():
try:
# Since we just want the status line parser, it's okay to use the
# version_0 parser directly; all other parsers extend it.
status = version_0.status_line.parse_line(line)
except:
status = None
# Assemble multi-line reasons into a single reason.
if not status and last_test:
reasons[last_test] += line
# Skip non-lines, empty lines, and successful tests.
if not status or not status.reason.strip() or status.status == 'GOOD':
continue
# Update last_test name, so we know which reason to append multi-line
# reasons to.
last_test = status.testname
reasons[last_test] = status.reason
return reasons
def main():
# Call the original parser.
parse.main()
# Results directory should be the last argument passed in.
results_dir = sys.argv[-1]
# Load the Chrome OS source tree location.
cros_src_dir = global_config.global_config.get_config_value(
'CROS', 'source_tree', default='')
# We want the standard Autotest parser to keep working even if we haven't
# been setup properly.
if not cros_src_dir:
tko_utils.dprint(
'Unable to load required components for site parser. Falling back'
' to default parser.')
return
# Load ResultCollector from the Chrome OS source tree.
sys.path.append(os.path.join(
cros_src_dir, 'src/platform/crostestutils/utils_py'))
from generate_test_report import ResultCollector
# Collect results using the standard Chrome OS test report generator. Doing
# so allows us to use the same crash white list and reporting standards the
# VM based test instances use.
results = ResultCollector().CollectResults(results_dir)
# We don't care about successful tests. We only want failed or crashing.
# Note: .items() generates a copy of the dictionary, so it's safe to delete.
for k, v in results.items():
if v['status'] == 'PASS' and not v['crashes']:
del results[k]
# Filter results and collect logs. If we can't find a log for the test, skip
# it. The Emailer will fill in the blanks using Database data later.
filtered_results = {}
for test in results:
result_log = ''
test_name = os.path.basename(test)
error = os.path.join(test, 'debug', '%s.ERROR' % test_name)
# If the error log doesn't exist, we don't care about this test.
if not os.path.isfile(error):
continue
# Parse failure reason for this test.
for t, r in parse_reason(test).iteritems():
# Server tests may have subtests which will each have their own
# reason, so display the test name for the subtest in that case.
if t != test_name:
result_log += '%s: ' % t
result_log += '%s\n\n' % r.strip()
# Trim results_log to last _STATUS_LOG_LIMIT lines.
short_result_log = '\n'.join(
result_log.splitlines()[-1 * _STATUS_LOG_LIMIT:]).strip()
# Let the reader know we've trimmed the log.
if short_result_log != result_log.strip():
short_result_log = (
'[...displaying only the last %d status log lines...]\n%s' % (
_STATUS_LOG_LIMIT, short_result_log))
# Pull out only the last _LOG_LIMIT lines of the file.
short_log = utils.system_output('tail -n %d %s' % (
_ERROR_LOG_LIMIT, error))
# Let the reader know we've trimmed the log.
if len(short_log.splitlines()) == _ERROR_LOG_LIMIT:
short_log = (
'[...displaying only the last %d error log lines...]\n%s' % (
_ERROR_LOG_LIMIT, short_log))
filtered_results[test_name] = results[test]
filtered_results[test_name]['log'] = '%s\n\n%s' % (
short_result_log, short_log)
# Generate JSON dump of results. Store in results dir.
json_file = open(os.path.join(results_dir, _JSON_REPORT_FILE), 'w')
json.dump(filtered_results, json_file)
json_file.close()
if __name__ == '__main__':
main()