| Index: infra/scripts/legacy/scripts/slave/gtest_slave_utils.py
|
| diff --git a/infra/scripts/legacy/scripts/slave/gtest_slave_utils.py b/infra/scripts/legacy/scripts/slave/gtest_slave_utils.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..55fe843ff1cdf7e62958ead282ad24af6e2221e5
|
| --- /dev/null
|
| +++ b/infra/scripts/legacy/scripts/slave/gtest_slave_utils.py
|
| @@ -0,0 +1,273 @@
|
| +#!/usr/bin/env python
|
| +# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +import logging
|
| +import optparse
|
| +import os
|
| +import re
|
| +import sys
|
| +
|
| +from common import gtest_utils
|
| +from xml.dom import minidom
|
| +from slave.gtest.json_results_generator import JSONResultsGenerator
|
| +from slave.gtest.test_result import canonical_name
|
| +from slave.gtest.test_result import TestResult
|
| +
|
| +
|
| +GENERATE_JSON_RESULTS_OPTIONS = [
|
| + 'builder_name', 'build_name', 'build_number', 'results_directory',
|
| + 'builder_base_url', 'webkit_revision', 'chrome_revision',
|
| + 'test_results_server', 'test_type', 'master_name']
|
| +
|
| +FULL_RESULTS_FILENAME = 'full_results.json'
|
| +TIMES_MS_FILENAME = 'times_ms.json'
|
| +
|
| +
|
| +# Note: GTestUnexpectedDeathTracker is being deprecated in favor of
|
| +# common.gtest_utils.GTestLogParser. See scripts/slave/runtest.py for details.
|
| +class GTestUnexpectedDeathTracker(object):
|
| + """A lightweight version of log parser that keeps track of running tests
|
| + for unexpected timeout or crash."""
|
| +
|
| + def __init__(self):
|
| + self._current_test = None
|
| + self._completed = False
|
| + self._test_start = re.compile(r'\[\s+RUN\s+\] (\w+\.\w+)')
|
| + self._test_ok = re.compile(r'\[\s+OK\s+\] (\w+\.\w+)')
|
| + self._test_fail = re.compile(r'\[\s+FAILED\s+\] (\w+\.\w+)')
|
| + self._test_passed = re.compile(r'\[\s+PASSED\s+\] \d+ tests?.')
|
| +
|
| + self._failed_tests = set()
|
| +
|
| + def OnReceiveLine(self, line):
|
| + results = self._test_start.search(line)
|
| + if results:
|
| + self._current_test = results.group(1)
|
| + return
|
| +
|
| + results = self._test_ok.search(line)
|
| + if results:
|
| + self._current_test = ''
|
| + return
|
| +
|
| + results = self._test_fail.search(line)
|
| + if results:
|
| + self._failed_tests.add(results.group(1))
|
| + self._current_test = ''
|
| + return
|
| +
|
| + results = self._test_passed.search(line)
|
| + if results:
|
| + self._completed = True
|
| + self._current_test = ''
|
| + return
|
| +
|
| + def GetResultsMap(self):
|
| + """Returns a map of TestResults."""
|
| +
|
| + if self._current_test:
|
| + self._failed_tests.add(self._current_test)
|
| +
|
| + test_results_map = dict()
|
| + for test in self._failed_tests:
|
| + test_results_map[canonical_name(test)] = [TestResult(test, failed=True)]
|
| +
|
| + return test_results_map
|
| +
|
| + def CompletedWithoutFailure(self):
|
| + """Returns True if all tests completed and no tests failed unexpectedly."""
|
| +
|
| + if not self._completed:
|
| + return False
|
| +
|
| + for test in self._failed_tests:
|
| + test_modifier = TestResult(test, failed=True).modifier
|
| + if test_modifier not in (TestResult.FAILS, TestResult.FLAKY):
|
| + return False
|
| +
|
| + return True
|
| +
|
| +
|
| +def GetResultsMap(observer):
|
| + """Returns a map of TestResults."""
|
| +
|
| + test_results_map = dict()
|
| + tests = (observer.FailedTests(include_fails=True, include_flaky=True) +
|
| + observer.PassedTests())
|
| + for test in tests:
|
| + key = canonical_name(test)
|
| + test_results_map[key] = []
|
| + tries = observer.TriesForTest(test)
|
| + for test_try in tries:
|
| + # FIXME: Store the actual failure type so we can expose whether the test
|
| + # crashed or timed out. See crbug.com/249965.
|
| + failed = (test_try != gtest_utils.TEST_SUCCESS_LABEL)
|
| + test_results_map[key].append(TestResult(test, failed=failed))
|
| +
|
| + return test_results_map
|
| +
|
| +
|
| +def GetResultsMapFromXML(results_xml):
|
| + """Parse the given results XML file and returns a map of TestResults."""
|
| +
|
| + results_xml_file = None
|
| + try:
|
| + results_xml_file = open(results_xml)
|
| + except IOError:
|
| + logging.error('Cannot open file %s', results_xml)
|
| + return dict()
|
| + node = minidom.parse(results_xml_file).documentElement
|
| + results_xml_file.close()
|
| +
|
| + test_results_map = dict()
|
| + testcases = node.getElementsByTagName('testcase')
|
| +
|
| + for testcase in testcases:
|
| + name = testcase.getAttribute('name')
|
| + classname = testcase.getAttribute('classname')
|
| + test_name = '%s.%s' % (classname, name)
|
| +
|
| + failures = testcase.getElementsByTagName('failure')
|
| + not_run = testcase.getAttribute('status') == 'notrun'
|
| + elapsed = float(testcase.getAttribute('time'))
|
| + result = TestResult(test_name,
|
| + failed=bool(failures),
|
| + not_run=not_run,
|
| + elapsed_time=elapsed)
|
| + test_results_map[canonical_name(test_name)] = [result]
|
| +
|
| + return test_results_map
|
| +
|
| +
|
| +def GenerateJSONResults(test_results_map, options):
|
| + """Generates a JSON results file from the given test_results_map,
|
| + returning the associated generator for use with UploadJSONResults, below.
|
| +
|
| + Args:
|
| + test_results_map: A map of TestResult.
|
| + options: options for json generation. See GENERATE_JSON_RESULTS_OPTIONS
|
| + and OptionParser's help messages below for expected options and their
|
| + details.
|
| + """
|
| +
|
| + if not test_results_map:
|
| + logging.warn('No input results map was given.')
|
| + return
|
| +
|
| + # Make sure we have all the required options (set empty string otherwise).
|
| + for opt in GENERATE_JSON_RESULTS_OPTIONS:
|
| + if not getattr(options, opt, None):
|
| + logging.warn('No value is given for option %s', opt)
|
| + setattr(options, opt, '')
|
| +
|
| + try:
|
| + int(options.build_number)
|
| + except ValueError:
|
| + logging.error('options.build_number needs to be a number: %s',
|
| + options.build_number)
|
| + return
|
| +
|
| + if not os.path.exists(options.results_directory):
|
| + os.makedirs(options.results_directory)
|
| +
|
| + print('Generating json: '
|
| + 'builder_name:%s, build_name:%s, build_number:%s, '
|
| + 'results_directory:%s, builder_base_url:%s, '
|
| + 'webkit_revision:%s, chrome_revision:%s '
|
| + 'test_results_server:%s, test_type:%s, master_name:%s' %
|
| + (options.builder_name, options.build_name, options.build_number,
|
| + options.results_directory, options.builder_base_url,
|
| + options.webkit_revision, options.chrome_revision,
|
| + options.test_results_server, options.test_type,
|
| + options.master_name))
|
| +
|
| + generator = JSONResultsGenerator(
|
| + options.builder_name, options.build_name, options.build_number,
|
| + options.results_directory, options.builder_base_url,
|
| + test_results_map,
|
| + svn_revisions=(('blink', options.webkit_revision),
|
| + ('chromium', options.chrome_revision)),
|
| + test_results_server=options.test_results_server,
|
| + test_type=options.test_type,
|
| + master_name=options.master_name)
|
| + generator.generate_json_output()
|
| + generator.generate_times_ms_file()
|
| + return generator
|
| +
|
| +def UploadJSONResults(generator):
|
| + """Conditionally uploads the results from GenerateJSONResults if
|
| + test_results_server was given."""
|
| + if generator:
|
| + generator.upload_json_files([FULL_RESULTS_FILENAME,
|
| + TIMES_MS_FILENAME])
|
| +
|
| +# For command-line testing.
|
| +def main():
|
| + # Builder base URL where we have the archived test results.
|
| + # (Note: to be deprecated)
|
| + BUILDER_BASE_URL = 'http://build.chromium.org/buildbot/gtest_results/'
|
| +
|
| + option_parser = optparse.OptionParser()
|
| + option_parser.add_option('', '--test-type', default='',
|
| + help='Test type that generated the results XML,'
|
| + ' e.g. unit-tests.')
|
| + option_parser.add_option('', '--results-directory', default='./',
|
| + help='Output results directory source dir.')
|
| + option_parser.add_option('', '--input-results-xml', default='',
|
| + help='Test results xml file (input for us).'
|
| + ' default is TEST_TYPE.xml')
|
| + option_parser.add_option('', '--builder-base-url', default='',
|
| + help=('A URL where we have the archived test '
|
| + 'results. (default=%sTEST_TYPE_results/)'
|
| + % BUILDER_BASE_URL))
|
| + option_parser.add_option('', '--builder-name',
|
| + default='DUMMY_BUILDER_NAME',
|
| + help='The name of the builder shown on the '
|
| + 'waterfall running this script e.g. WebKit.')
|
| + option_parser.add_option('', '--build-name',
|
| + default='DUMMY_BUILD_NAME',
|
| + help='The name of the builder used in its path, '
|
| + 'e.g. webkit-rel.')
|
| + option_parser.add_option('', '--build-number', default='',
|
| + help='The build number of the builder running'
|
| + 'this script.')
|
| + option_parser.add_option('', '--test-results-server',
|
| + default='',
|
| + help='The test results server to upload the '
|
| + 'results.')
|
| + option_parser.add_option('--master-name', default='',
|
| + help='The name of the buildbot master. '
|
| + 'Both test-results-server and master-name '
|
| + 'need to be specified to upload the results '
|
| + 'to the server.')
|
| + option_parser.add_option('--webkit-revision', default='0',
|
| + help='The WebKit revision being tested. If not '
|
| + 'given, defaults to 0.')
|
| + option_parser.add_option('--chrome-revision', default='0',
|
| + help='The Chromium revision being tested. If not '
|
| + 'given, defaults to 0.')
|
| +
|
| + options = option_parser.parse_args()[0]
|
| +
|
| + if not options.test_type:
|
| + logging.error('--test-type needs to be specified.')
|
| + sys.exit(1)
|
| +
|
| + if not options.input_results_xml:
|
| + logging.error('--input-results-xml needs to be specified.')
|
| + sys.exit(1)
|
| +
|
| + if options.test_results_server and not options.master_name:
|
| + logging.warn('--test-results-server is given but '
|
| + '--master-name is not specified; the results won\'t be '
|
| + 'uploaded to the server.')
|
| +
|
| + results_map = GetResultsMapFromXML(options.input_results_xml)
|
| + generator = GenerateJSONResults(results_map, options)
|
| + UploadJSONResults(generator)
|
| +
|
| +
|
| +if '__main__' == __name__:
|
| + main()
|
|
|