other.py (43549B)
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 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 import copy 6 import hashlib 7 import re 8 9 from mozbuild.schedules import INCLUSIVE_COMPONENTS 10 from taskgraph.transforms.base import TransformSequence 11 from taskgraph.util import json 12 from taskgraph.util.attributes import keymatch 13 from taskgraph.util.keyed_by import evaluate_keyed_by 14 from taskgraph.util.readonlydict import ReadOnlyDict 15 from taskgraph.util.schema import Schema, resolve_keyed_by 16 from taskgraph.util.taskcluster import get_artifact_path 17 from taskgraph.util.templates import merge 18 from voluptuous import Any, Optional, Required 19 20 from gecko_taskgraph.transforms.test.variant import TEST_VARIANTS 21 from gecko_taskgraph.util.perftest import is_external_browser 22 from gecko_taskgraph.util.platforms import platform_family 23 from gecko_taskgraph.util.taskcluster import get_index_url 24 25 transforms = TransformSequence() 26 27 28 @transforms.add 29 def limit_platforms(config, tasks): 30 for task in tasks: 31 if not task["limit-platforms"]: 32 yield task 33 continue 34 35 limited_platforms = {key: key for key in task["limit-platforms"]} 36 if keymatch(limited_platforms, task["test-platform"]): 37 yield task 38 39 40 @transforms.add 41 def handle_suite_category(config, tasks): 42 for task in tasks: 43 task.setdefault("suite", {}) 44 45 if isinstance(task["suite"], str): 46 task["suite"] = {"name": task["suite"]} 47 48 suite = task["suite"].setdefault("name", task["test-name"]) 49 category = task["suite"].setdefault("category", suite) 50 51 task.setdefault("attributes", {}) 52 task["attributes"]["unittest_suite"] = suite 53 task["attributes"]["unittest_category"] = category 54 55 script = task["mozharness"]["script"] 56 category_arg = None 57 if suite.startswith("test-verify") or suite.startswith("test-coverage"): 58 pass 59 elif script in ("android_emulator_unittest.py", "android_hardware_unittest.py"): 60 category_arg = "--test-suite" 61 elif script == "desktop_unittest.py": 62 category_arg = f"--{category}-suite" 63 64 if category_arg: 65 resolve_keyed_by( 66 task, 67 "mozharness.extra-options", 68 item_name=task["test-name"], 69 enforce_single_match=False, 70 variant=task["attributes"].get("unittest_variant"), 71 ) 72 73 task["mozharness"].setdefault("extra-options", []) 74 extra = task["mozharness"]["extra-options"] 75 if not any(arg.startswith(category_arg) for arg in extra): 76 extra.append(f"{category_arg}={suite}") 77 78 # From here on out we only use the suite name. 79 task["suite"] = suite 80 81 # in the future we might need to refactor new-test-config to be suite specific 82 if "mochitest" in task["suite"] and config.params["try_task_config"].get( 83 "new-test-config", False 84 ): 85 task = merge( 86 task, {"mozharness": {"extra-options": ["--restartAfterFailure"]}} 87 ) 88 yield task 89 90 91 @transforms.add 92 def setup_talos(config, tasks): 93 """Add options that are specific to talos jobs (identified by suite=talos)""" 94 for task in tasks: 95 if task["suite"] != "talos": 96 yield task 97 continue 98 99 resolve_keyed_by( 100 task, 101 "mozharness.extra-options", 102 item_name=task["test-name"], 103 enforce_single_match=False, 104 variant=task["attributes"].get("unittest_variant"), 105 ) 106 107 extra_options = task.setdefault("mozharness", {}).setdefault( 108 "extra-options", [] 109 ) 110 extra_options.append("--use-talos-json") 111 112 if config.params.get("project", None): 113 extra_options.append("--project=%s" % config.params["project"]) 114 115 if "pdfpaint" in task["try-name"]: 116 max_chunks = 10 117 for chunk in range(1, max_chunks + 1): 118 new_task = copy.deepcopy(task) 119 new_task["mozharness"]["extra-options"].append( 120 f"--pdfPaintChunk={chunk}" 121 ) 122 new_task["test-name"] = task["test-name"].replace( 123 "pdfpaint", f"pdfpaint-{chunk}" 124 ) 125 new_task["try-name"] = task["try-name"].replace( 126 "pdfpaint", f"pdfpaint-{chunk}" 127 ) 128 new_task["treeherder-symbol"] = task["treeherder-symbol"].replace( 129 "pdfpaint", f"pdfpaint-{chunk}" 130 ) 131 yield new_task 132 continue 133 134 yield task 135 136 137 @transforms.add 138 def setup_browsertime_flag(config, tasks): 139 """Optionally add `--browsertime` flag to Raptor pageload tests.""" 140 141 browsertime_flag = config.params["try_task_config"].get("browsertime", False) 142 143 for task in tasks: 144 if not browsertime_flag or task["suite"] != "raptor": 145 yield task 146 continue 147 148 if task["treeherder-symbol"].startswith("Rap"): 149 # The Rap group is subdivided as Rap{-fenix,-refbrow(...), 150 # so `taskgraph.util.treeherder.replace_group` isn't appropriate. 151 task["treeherder-symbol"] = task["treeherder-symbol"].replace( 152 "Rap", "Btime", 1 153 ) 154 155 extra_options = task.setdefault("mozharness", {}).setdefault( 156 "extra-options", [] 157 ) 158 extra_options.append("--browsertime") 159 160 yield task 161 162 163 @transforms.add 164 def handle_artifact_prefix(config, tasks): 165 """Handle translating `artifact_prefix` appropriately""" 166 for task in tasks: 167 if task["build-attributes"].get("artifact_prefix"): 168 task.setdefault("attributes", {}).setdefault( 169 "artifact_prefix", task["build-attributes"]["artifact_prefix"] 170 ) 171 yield task 172 173 174 @transforms.add 175 def set_treeherder_machine_platform(config, tasks): 176 """Set the appropriate task.extra.treeherder.machine.platform""" 177 translation = { 178 # Linux64 build platform for asan is specified differently to 179 # treeherder. 180 "macosx1400-64/opt": "osx-1300/opt", 181 "macosx1400-64-shippable/opt": "osx-1400-shippable/opt", 182 "macosx1500-64/opt": "osx-1500/opt", 183 "macosx1500-64-shippable/opt": "osx-1500-shippable/opt", 184 "win64-asan/opt": "windows11-64-24h2/asan", 185 "win64-aarch64/opt": "windows11-aarch64/opt", 186 } 187 for task in tasks: 188 # For most desktop platforms, the above table is not used for "regular" 189 # builds, so we'll always pick the test platform here. 190 # On macOS though, the regular builds are in the table. This causes a 191 # conflict in `verify_task_graph_symbol` once you add a new test 192 # platform based on regular macOS builds, such as for QR. 193 # Since it's unclear if the regular macOS builds can be removed from 194 # the table, workaround the issue for QR. 195 if "android" in task["test-platform"] and "pgo/opt" in task["test-platform"]: 196 platform_new = task["test-platform"].replace("-pgo/opt", "/pgo") 197 task["treeherder-machine-platform"] = platform_new 198 elif "android-em-" in task["test-platform"]: 199 task["treeherder-machine-platform"] = task["test-platform"] 200 elif "android-hw" in task["test-platform"]: 201 task["treeherder-machine-platform"] = task["test-platform"] 202 203 # Bug 1602863 - must separately define linux64/asan and linux1804-64/asan 204 # otherwise causes an exception during taskgraph generation about 205 # duplicate treeherder platform/symbol. 206 elif "linux64-asan/opt" in task["test-platform"]: 207 task["treeherder-machine-platform"] = "linux64/asan" 208 elif "linux1804-asan/opt" in task["test-platform"]: 209 task["treeherder-machine-platform"] = "linux1804-64/asan" 210 elif "-qr" in task["test-platform"]: 211 task["treeherder-machine-platform"] = task["test-platform"] 212 else: 213 task["treeherder-machine-platform"] = translation.get( 214 task["build-platform"], task["test-platform"] 215 ) 216 yield task 217 218 219 @transforms.add 220 def set_download_symbols(config, tasks): 221 """In general, we download symbols immediately for debug builds, but only 222 on demand for everything else. ASAN builds shouldn't download 223 symbols since they don't product symbol zips see bug 1283879""" 224 for task in tasks: 225 if task["test-platform"].split("/")[-1] == "debug": 226 task["mozharness"]["download-symbols"] = True 227 elif "asan" in task["build-platform"] or "tsan" in task["build-platform"]: 228 if "download-symbols" in task["mozharness"]: 229 del task["mozharness"]["download-symbols"] 230 else: 231 task["mozharness"]["download-symbols"] = "ondemand" 232 yield task 233 234 235 @transforms.add 236 def handle_keyed_by(config, tasks): 237 """Resolve fields that can be keyed by platform, etc.""" 238 fields = [ 239 "instance-size", 240 "docker-image", 241 "max-run-time", 242 "chunks", 243 "suite", 244 "run-on-projects", 245 "os-groups", 246 "run-as-administrator", 247 "workdir", 248 "worker-type", 249 "virtualization", 250 "fetches.fetch", 251 "fetches.toolchain", 252 "target", 253 "webrender-run-on-projects", 254 "mozharness.extra-options", 255 "mozharness.requires-signed-builds", 256 "build-signing-label", 257 "dependencies", 258 ] 259 for task in tasks: 260 for field in fields: 261 resolve_keyed_by( 262 task, 263 field, 264 item_name=task["test-name"], 265 enforce_single_match=False, 266 project=config.params["project"], 267 variant=task["attributes"].get("unittest_variant"), 268 ) 269 yield task 270 271 272 @transforms.add 273 def setup_raptor_external_browser_platforms(config, tasks): 274 for task in tasks: 275 if task["suite"] != "raptor": 276 yield task 277 continue 278 279 if is_external_browser(task["try-name"]): 280 if "win" in task["build-label"]: 281 task["build-platform"] = "windows2012-64/opt" 282 task["build-label"] = "build-win64/opt" 283 else: 284 task["build-platform"] = "linux64/opt" 285 task["build-label"] = "build-linux64/opt" 286 287 yield task 288 289 290 @transforms.add 291 def set_target(config, tasks): 292 for task in tasks: 293 build_platform = task["build-platform"] 294 target = None 295 if "target" in task: 296 target = task["target"] 297 if not target: 298 if build_platform.startswith("macosx"): 299 target = "target.dmg" 300 elif build_platform.startswith("android"): 301 target = "target.apk" 302 elif build_platform.startswith("win"): 303 target = "target.zip" 304 else: 305 target = "target.tar.xz" 306 307 if isinstance(target, dict): 308 if "index" in target: 309 # TODO Remove hardcoded mobile artifact prefix 310 index_url = get_index_url(target["index"]) 311 installer_url = "{}/artifacts/public/{}".format( 312 index_url, target["name"] 313 ) 314 task["mozharness"]["installer-url"] = installer_url 315 else: 316 task["mozharness"]["installer-url"] = ( 317 f"<{target['upstream-task']}/{target['name']}>" 318 ) 319 else: 320 task["mozharness"]["build-artifact-name"] = get_artifact_path(task, target) 321 322 yield task 323 324 325 @transforms.add 326 def setup_browsertime(config, tasks): 327 """Configure browsertime dependencies for Raptor pageload tests that have 328 `--browsertime` extra option.""" 329 330 for task in tasks: 331 # We need to make non-trivial changes to various fetches, and our 332 # `by-test-platform` may not be "compatible" with existing 333 # `by-test-platform` filters. Therefore we do everything after 334 # `handle_keyed_by` so that existing fields have been resolved down to 335 # simple lists. But we use the `by-test-platform` machinery to express 336 # filters so that when the time comes to move browsertime into YAML 337 # files, the transition is straight-forward. 338 extra_options = task.get("mozharness", {}).get("extra-options", []) 339 340 if task["suite"] != "raptor": 341 yield task 342 continue 343 344 ts = { 345 "by-test-platform": { 346 "android.*": ["browsertime", "linux64-geckodriver", "linux64-node"], 347 "linux.*": ["browsertime", "linux64-geckodriver", "linux64-node"], 348 "macosx1470.*": [ 349 "browsertime", 350 "macosx64-geckodriver", 351 "macosx64-node", 352 ], 353 "macosx1400.*": [ 354 "browsertime", 355 "macosx64-aarch64-geckodriver", 356 "macosx64-aarch64-node", 357 ], 358 "macosx1500.*": [ 359 "browsertime", 360 "macosx64-aarch64-geckodriver", 361 "macosx64-aarch64-node", 362 ], 363 "windows.*aarch64.*": [ 364 "browsertime", 365 "win32-geckodriver", 366 "win32-node", 367 ], 368 "windows.*-64.*": ["browsertime", "win64-geckodriver", "win64-node"], 369 }, 370 } 371 372 task.setdefault("fetches", {}).setdefault("toolchain", []).extend( 373 evaluate_keyed_by(ts, "fetches.toolchain", task) 374 ) 375 376 fs = { 377 "by-test-platform": { 378 "android.*": ["linux64-ffmpeg-7.1"], 379 "linux.*": ["linux64-ffmpeg-7.1"], 380 "macosx1470.*": ["mac64-ffmpeg-7.1"], 381 "macosx1400.*": ["mac64-ffmpeg-7.1"], 382 "macosx1500.*": ["mac64-ffmpeg-7.1"], 383 "windows.*aarch64.*": ["win64-ffmpeg-7.1"], 384 "windows.*-64.*": ["win64-ffmpeg-7.1"], 385 }, 386 } 387 388 cd_fetches = { 389 "android.*": [ 390 "linux64-cft-cd-backup", 391 "linux64-cft-cd-stable", 392 "linux64-cft-cd-beta", 393 ], 394 "linux.*": [ 395 "linux64-cft-cd-backup", 396 "linux64-cft-cd-stable", 397 "linux64-cft-cd-beta", 398 ], 399 "macosx1470.*": [ 400 "mac-cft-cd-backup", 401 "mac-cft-cd-stable", 402 "mac-cft-cd-beta", 403 ], 404 "macosx1400.*": [ 405 "mac-cft-cd-arm-backup", 406 "mac-cft-cd-arm-stable", 407 "mac-cft-cd-arm-beta", 408 ], 409 "macosx1500.*": [ 410 "mac-cft-cd-arm-backup", 411 "mac-cft-cd-arm-stable", 412 "mac-cft-cd-arm-beta", 413 ], 414 "windows.*-64.*": [ 415 "win64-cft-cd-backup", 416 "win64-cft-cd-stable", 417 "win64-cft-cd-beta", 418 ], 419 } 420 421 chromium_fetches = { 422 "linux.*": ["linux64-cft-cd-canary"], 423 "macosx1400.*": ["mac-cft-cd-arm-canary"], 424 "macosx1470.*": ["mac-cft-cd-canary"], 425 "macosx1500.*": ["mac-cft-cd-arm-canary"], 426 "windows.*-64.*": ["win64-cft-cd-canary"], 427 "android.*": ["linux64-cft-cd-canary"], 428 } 429 430 cd_extracted_name = { 431 "windows": "{}chromedriver.exe", 432 "mac": "{}chromedriver", 433 "default": "{}chromedriver", 434 } 435 436 if "--app=chrome" in extra_options or "--app=chrome-m" in extra_options: 437 # Only add the chromedriver fetches when chrome is running 438 for platform in cd_fetches: 439 fs["by-test-platform"][platform].extend(cd_fetches[platform]) 440 if "--app=custom-car" in extra_options or "--app=cstm-car-m" in extra_options: 441 for platform in chromium_fetches: 442 fs["by-test-platform"][platform].extend(chromium_fetches[platform]) 443 444 # The Chrome-for-Testing chromedrivers are repackaged into the following 445 # platform specific archives. The versions will always be compatible as 446 # these are fetched from the `Canary` channel. 447 cd_extracted_name = { 448 "windows": "cft-chromedriver-win64/chromedriver.exe", 449 "mac": "cft-chromedriver-mac/chromedriver", 450 "default": "cft-chromedriver-linux/chromedriver", 451 } 452 453 # Disable the Raptor install step 454 if "--app=chrome-m" in extra_options or "--app=cstm-car-m" in extra_options: 455 extra_options.append("--no-install") 456 457 task.setdefault("fetches", {}).setdefault("fetch", []).extend( 458 evaluate_keyed_by(fs, "fetches.fetch", task) 459 ) 460 461 extra_options.extend(( 462 "--browsertime-browsertimejs", 463 "$MOZ_FETCHES_DIR/browsertime/node_modules/browsertime/bin/browsertime.js", 464 )) # noqa: E501 465 466 eos = { 467 "by-test-platform": { 468 "windows.*": [ 469 "--browsertime-node", 470 "$MOZ_FETCHES_DIR/node/node.exe", 471 "--browsertime-geckodriver", 472 "$MOZ_FETCHES_DIR/geckodriver.exe", 473 "--browsertime-chromedriver", 474 "$MOZ_FETCHES_DIR/" + cd_extracted_name["windows"], 475 "--browsertime-ffmpeg", 476 "$MOZ_FETCHES_DIR/ffmpeg-n7.1-latest-win64-gpl-shared-7.1/bin/ffmpeg.exe", 477 ], 478 "macosx.*": [ 479 "--browsertime-node", 480 "$MOZ_FETCHES_DIR/node/bin/node", 481 "--browsertime-geckodriver", 482 "$MOZ_FETCHES_DIR/geckodriver", 483 "--browsertime-chromedriver", 484 "$MOZ_FETCHES_DIR/" + cd_extracted_name["mac"], 485 "--browsertime-ffmpeg", 486 "$MOZ_FETCHES_DIR/ffmpeg-7.1/bin/ffmpeg", 487 ], 488 "default": [ 489 "--browsertime-node", 490 "$MOZ_FETCHES_DIR/node/bin/node", 491 "--browsertime-geckodriver", 492 "$MOZ_FETCHES_DIR/geckodriver", 493 "--browsertime-chromedriver", 494 "$MOZ_FETCHES_DIR/" + cd_extracted_name["default"], 495 "--browsertime-ffmpeg", 496 "$MOZ_FETCHES_DIR/ffmpeg-n7.1-linux64-gpl-7.1/bin/ffmpeg", 497 ], 498 } 499 } 500 501 extra_options.extend(evaluate_keyed_by(eos, "mozharness.extra-options", task)) 502 503 yield task 504 505 506 def get_mobile_project(task): 507 """Returns the mobile project of the specified task or None.""" 508 509 if not task["build-platform"].startswith("android"): 510 return 511 512 mobile_projects = ("fenix", "geckoview", "refbrow", "chrome-m", "cstm-car-m") 513 514 for name in mobile_projects: 515 if name in task["test-name"]: 516 return name 517 518 target = None 519 if "target" in task: 520 resolve_keyed_by( 521 task, "target", item_name=task["test-name"], enforce_single_match=False 522 ) 523 target = task["target"] 524 if target: 525 if isinstance(target, dict): 526 target = target["name"] 527 528 for name in mobile_projects: 529 if name in target: 530 return name 531 532 return None 533 534 535 @transforms.add 536 def disable_wpt_timeouts_on_autoland(config, tasks): 537 """do not run web-platform-tests that are expected TIMEOUT on autoland""" 538 for task in tasks: 539 if ( 540 "web-platform-tests" in task["test-name"] 541 and config.params["project"] == "autoland" 542 ): 543 resolve_keyed_by( 544 task, 545 "mozharness.extra-options", 546 item_name=task["test-name"], 547 enforce_single_match=False, 548 variant=task["attributes"].get("unittest_variant"), 549 ) 550 551 task["mozharness"].setdefault("extra-options", []).append("--skip-timeout") 552 yield task 553 554 555 @transforms.add 556 def enable_code_coverage(config, tasks): 557 """Enable code coverage for the ccov build-platforms""" 558 for task in tasks: 559 if "ccov" in task["build-platform"]: 560 # Do not run tests on fuzzing builds 561 if "fuzzing" in task["build-platform"]: 562 task["run-on-projects"] = [] 563 continue 564 565 # Skip this transform for android code coverage builds. 566 if "android" in task["build-platform"]: 567 task.setdefault("fetches", {}).setdefault("toolchain", []).append( 568 "linux64-grcov" 569 ) 570 task["mozharness"].setdefault("extra-options", []).append( 571 "--java-code-coverage" 572 ) 573 yield task 574 continue 575 task["mozharness"].setdefault("extra-options", []).append("--code-coverage") 576 task["instance-size"] = "xlarge-noscratch" 577 if "jittest" in task["test-name"]: 578 task["instance-size"] = "xlarge" 579 580 # Temporarily disable Mac tests on mozilla-central 581 if "mac" in task["build-platform"]: 582 task["run-on-projects"] = [] 583 584 # Ensure we always run on the projects defined by the build, unless the test 585 # is try only or shouldn't run at all. 586 if task["run-on-projects"] not in [[]]: 587 task["run-on-projects"] = "built-projects" 588 589 # Ensure we don't optimize test suites out. 590 # We always want to run all test suites for coverage purposes. 591 task.pop("schedules-component", None) 592 task.pop("when", None) 593 task["optimization"] = None 594 595 # Add a toolchain and a fetch task for the grcov binary. 596 if any(p in task["build-platform"] for p in ("linux", "osx", "win")): 597 task.setdefault("fetches", {}) 598 task["fetches"].setdefault("fetch", []) 599 task["fetches"].setdefault("toolchain", []) 600 task["fetches"].setdefault("build", []) 601 602 if "linux" in task["build-platform"]: 603 task["fetches"]["toolchain"].append("linux64-grcov") 604 elif "osx" in task["build-platform"]: 605 task["fetches"]["toolchain"].append("macosx64-grcov") 606 elif "win" in task["build-platform"]: 607 task["fetches"]["toolchain"].append("win64-grcov") 608 609 task["fetches"]["build"].append({"artifact": "target.mozinfo.json"}) 610 611 if "talos" in task["test-name"]: 612 task["max-run-time"] = 7200 613 if "linux" in task["build-platform"]: 614 task["docker-image"] = {"in-tree": "ubuntu1804-test"} 615 task["mozharness"]["extra-options"].append("--add-option") 616 task["mozharness"]["extra-options"].append("--cycles,1") 617 task["mozharness"]["extra-options"].append("--add-option") 618 task["mozharness"]["extra-options"].append("--tppagecycles,1") 619 task["mozharness"]["extra-options"].append("--add-option") 620 task["mozharness"]["extra-options"].append("--no-upload-results") 621 task["mozharness"]["extra-options"].append("--add-option") 622 task["mozharness"]["extra-options"].append("--tptimeout,15000") 623 if "raptor" in task["test-name"]: 624 task["max-run-time"] = 1800 625 yield task 626 627 628 @transforms.add 629 def handle_run_on_projects(config, tasks): 630 """Handle translating `built-projects` appropriately""" 631 for task in tasks: 632 if task["run-on-projects"] == "built-projects": 633 task["run-on-projects"] = task["build-attributes"].get( 634 "run_on_projects", ["all"] 635 ) 636 637 if task.pop("built-projects-only", False): 638 built_projects = set( 639 task["build-attributes"].get("run_on_projects", {"all"}) 640 ) 641 run_on_projects = set(task.get("run-on-projects", set())) 642 643 # If 'all' exists in run-on-projects, then the intersection of both 644 # is built-projects. Similarly if 'all' exists in built-projects, 645 # the intersection is run-on-projects (so do nothing). When neither 646 # contains 'all', take the actual set intersection. 647 if "all" in run_on_projects: 648 task["run-on-projects"] = sorted(built_projects) 649 elif "all" not in built_projects: 650 task["run-on-projects"] = sorted(run_on_projects & built_projects) 651 yield task 652 653 654 @transforms.add 655 def handle_tier(config, tasks): 656 """Set the tier based on policy for all test descriptions that do not 657 specify a tier otherwise.""" 658 for task in tasks: 659 if "tier" in task: 660 resolve_keyed_by( 661 task, 662 "tier", 663 item_name=task["test-name"], 664 variant=task["attributes"].get("unittest_variant"), 665 enforce_single_match=False, 666 ) 667 668 # only override if not set for the test 669 if "tier" not in task or task["tier"] == "default": 670 if task["test-platform"] in [ 671 "linux64/opt", 672 "linux64/debug", 673 "linux64-shippable/opt", 674 "linux64-devedition/opt", 675 "linux64-asan/opt", 676 "linux64-qr/opt", 677 "linux64-qr/debug", 678 "linux64-shippable-qr/opt", 679 "linux1804-64/opt", 680 "linux1804-64/debug", 681 "linux1804-64-shippable/opt", 682 "linux1804-64-devedition/opt", 683 "linux1804-64-qr/opt", 684 "linux1804-64-qr/debug", 685 "linux1804-64-shippable-qr/opt", 686 "linux1804-64-asan-qr/opt", 687 "linux1804-64-tsan-qr/opt", 688 "linux2204-64-wayland/debug", 689 "linux2204-64-wayland/opt", 690 "linux2204-64-wayland-shippable/opt", 691 "linux2404-64/opt", 692 "linux2404-64/debug", 693 "linux2404-64-shippable/opt", 694 "linux2404-64-devedition/opt", 695 "linux2404-64-asan/opt", 696 "linux2404-64-tsan/opt", 697 "windows11-32-24h2/debug", 698 "windows11-32-24h2/opt", 699 "windows11-32-24h2-shippable/opt", 700 "windows11-64-24h2-hw-ref/opt", 701 "windows11-64-24h2-hw-ref-shippable/opt", 702 "windows11-64-24h2/opt", 703 "windows11-64-24h2/debug", 704 "windows11-64-24h2-shippable/opt", 705 "windows11-64-24h2-devedition/opt", 706 "windows11-64-24h2-asan/opt", 707 "macosx1015-64/opt", 708 "macosx1015-64/debug", 709 "macosx1015-64-shippable/opt", 710 "macosx1015-64-devedition/opt", 711 "macosx1015-64-devedition-qr/opt", 712 "macosx1015-64-qr/opt", 713 "macosx1015-64-shippable-qr/opt", 714 "macosx1015-64-qr/debug", 715 "macosx1470-64/opt", 716 "macosx1470-64/debug", 717 "macosx1470-64-shippable/opt", 718 "macosx1470-64-devedition/opt", 719 "macosx1400-64-shippable-qr/opt", 720 "macosx1400-64-qr/debug", 721 "macosx1500-64-shippable/opt", 722 "macosx1500-64/debug", 723 "android-em-14-x86_64-shippable/opt", 724 "android-em-14-x86_64/opt", 725 "android-em-14-x86_64-shippable-lite/opt", 726 "android-em-14-x86_64-lite/opt", 727 "android-em-14-x86_64/debug", 728 "android-em-14-x86_64/debug-isolated-process", 729 "android-em-14-x86-shippable/opt", 730 "android-em-14-x86/opt", 731 ]: 732 task["tier"] = 1 733 else: 734 task["tier"] = 2 735 736 yield task 737 738 739 @transforms.add 740 def apply_raptor_tier_optimization(config, tasks): 741 for task in tasks: 742 if task["suite"] != "raptor": 743 yield task 744 continue 745 746 if "regression-tests" in task["test-name"]: 747 # Don't optimize the regression tests 748 yield task 749 continue 750 751 if not task["test-platform"].startswith("android-hw"): 752 task["optimization"] = {"skip-unless-expanded": None} 753 if task["tier"] > 1: 754 task["optimization"] = {"skip-unless-backstop": None} 755 756 if task["attributes"].get("unittest_variant"): 757 task["tier"] = max(task["tier"], 2) 758 yield task 759 760 761 @transforms.add 762 def disable_try_only_platforms(config, tasks): 763 """Turns off platforms that should only run on try.""" 764 try_only_platforms = () 765 for task in tasks: 766 if any(re.match(k + "$", task["test-platform"]) for k in try_only_platforms): 767 task["run-on-projects"] = [] 768 yield task 769 770 771 @transforms.add 772 def ensure_spi_disabled_on_all_but_spi(config, tasks): 773 for task in tasks: 774 variant = task["attributes"].get("unittest_variant") or "" 775 has_no_setpref = ( 776 "gtest", 777 "cppunit", 778 "jittest", 779 "junit", 780 "raptor", 781 "reftest", 782 "web-platform-tests", 783 ) 784 785 if ( 786 all(s not in task["suite"] for s in has_no_setpref) 787 and "socketprocess" not in variant 788 ): 789 task["mozharness"]["extra-options"].append( 790 "--setpref=media.peerconnection.mtransport_process=false" 791 ) 792 task["mozharness"]["extra-options"].append( 793 "--setpref=network.process.enabled=false" 794 ) 795 796 yield task 797 798 799 test_setting_description_schema = Schema( 800 { 801 Required("_hash"): str, 802 "platform": { 803 Required("arch"): Any("32", "64", "aarch64", "arm7", "x86", "x86_64"), 804 Required("os"): { 805 Required("name"): Any("android", "linux", "macosx", "windows"), 806 Required("version"): str, 807 Optional("build"): str, 808 }, 809 Optional("device"): str, 810 Optional("display"): "wayland", 811 Optional("machine"): "hw-ref", 812 }, 813 "build": { 814 Required("type"): Any("opt", "debug", "debug-isolated-process"), 815 Any( 816 "asan", 817 "ccov", 818 "clang-trunk", 819 "devedition", 820 "lite", 821 "mingwclang", 822 "nightlyasrelease", 823 "shippable", 824 "tsan", 825 ): bool, 826 }, 827 "runtime": {Any(*list(TEST_VARIANTS.keys()) + ["1proc"]): bool}, 828 }, 829 check=False, 830 ) 831 """Schema test settings must conform to. Validated by 832 :py:func:`~test.test_mozilla_central.test_test_setting`""" 833 834 835 @transforms.add 836 def set_test_setting(config, tasks): 837 """A test ``setting`` is the set of configuration that uniquely 838 distinguishes a test task from other tasks that run the same suite 839 (ignoring chunks). 840 841 There are three different types of information that make up a setting: 842 843 1. Platform - Information describing the underlying platform tests run on, 844 e.g, OS, CPU architecture, etc. 845 846 2. Build - Information describing the build being tested, e.g build type, 847 ccov, asan/tsan, etc. 848 849 3. Runtime - Information describing which runtime parameters are enabled, 850 e.g, prefs, environment variables, etc. 851 852 This transform adds a ``test-setting`` object to the ``extra`` portion of 853 all test tasks, of the form: 854 855 .. code-block:: 856 857 { 858 "platform": { ... }, 859 "build": { ... }, 860 "runtime": { ... } 861 } 862 863 This information could be derived from the label, but consuming this 864 object is less brittle. 865 """ 866 # Some attributes have a dash in them which complicates parsing. Ensure we 867 # don't split them up. 868 # TODO Rename these so they don't have a dash. 869 dash_attrs = [ 870 "clang-trunk", 871 "hw-ref", 872 ] 873 dash_token = "%D%" 874 platform_re = re.compile(r"(\D+)(\d*)") 875 876 for task in tasks: 877 setting = { 878 "platform": { 879 "os": {}, 880 }, 881 "build": {}, 882 "runtime": {}, 883 } 884 885 # parse platform and build information out of 'test-platform' 886 platform, build_type = task["test-platform"].split("/", 1) 887 888 # ensure dashed attributes don't get split up 889 for attr in dash_attrs: 890 if attr in platform: 891 platform = platform.replace(attr, attr.replace("-", dash_token)) 892 893 parts = platform.split("-") 894 895 # restore dashes now that split is finished 896 for i, part in enumerate(parts): 897 if dash_token in part: 898 parts[i] = part.replace(dash_token, "-") 899 900 match = platform_re.match(parts.pop(0)) 901 assert match 902 os_name, os_version = match.groups() 903 904 device = machine = os_build = display = None 905 if os_name == "android": 906 device = parts.pop(0) 907 if device == "hw": 908 device = parts.pop(0) 909 else: 910 device = "emulator" 911 912 os_version = parts.pop(0) 913 if parts[0].isdigit(): 914 os_version = f"{os_version}.{parts.pop(0)}" 915 916 if parts[0] == "android": 917 parts.pop(0) 918 919 arch = parts.pop(0) 920 921 else: 922 arch = parts.pop(0) 923 if parts and (parts[0].isdigit() or parts[0] in ["24h2"]): 924 os_build = parts.pop(0) 925 926 if parts and parts[0] == "hw-ref": 927 machine = parts.pop(0) 928 929 if parts and parts[0] == "wayland": 930 display = parts.pop(0) 931 932 if parts and parts[0] == "aarch64": 933 arch = parts.pop(0) 934 935 # It's not always possible to glean the exact architecture used from 936 # the task, so sometimes this will just be set to "32" or "64". 937 setting["platform"]["arch"] = arch 938 setting["platform"]["os"] = { 939 "name": os_name, 940 "version": os_version, 941 } 942 943 if os_build: 944 setting["platform"]["os"]["build"] = os_build 945 946 if device: 947 setting["platform"]["device"] = device 948 949 if machine: 950 setting["platform"]["machine"] = machine 951 952 if display: 953 setting["platform"]["display"] = display 954 955 # parse remaining parts as build attributes 956 setting["build"]["type"] = build_type 957 while parts: 958 attr = parts.pop(0) 959 if attr == "qr": 960 # all tasks are webrender now, no need to store it 961 continue 962 963 setting["build"][attr] = True 964 965 unittest_variant = task["attributes"].get("unittest_variant") 966 if unittest_variant: 967 for variant in unittest_variant.split("+"): 968 setting["runtime"][variant] = True 969 970 # add a hash of the setting object for easy comparisons 971 setting["_hash"] = hashlib.sha256( 972 json.dumps(setting, sort_keys=True).encode("utf-8") 973 ).hexdigest()[:12] 974 975 task["test-setting"] = ReadOnlyDict(**setting) 976 yield task 977 978 979 @transforms.add 980 def allow_software_gl_layers(config, tasks): 981 """ 982 Handle the "allow-software-gl-layers" property for platforms where it 983 applies. 984 """ 985 for task in tasks: 986 if task.get("allow-software-gl-layers"): 987 # This should be set always once bug 1296086 is resolved. 988 task["mozharness"].setdefault("extra-options", []).append( 989 "--allow-software-gl-layers" 990 ) 991 992 yield task 993 994 995 @transforms.add 996 def enable_webrender(config, tasks): 997 """ 998 Handle the "webrender" property by passing a flag to mozharness if it is 999 enabled. 1000 """ 1001 for task in tasks: 1002 # TODO: this was all conditionally in enable_webrender- do we still need this? 1003 extra_options = task["mozharness"].setdefault("extra-options", []) 1004 # We only want to 'setpref' on tests that have a profile 1005 if not task["attributes"]["unittest_category"] in [ 1006 "cppunittest", 1007 "geckoview-junit", 1008 "gtest", 1009 "jittest", 1010 "raptor", 1011 ]: 1012 extra_options.append("--setpref=layers.d3d11.enable-blacklist=false") 1013 1014 yield task 1015 1016 1017 @transforms.add 1018 def set_schedules_for_webrender_android(config, tasks): 1019 """android-hw has limited resources, we need webrender on phones""" 1020 for task in tasks: 1021 if task["suite"] in ["crashtest", "reftest"] and task[ 1022 "test-platform" 1023 ].startswith("android-hw"): 1024 task["schedules-component"] = "android-hw-gfx" 1025 yield task 1026 1027 1028 @transforms.add 1029 def set_retry_exit_status(config, tasks): 1030 """Set the retry exit status to TBPL_RETRY, the value returned by mozharness 1031 scripts to indicate a transient failure that should be retried.""" 1032 for task in tasks: 1033 task["retry-exit-status"] = [4] 1034 yield task 1035 1036 1037 @transforms.add 1038 def set_profile(config, tasks): 1039 """Set profiling mode for tests.""" 1040 ttconfig = config.params["try_task_config"] 1041 profile = ttconfig.get("gecko-profile", False) 1042 settings = ( 1043 "gecko-profile-interval", 1044 "gecko-profile-entries", 1045 "gecko-profile-threads", 1046 "gecko-profile-features", 1047 ) 1048 1049 for task in tasks: 1050 if profile and task["suite"] in ["talos", "raptor"]: 1051 extras = task["mozharness"]["extra-options"] 1052 extras.append("--gecko-profile") 1053 for setting in settings: 1054 value = ttconfig.get(setting) 1055 if value is not None: 1056 # These values can contain spaces (eg the "DOM Worker" 1057 # thread) and the command is constructed in different, 1058 # incompatible ways on different platforms. 1059 1060 if task["test-platform"].startswith("win"): 1061 # Double quotes for Windows (single won't work). 1062 extras.append("--" + setting + '="' + str(value) + '"') 1063 else: 1064 # Other platforms keep things as separate values, 1065 # rather than joining with spaces. 1066 extras.append("--" + setting + "=" + str(value)) 1067 1068 yield task 1069 1070 1071 @transforms.add 1072 def add_gecko_profile_symbolication_deps(config, tasks): 1073 """Add symbolication dependencies when profiling raptor, talos, or mochitest tests""" 1074 1075 try_task_config = config.params.get("try_task_config", {}) 1076 gecko_profile = try_task_config.get("gecko-profile", False) 1077 env = try_task_config.get("env", {}) 1078 startup_profile = env.get("MOZ_PROFILER_STARTUP") == "1" 1079 1080 for task in tasks: 1081 if (gecko_profile and task["suite"] in ["talos", "raptor"]) or ( 1082 startup_profile and "mochitest" in task["suite"] 1083 ): 1084 fetches = task.setdefault("fetches", {}) 1085 fetch_toolchains = fetches.setdefault("toolchain", []) 1086 fetch_toolchains.append("symbolicator-cli") 1087 1088 test_platform = task["test-platform"] 1089 1090 if "macosx" in test_platform and "aarch64" in test_platform: 1091 fetch_toolchains.append("macosx64-aarch64-samply") 1092 elif "macosx" in test_platform: 1093 fetch_toolchains.append("macosx64-samply") 1094 elif "win" in test_platform: 1095 fetch_toolchains.append("win64-samply") 1096 else: 1097 fetch_toolchains.append("linux64-samply") 1098 1099 # Add node as a dependency for talos and mochitest tasks if needed. 1100 # node is used to run symbolicator-cli, our profile symbolication tool 1101 if task["suite"] == "talos" or "mochitest" in task["suite"]: 1102 if "macosx" in test_platform and "aarch64" in test_platform: 1103 node_toolchain = "macosx64-aarch64-node" 1104 elif "macosx" in test_platform: 1105 node_toolchain = "macosx64-node" 1106 elif "win" in test_platform: 1107 node_toolchain = "win64-node" 1108 else: 1109 node_toolchain = "linux64-node" 1110 1111 if node_toolchain not in fetch_toolchains: 1112 fetch_toolchains.append(node_toolchain) 1113 1114 yield task 1115 1116 1117 @transforms.add 1118 def set_tag(config, tasks): 1119 """Set test for a specific tag.""" 1120 tag = None 1121 for task in tasks: 1122 if tag: 1123 task["mozharness"]["extra-options"].extend(["--tag", tag]) 1124 yield task 1125 1126 1127 @transforms.add 1128 def set_test_type(config, tasks): 1129 types = ["mochitest", "reftest", "talos", "raptor", "geckoview-junit", "gtest"] 1130 for task in tasks: 1131 for test_type in types: 1132 if test_type in task["suite"] and "web-platform" not in task["suite"]: 1133 task.setdefault("tags", {})["test-type"] = test_type 1134 yield task 1135 1136 1137 @transforms.add 1138 def set_schedules_components(config, tasks): 1139 for task in tasks: 1140 if "optimization" in task or "when" in task: 1141 yield task 1142 continue 1143 1144 category = task["attributes"]["unittest_category"] 1145 schedules = task.get("schedules-component", category) 1146 if isinstance(schedules, str): 1147 schedules = [schedules] 1148 1149 schedules = set(schedules) 1150 if schedules & set(INCLUSIVE_COMPONENTS): 1151 # if this is an "inclusive" test, then all files which might 1152 # cause it to run are annotated with SCHEDULES in moz.build, 1153 # so do not include the platform or any other components here 1154 task["schedules-component"] = sorted(schedules) 1155 yield task 1156 continue 1157 1158 schedules.add(category) 1159 schedules.add(platform_family(task["build-platform"])) 1160 schedules.add("firefox") 1161 1162 task["schedules-component"] = sorted(schedules) 1163 yield task 1164 1165 1166 @transforms.add 1167 def enable_parallel_marking_in_tsan_tests(config, tasks): 1168 """Enable parallel marking in TSAN tests""" 1169 skip_list = ["cppunittest", "gtest"] 1170 for task in tasks: 1171 if "-tsan-" in task["test-platform"]: 1172 if task["suite"] not in skip_list: 1173 extra_options = task["mozharness"].setdefault("extra-options", []) 1174 extra_options.append( 1175 "--setpref=javascript.options.mem.gc_parallel_marking=true" 1176 ) 1177 1178 yield task 1179 1180 1181 @transforms.add 1182 def set_webgpu_ignore_blocklist(config, tasks): 1183 """ 1184 Ignore the WebGPU blocklist on Linux because CI's Mesa is old 1185 1186 See <https://bugzilla.mozilla.org/show_bug.cgi?id=1985348> 1187 """ 1188 for task in tasks: 1189 if "web-platform-tests-webgpu" in task["test-name"] and task[ 1190 "test-platform" 1191 ].startswith("linux"): 1192 extra_options = task["mozharness"].setdefault("extra-options", []) 1193 extra_options.append("--setpref=gfx.webgpu.ignore-blocklist=true") 1194 1195 yield task