1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this file, 3# You can obtain one at http://mozilla.org/MPL/2.0/. 4from __future__ import absolute_import, print_function 5 6import copy 7import os 8import re 9import sys 10 11from mozlog.commandline import setup_logging 12from talos import utils, test 13from talos.cmdline import parse_args 14 15 16class ConfigurationError(Exception): 17 pass 18 19 20DEFAULTS = dict( 21 # args to pass to browser 22 extra_args=[], 23 buildid="testbuildid", 24 init_url="getInfo.html", 25 env={"NO_EM_RESTART": "1"}, 26 # base data for all tests. Note that any None here will end up converted to 27 # an empty string in useBaseTestDefaults. 28 basetest=dict( 29 cycles=1, 30 profile_path="${talos}/base_profile", 31 responsiveness=False, 32 gecko_profile=False, 33 gecko_profile_interval=1, 34 gecko_profile_entries=100000, 35 resolution=1, 36 mainthread=False, 37 shutdown=False, 38 timeout=3600, 39 tpchrome=True, 40 tpcycles=10, 41 tpmozafterpaint=False, 42 tphero=False, 43 pdfpaint=True, 44 fnbpaint=False, 45 firstpaint=False, 46 format_pagename=True, 47 userready=False, 48 testeventmap=[], 49 base_vs_ref=False, 50 tppagecycles=1, 51 tploadnocache=False, 52 tpscrolltest=False, 53 win_counters=[], 54 w7_counters=[], 55 linux_counters=[], 56 mac_counters=[], 57 xperf_counters=[], 58 setup=None, 59 cleanup=None, 60 preferences={}, 61 ), 62) 63 64 65# keys to generated self.config that are global overrides to tests 66GLOBAL_OVERRIDES = ( 67 "cycles", 68 "gecko_profile", 69 "gecko_profile_interval", 70 "gecko_profile_entries", 71 "gecko_profile_features", 72 "gecko_profile_threads", 73 "tpcycles", 74 "tppagecycles", 75 "tpmanifest", 76 "tptimeout", 77 "tpmozafterpaint", 78 "tphero", 79 "fnbpaint", 80 "pdfpaint", 81 "firstpaint", 82 "userready", 83) 84 85 86CONF_VALIDATORS = [] 87 88 89def validator(func): 90 """ 91 A decorator that register configuration validators to execute against the 92 configuration. 93 94 They will be executed in the order of declaration. 95 """ 96 CONF_VALIDATORS.append(func) 97 return func 98 99 100def convert_url(config, url): 101 webserver = config["webserver"] 102 if not webserver: 103 return url 104 105 if "://" in url: 106 # assume a fully qualified url 107 return url 108 109 if ".html" in url: 110 url = "http://%s/%s" % (webserver, url) 111 112 return url 113 114 115@validator 116def fix_xperf(config): 117 # BBB: remove doubly-quoted xperf values from command line 118 # (needed for buildbot) 119 # https://bugzilla.mozilla.org/show_bug.cgi?id=704654#c43 120 win7_path = "c:/Program Files/Microsoft Windows Performance Toolkit/xperf.exe" 121 if config["xperf_path"]: 122 xperf_path = config["xperf_path"] 123 quotes = ('"', "'") 124 for quote in quotes: 125 if xperf_path.startswith(quote) and xperf_path.endswith(quote): 126 config["xperf_path"] = xperf_path[1:-1] 127 break 128 if not os.path.exists(config["xperf_path"]): 129 # look for old win7 path 130 if not os.path.exists(win7_path): 131 raise ConfigurationError( 132 "xperf.exe cannot be found at the path specified" 133 ) 134 config["xperf_path"] = win7_path 135 136 137@validator 138def set_webserver(config): 139 # pick a free port 140 import socket 141 142 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 143 sock.bind(("", 0)) 144 port = sock.getsockname()[1] 145 sock.close() 146 147 config["webserver"] = "127.0.0.1:%d" % port 148 149 150@validator 151def update_prefs(config): 152 config.setdefault("preferences", {}) 153 154 # update prefs from command line 155 prefs = config.pop("extraPrefs") 156 if prefs: 157 for arg in prefs: 158 k, v = arg.split("=", 1) 159 config["preferences"][k] = utils.parse_pref(v) 160 161 162@validator 163def fix_init_url(config): 164 if "init_url" in config: 165 config["init_url"] = convert_url(config, config["init_url"]) 166 167 168@validator 169def determine_local_symbols_path(config): 170 if "symbols_path" not in config: 171 return 172 173 # use objdir/dist/crashreporter-symbols for symbolsPath if none provided 174 if ( 175 not config["symbols_path"] 176 and config["develop"] 177 and "MOZ_DEVELOPER_OBJ_DIR" in os.environ 178 ): 179 config["symbols_path"] = os.path.join( 180 os.environ["MOZ_DEVELOPER_OBJ_DIR"], "dist", "crashreporter-symbols" 181 ) 182 183 184def get_counters(config): 185 counters = set() 186 return counters 187 188 189def get_active_tests(config): 190 activeTests = config.pop("activeTests").strip().split(":") 191 192 # ensure tests are available 193 availableTests = test.test_dict() 194 if not set(activeTests).issubset(availableTests): 195 missing = [i for i in activeTests if i not in availableTests] 196 raise ConfigurationError("No definition found for test(s): %s" % missing) 197 198 return activeTests 199 200 201def get_global_overrides(config): 202 global_overrides = {} 203 for key in GLOBAL_OVERRIDES: 204 # get global overrides for all tests 205 value = config[key] 206 if value is not None: 207 global_overrides[key] = value 208 if key != "gecko_profile": 209 config.pop(key) 210 211 return global_overrides 212 213 214def get_test_host(manifest_line): 215 match = re.match(r"^http://localhost/page_load_test/tp5n/([^/]+)/", manifest_line) 216 host = match.group(1) 217 return host + "-talos" 218 219 220def build_manifest(config, is_multidomain, manifestName): 221 # read manifest lines 222 with open(manifestName, "r") as fHandle: 223 manifestLines = fHandle.readlines() 224 225 # write modified manifest lines 226 with open(manifestName + ".develop", "w") as newHandle: 227 for line in manifestLines: 228 new_host = get_test_host(line) if is_multidomain else config["webserver"] 229 newline = line.replace("localhost", new_host) 230 newline = newline.replace("page_load_test", "tests") 231 newHandle.write(newline) 232 233 newManifestName = manifestName + ".develop" 234 235 # return new manifest 236 return newManifestName 237 238 239def get_test(config, global_overrides, counters, test_instance): 240 mozAfterPaint = getattr(test_instance, "tpmozafterpaint", None) 241 hero = getattr(test_instance, "tphero", None) 242 firstPaint = getattr(test_instance, "firstpaint", None) 243 userReady = getattr(test_instance, "userready", None) 244 firstNonBlankPaint = getattr(test_instance, "fnbpaint", None) 245 pdfPaint = getattr(test_instance, "pdfpaint", None) 246 247 test_instance.update(**global_overrides) 248 249 # update original value of mozAfterPaint, this could be 'false', 250 # so check for None 251 if mozAfterPaint is not None: 252 test_instance.tpmozafterpaint = mozAfterPaint 253 if firstNonBlankPaint is not None: 254 test_instance.fnbpaint = firstNonBlankPaint 255 if firstPaint is not None: 256 test_instance.firstpaint = firstPaint 257 if userReady is not None: 258 test_instance.userready = userReady 259 if hero is not None: 260 test_instance.tphero = hero 261 if pdfPaint is not None: 262 test_instance.pdfpaint = pdfPaint 263 264 # fix up url 265 url = getattr(test_instance, "url", None) 266 if url: 267 test_instance.url = utils.interpolate(convert_url(config, url)) 268 269 # fix up tpmanifest 270 tpmanifest = getattr(test_instance, "tpmanifest", None) 271 if tpmanifest: 272 is_multidomain = getattr(test_instance, "multidomain", False) 273 test_instance.tpmanifest = build_manifest( 274 config, is_multidomain, utils.interpolate(tpmanifest) 275 ) 276 277 # add any counters 278 if counters: 279 keys = ( 280 "linux_counters", 281 "mac_counters", 282 "win_counters", 283 "w7_counters", 284 "xperf_counters", 285 ) 286 for key in keys: 287 if key not in test_instance.keys: 288 # only populate attributes that will be output 289 continue 290 if not isinstance(getattr(test_instance, key, None), list): 291 setattr(test_instance, key, []) 292 _counters = getattr(test_instance, key) 293 _counters.extend( 294 [counter for counter in counters if counter not in _counters] 295 ) 296 return dict(test_instance.items()) 297 298 299@validator 300def tests(config): 301 counters = get_counters(config) 302 global_overrides = get_global_overrides(config) 303 activeTests = get_active_tests(config) 304 test_dict = test.test_dict() 305 306 tests = [] 307 for test_name in activeTests: 308 test_class = test_dict[test_name] 309 tests.append(get_test(config, global_overrides, counters, test_class())) 310 config["tests"] = tests 311 312 313def get_browser_config(config): 314 required = ( 315 "extensions", 316 "browser_path", 317 "browser_wait", 318 "extra_args", 319 "buildid", 320 "env", 321 "init_url", 322 "webserver", 323 ) 324 optional = { 325 "bcontroller_config": "${talos}/bcontroller.json", 326 "child_process": "plugin-container", 327 "debug": False, 328 "debugger": None, 329 "debugger_args": None, 330 "develop": False, 331 "enable_webrender": False, 332 "enable_fission": False, 333 "process": "", 334 "framework": "talos", 335 "repository": None, 336 "sourcestamp": None, 337 "symbols_path": None, 338 "test_timeout": 1200, 339 "xperf_path": None, 340 "error_filename": None, 341 "no_upload_results": False, 342 "stylothreads": 0, 343 "subtests": None, 344 "preferences": {}, 345 } 346 browser_config = dict(title=config["title"]) 347 browser_config.update(dict([(i, config[i]) for i in required])) 348 browser_config.update(dict([(i, config.get(i, j)) for i, j in optional.items()])) 349 return browser_config 350 351 352def suites_conf(): 353 import json 354 355 with open(os.path.join(os.path.dirname(utils.here), "talos.json")) as f: 356 return json.load(f)["suites"] 357 358 359def get_config(argv=None): 360 argv = argv or sys.argv[1:] 361 cli_opts = parse_args(argv=argv) 362 if cli_opts.suite: 363 # read the suite config, update the args 364 try: 365 suite_conf = suites_conf()[cli_opts.suite] 366 except KeyError: 367 raise ConfigurationError("No such suite: %r" % cli_opts.suite) 368 argv += ["-a", ":".join(suite_conf["tests"])] 369 # talos_options in the suite config should not override command line 370 # options, so we prepend argv with talos_options so that, when parsed, 371 # the command line options will clobber the suite config options. 372 argv = suite_conf.get("talos_options", []) + argv 373 # args needs to be reparsed now 374 elif not cli_opts.activeTests: 375 raise ConfigurationError("--activeTests or --suite required!") 376 377 cli_opts = parse_args(argv=argv) 378 setup_logging("talos", cli_opts, {"tbpl": sys.stdout}) 379 config = copy.deepcopy(DEFAULTS) 380 config.update(cli_opts.__dict__) 381 for validate in CONF_VALIDATORS: 382 validate(config) 383 # remove None Values 384 for k, v in config.copy().items(): 385 if v is None: 386 del config[k] 387 return config 388 389 390def get_configs(argv=None): 391 config = get_config(argv=argv) 392 browser_config = get_browser_config(config) 393 return config, browser_config 394 395 396if __name__ == "__main__": 397 cfgs = get_configs() 398 print(cfgs[0]) 399 print() 400 print(cfgs[1]) 401