| Index: tools/telemetry/telemetry/unittest/run_tests.py
|
| diff --git a/tools/telemetry/telemetry/unittest/run_tests.py b/tools/telemetry/telemetry/unittest/run_tests.py
|
| index 1a827a9207155ff2f2a9476840ae102401e4be3c..de825d27986eebf83bdbcc3e27918b1495404b9c 100644
|
| --- a/tools/telemetry/telemetry/unittest/run_tests.py
|
| +++ b/tools/telemetry/telemetry/unittest/run_tests.py
|
| @@ -1,29 +1,120 @@
|
| # Copyright 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 sys
|
| +
|
| +import logging
|
| +import unittest
|
|
|
| from telemetry import decorators
|
| from telemetry.core import browser_finder
|
| from telemetry.core import browser_options
|
| from telemetry.core import command_line
|
| -from telemetry.core import util
|
| -from telemetry.unittest import options_for_unittests
|
| -from telemetry.unittest import browser_test_case
|
| -
|
| -util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'third_party', 'typ')
|
| -
|
| -import typ
|
| +from telemetry.core import discover
|
| +from telemetry.unittest import json_results
|
| +from telemetry.unittest import progress_reporter
|
| +
|
| +
|
| +class Config(object):
|
| + def __init__(self, top_level_dir, test_dirs, progress_reporters):
|
| + self._top_level_dir = top_level_dir
|
| + self._test_dirs = tuple(test_dirs)
|
| + self._progress_reporters = tuple(progress_reporters)
|
| +
|
| + @property
|
| + def top_level_dir(self):
|
| + return self._top_level_dir
|
| +
|
| + @property
|
| + def test_dirs(self):
|
| + return self._test_dirs
|
| +
|
| + @property
|
| + def progress_reporters(self):
|
| + return self._progress_reporters
|
| +
|
| +
|
| +def Discover(start_dir, top_level_dir=None, pattern='test*.py'):
|
| + loader = unittest.defaultTestLoader
|
| + loader.suiteClass = progress_reporter.TestSuite
|
| +
|
| + test_suites = []
|
| + modules = discover.DiscoverModules(start_dir, top_level_dir, pattern)
|
| + for module in modules:
|
| + if hasattr(module, 'suite'):
|
| + suite = module.suite()
|
| + else:
|
| + suite = loader.loadTestsFromModule(module)
|
| + if suite.countTestCases():
|
| + test_suites.append(suite)
|
| + return test_suites
|
| +
|
| +
|
| +def FilterSuite(suite, predicate):
|
| + new_suite = suite.__class__()
|
| + for test in suite:
|
| + if isinstance(test, unittest.TestSuite):
|
| + subsuite = FilterSuite(test, predicate)
|
| + if subsuite.countTestCases():
|
| + new_suite.addTest(subsuite)
|
| + else:
|
| + assert isinstance(test, unittest.TestCase)
|
| + if predicate(test):
|
| + new_suite.addTest(test)
|
| +
|
| + return new_suite
|
| +
|
| +
|
| +def DiscoverTests(search_dirs, top_level_dir, possible_browser,
|
| + selected_tests=None, selected_tests_are_exact=False,
|
| + run_disabled_tests=False):
|
| + def IsTestSelected(test):
|
| + if selected_tests:
|
| + found = False
|
| + for name in selected_tests:
|
| + if selected_tests_are_exact:
|
| + if name == test.id():
|
| + found = True
|
| + else:
|
| + if name in test.id():
|
| + found = True
|
| + if not found:
|
| + return False
|
| + if run_disabled_tests:
|
| + return True
|
| + # pylint: disable=W0212
|
| + if not hasattr(test, '_testMethodName'):
|
| + return True
|
| + method = getattr(test, test._testMethodName)
|
| + return decorators.IsEnabled(method, possible_browser)
|
| +
|
| + wrapper_suite = progress_reporter.TestSuite()
|
| + for search_dir in search_dirs:
|
| + wrapper_suite.addTests(Discover(search_dir, top_level_dir, '*_unittest.py'))
|
| + return FilterSuite(wrapper_suite, IsTestSelected)
|
| +
|
| +
|
| +def RestoreLoggingLevel(func):
|
| + def _LoggingRestoreWrapper(*args, **kwargs):
|
| + # Cache the current logging level, this needs to be done before calling
|
| + # parser.parse_args, which changes logging level based on verbosity
|
| + # setting.
|
| + logging_level = logging.getLogger().getEffectiveLevel()
|
| + try:
|
| + return func(*args, **kwargs)
|
| + finally:
|
| + # Restore logging level, which may be changed in parser.parse_args.
|
| + logging.getLogger().setLevel(logging_level)
|
| +
|
| + return _LoggingRestoreWrapper
|
| +
|
| +
|
| +config = None
|
|
|
|
|
| class RunTestsCommand(command_line.OptparseCommand):
|
| """Run unit tests"""
|
|
|
| usage = '[test_name ...] [<options>]'
|
| -
|
| - def __init__(self):
|
| - super(RunTestsCommand, self).__init__()
|
| - self.stream = sys.stdout
|
|
|
| @classmethod
|
| def CreateParser(cls):
|
| @@ -40,24 +131,22 @@
|
| dest='run_disabled_tests',
|
| action='store_true', default=False,
|
| help='Ignore @Disabled and @Enabled restrictions.')
|
| + parser.add_option('--retry-limit', type='int',
|
| + help='Retry each failure up to N times'
|
| + ' to de-flake things.')
|
| parser.add_option('--exact-test-filter', action='store_true', default=False,
|
| help='Treat test filter as exact matches (default is '
|
| 'substring matches).')
|
| -
|
| - typ.ArgumentParser.add_option_group(parser,
|
| - "Options for running the tests",
|
| - running=True,
|
| - skip=['-d', '--path', '-v',
|
| - '--verbose'])
|
| - typ.ArgumentParser.add_option_group(parser,
|
| - "Options for reporting the results",
|
| - reporting=True)
|
| + json_results.AddOptions(parser)
|
|
|
| @classmethod
|
| def ProcessCommandLineArgs(cls, parser, args):
|
| + if args.verbosity == 0:
|
| + logging.getLogger().setLevel(logging.WARN)
|
| +
|
| # We retry failures by default unless we're running a list of tests
|
| # explicitly.
|
| - if not args.retry_limit and not args.positional_args:
|
| + if args.retry_limit is None and not args.positional_args:
|
| args.retry_limit = 3
|
|
|
| try:
|
| @@ -70,110 +159,50 @@
|
| 'Re-run with --browser=list to see '
|
| 'available browser types.' % args.browser_type)
|
|
|
| - @classmethod
|
| - def main(cls, args=None, stream=None): # pylint: disable=W0221
|
| - # We override the superclass so that we can hook in the 'stream' arg.
|
| - parser = cls.CreateParser()
|
| - cls.AddCommandLineArgs(parser)
|
| - options, positional_args = parser.parse_args(args)
|
| - options.positional_args = positional_args
|
| - cls.ProcessCommandLineArgs(parser, options)
|
| -
|
| - obj = cls()
|
| - if stream is not None:
|
| - obj.stream = stream
|
| - return obj.Run(options)
|
| + json_results.ValidateArgs(parser, args)
|
|
|
| def Run(self, args):
|
| possible_browser = browser_finder.FindBrowser(args)
|
|
|
| - runner = typ.Runner()
|
| - if self.stream:
|
| - runner.host.stdout = self.stream
|
| -
|
| - # Telemetry seems to overload the system if we run one test per core,
|
| - # so we scale things back a fair amount. Many of the telemetry tests
|
| - # are long-running, so there's a limit to how much parallelism we
|
| - # can effectively use for now anyway.
|
| - #
|
| - # It should be possible to handle multiple devices if we adjust
|
| - # the browser_finder code properly, but for now we only handle the one
|
| - # on Android and ChromeOS.
|
| - if possible_browser.platform.GetOSName() in ('android', 'chromeos'):
|
| - runner.args.jobs = 1
|
| - else:
|
| - runner.args.jobs = max(int(args.jobs) // 4, 1)
|
| -
|
| - runner.args.metadata = args.metadata
|
| - runner.args.passthrough = args.passthrough
|
| - runner.args.retry_limit = args.retry_limit
|
| - runner.args.test_results_server = args.test_results_server
|
| - runner.args.test_type = args.test_type
|
| - runner.args.timing = args.timing
|
| - runner.args.top_level_dir = args.top_level_dir
|
| - runner.args.verbose = args.verbosity
|
| - runner.args.write_full_results_to = args.write_full_results_to
|
| - runner.args.write_trace_to = args.write_trace_to
|
| -
|
| - runner.args.path.append(util.GetUnittestDataDir())
|
| -
|
| - runner.classifier = GetClassifier(args, possible_browser)
|
| - runner.context = args
|
| - runner.setup_fn = _SetUpProcess
|
| - runner.teardown_fn = _TearDownProcess
|
| - runner.win_multiprocessing = typ.WinMultiprocessing.importable
|
| - try:
|
| - ret, _, _ = runner.run()
|
| - except KeyboardInterrupt:
|
| - print >> sys.stderr, "interrupted, exiting"
|
| - ret = 130
|
| - return ret
|
| -
|
| -
|
| -def GetClassifier(args, possible_browser):
|
| - def ClassifyTest(test_set, test):
|
| - name = test.id()
|
| - if args.positional_args:
|
| - if _MatchesSelectedTest(name, args.positional_args,
|
| - args.exact_test_filter):
|
| - assert hasattr(test, '_testMethodName')
|
| - method = getattr(test, test._testMethodName) # pylint: disable=W0212
|
| - if decorators.ShouldBeIsolated(method, possible_browser):
|
| - test_set.isolated_tests.append(typ.TestInput(name))
|
| - else:
|
| - test_set.parallel_tests.append(typ.TestInput(name))
|
| - else:
|
| - assert hasattr(test, '_testMethodName')
|
| - method = getattr(test, test._testMethodName) # pylint: disable=W0212
|
| - should_skip, reason = decorators.ShouldSkip(method, possible_browser)
|
| - if should_skip and not args.run_disabled_tests:
|
| - test_set.tests_to_skip.append(typ.TestInput(name, msg=reason))
|
| - elif decorators.ShouldBeIsolated(method, possible_browser):
|
| - test_set.isolated_tests.append(typ.TestInput(name))
|
| - else:
|
| - test_set.parallel_tests.append(typ.TestInput(name))
|
| -
|
| - return ClassifyTest
|
| -
|
| -
|
| -def _MatchesSelectedTest(name, selected_tests, selected_tests_are_exact):
|
| - if not selected_tests:
|
| - return False
|
| - if selected_tests_are_exact:
|
| - return any(name in selected_tests)
|
| - else:
|
| - return any(test in name for test in selected_tests)
|
| -
|
| -
|
| -def _SetUpProcess(child, context): # pylint: disable=W0613
|
| - args = context
|
| - options_for_unittests.Push(args)
|
| -
|
| -
|
| -def _TearDownProcess(child, context): # pylint: disable=W0613
|
| - browser_test_case.teardown_browser()
|
| - options_for_unittests.Pop()
|
| -
|
| -
|
| -if __name__ == '__main__':
|
| - RunTestsCommand.main()
|
| + test_suite, result = self.RunOneSuite(possible_browser, args)
|
| +
|
| + results = [result]
|
| +
|
| + failed_tests = json_results.FailedTestNames(test_suite, result)
|
| + retry_limit = args.retry_limit
|
| +
|
| + while retry_limit and failed_tests:
|
| + args.positional_args = failed_tests
|
| + args.exact_test_filter = True
|
| +
|
| + _, result = self.RunOneSuite(possible_browser, args)
|
| + results.append(result)
|
| +
|
| + failed_tests = json_results.FailedTestNames(test_suite, result)
|
| + retry_limit -= 1
|
| +
|
| + full_results = json_results.FullResults(args, test_suite, results)
|
| + json_results.WriteFullResultsIfNecessary(args, full_results)
|
| +
|
| + err_occurred, err_str = json_results.UploadFullResultsIfNecessary(
|
| + args, full_results)
|
| + if err_occurred:
|
| + for line in err_str.splitlines():
|
| + logging.error(line)
|
| + return 1
|
| +
|
| + return json_results.ExitCodeFromFullResults(full_results)
|
| +
|
| + def RunOneSuite(self, possible_browser, args):
|
| + test_suite = DiscoverTests(config.test_dirs, config.top_level_dir,
|
| + possible_browser, args.positional_args,
|
| + args.exact_test_filter, args.run_disabled_tests)
|
| + runner = progress_reporter.TestRunner()
|
| + result = runner.run(test_suite, config.progress_reporters,
|
| + args.repeat_count, args)
|
| + return test_suite, result
|
| +
|
| + @classmethod
|
| + @RestoreLoggingLevel
|
| + def main(cls, args=None):
|
| + return super(RunTestsCommand, cls).main(args)
|
|
|