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

Side by Side Diff: tools/telemetry/telemetry/unittest_util/run_tests.py

Issue 743463003: Attempt #4 to convert telemetry to the typ framework. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix run_tests_unittest failures, remove debug logging Created 6 years, 1 month 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 unified diff | Download patch
OLDNEW
1 # Copyright 2012 The Chromium Authors. All rights reserved. 1 # Copyright 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4 import sys
5 import logging
6 import unittest
7 5
8 from telemetry import decorators 6 from telemetry import decorators
9 from telemetry.core import browser_finder 7 from telemetry.core import browser_finder
10 from telemetry.core import browser_options 8 from telemetry.core import browser_options
11 from telemetry.core import command_line 9 from telemetry.core import command_line
12 from telemetry.core import discover 10 from telemetry.core import util
13 from telemetry.unittest_util import json_results 11 from telemetry.unittest_util import options_for_unittests
14 from telemetry.unittest_util import progress_reporter 12 from telemetry.unittest_util import browser_test_case
15 13
14 util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'third_party', 'typ')
16 15
17 class Config(object): 16 import typ
18 def __init__(self, top_level_dir, test_dirs, progress_reporters):
19 self._top_level_dir = top_level_dir
20 self._test_dirs = tuple(test_dirs)
21 self._progress_reporters = tuple(progress_reporters)
22
23 @property
24 def top_level_dir(self):
25 return self._top_level_dir
26
27 @property
28 def test_dirs(self):
29 return self._test_dirs
30
31 @property
32 def progress_reporters(self):
33 return self._progress_reporters
34
35
36 def Discover(start_dir, top_level_dir=None, pattern='test*.py'):
37 loader = unittest.defaultTestLoader
38 loader.suiteClass = progress_reporter.TestSuite
39
40 test_suites = []
41 modules = discover.DiscoverModules(start_dir, top_level_dir, pattern)
42 for module in modules:
43 if hasattr(module, 'suite'):
44 suite = module.suite()
45 else:
46 suite = loader.loadTestsFromModule(module)
47 if suite.countTestCases():
48 test_suites.append(suite)
49 return test_suites
50
51
52 def FilterSuite(suite, predicate):
53 new_suite = suite.__class__()
54 for test in suite:
55 if isinstance(test, unittest.TestSuite):
56 subsuite = FilterSuite(test, predicate)
57 if subsuite.countTestCases():
58 new_suite.addTest(subsuite)
59 else:
60 assert isinstance(test, unittest.TestCase)
61 if predicate(test):
62 new_suite.addTest(test)
63
64 return new_suite
65
66
67 def DiscoverTests(search_dirs, top_level_dir, possible_browser,
68 selected_tests=None, selected_tests_are_exact=False,
69 run_disabled_tests=False):
70 def IsTestSelected(test):
71 if selected_tests:
72 found = False
73 for name in selected_tests:
74 if selected_tests_are_exact:
75 if name == test.id():
76 found = True
77 else:
78 if name in test.id():
79 found = True
80 if not found:
81 return False
82 if run_disabled_tests:
83 return True
84 # pylint: disable=W0212
85 if not hasattr(test, '_testMethodName'):
86 return True
87 method = getattr(test, test._testMethodName)
88 return decorators.IsEnabled(method, possible_browser)
89
90 wrapper_suite = progress_reporter.TestSuite()
91 for search_dir in search_dirs:
92 wrapper_suite.addTests(Discover(search_dir, top_level_dir, '*_unittest.py'))
93 return FilterSuite(wrapper_suite, IsTestSelected)
94
95
96 def RestoreLoggingLevel(func):
97 def _LoggingRestoreWrapper(*args, **kwargs):
98 # Cache the current logging level, this needs to be done before calling
99 # parser.parse_args, which changes logging level based on verbosity
100 # setting.
101 logging_level = logging.getLogger().getEffectiveLevel()
102 try:
103 return func(*args, **kwargs)
104 finally:
105 # Restore logging level, which may be changed in parser.parse_args.
106 logging.getLogger().setLevel(logging_level)
107
108 return _LoggingRestoreWrapper
109
110
111 config = None
112 17
113 18
114 class RunTestsCommand(command_line.OptparseCommand): 19 class RunTestsCommand(command_line.OptparseCommand):
115 """Run unit tests""" 20 """Run unit tests"""
116 21
117 usage = '[test_name ...] [<options>]' 22 usage = '[test_name ...] [<options>]'
118 23
24 def __init__(self):
25 super(RunTestsCommand, self).__init__()
26 self.stream = sys.stdout
27
119 @classmethod 28 @classmethod
120 def CreateParser(cls): 29 def CreateParser(cls):
121 options = browser_options.BrowserFinderOptions() 30 options = browser_options.BrowserFinderOptions()
122 options.browser_type = 'any' 31 options.browser_type = 'any'
123 parser = options.CreateParser('%%prog %s' % cls.usage) 32 parser = options.CreateParser('%%prog %s' % cls.usage)
124 return parser 33 return parser
125 34
126 @classmethod 35 @classmethod
127 def AddCommandLineArgs(cls, parser): 36 def AddCommandLineArgs(cls, parser):
128 parser.add_option('--repeat-count', type='int', default=1, 37 parser.add_option('--repeat-count', type='int', default=1,
129 help='Repeats each a provided number of times.') 38 help='Repeats each a provided number of times.')
130 parser.add_option('-d', '--also-run-disabled-tests', 39 parser.add_option('-d', '--also-run-disabled-tests',
131 dest='run_disabled_tests', 40 dest='run_disabled_tests',
132 action='store_true', default=False, 41 action='store_true', default=False,
133 help='Ignore @Disabled and @Enabled restrictions.') 42 help='Ignore @Disabled and @Enabled restrictions.')
134 parser.add_option('--retry-limit', type='int',
135 help='Retry each failure up to N times'
136 ' to de-flake things.')
137 parser.add_option('--exact-test-filter', action='store_true', default=False, 43 parser.add_option('--exact-test-filter', action='store_true', default=False,
138 help='Treat test filter as exact matches (default is ' 44 help='Treat test filter as exact matches (default is '
139 'substring matches).') 45 'substring matches).')
140 json_results.AddOptions(parser) 46
47 typ.ArgumentParser.add_option_group(parser,
48 "Options for running the tests",
49 running=True,
50 skip=['-d', '--path', '-v',
51 '--verbose'])
52 typ.ArgumentParser.add_option_group(parser,
53 "Options for reporting the results",
54 reporting=True)
141 55
142 @classmethod 56 @classmethod
143 def ProcessCommandLineArgs(cls, parser, args): 57 def ProcessCommandLineArgs(cls, parser, args):
144 if args.verbosity == 0:
145 logging.getLogger().setLevel(logging.WARN)
146
147 # We retry failures by default unless we're running a list of tests 58 # We retry failures by default unless we're running a list of tests
148 # explicitly. 59 # explicitly.
149 if args.retry_limit is None and not args.positional_args: 60 if not args.retry_limit and not args.positional_args:
150 args.retry_limit = 3 61 args.retry_limit = 3
151 62
152 try: 63 try:
153 possible_browser = browser_finder.FindBrowser(args) 64 possible_browser = browser_finder.FindBrowser(args)
154 except browser_finder.BrowserFinderException, ex: 65 except browser_finder.BrowserFinderException, ex:
155 parser.error(ex) 66 parser.error(ex)
156 67
157 if not possible_browser: 68 if not possible_browser:
158 parser.error('No browser found of type %s. Cannot run tests.\n' 69 parser.error('No browser found of type %s. Cannot run tests.\n'
159 'Re-run with --browser=list to see ' 70 'Re-run with --browser=list to see '
160 'available browser types.' % args.browser_type) 71 'available browser types.' % args.browser_type)
161 72
162 json_results.ValidateArgs(parser, args) 73 @classmethod
74 def main(cls, args=None, stream=None): # pylint: disable=W0221
75 # We override the superclass so that we can hook in the 'stream' arg.
76 parser = cls.CreateParser()
77 cls.AddCommandLineArgs(parser)
78 options, positional_args = parser.parse_args(args)
79 options.positional_args = positional_args
80 cls.ProcessCommandLineArgs(parser, options)
81
82 obj = cls()
83 if stream is not None:
84 obj.stream = stream
85 return obj.Run(options)
163 86
164 def Run(self, args): 87 def Run(self, args):
165 possible_browser = browser_finder.FindBrowser(args) 88 possible_browser = browser_finder.FindBrowser(args)
166 89
167 test_suite, result = self.RunOneSuite(possible_browser, args) 90 runner = typ.Runner()
91 if self.stream:
92 runner.host.stdout = self.stream
168 93
169 results = [result] 94 # Telemetry seems to overload the system if we run one test per core,
95 # so we scale things back a fair amount. Many of the telemetry tests
96 # are long-running, so there's a limit to how much parallelism we
97 # can effectively use for now anyway.
98 #
99 # It should be possible to handle multiple devices if we adjust
100 # the browser_finder code properly, but for now we only handle the one
101 # on Android and ChromeOS.
102 if possible_browser.platform.GetOSName() in ('android', 'chromeos'):
103 runner.args.jobs = 1
104 else:
105 runner.args.jobs = max(int(args.jobs) // 4, 1)
170 106
171 failed_tests = json_results.FailedTestNames(test_suite, result) 107 runner.args.metadata = args.metadata
172 retry_limit = args.retry_limit 108 runner.args.passthrough = args.passthrough
109 runner.args.retry_limit = args.retry_limit
110 runner.args.test_results_server = args.test_results_server
111 runner.args.test_type = args.test_type
112 runner.args.timing = args.timing
113 runner.args.top_level_dir = args.top_level_dir
114 runner.args.verbose = args.verbosity
115 runner.args.write_full_results_to = args.write_full_results_to
116 runner.args.write_trace_to = args.write_trace_to
173 117
174 while retry_limit and failed_tests: 118 runner.args.path.append(util.GetUnittestDataDir())
175 args.positional_args = failed_tests
176 args.exact_test_filter = True
177 119
178 _, result = self.RunOneSuite(possible_browser, args) 120 runner.classifier = GetClassifier(args, possible_browser)
179 results.append(result) 121 runner.context = args
122 runner.setup_fn = _SetUpProcess
123 runner.teardown_fn = _TearDownProcess
124 runner.win_multiprocessing = typ.WinMultiprocessing.importable
125 try:
126 ret, _, _ = runner.run()
127 except KeyboardInterrupt:
128 print >> sys.stderr, "interrupted, exiting"
129 ret = 130
130 return ret
180 131
181 failed_tests = json_results.FailedTestNames(test_suite, result)
182 retry_limit -= 1
183 132
184 full_results = json_results.FullResults(args, test_suite, results) 133 def GetClassifier(args, possible_browser):
185 json_results.WriteFullResultsIfNecessary(args, full_results) 134 def ClassifyTest(test_set, test):
135 name = test.id()
136 if args.positional_args:
137 if _MatchesSelectedTest(name, args.positional_args,
138 args.exact_test_filter):
139 assert hasattr(test, '_testMethodName')
140 method = getattr(test, test._testMethodName) # pylint: disable=W0212
141 if decorators.ShouldBeIsolated(method, possible_browser):
142 test_set.isolated_tests.append(typ.TestInput(name))
143 else:
144 test_set.parallel_tests.append(typ.TestInput(name))
145 else:
146 assert hasattr(test, '_testMethodName')
147 method = getattr(test, test._testMethodName) # pylint: disable=W0212
148 should_skip, reason = decorators.ShouldSkip(method, possible_browser)
149 if should_skip and not args.run_disabled_tests:
150 test_set.tests_to_skip.append(typ.TestInput(name, msg=reason))
151 elif decorators.ShouldBeIsolated(method, possible_browser):
152 test_set.isolated_tests.append(typ.TestInput(name))
153 else:
154 test_set.parallel_tests.append(typ.TestInput(name))
186 155
187 err_occurred, err_str = json_results.UploadFullResultsIfNecessary( 156 return ClassifyTest
188 args, full_results)
189 if err_occurred:
190 for line in err_str.splitlines():
191 logging.error(line)
192 return 1
193 157
194 return json_results.ExitCodeFromFullResults(full_results)
195 158
196 def RunOneSuite(self, possible_browser, args): 159 def _MatchesSelectedTest(name, selected_tests, selected_tests_are_exact):
197 test_suite = DiscoverTests(config.test_dirs, config.top_level_dir, 160 if not selected_tests:
198 possible_browser, args.positional_args, 161 return False
199 args.exact_test_filter, args.run_disabled_tests) 162 if selected_tests_are_exact:
200 runner = progress_reporter.TestRunner() 163 return any(name in selected_tests)
201 result = runner.run(test_suite, config.progress_reporters, 164 else:
202 args.repeat_count, args) 165 return any(test in name for test in selected_tests)
203 return test_suite, result
204 166
205 @classmethod 167
206 @RestoreLoggingLevel 168 def _SetUpProcess(child, context): # pylint: disable=W0613
207 def main(cls, args=None): 169 args = context
208 return super(RunTestsCommand, cls).main(args) 170 options_for_unittests.Push(args)
171
172
173 def _TearDownProcess(child, context): # pylint: disable=W0613
174 browser_test_case.teardown_browser()
175 options_for_unittests.Pop()
176
177
178 if __name__ == '__main__':
179 ret_code = RunTestsCommand.main()
180 sys.exit(ret_code)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698