blob: 7bac9163f1e1ce7e1a5e3d30cb9c05678c4ca969 [file] [log] [blame]
#!/usr/bin/python2
# Copyright 2015 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.
# Script to display latest test run status and test results for a user-
# specified list of browsertests. The names of the desired browertests are
# read from a file located (by default) in the same directory as this script.
#
# Latest test run status for a build is fetched from the builder, read from
# the 'stdio' text file located at:
# http://BUILDER_HOST/p/BUILDER_PROJECT/builders/BUILDER_NAME/
# builds/BUILD_NUMBER/steps/TEST_TYPE/logs/stdio/text
#
# Recent test results history are fetched from the test-results server, read
# from the results.json file located at:
# https://TR_HOST/testfile?master=TR_MASTER&builder=BUILDER_NAME
# &testtype=TEST_TYPE&name=results.json
#
"""Script to report test status and results of user-specified browsertests."""
from __future__ import print_function
import argparse
import json
import os
import re
import sys
import urllib2
__author__ = 'scunningham@google.com (Scott Cunningham)'
# Builder url parameter defaults.
_BUILDER_HOST = 'chromegw.corp.google.com' # Host name of Builder.
_BUILDER_PROJECT = 'chromeos.chrome' # Project name of Builder.
_BUILDER_NAME = 'Linux ChromeOS Buildspec Tests' # Builder name.
_BUILD_NUMBER = -1
_TEST_TYPE = 'browser_tests'
# Test-results server url parameter defaults.
_TR_HOST = 'test-results.appspot.com' # Host name of test-results server.
_TR_MASTER = 'ChromiumChromiumOS' # Project master of test-results server.
# Input file and report directory parameter defaults.
_TESTS_FILE = './tests' # Path to the file that contains the tests names.
_REPORT_DIR = os.getcwd() # Path to the directory to store the results report.
# Contents of test result types.
_RESULT_TYPES = {
'A': 'AUDIO',
'C': 'CRASH',
'F': 'TEXT',
'I': 'IMAGE',
'L': 'FLAKY',
'O': 'MISSING',
'N': 'NO DATA',
'Q': 'FAIL',
'P': 'PASS',
'T': 'TIMEOUT',
'Y': 'NOTRUN',
'X': 'SKIP',
'Z': 'IMAGE+TEXT'
}
# Report header result types.
_NOTRUN = 'NotRun'
_FAILED = 'Failed'
_PASSED = 'Passed'
_MISSING = 'Missing'
def _GetUserSpecifiedTests(tests_file):
"""Get list of user-specified tests from the given tests_file.
File must be a text file, formatted with one line per test. Leading and
trailing spaces and blanklines are stripped from the test list. If a line
still contains a space return only the first word (to remove comments).
Args:
tests_file: Path and name of tests file.
Returns:
List of user tests read from lines in the tests_file.
"""
print('\nReading user-specified tests from: %s' % tests_file)
content = open(tests_file, 'r').read().strip()
user_tests = [line.strip().split()[0] for line in content.splitlines()
if line.strip()]
if not user_tests:
print('Error: tests file is empty.')
sys.exit(2)
print(' Number of user tests: %s' % len(user_tests))
return user_tests
def _GetBuildsList(build_dict):
"""Get list of available (cached) builds on the builder.
Args:
build_dict: build info: builder host, project, and name.
Returns:
List of builds available on builder.
"""
# Generate percent-encoded builder url.
builder_url = ('https://%s/p/%s/json/builders/%s' %
(urllib2.quote(build_dict['builder_host']),
urllib2.quote(build_dict['builder_proj']),
urllib2.quote(build_dict['builder_name'])))
# Fetch builder status file from builder url.
print('\nFetching builder status file from: %s' % builder_url)
try:
response = urllib2.urlopen(builder_url)
builder_status_file = response.read()
response.close()
except urllib2.HTTPError as err:
print('HTTP error: %s' % err)
sys.exit(2)
except urllib2.URLError as err:
print('Notice: Builder was not available: %s' % err)
sys.exit(2)
# Convert json builder status file to dictionary.
builder_status_dict = json.loads(builder_status_file)
build_list = builder_status_dict['cachedBuilds']
print('\nList of avaiable builds: %s\n' % build_list)
return build_list
def _FindBuildByChromiumVersion(build_dict):
"""Find the latest completed build with the given chromium version.
Search from the top of the cached build list for a build with the given
chromium |version| number as it's buildspec_version. If found, return the
build number. If no build is found containing the |version|, return None.
Args:
build_dict: build info, with chromium version number.
Returns:
Number of most recent build containing the given chromium version.
"""
# TODO(scunningham): Implement real function in later version.
cr_version = build_dict['cr_version']
if cr_version:
pass
return _BUILD_NUMBER
def _GetNominalBuildNumber(build_num, builds_list):
"""Verify the build number is in the available builds list.
If the user-specified |build_num| is negative, then verify it is a valid
ordinal index, and get the nominal build number for that index. If it is
positive, then verify the nominal build number exists in the |builds_list|.
Return the nominal build number. Note that if a build number is available
does not mean that the build is valid or completed.
Args:
build_num: nominal build number (positive) or ordinal index (negative).
builds_list: List of available build numbers on the builder.
Returns:
Nominal build number in |builds_list|.
"""
# Verify that build number exists in the builds list.
if build_num < 0: # User entered an index, not a build number.
if len(builds_list) < -build_num:
print('Error: The build_num index %s is too large.' % build_num)
sys.exit(2)
build_number = builds_list[build_num]
print('\n Index %s selects build %s' % (build_num, build_number))
build_num = build_number
elif build_num not in builds_list:
print('Error: build %s is not available.' % build_num)
sys.exit(2)
return build_num
def _LatestCompletedBuild(build_dict, builds_list):
"""Find the latest completed build number from the given list of builds.
Check each build in the |builds_list|, starting with the build number in
the given |build_dict|, to determine if the build completed successfully.
If completed successfully, return the number of the build. If not, continue
checking. If none of the builds completed successfully, exits.
Args:
build_dict: build info dictionary, with build number to start search.
builds_list: List of cached build numbers on the builder.
Returns:
Build number of the latest successfully completed build, and the build
test status dictionary of that build.
"""
# Find the latest completed build, starting from build_num.
build_num = build_dict['build_num']
starting_build_num = build_num
starting_build_num_failed = False
starting_build_index = builds_list.index(starting_build_num)
for build_num in reversed(builds_list[0:starting_build_index+1]):
build_test_status_dict = _BuildIsCompleted(build_dict, build_num)
if build_test_status_dict is not None:
break
starting_build_num_failed = True
else:
print('No completed builds are available.')
sys.exit(2)
if starting_build_num_failed:
print('\nError: Requested build_num %s was not completed successfully.' %
starting_build_num)
print('Using latest successfully completed build: %s\n' % build_num)
return build_num, build_test_status_dict
def _BuildIsCompleted(build_dict, build_num):
"""Determine whether build was completed successfully.
Get the build test status. Check whether the build given by |build_num]
was terminated by an error, or is not finished building. If so, return
None. Otherwise, return the build test status dictionary.
Args:
build_dict: build info dictionary.
build_num: build number to check.
Returns:
Dictionary of build test status if build is completed. Otherwise, None.
"""
# Copy original build_dict, and point copy to |build_num|.
temp_build_dict = dict(build_dict)
temp_build_dict['build_num'] = build_num
# Get build test status dictionary from builder for |build_num|.
build_test_status_dict = _GetBuildTestStatus(temp_build_dict)
steps_dict = build_test_status_dict[str(build_num)]['steps']
test_type_dict = steps_dict[build_dict['test_type']]
# Check if build failed or threw an exception:
if 'error' in test_type_dict:
return None
# Check isFinished status in test_type_dict.
if test_type_dict['isFinished']:
return build_test_status_dict
return None
def _GetBuildTestStatus(build_dict):
"""Get the build test status for the given build.
Fetch the build test status file from the builder for the build number,
specified in the build info dictionary given by |build_dict|.
Args:
build_dict: Build info dictionary.
Returns:
Build Test Status dictionary.
"""
# Generate percent-encoded build test status url.
build_url = (('https://%s/p/%s/json/builders/%s/builds?'
'select=%s/steps/%s/') %
(urllib2.quote(build_dict['builder_host']),
urllib2.quote(build_dict['builder_proj']),
urllib2.quote(build_dict['builder_name']),
build_dict['build_num'],
urllib2.quote(build_dict['test_type'])))
print('Fetching build test status file from: %s' % build_url)
return _FetchStatusFromUrl(build_dict, build_url)
def _GetBuildStatus(build_dict):
"""Get the build status for the given build.
Fetch the build status file from the builder for the build number, specified
in the build info dictionary given by |build_dict|.
Args:
build_dict: Build info dictionary.
Returns:
Build Status dictionary.
"""
# Generate percent-encoded build status url.
build_url = (('https://%s/p/%s/json/builders/%s/builds/%s') %
(urllib2.quote(build_dict['builder_host']),
urllib2.quote(build_dict['builder_proj']),
urllib2.quote(build_dict['builder_name']),
build_dict['build_num']))
print('Fetching build status file from: %s' % build_url)
return _FetchStatusFromUrl(build_dict, build_url)
def _FetchStatusFromUrl(build_dict, build_url):
"""Get the status from the given URL.
Args:
build_dict: Build info dictionary.
build_url: URL to the status json file.
Returns:
Status dictionary.
"""
# Fetch json status file from build url.
hosted_url = _SetUrlHost(build_url, build_dict['builder_host'])
url = urllib2.urlopen(hosted_url)
status_file = url.read()
url.close()
# Convert json status file to dictionary.
status_dict = json.loads(status_file)
return status_dict
def _PrintChromiumVersion(build_properties):
"""Get and print the version number of chromium used in the build.
Args:
build_properties: The properties dictionary for the build.
Returns:
A string containing the chromium version.
"""
for property_list in build_properties:
if 'buildspec_version' in property_list:
chromium_version = property_list[1]
print(' Chromium version: %s' % chromium_version)
break
else:
chromium_version = None
print(' Warning: Build properties has no chromium version.')
return chromium_version
def _GetTestsFailedList(build_dict, build_status_dict):
"""Get list of failed tests for given build.
Extract test status summary, including number of tests disabled, flaky, and
failed, from the test step in |build_status_dict|. If the test step was
finished, then create a list of tests that failed from the failures string.
Args:
build_dict: Build info dictionary.
build_status_dict: Build status dictionary.
Returns:
List of tests that failed.
"""
# Get test type from build_dict and tests steps from build_status_dict.
test_type = build_dict['test_type']
build_steps = build_status_dict['steps']
# Get count of disabled, flaky, failed, and test failures.
num_tests_disabled = 0
num_tests_flaky = 0
num_tests_failed = 0
test_failures = ''
for step_dict in build_steps:
text_list = step_dict['text']
if test_type in text_list:
for item in text_list:
m = re.match(r'(\d+) disabled', item)
if m:
num_tests_disabled = m.group(1)
continue
m = re.match(r'(\d+) flaky', item)
if m:
num_tests_flaky = m.group(1)
continue
m = re.match(r'failed (\d+)', item)
if m:
num_tests_failed = m.group(1)
continue
m = re.match(r'<br\/>failures:<br\/>(.*)<br\/>', item)
if m:
test_failures = m.group(1)
continue
break # Exit step_dict loop if test_type is in text_list.
is_finished = step_dict['isFinished']
else:
print('Error: build_steps has no \'%s\' step.' % test_type)
is_finished = 'Error'
# Split the test_failures into a tests_failed_list.
tests_failed_list = []
if num_tests_failed:
tests_failed_list = test_failures.split('<br/>')
print(' Test finished: %s' % is_finished)
print(' Disabled: %s' % num_tests_disabled)
print(' Flaky: %s' % num_tests_flaky)
print(' Failed: %s : %s' % (num_tests_failed, tests_failed_list))
return tests_failed_list
def _GetStdioLogUrlFromBuildStatus(build_dict, build_test_status_dict):
"""Get url to Stdio Log file from given build test status dictionary.
Args:
build_dict: Build info dictionary.
build_test_status_dict: Build Test Status dictionary.
Returns:
Url to the Stdio Log text file.
"""
steps_dict = build_test_status_dict[str(build_dict['build_num'])]['steps']
test_type_dict = steps_dict[build_dict['test_type']]
if 'error' in test_type_dict:
return None
log_url = test_type_dict['logs'][0][1]
stdio_log_url = _SetUrlHost(log_url, build_dict['builder_host'])+'/text'
return stdio_log_url
def _GetStdioLogTests(stdio_log_url, tests_failed_list):
"""Get Stdio Log Tests from the given url.
Extracts tests from the stdio log file of the builder referenced by
|stdio_log_url|, and packs them into a stdio tests dictionary. This
dictionary uses the long test name as the key, and the value is a list
that contains the test result. We use a list for the test result to mirror
the format used by the test-result server.
If a test is in the |tests_failed_list|, then set the test result to the
the failure code: 'Q'. Otherwise, set result to the pass code: 'P'. The
result repetition count of '999' is a placeholder that indicates that the
value is not from the test-result server.
Here is the format of the dictionary:
{
MaybeSetMetadata/SafeBrowseService.MalwareImg/1: [[999, 'P']],
MaybeSetMetadata/SafeBrowseService.MalwareImg/2: [[999, 'Q']],
PlatformAppBrowserTest.ComponentBackgroundPage: [[999, 'P']],
NoSessionRestoreTest.LocalStorageClearedOnExit: [[999, 'P']]
}
Args:
stdio_log_url: url to stdio log tests text file.
tests_failed_list: list of failed tests, from the build status page.
Returns:
Dictionary of test instances from stdio log tests text file.
"""
# Fetch builder stdio log text file from url.
print('\nFetching builder stdio log file from: %s' % stdio_log_url)
stdio_text_file = urllib2.urlopen(stdio_log_url)
# Extract test lines from stdio log text file to test_lines dictionary.
p_test = r'\[\d*/\d*\] (.*?) \(.*\)'
p_exit = r'exit code \(as seen by runtest.py\)\: (\d+)'
test_lines = []
exit_flag = False
exit_code = None
for line in stdio_text_file:
if not exit_flag:
m = re.match(p_test, line)
if m:
if line not in test_lines:
test_lines.append(line)
m = re.match(p_exit, line)
if m:
exit_flag = True
exit_code = m.group(1)
stdio_text_file.close()
print(' Total run tests extracted: %s' % len(test_lines))
if test_lines:
print(' Last run test line: "%s"' % test_lines[-1].strip())
# Extract test_lines data and pack into stdio tests dictionary.
stdio_tests_dict = {}
for i, line in enumerate(test_lines):
m = re.match(p_test, line)
if m:
long_test_name = m.group(1)
if long_test_name in tests_failed_list:
test_result = [[999, u'Q']] # Test result Failed code 'Q'.
else:
test_result = [[999, u'P']] # Test result Passed code 'P'.
stdio_tests_dict[long_test_name] = test_result
else:
print('Error: Invalid test line %s) %s' % (i, line.strip()))
print(' Test Exit Code: %s' % exit_code)
return stdio_tests_dict
def _RunAndNotrunTests(stdio_tests, user_tests):
"""Return lists of run and not-run instances of given user tests.
The first list is of test instances present in the |stdio_tests| list.
Presence indicates that the test instance was run on the build. The second
list is tests that are absent from the |stdio_tests| list. Absence means
that no instance of the test was run on the build.
Note that there can be multiple instances of a user-specified test run on a
build if a) The test belongs to test group, and b) the test was run with
multiple test data values. This function uses a regex to search for multiple
instances of tests that match the user-specifed test name.
Args:
stdio_tests: List of test instances run on build.
user_tests: List of test names specified by user.
Returns:
1) run_tests: list of test instances of user tests run on build.
2) notrun_tests: list of user tests not run on build.
"""
run_user_tests = []
notrun_user_tests = []
for user_test in user_tests:
pattern = r'(.*/)?%s(/\d*)?$' % user_test # pattern for test name.
found_run_test = False
for stdio_test in stdio_tests:
if re.search(pattern, stdio_test):
found_run_test = True
run_user_tests.append(stdio_test)
if not found_run_test:
notrun_user_tests.append(user_test)
print(' User tests: Instances run: %s' % len(run_user_tests))
print(' User tests: Not run: %s\n' % len(notrun_user_tests))
return run_user_tests, notrun_user_tests
def _GetTestResultsJson(master, builder_name, test_type):
"""Get test results data from results.json file for the builder.
The results.json file contains historical data about the tests run on the
given |builder_name| for the most recent (up to the last 500) builds. The
data includes test names, test result codes, build numbers, and chrome
revision numbers.
Args:
master: Master repo (e.g., 'ChromiumChromiumOS')
builder_name: Builder name (e.g., 'Linux ChromiumOS Tests (dbg)(1)')
test_type: Type of browsertests: browser_tests or interactive_ui_tests
Returns:
Contents of the results.json file from the test-result server for the
specified builder.
"""
# Generate percent-encoded test results url for specified builder.
results_url = (('https://%s/testfile?master=%s&builder=%s'
'&testtype=%s&name=results.json') %
(urllib2.quote(_TR_HOST), urllib2.quote(master),
urllib2.quote(builder_name), urllib2.quote(test_type)))
# Fetch results file from test results url.
print('Fetching test results file from %s' % results_url)
try:
url = urllib2.urlopen(results_url)
results_json = url.read()
url.close()
except urllib2.HTTPError:
results_json = None
print((' Warning: test-result history was not available '
'for builder \'%s\'.\n' % builder_name))
return results_json
def _CreateTestsResultsDictionary(tr_tests_dict):
"""Create dictionary of all tests+results from the given tests dictionary.
Parse individual tests and results from the |tr_tests_dict|, and place them
into a flattened tests results dictionary. Most tests are standalone, and
keyed by their test name. Some tests belong to a testinstance group, and are
keyed by their testinstance group name, then the testinstance number
(e.g., '0', '1', '2'), and finally the test name.
For example, a standalone test:result is formatted thus:
"BookmarksTest.CommandOpensBookmarksTab": {
"results": [...]
"times": [...]
}
Tests grouped under a testinstance, are formatted thus:
"KioskUpdateSuite": {
"KioskUpdateTest.PermissionChange": {
"1": {
"results": [...],
"times": [...]
}
"KioskUpdateTest.PermissionChange": {
"0": {
"results": [...],
"times": [...]
}
}
The flattened test results dictionary is formatted thus:
{
BookmarksTest.CommandOpensBookmarksTab: [[60, u'Q'], [440, u'P']],
KioskUpdateTest.PermissionChange/0: [[498, u'P'], [2, u'Q']]
KioskUpdateTest.PermissionChange/1: [[493, u'P'], [7, u'Q']]
}
Args:
tr_tests_dict: Dictionary of test groups & tests.
Returns:
Dictionary of flattened tests and their results.
"""
tests_results_dict = {}
standalone = 0
group = 0
subtest = 0
for group_name in tr_tests_dict:
test_group = tr_tests_dict[group_name]
if '.' in group_name:
standalone += 1
test_result = test_group['results']
tests_results_dict[group_name] = test_result
else:
group += 1
for test_name in test_group.keys():
test_values = test_group[test_name]
for value in test_values.keys():
subtest += 1
test_result = test_values[value]['results']
long_test_name = '%s/%s/%s' % (group_name, test_name, value)
tests_results_dict[long_test_name] = test_result
print(' Number of standalone tests: %s' % standalone)
print(' Number of instance tests (in %s groups): %s' % (group, subtest))
print(' Total tests results: %s\n' % len(tests_results_dict))
return tests_results_dict
def _CreateUserTestsResults(run_user_tests, stdio_tests_dict,
tests_results_dict):
"""Create dictionary of tests results for all user-specified tests.
If a user test is failed in the build status given by |stdio_tests_dict|,
then set the test result to failed code: 'Q'. If a user test is in the test
results given by |test_results_dict|, then use those results. Otherwise,
use the test result given by |stdio_tests_dict|. If a user test is missing
from both |stdio_tests_dict| and |test_results_dict|, then set the test
test result to missing code: 'O'.
Args:
run_user_tests: List of run instances of user specified tests.
stdio_tests_dict: builder's results.json test results.
tests_results_dict: test results from the tests-results server.
Returns:
Dictionary of tests and results for all user specified tests.
"""
user_tests_results_dict = {}
# Iterate over tests in the run user-specified tests list.
for test_name in run_user_tests:
if (test_name in stdio_tests_dict and
stdio_tests_dict[test_name] == [[999, u'Q']]):
test_result = stdio_tests_dict[test_name]
elif test_name in tests_results_dict: # Use test-results server results.
test_result = tests_results_dict[test_name]
elif test_name in stdio_tests_dict: # Use builder results.json results.
test_result = stdio_tests_dict[test_name]
else:
test_result = [[999, u'O']] # Set result to missing.
user_tests_results_dict[test_name] = test_result
return user_tests_results_dict
def _CreateResultOfTests(user_tests_results_dict):
"""Create dictionary of user tests keyed by result.
Args:
user_tests_results_dict: dictionary of user tests to results.
Returns:
Dictionary of results of tests.
"""
# Test result type lists.
missing = ['O']
passed = ['P']
fails = [key for key in _RESULT_TYPES if key not in missing+passed]
failed_tests = []
passed_tests = []
missing_tests = []
for test in user_tests_results_dict:
result = user_tests_results_dict[test][0][1]
if result in fails:
failed_tests.append(test)
elif result in passed:
passed_tests.append(test)
elif result in missing:
missing_tests.append(test)
return {_FAILED: failed_tests,
_PASSED: passed_tests,
_MISSING: missing_tests}
def _ReportTestsByResult(result_of_tests_dict, tr_dict, rout, rdir):
"""Print and write report of tests, grouped by result type.
Args:
result_of_tests_dict: Dictionary of results for tests.
tr_dict: Dictionary of tests to results for builder.
rout: flag whether to write report file.
rdir: Directory where to write report.
"""
# Test report result types and section headers.
report_results_headers = {
_NOTRUN: 'Test Status: Not Run',
_FAILED: 'Test Result: Fail or other Error',
_PASSED: 'Test Result: Passed recently',
_MISSING: 'Test Result: Passing long-term'
}
report_section_order = [_NOTRUN, _FAILED, _PASSED, _MISSING]
if rout:
ofile = open(rdir+'/report', 'w')
for result_type in report_section_order:
header = report_results_headers[result_type]
tests = result_of_tests_dict[result_type]
print('%s (%s)' % (header, len(tests)))
if rout:
ofile.write('%s (%s)\n' % (header, len(tests)))
for num, test in enumerate(sorted(tests)):
if test in tr_dict:
print(' %s) %s: %s' % (num+1, test, tr_dict[test][0:2]))
if rout:
ofile.write(' %s) %s: %s\n' % (num+1, test, tr_dict[test]))
else:
print(' %s) %s' % (num+1, test))
if rout:
ofile.write(' %s) %s\n' % (num+1, test))
if rout:
ofile.close()
def _SetUrlHost(url, host):
"""Modify builder |url| to point to the the correct |host|.
Args:
url: Builder URL, which may not have the correct host.
host: Correct builder host.
Returns:
Builder URL with the correct host.
"""
pattern = '(?:http.?://)?(?P<host>[^:/ ]+).*'
m = re.search(pattern, url)
original_host = m.group('host')
rehosted_url = re.sub(original_host, host, url)
return rehosted_url
def main():
"""Report test results of specified browsertests."""
parser = argparse.ArgumentParser(
description=('Report run status and test results for a user-specified '
'list of browsertest tests.'),
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('--tests_file', dest='tests_file', default=_TESTS_FILE,
help=('Specify tests path/file '
'(default is %s).' % _TESTS_FILE))
parser.add_argument('--report_out', dest='report_out', default=None,
help=('Write report (default is None).'))
parser.add_argument('--report_dir', dest='report_dir', default=_REPORT_DIR,
help=('Specify path to report directory '
'(default is %s).' % _REPORT_DIR))
parser.add_argument('--master', dest='master', default=_TR_MASTER,
help=('Specify build master repository '
'(default is %s).' % _TR_MASTER))
parser.add_argument('--builder_host', dest='builder_host',
default=_BUILDER_HOST,
help=('Specify builder host name '
'(default is %s).' % _BUILDER_HOST))
parser.add_argument('--builder_project', dest='builder_project',
default=_BUILDER_PROJECT,
help=('Specify builder project name '
'(default is %s).' % _BUILDER_PROJECT))
parser.add_argument('--builder_name', dest='builder_name',
default=_BUILDER_NAME,
help=('Specify builder name '
'(default is %s).' % _BUILDER_NAME))
parser.add_argument('--build_num', dest='build_num', type=int,
default=_BUILD_NUMBER,
help=('Specify positive build number, or negative '
'index from latest build '
'(default is %s).' % _BUILD_NUMBER))
parser.add_argument('--test_type', dest='test_type', default=_TEST_TYPE,
help=('Specify test type: browser_tests or '
'interactive_ui_tests '
'(default is %s).' % _TEST_TYPE))
parser.add_argument('--version', dest='cr_version', default=None,
help=('Specify chromium version number '
'(default is None).'))
parser.add_argument('--print_types', dest='print_types',
action='store_true', help='Print test result types.')
arguments = parser.parse_args()
### Set parameters from CLI arguments, and check for valid values.
# Set parameters from command line arguments.
tests_file = arguments.tests_file
report_out = arguments.report_out
report_dir = arguments.report_dir
master = arguments.master
builder_host = arguments.builder_host
builder_proj = arguments.builder_project
builder_name = arguments.builder_name
build_num = arguments.build_num
test_type = arguments.test_type
cr_version = arguments.cr_version
print_types = arguments.print_types
# Print map of test result types and exit.
if print_types:
print('Test result types:')
print(json.dumps(_RESULT_TYPES, indent=4))
sys.exit(0)
# Ensure default or user-defined |tests_file| points to a real file.
if not os.path.isfile(tests_file):
print('Error: Could not find tests file. Try passing in --tests_file.')
sys.exit(2)
# Ensure default or user-defined |report_dir| points to a real directory.
if not os.path.exists(report_dir):
print(('Error: Could not find report directory. '
'Try passing in --report_dir.'))
sys.exit(2)
# Verify that user gave |build_num| or |cr_version|, but not both.
if build_num != _BUILD_NUMBER and cr_version:
print(('Error: You may specify the build_num or the cr_version, '
'but not both.'))
sys.exit(2)
# Verify user gave valid |test_type|.
if test_type not in ['browser_tests', 'interactive_ui_tests']:
print(('Error: Invalid test_type: %s. Use \'browser_tests\' or '
'\'interactive_ui_tests\'') % test_type)
sys.exit(2)
# Pack valid build info into a portable dictionary.
build_dict = {
'builder_host': builder_host,
'builder_proj': builder_proj,
'builder_name': builder_name,
'build_num': build_num,
'test_type': test_type,
'cr_version': cr_version
}
### Get list of user tests from |tests_file|.
user_tests = _GetUserSpecifiedTests(tests_file)
### Determine build number from which to get status.
# Get list of available builds from builder.
builds_list = _GetBuildsList(build_dict)
# Find the latest completed build by chromium version.
if cr_version:
build_num = _FindBuildByChromiumVersion(build_dict)
# Set nominal build_num from builds available in |builds_list|.
build_dict['build_num'] = _GetNominalBuildNumber(build_num, builds_list)
# Get number of latest completed build, and update build_dict with it.
build_num, build_test_status_dict = (
_LatestCompletedBuild(build_dict, builds_list))
build_dict['build_num'] = build_num
### Get build status from the builder for the build number.
# Get the build status of the latest completed build.
build_status_dict = _GetBuildStatus(build_dict)
# Extract the build properties, and print chromium version.
build_properties = build_status_dict['properties']
_PrintChromiumVersion(build_properties)
### Get test status from the builder for the build number.
# Get list of failed tests from build status.
tests_failed_list = _GetTestsFailedList(build_dict, build_status_dict)
# Get stdio log URL from build test status for the latest build_num.
stdio_log_url = (
_GetStdioLogUrlFromBuildStatus(build_dict, build_test_status_dict))
# Get dictionary of test instances run on selected build.
stdio_tests_dict = _GetStdioLogTests(stdio_log_url, tests_failed_list)
# Get instances of run and not run user tests.
run_user_tests, notrun_user_tests = (
_RunAndNotrunTests(stdio_tests_dict, user_tests))
### Read test results from test-results server for the builder.
test_results_json = _GetTestResultsJson(master, builder_name, test_type)
if test_results_json:
# Extract tests results dictionary from results json for builder.
tr_tests_dict = json.loads(test_results_json)[builder_name]['tests']
tests_results_dict = _CreateTestsResultsDictionary(tr_tests_dict)
else:
# Extract test results from stdio tests dictionary.
tests_results_dict = stdio_tests_dict
### Combine run user tests, build test status, and test results into a
### single dictionary of user tests and their results, and then into a
### dictionary of results and their tests.
# Create dictionary of run user test instances and results.
user_tests_results_dict = (
_CreateUserTestsResults(run_user_tests, stdio_tests_dict,
tests_results_dict))
# Create dictionary of run tests that are failed, passed, and missing.
result_of_tests_dict = _CreateResultOfTests(user_tests_results_dict)
# Add list of not run tests to the result of tests dictionary.
result_of_tests_dict[_NOTRUN] = notrun_user_tests
### Output report of tests grouped by result. Result types are notrun,
### failed, passed, and missing
_ReportTestsByResult(result_of_tests_dict, tests_results_dict,
report_out, report_dir)
if __name__ == '__main__':
main()