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. 4 5import copy 6import logging 7import optparse 8import os 9import shlex 10import socket 11import sys 12 13from catapult_base import cloud_storage # pylint: disable=import-error 14 15from telemetry.core import platform 16from telemetry.core import util 17from telemetry.internal.browser import browser_finder 18from telemetry.internal.browser import browser_finder_exceptions 19from telemetry.internal.browser import profile_types 20from telemetry.internal.platform import device_finder 21from telemetry.internal.platform.profiler import profiler_finder 22from telemetry.internal.util import binary_manager 23from telemetry.util import wpr_modes 24 25 26class BrowserFinderOptions(optparse.Values): 27 """Options to be used for discovering a browser.""" 28 29 def __init__(self, browser_type=None): 30 optparse.Values.__init__(self) 31 32 self.browser_type = browser_type 33 self.browser_executable = None 34 self.chrome_root = None # Path to src/ 35 self.chromium_output_dir = None # E.g.: out/Debug 36 self.device = None 37 self.cros_ssh_identity = None 38 39 self.extensions_to_load = [] 40 41 # If set, copy the generated profile to this path on exit. 42 self.output_profile_path = None 43 44 self.cros_remote = None 45 46 self.profiler = None 47 self.verbosity = 0 48 49 self.browser_options = BrowserOptions() 50 self.output_file = None 51 52 self.android_blacklist_file = None 53 self.no_performance_mode = False 54 55 def __repr__(self): 56 return str(sorted(self.__dict__.items())) 57 58 def Copy(self): 59 return copy.deepcopy(self) 60 61 def CreateParser(self, *args, **kwargs): 62 parser = optparse.OptionParser(*args, **kwargs) 63 64 # Selection group 65 group = optparse.OptionGroup(parser, 'Which browser to use') 66 group.add_option('--browser', 67 dest='browser_type', 68 default=None, 69 help='Browser type to run, ' 70 'in order of priority. Supported values: list,%s' % 71 ','.join(browser_finder.FindAllBrowserTypes(self))) 72 group.add_option('--browser-executable', 73 dest='browser_executable', 74 help='The exact browser to run.') 75 group.add_option('--chrome-root', 76 dest='chrome_root', 77 help='Where to look for chrome builds. ' 78 'Defaults to searching parent dirs by default.') 79 group.add_option('--chromium-output-directory', 80 dest='chromium_output_dir', 81 help='Where to look for build artifacts. ' 82 'Can also be specified by setting environment variable ' 83 'CHROMIUM_OUTPUT_DIR.') 84 group.add_option('--device', 85 dest='device', 86 help='The device ID to use. ' 87 'If not specified, only 0 or 1 connected devices are supported. ' 88 'If specified as "android", all available Android devices are ' 89 'used.') 90 group.add_option( 91 '--remote', 92 dest='cros_remote', 93 help='The hostname of a remote ChromeOS device to use.') 94 group.add_option( 95 '--remote-ssh-port', 96 type=int, 97 default=socket.getservbyname('ssh'), 98 dest='cros_remote_ssh_port', 99 help='The SSH port of the remote ChromeOS device (requires --remote).') 100 identity = None 101 testing_rsa = os.path.join( 102 util.GetTelemetryThirdPartyDir(), 'chromite', 'ssh_keys', 'testing_rsa') 103 if os.path.exists(testing_rsa): 104 identity = testing_rsa 105 group.add_option('--identity', 106 dest='cros_ssh_identity', 107 default=identity, 108 help='The identity file to use when ssh\'ing into the ChromeOS device') 109 parser.add_option_group(group) 110 111 # Debugging options 112 group = optparse.OptionGroup(parser, 'When things go wrong') 113 profiler_choices = profiler_finder.GetAllAvailableProfilers() 114 group.add_option( 115 '--profiler', default=None, type='choice', 116 choices=profiler_choices, 117 help='Record profiling data using this tool. Supported values: %s. ' 118 '(Notice: this flag cannot be used for Timeline Based Measurement ' 119 'benchmarks.)' % ', '.join(profiler_choices)) 120 group.add_option( 121 '-v', '--verbose', action='count', dest='verbosity', 122 help='Increase verbosity level (repeat as needed)') 123 group.add_option('--print-bootstrap-deps', 124 action='store_true', 125 help='Output bootstrap deps list.') 126 parser.add_option_group(group) 127 128 # Platform options 129 group = optparse.OptionGroup(parser, 'Platform options') 130 group.add_option('--no-performance-mode', action='store_true', 131 help='Some platforms run on "full performance mode" where the ' 132 'test is executed at maximum CPU speed in order to minimize noise ' 133 '(specially important for dashboards / continuous builds). ' 134 'This option prevents Telemetry from tweaking such platform settings.') 135 group.add_option('--android-blacklist-file', 136 help='Device blacklist JSON file.') 137 parser.add_option_group(group) 138 139 # Browser options. 140 self.browser_options.AddCommandLineArgs(parser) 141 142 real_parse = parser.parse_args 143 def ParseArgs(args=None): 144 defaults = parser.get_default_values() 145 for k, v in defaults.__dict__.items(): 146 if k in self.__dict__ and self.__dict__[k] != None: 147 continue 148 self.__dict__[k] = v 149 ret = real_parse(args, self) # pylint: disable=E1121 150 151 if self.verbosity >= 2: 152 logging.getLogger().setLevel(logging.DEBUG) 153 elif self.verbosity: 154 logging.getLogger().setLevel(logging.INFO) 155 else: 156 logging.getLogger().setLevel(logging.WARNING) 157 158 if self.chromium_output_dir: 159 os.environ['CHROMIUM_OUTPUT_DIR'] = self.chromium_output_dir 160 161 if self.device == 'list': 162 if binary_manager.NeedsInit(): 163 binary_manager.InitDependencyManager(None) 164 devices = device_finder.GetDevicesMatchingOptions(self) 165 print 'Available devices:' 166 for device in devices: 167 print ' ', device.name 168 sys.exit(0) 169 170 if self.browser_executable and not self.browser_type: 171 self.browser_type = 'exact' 172 if self.browser_type == 'list': 173 if binary_manager.NeedsInit(): 174 binary_manager.InitDependencyManager(None) 175 devices = device_finder.GetDevicesMatchingOptions(self) 176 if not devices: 177 sys.exit(0) 178 browser_types = {} 179 for device in devices: 180 try: 181 possible_browsers = browser_finder.GetAllAvailableBrowsers(self, 182 device) 183 browser_types[device.name] = sorted( 184 [browser.browser_type for browser in possible_browsers]) 185 except browser_finder_exceptions.BrowserFinderException as ex: 186 print >> sys.stderr, 'ERROR: ', ex 187 sys.exit(1) 188 print 'Available browsers:' 189 if len(browser_types) == 0: 190 print ' No devices were found.' 191 for device_name in sorted(browser_types.keys()): 192 print ' ', device_name 193 for browser_type in browser_types[device_name]: 194 print ' ', browser_type 195 sys.exit(0) 196 197 # Parse browser options. 198 self.browser_options.UpdateFromParseResults(self) 199 200 return ret 201 parser.parse_args = ParseArgs 202 return parser 203 204 def AppendExtraBrowserArgs(self, args): 205 self.browser_options.AppendExtraBrowserArgs(args) 206 207 def MergeDefaultValues(self, defaults): 208 for k, v in defaults.__dict__.items(): 209 self.ensure_value(k, v) 210 211class BrowserOptions(object): 212 """Options to be used for launching a browser.""" 213 def __init__(self): 214 self.browser_type = None 215 self.show_stdout = False 216 217 # When set to True, the browser will use the default profile. Telemetry 218 # will not provide an alternate profile directory. 219 self.dont_override_profile = False 220 self.profile_dir = None 221 self.profile_type = None 222 self._extra_browser_args = set() 223 self.extra_wpr_args = [] 224 self.wpr_mode = wpr_modes.WPR_OFF 225 self.full_performance_mode = True 226 227 # The amount of time Telemetry should wait for the browser to start. 228 # This property is not exposed as a command line option. 229 self._browser_startup_timeout = 60 230 231 self.disable_background_networking = True 232 self.no_proxy_server = False 233 self.browser_user_agent_type = None 234 235 self.clear_sytem_cache_for_browser_and_profile_on_start = False 236 self.startup_url = 'about:blank' 237 238 # Background pages of built-in component extensions can interfere with 239 # performance measurements. 240 self.disable_component_extensions_with_background_pages = True 241 # Disable default apps. 242 self.disable_default_apps = True 243 244 self.enable_logging = False 245 # The cloud storage bucket & path for uploading logs data produced by the 246 # browser to. 247 # If logs_cloud_remote_path is None, a random remote path is generated every 248 # time the logs data is uploaded. 249 self.logs_cloud_bucket = cloud_storage.TELEMETRY_OUTPUT 250 self.logs_cloud_remote_path = None 251 252 # TODO(danduong): Find a way to store target_os here instead of 253 # finder_options. 254 self._finder_options = None 255 256 # Whether to take screen shot for failed page & put them in telemetry's 257 # profiling results. 258 self.take_screenshot_for_failed_page = False 259 260 def __repr__(self): 261 # This works around the infinite loop caused by the introduction of a 262 # circular reference with _finder_options. 263 obj = self.__dict__.copy() 264 del obj['_finder_options'] 265 return str(sorted(obj.items())) 266 267 def IsCrosBrowserOptions(self): 268 return False 269 270 @classmethod 271 def AddCommandLineArgs(cls, parser): 272 273 ############################################################################ 274 # Please do not add any more options here without first discussing with # 275 # a telemetry owner. This is not the right place for platform-specific # 276 # options. # 277 ############################################################################ 278 279 group = optparse.OptionGroup(parser, 'Browser options') 280 profile_choices = profile_types.GetProfileTypes() 281 group.add_option('--profile-type', 282 dest='profile_type', 283 type='choice', 284 default='clean', 285 choices=profile_choices, 286 help=('The user profile to use. A clean profile is used by default. ' 287 'Supported values: ' + ', '.join(profile_choices))) 288 group.add_option('--profile-dir', 289 dest='profile_dir', 290 help='Profile directory to launch the browser with. ' 291 'A clean profile is used by default') 292 group.add_option('--extra-browser-args', 293 dest='extra_browser_args_as_string', 294 help='Additional arguments to pass to the browser when it starts') 295 group.add_option('--extra-wpr-args', 296 dest='extra_wpr_args_as_string', 297 help=('Additional arguments to pass to Web Page Replay. ' 298 'See third_party/webpagereplay/replay.py for usage.')) 299 group.add_option('--show-stdout', 300 action='store_true', 301 help='When possible, will display the stdout of the process') 302 group.add_option('--enable-browser-logging', 303 dest='enable_logging', 304 action='store_true', 305 help=('Enable browser logging. The log file is saved in temp directory.' 306 "Note that enabling this flag affects the browser's " 307 'performance')) 308 parser.add_option_group(group) 309 310 group = optparse.OptionGroup(parser, 'Compatibility options') 311 group.add_option('--gtest_output', 312 help='Ignored argument for compatibility with runtest.py harness') 313 parser.add_option_group(group) 314 315 def UpdateFromParseResults(self, finder_options): 316 """Copies our options from finder_options""" 317 browser_options_list = [ 318 'extra_browser_args_as_string', 319 'extra_wpr_args_as_string', 320 'enable_logging', 321 'profile_dir', 322 'profile_type', 323 'show_stdout', 324 ] 325 for o in browser_options_list: 326 a = getattr(finder_options, o, None) 327 if a is not None: 328 setattr(self, o, a) 329 delattr(finder_options, o) 330 331 self.browser_type = finder_options.browser_type 332 self._finder_options = finder_options 333 334 if hasattr(self, 'extra_browser_args_as_string'): 335 tmp = shlex.split( 336 self.extra_browser_args_as_string) 337 self.AppendExtraBrowserArgs(tmp) 338 delattr(self, 'extra_browser_args_as_string') 339 if hasattr(self, 'extra_wpr_args_as_string'): 340 tmp = shlex.split( 341 self.extra_wpr_args_as_string) 342 self.extra_wpr_args.extend(tmp) 343 delattr(self, 'extra_wpr_args_as_string') 344 if self.profile_type == 'default': 345 self.dont_override_profile = True 346 347 if self.profile_dir and self.profile_type != 'clean': 348 logging.critical( 349 "It's illegal to specify both --profile-type and --profile-dir.\n" 350 "For more information see: http://goo.gl/ngdGD5") 351 sys.exit(1) 352 353 if self.profile_dir and not os.path.isdir(self.profile_dir): 354 logging.critical( 355 "Directory specified by --profile-dir (%s) doesn't exist " 356 "or isn't a directory.\n" 357 "For more information see: http://goo.gl/ngdGD5" % self.profile_dir) 358 sys.exit(1) 359 360 if not self.profile_dir: 361 self.profile_dir = profile_types.GetProfileDir(self.profile_type) 362 363 # This deferred import is necessary because browser_options is imported in 364 # telemetry/telemetry/__init__.py. 365 finder_options.browser_options = CreateChromeBrowserOptions(self) 366 367 @property 368 def finder_options(self): 369 return self._finder_options 370 371 @property 372 def extra_browser_args(self): 373 return self._extra_browser_args 374 375 @property 376 def browser_startup_timeout(self): 377 return self._browser_startup_timeout 378 379 @browser_startup_timeout.setter 380 def browser_startup_timeout(self, value): 381 self._browser_startup_timeout = value 382 383 def AppendExtraBrowserArgs(self, args): 384 if isinstance(args, list): 385 self._extra_browser_args.update(args) 386 else: 387 self._extra_browser_args.add(args) 388 389 390def CreateChromeBrowserOptions(br_options): 391 browser_type = br_options.browser_type 392 393 if (platform.GetHostPlatform().GetOSName() == 'chromeos' or 394 (browser_type and browser_type.startswith('cros'))): 395 return CrosBrowserOptions(br_options) 396 397 return br_options 398 399 400class ChromeBrowserOptions(BrowserOptions): 401 """Chrome-specific browser options.""" 402 403 def __init__(self, br_options): 404 super(ChromeBrowserOptions, self).__init__() 405 # Copy to self. 406 self.__dict__.update(br_options.__dict__) 407 408 409class CrosBrowserOptions(ChromeBrowserOptions): 410 """ChromeOS-specific browser options.""" 411 412 def __init__(self, br_options): 413 super(CrosBrowserOptions, self).__init__(br_options) 414 # Create a browser with oobe property. 415 self.create_browser_with_oobe = False 416 # Clear enterprise policy before logging in. 417 self.clear_enterprise_policy = True 418 # Disable GAIA/enterprise services. 419 self.disable_gaia_services = True 420 421 self.auto_login = True 422 self.gaia_login = False 423 self.username = 'test@test.test' 424 self.password = '' 425 self.gaia_id = '12345' 426 427 def IsCrosBrowserOptions(self): 428 return True 429