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 3# found in the LICENSE file. 4import logging 5import sys 6 7from telemetry.core import util 8from telemetry.core import platform as platform_module 9from telemetry import decorators 10from telemetry.internal.browser import browser_finder 11from telemetry.internal.browser import browser_finder_exceptions 12from telemetry.internal.browser import browser_options 13from telemetry.internal.platform import android_device 14from telemetry.internal.util import binary_manager 15from telemetry.internal.util import command_line 16from telemetry.internal.util import ps_util 17from telemetry.testing import browser_test_case 18from telemetry.testing import options_for_unittests 19 20from catapult_base import xvfb 21 22import typ 23 24 25class RunTestsCommand(command_line.OptparseCommand): 26 """Run unit tests""" 27 28 usage = '[test_name ...] [<options>]' 29 xvfb_process = None 30 31 def __init__(self): 32 super(RunTestsCommand, self).__init__() 33 self.stream = sys.stdout 34 35 @classmethod 36 def CreateParser(cls): 37 options = browser_options.BrowserFinderOptions() 38 options.browser_type = 'any' 39 parser = options.CreateParser('%%prog %s' % cls.usage) 40 return parser 41 42 @classmethod 43 def AddCommandLineArgs(cls, parser, _): 44 parser.add_option('--start-xvfb', action='store_true', 45 default=False, help='Start Xvfb display if needed.') 46 parser.add_option('--repeat-count', type='int', default=1, 47 help='Repeats each a provided number of times.') 48 parser.add_option('--no-browser', action='store_true', default=False, 49 help='Don\'t require an actual browser to run the tests.') 50 parser.add_option('-d', '--also-run-disabled-tests', 51 dest='run_disabled_tests', 52 action='store_true', default=False, 53 help='Ignore @Disabled and @Enabled restrictions.') 54 parser.add_option('--exact-test-filter', action='store_true', default=False, 55 help='Treat test filter as exact matches (default is ' 56 'substring matches).') 57 parser.add_option('--client-config', dest='client_config', default=None) 58 parser.add_option('--disable-logging-config', action='store_true', 59 default=False, help='Configure logging (default on)') 60 61 typ.ArgumentParser.add_option_group(parser, 62 "Options for running the tests", 63 running=True, 64 skip=['-d', '-v', '--verbose']) 65 typ.ArgumentParser.add_option_group(parser, 66 "Options for reporting the results", 67 reporting=True) 68 69 @classmethod 70 def ProcessCommandLineArgs(cls, parser, args, _): 71 # We retry failures by default unless we're running a list of tests 72 # explicitly. 73 if not args.retry_limit and not args.positional_args: 74 args.retry_limit = 3 75 76 if args.no_browser: 77 return 78 79 if args.start_xvfb and xvfb.ShouldStartXvfb(): 80 cls.xvfb_process = xvfb.StartXvfb() 81 try: 82 possible_browser = browser_finder.FindBrowser(args) 83 except browser_finder_exceptions.BrowserFinderException, ex: 84 parser.error(ex) 85 86 if not possible_browser: 87 parser.error('No browser found of type %s. Cannot run tests.\n' 88 'Re-run with --browser=list to see ' 89 'available browser types.' % args.browser_type) 90 91 @classmethod 92 def main(cls, args=None, stream=None): # pylint: disable=arguments-differ 93 # We override the superclass so that we can hook in the 'stream' arg. 94 parser = cls.CreateParser() 95 cls.AddCommandLineArgs(parser, None) 96 options, positional_args = parser.parse_args(args) 97 options.positional_args = positional_args 98 99 try: 100 # Must initialize the DependencyManager before calling 101 # browser_finder.FindBrowser(args) 102 binary_manager.InitDependencyManager(options.client_config) 103 cls.ProcessCommandLineArgs(parser, options, None) 104 105 obj = cls() 106 if stream is not None: 107 obj.stream = stream 108 return obj.Run(options) 109 finally: 110 if cls.xvfb_process: 111 cls.xvfb_process.kill() 112 113 def Run(self, args): 114 runner = typ.Runner() 115 if self.stream: 116 runner.host.stdout = self.stream 117 118 if args.no_browser: 119 possible_browser = None 120 platform = platform_module.GetHostPlatform() 121 else: 122 possible_browser = browser_finder.FindBrowser(args) 123 platform = possible_browser.platform 124 125 # Telemetry seems to overload the system if we run one test per core, 126 # so we scale things back a fair amount. Many of the telemetry tests 127 # are long-running, so there's a limit to how much parallelism we 128 # can effectively use for now anyway. 129 # 130 # It should be possible to handle multiple devices if we adjust the 131 # browser_finder code properly, but for now we only handle one on ChromeOS. 132 if platform.GetOSName() == 'chromeos': 133 runner.args.jobs = 1 134 elif platform.GetOSName() == 'android': 135 android_devs = android_device.FindAllAvailableDevices(args) 136 runner.args.jobs = len(android_devs) 137 if runner.args.jobs == 0: 138 raise RuntimeError("No Android device found") 139 print 'Running tests with %d Android device(s).' % runner.args.jobs 140 elif platform.GetOSVersionName() == 'xp': 141 # For an undiagnosed reason, XP falls over with more parallelism. 142 # See crbug.com/388256 143 runner.args.jobs = max(int(args.jobs) // 4, 1) 144 else: 145 runner.args.jobs = max(int(args.jobs) // 2, 1) 146 147 runner.args.metadata = args.metadata 148 runner.args.passthrough = args.passthrough 149 runner.args.path = args.path 150 runner.args.retry_limit = args.retry_limit 151 runner.args.test_results_server = args.test_results_server 152 runner.args.test_type = args.test_type 153 runner.args.top_level_dir = args.top_level_dir 154 runner.args.write_full_results_to = args.write_full_results_to 155 runner.args.write_trace_to = args.write_trace_to 156 runner.args.list_only = args.list_only 157 158 runner.args.path.append(util.GetUnittestDataDir()) 159 160 # Always print out these info for the ease of debugging. 161 runner.args.timing = True 162 runner.args.verbose = 3 163 164 runner.classifier = GetClassifier(args, possible_browser) 165 runner.context = args 166 runner.setup_fn = _SetUpProcess 167 runner.teardown_fn = _TearDownProcess 168 runner.win_multiprocessing = typ.WinMultiprocessing.importable 169 try: 170 ret, _, _ = runner.run() 171 except KeyboardInterrupt: 172 print >> sys.stderr, "interrupted, exiting" 173 ret = 130 174 return ret 175 176 177def GetClassifier(args, possible_browser): 178 179 def ClassifyTestWithoutBrowser(test_set, test): 180 name = test.id() 181 if (not args.positional_args 182 or _MatchesSelectedTest(name, args.positional_args, 183 args.exact_test_filter)): 184 # TODO(telemetry-team): Make sure that all telemetry unittest that invokes 185 # actual browser are subclasses of browser_test_case.BrowserTestCase 186 # (crbug.com/537428) 187 if issubclass(test.__class__, browser_test_case.BrowserTestCase): 188 test_set.tests_to_skip.append(typ.TestInput( 189 name, msg='Skip the test because it requires a browser.')) 190 else: 191 test_set.parallel_tests.append(typ.TestInput(name)) 192 193 def ClassifyTestWithBrowser(test_set, test): 194 name = test.id() 195 if (not args.positional_args 196 or _MatchesSelectedTest(name, args.positional_args, 197 args.exact_test_filter)): 198 assert hasattr(test, '_testMethodName') 199 method = getattr( 200 test, test._testMethodName) # pylint: disable=protected-access 201 should_skip, reason = decorators.ShouldSkip(method, possible_browser) 202 if should_skip and not args.run_disabled_tests: 203 test_set.tests_to_skip.append(typ.TestInput(name, msg=reason)) 204 elif decorators.ShouldBeIsolated(method, possible_browser): 205 test_set.isolated_tests.append(typ.TestInput(name)) 206 else: 207 test_set.parallel_tests.append(typ.TestInput(name)) 208 209 if possible_browser: 210 return ClassifyTestWithBrowser 211 else: 212 return ClassifyTestWithoutBrowser 213 214 215def _MatchesSelectedTest(name, selected_tests, selected_tests_are_exact): 216 if not selected_tests: 217 return False 218 if selected_tests_are_exact: 219 return any(name in selected_tests) 220 else: 221 return any(test in name for test in selected_tests) 222 223 224def _SetUpProcess(child, context): # pylint: disable=unused-argument 225 ps_util.EnableListingStrayProcessesUponExitHook() 226 if binary_manager.NeedsInit(): 227 # Typ doesn't keep the DependencyManager initialization in the child 228 # processes. 229 binary_manager.InitDependencyManager(context.client_config) 230 args = context 231 # We need to reset the handlers in case some other parts of telemetry already 232 # set it to make this work. 233 if not args.disable_logging_config: 234 logging.getLogger().handlers = [] 235 logging.basicConfig( 236 level=logging.INFO, 237 format='(%(levelname)s) %(asctime)s %(module)s.%(funcName)s:%(lineno)d' 238 ' %(message)s') 239 if args.device and args.device == 'android': 240 android_devices = android_device.FindAllAvailableDevices(args) 241 if not android_devices: 242 raise RuntimeError("No Android device found") 243 android_devices.sort(key=lambda device: device.name) 244 args.device = android_devices[child.worker_num-1].guid 245 options_for_unittests.Push(args) 246 247 248def _TearDownProcess(child, context): # pylint: disable=unused-argument 249 # It's safe to call teardown_browser even if we did not start any browser 250 # in any of the tests. 251 browser_test_case.teardown_browser() 252 options_for_unittests.Pop() 253 254 255if __name__ == '__main__': 256 ret_code = RunTestsCommand.main() 257 sys.exit(ret_code) 258