diff options
Diffstat (limited to '')
-rw-r--r-- | taskcluster/gecko_taskgraph/transforms/test/other.py | 1107 |
1 files changed, 1107 insertions, 0 deletions
diff --git a/taskcluster/gecko_taskgraph/transforms/test/other.py b/taskcluster/gecko_taskgraph/transforms/test/other.py new file mode 100644 index 0000000000..dc258ef97a --- /dev/null +++ b/taskcluster/gecko_taskgraph/transforms/test/other.py @@ -0,0 +1,1107 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import hashlib +import json +import re + +from mozbuild.schedules import INCLUSIVE_COMPONENTS +from mozbuild.util import ReadOnlyDict +from taskgraph.transforms.base import TransformSequence +from taskgraph.util.attributes import keymatch +from taskgraph.util.keyed_by import evaluate_keyed_by +from taskgraph.util.schema import Schema, resolve_keyed_by +from taskgraph.util.taskcluster import get_artifact_path, get_index_url +from voluptuous import Any, Optional, Required + +from gecko_taskgraph.transforms.test.variant import TEST_VARIANTS +from gecko_taskgraph.util.platforms import platform_family +from gecko_taskgraph.util.templates import merge + +transforms = TransformSequence() + + +@transforms.add +def limit_platforms(config, tasks): + for task in tasks: + if not task["limit-platforms"]: + yield task + continue + + limited_platforms = {key: key for key in task["limit-platforms"]} + if keymatch(limited_platforms, task["test-platform"]): + yield task + + +@transforms.add +def handle_suite_category(config, tasks): + for task in tasks: + task.setdefault("suite", {}) + + if isinstance(task["suite"], str): + task["suite"] = {"name": task["suite"]} + + suite = task["suite"].setdefault("name", task["test-name"]) + category = task["suite"].setdefault("category", suite) + + task.setdefault("attributes", {}) + task["attributes"]["unittest_suite"] = suite + task["attributes"]["unittest_category"] = category + + script = task["mozharness"]["script"] + category_arg = None + if suite.startswith("test-verify") or suite.startswith("test-coverage"): + pass + elif script in ("android_emulator_unittest.py", "android_hardware_unittest.py"): + category_arg = "--test-suite" + elif script == "desktop_unittest.py": + category_arg = f"--{category}-suite" + + if category_arg: + task["mozharness"].setdefault("extra-options", []) + extra = task["mozharness"]["extra-options"] + if not any(arg.startswith(category_arg) for arg in extra): + extra.append(f"{category_arg}={suite}") + + # From here on out we only use the suite name. + task["suite"] = suite + + # in the future we might need to refactor new-test-config to be suite specific + if "mochitest" in task["suite"] and config.params["try_task_config"].get( + "new-test-config", False + ): + task = merge( + task, {"mozharness": {"extra-options": ["--restartAfterFailure"]}} + ) + yield task + + +@transforms.add +def setup_talos(config, tasks): + """Add options that are specific to talos jobs (identified by suite=talos)""" + for task in tasks: + if task["suite"] != "talos": + yield task + continue + + extra_options = task.setdefault("mozharness", {}).setdefault( + "extra-options", [] + ) + extra_options.append("--use-talos-json") + + # win7 needs to test skip + if task["build-platform"].startswith("win32"): + extra_options.append("--add-option") + extra_options.append("--setpref,gfx.direct2d.disabled=true") + + if config.params.get("project", None): + extra_options.append("--project=%s" % config.params["project"]) + + yield task + + +@transforms.add +def setup_browsertime_flag(config, tasks): + """Optionally add `--browsertime` flag to Raptor pageload tests.""" + + browsertime_flag = config.params["try_task_config"].get("browsertime", False) + + for task in tasks: + if not browsertime_flag or task["suite"] != "raptor": + yield task + continue + + if task["treeherder-symbol"].startswith("Rap"): + # The Rap group is subdivided as Rap{-fenix,-refbrow(...), + # so `taskgraph.util.treeherder.replace_group` isn't appropriate. + task["treeherder-symbol"] = task["treeherder-symbol"].replace( + "Rap", "Btime", 1 + ) + + extra_options = task.setdefault("mozharness", {}).setdefault( + "extra-options", [] + ) + extra_options.append("--browsertime") + + yield task + + +@transforms.add +def handle_artifact_prefix(config, tasks): + """Handle translating `artifact_prefix` appropriately""" + for task in tasks: + if task["build-attributes"].get("artifact_prefix"): + task.setdefault("attributes", {}).setdefault( + "artifact_prefix", task["build-attributes"]["artifact_prefix"] + ) + yield task + + +@transforms.add +def set_treeherder_machine_platform(config, tasks): + """Set the appropriate task.extra.treeherder.machine.platform""" + translation = { + # Linux64 build platform for asan is specified differently to + # treeherder. + "macosx1100-64/opt": "osx-1100/opt", + "macosx1100-64-shippable/opt": "osx-1100-shippable/opt", + "macosx1400-64/opt": "osx-1300/opt", + "macosx1400-64-shippable/opt": "osx-1400-shippable/opt", + "win64-asan/opt": "windows10-64/asan", + "win64-aarch64/opt": "windows10-aarch64/opt", + } + for task in tasks: + # For most desktop platforms, the above table is not used for "regular" + # builds, so we'll always pick the test platform here. + # On macOS though, the regular builds are in the table. This causes a + # conflict in `verify_task_graph_symbol` once you add a new test + # platform based on regular macOS builds, such as for QR. + # Since it's unclear if the regular macOS builds can be removed from + # the table, workaround the issue for QR. + if "android" in task["test-platform"] and "pgo/opt" in task["test-platform"]: + platform_new = task["test-platform"].replace("-pgo/opt", "/pgo") + task["treeherder-machine-platform"] = platform_new + elif "android-em-7.0-x86_64-qr" in task["test-platform"]: + task["treeherder-machine-platform"] = task["test-platform"].replace( + ".", "-" + ) + elif "android-em-7.0-x86_64-shippable-qr" in task["test-platform"]: + task["treeherder-machine-platform"] = task["test-platform"].replace( + ".", "-" + ) + elif "android-em-7.0-x86_64-lite-qr" in task["test-platform"]: + task["treeherder-machine-platform"] = task["test-platform"].replace( + ".", "-" + ) + elif "android-em-7.0-x86_64-shippable-lite-qr" in task["test-platform"]: + task["treeherder-machine-platform"] = task["test-platform"].replace( + ".", "-" + ) + elif "android-em-7.0-x86-qr" in task["test-platform"]: + task["treeherder-machine-platform"] = task["test-platform"].replace( + ".", "-" + ) + elif "-qr" in task["test-platform"]: + task["treeherder-machine-platform"] = task["test-platform"] + elif "android-hw" in task["test-platform"]: + task["treeherder-machine-platform"] = task["test-platform"] + elif "android-em-7.0-x86_64" in task["test-platform"]: + task["treeherder-machine-platform"] = task["test-platform"].replace( + ".", "-" + ) + elif "android-em-7.0-x86" in task["test-platform"]: + task["treeherder-machine-platform"] = task["test-platform"].replace( + ".", "-" + ) + # Bug 1602863 - must separately define linux64/asan and linux1804-64/asan + # otherwise causes an exception during taskgraph generation about + # duplicate treeherder platform/symbol. + elif "linux64-asan/opt" in task["test-platform"]: + task["treeherder-machine-platform"] = "linux64/asan" + elif "linux1804-asan/opt" in task["test-platform"]: + task["treeherder-machine-platform"] = "linux1804-64/asan" + else: + task["treeherder-machine-platform"] = translation.get( + task["build-platform"], task["test-platform"] + ) + yield task + + +@transforms.add +def set_download_symbols(config, tasks): + """In general, we download symbols immediately for debug builds, but only + on demand for everything else. ASAN builds shouldn't download + symbols since they don't product symbol zips see bug 1283879""" + for task in tasks: + if task["test-platform"].split("/")[-1] == "debug": + task["mozharness"]["download-symbols"] = True + elif "asan" in task["build-platform"] or "tsan" in task["build-platform"]: + if "download-symbols" in task["mozharness"]: + del task["mozharness"]["download-symbols"] + else: + task["mozharness"]["download-symbols"] = "ondemand" + yield task + + +@transforms.add +def handle_keyed_by(config, tasks): + """Resolve fields that can be keyed by platform, etc.""" + fields = [ + "instance-size", + "docker-image", + "max-run-time", + "chunks", + "suite", + "run-on-projects", + "os-groups", + "run-as-administrator", + "workdir", + "worker-type", + "virtualization", + "fetches.fetch", + "fetches.toolchain", + "target", + "webrender-run-on-projects", + "mozharness.requires-signed-builds", + "build-signing-label", + ] + for task in tasks: + for field in fields: + resolve_keyed_by( + task, + field, + item_name=task["test-name"], + enforce_single_match=False, + project=config.params["project"], + variant=task["attributes"].get("unittest_variant"), + ) + yield task + + +@transforms.add +def set_target(config, tasks): + for task in tasks: + build_platform = task["build-platform"] + target = None + if "target" in task: + target = task["target"] + if not target: + if build_platform.startswith("macosx"): + target = "target.dmg" + elif build_platform.startswith("android"): + target = "target.apk" + elif build_platform.startswith("win"): + target = "target.zip" + else: + target = "target.tar.bz2" + + if isinstance(target, dict): + # TODO Remove hardcoded mobile artifact prefix + index_url = get_index_url(target["index"]) + installer_url = "{}/artifacts/public/{}".format(index_url, target["name"]) + task["mozharness"]["installer-url"] = installer_url + else: + task["mozharness"]["build-artifact-name"] = get_artifact_path(task, target) + + yield task + + +@transforms.add +def setup_browsertime(config, tasks): + """Configure browsertime dependencies for Raptor pageload tests that have + `--browsertime` extra option.""" + + for task in tasks: + # We need to make non-trivial changes to various fetches, and our + # `by-test-platform` may not be "compatible" with existing + # `by-test-platform` filters. Therefore we do everything after + # `handle_keyed_by` so that existing fields have been resolved down to + # simple lists. But we use the `by-test-platform` machinery to express + # filters so that when the time comes to move browsertime into YAML + # files, the transition is straight-forward. + extra_options = task.get("mozharness", {}).get("extra-options", []) + + if task["suite"] != "raptor": + yield task + continue + + ts = { + "by-test-platform": { + "android.*": ["browsertime", "linux64-geckodriver", "linux64-node-16"], + "linux.*": ["browsertime", "linux64-geckodriver", "linux64-node-16"], + "macosx1015.*": [ + "browsertime", + "macosx64-geckodriver", + "macosx64-node-16", + ], + "macosx1400.*": [ + "browsertime", + "macosx64-aarch64-geckodriver", + "macosx64-aarch64-node-16", + ], + "windows.*aarch64.*": [ + "browsertime", + "win32-geckodriver", + "win32-node-16", + ], + "windows.*-32.*": ["browsertime", "win32-geckodriver", "win32-node-16"], + "windows.*-64.*": ["browsertime", "win64-geckodriver", "win64-node-16"], + }, + } + + task.setdefault("fetches", {}).setdefault("toolchain", []).extend( + evaluate_keyed_by(ts, "fetches.toolchain", task) + ) + + fs = { + "by-test-platform": { + "android.*": ["linux64-ffmpeg-4.4.1"], + "linux.*": ["linux64-ffmpeg-4.4.1"], + "macosx1015.*": ["mac64-ffmpeg-4.4.1"], + "macosx1400.*": ["mac64-ffmpeg-4.4.1"], + "windows.*aarch64.*": ["win64-ffmpeg-4.4.1"], + "windows.*-32.*": ["win64-ffmpeg-4.4.1"], + "windows.*-64.*": ["win64-ffmpeg-4.4.1"], + }, + } + + cd_fetches = { + "android.*": [ + "linux64-chromedriver-120", + "linux64-chromedriver-121", + "linux64-chromedriver-122", + ], + "linux.*": [ + "linux64-chromedriver-120", + "linux64-chromedriver-121", + "linux64-chromedriver-122", + ], + "macosx1015.*": [ + "mac64-chromedriver-120", + "mac64-chromedriver-121", + "mac64-chromedriver-122", + ], + "macosx1400.*": [ + "mac-arm-chromedriver-120", + "mac-arm-chromedriver-121", + "mac-arm-chromedriver-122", + ], + "windows.*aarch64.*": [ + "win32-chromedriver-120", + "win32-chromedriver-121", + "win32-chromedriver-122", + ], + "windows.*-32.*": [ + "win32-chromedriver-120", + "win32-chromedriver-121", + "win32-chromedriver-122", + ], + "windows.*-64.*": [ + "win32-chromedriver-120", + "win32-chromedriver-121", + "win32-chromedriver-122", + ], + } + + chromium_fetches = { + "linux.*": ["linux64-chromium"], + "macosx1015.*": ["mac-chromium"], + "macosx1400.*": ["mac-chromium-arm"], + "windows.*aarch64.*": ["win32-chromium"], + "windows.*-32.*": ["win32-chromium"], + "windows.*-64.*": ["win64-chromium"], + "android.*": ["linux64-chromium"], + } + + cd_extracted_name = { + "windows": "{}chromedriver.exe", + "mac": "{}chromedriver", + "default": "{}chromedriver", + } + + if "--app=chrome" in extra_options or "--app=chrome-m" in extra_options: + # Only add the chromedriver fetches when chrome is running + for platform in cd_fetches: + fs["by-test-platform"][platform].extend(cd_fetches[platform]) + if ( + "--app=chromium" in extra_options + or "--app=custom-car" in extra_options + or "--app=cstm-car-m" in extra_options + ): + for platform in chromium_fetches: + fs["by-test-platform"][platform].extend(chromium_fetches[platform]) + + # The chromedrivers for chromium are repackaged into the archives + # that we get the chromium binary from so we always have a compatible + # version. + cd_extracted_name = { + "windows": "chrome-win/chromedriver.exe", + "mac": "chrome-mac/chromedriver", + "default": "chrome-linux/chromedriver", + } + + # Disable the Raptor install step + if "--app=chrome-m" in extra_options or "--app=cstm-car-m" in extra_options: + extra_options.append("--noinstall") + + task.setdefault("fetches", {}).setdefault("fetch", []).extend( + evaluate_keyed_by(fs, "fetches.fetch", task) + ) + + extra_options.extend( + ( + "--browsertime-browsertimejs", + "$MOZ_FETCHES_DIR/browsertime/node_modules/browsertime/bin/browsertime.js", + ) + ) # noqa: E501 + + eos = { + "by-test-platform": { + "windows.*": [ + "--browsertime-node", + "$MOZ_FETCHES_DIR/node/node.exe", + "--browsertime-geckodriver", + "$MOZ_FETCHES_DIR/geckodriver.exe", + "--browsertime-chromedriver", + "$MOZ_FETCHES_DIR/" + cd_extracted_name["windows"], + "--browsertime-ffmpeg", + "$MOZ_FETCHES_DIR/ffmpeg-4.4.1-full_build/bin/ffmpeg.exe", + ], + "macosx.*": [ + "--browsertime-node", + "$MOZ_FETCHES_DIR/node/bin/node", + "--browsertime-geckodriver", + "$MOZ_FETCHES_DIR/geckodriver", + "--browsertime-chromedriver", + "$MOZ_FETCHES_DIR/" + cd_extracted_name["mac"], + "--browsertime-ffmpeg", + "$MOZ_FETCHES_DIR/ffmpeg-macos/ffmpeg", + ], + "default": [ + "--browsertime-node", + "$MOZ_FETCHES_DIR/node/bin/node", + "--browsertime-geckodriver", + "$MOZ_FETCHES_DIR/geckodriver", + "--browsertime-chromedriver", + "$MOZ_FETCHES_DIR/" + cd_extracted_name["default"], + "--browsertime-ffmpeg", + "$MOZ_FETCHES_DIR/ffmpeg-4.4.1-i686-static/ffmpeg", + ], + } + } + + extra_options.extend(evaluate_keyed_by(eos, "mozharness.extra-options", task)) + + yield task + + +def get_mobile_project(task): + """Returns the mobile project of the specified task or None.""" + + if not task["build-platform"].startswith("android"): + return + + mobile_projects = ("fenix", "geckoview", "refbrow", "chrome-m", "cstm-car-m") + + for name in mobile_projects: + if name in task["test-name"]: + return name + + target = None + if "target" in task: + resolve_keyed_by( + task, "target", item_name=task["test-name"], enforce_single_match=False + ) + target = task["target"] + if target: + if isinstance(target, dict): + target = target["name"] + + for name in mobile_projects: + if name in target: + return name + + return None + + +@transforms.add +def disable_wpt_timeouts_on_autoland(config, tasks): + """do not run web-platform-tests that are expected TIMEOUT on autoland""" + for task in tasks: + if ( + "web-platform-tests" in task["test-name"] + and config.params["project"] == "autoland" + ): + task["mozharness"].setdefault("extra-options", []).append("--skip-timeout") + yield task + + +@transforms.add +def enable_code_coverage(config, tasks): + """Enable code coverage for the ccov build-platforms""" + for task in tasks: + if "ccov" in task["build-platform"]: + # Do not run tests on fuzzing builds + if "fuzzing" in task["build-platform"]: + task["run-on-projects"] = [] + continue + + # Skip this transform for android code coverage builds. + if "android" in task["build-platform"]: + task.setdefault("fetches", {}).setdefault("toolchain", []).append( + "linux64-grcov" + ) + task["mozharness"].setdefault("extra-options", []).append( + "--java-code-coverage" + ) + yield task + continue + task["mozharness"].setdefault("extra-options", []).append("--code-coverage") + task["instance-size"] = "xlarge" + + # Temporarily disable Mac tests on mozilla-central + if "mac" in task["build-platform"]: + task["run-on-projects"] = [] + + # Ensure we always run on the projects defined by the build, unless the test + # is try only or shouldn't run at all. + if task["run-on-projects"] not in [[]]: + task["run-on-projects"] = "built-projects" + + # Ensure we don't optimize test suites out. + # We always want to run all test suites for coverage purposes. + task.pop("schedules-component", None) + task.pop("when", None) + task["optimization"] = None + + # Add a toolchain and a fetch task for the grcov binary. + if any(p in task["build-platform"] for p in ("linux", "osx", "win")): + task.setdefault("fetches", {}) + task["fetches"].setdefault("fetch", []) + task["fetches"].setdefault("toolchain", []) + task["fetches"].setdefault("build", []) + + if "linux" in task["build-platform"]: + task["fetches"]["toolchain"].append("linux64-grcov") + elif "osx" in task["build-platform"]: + task["fetches"]["toolchain"].append("macosx64-grcov") + elif "win" in task["build-platform"]: + task["fetches"]["toolchain"].append("win64-grcov") + + task["fetches"]["build"].append({"artifact": "target.mozinfo.json"}) + + if "talos" in task["test-name"]: + task["max-run-time"] = 7200 + if "linux" in task["build-platform"]: + task["docker-image"] = {"in-tree": "ubuntu1804-test"} + task["mozharness"]["extra-options"].append("--add-option") + task["mozharness"]["extra-options"].append("--cycles,1") + task["mozharness"]["extra-options"].append("--add-option") + task["mozharness"]["extra-options"].append("--tppagecycles,1") + task["mozharness"]["extra-options"].append("--add-option") + task["mozharness"]["extra-options"].append("--no-upload-results") + task["mozharness"]["extra-options"].append("--add-option") + task["mozharness"]["extra-options"].append("--tptimeout,15000") + if "raptor" in task["test-name"]: + task["max-run-time"] = 1800 + yield task + + +@transforms.add +def handle_run_on_projects(config, tasks): + """Handle translating `built-projects` appropriately""" + for task in tasks: + if task["run-on-projects"] == "built-projects": + task["run-on-projects"] = task["build-attributes"].get( + "run_on_projects", ["all"] + ) + + if task.pop("built-projects-only", False): + built_projects = set( + task["build-attributes"].get("run_on_projects", {"all"}) + ) + run_on_projects = set(task.get("run-on-projects", set())) + + # If 'all' exists in run-on-projects, then the intersection of both + # is built-projects. Similarly if 'all' exists in built-projects, + # the intersection is run-on-projects (so do nothing). When neither + # contains 'all', take the actual set intersection. + if "all" in run_on_projects: + task["run-on-projects"] = sorted(built_projects) + elif "all" not in built_projects: + task["run-on-projects"] = sorted(run_on_projects & built_projects) + yield task + + +@transforms.add +def handle_tier(config, tasks): + """Set the tier based on policy for all test descriptions that do not + specify a tier otherwise.""" + for task in tasks: + if "tier" in task: + resolve_keyed_by( + task, + "tier", + item_name=task["test-name"], + variant=task["attributes"].get("unittest_variant"), + enforce_single_match=False, + ) + + # only override if not set for the test + if "tier" not in task or task["tier"] == "default": + if task["test-platform"] in [ + "linux64/opt", + "linux64/debug", + "linux64-shippable/opt", + "linux64-devedition/opt", + "linux64-asan/opt", + "linux64-qr/opt", + "linux64-qr/debug", + "linux64-shippable-qr/opt", + "linux1804-64/opt", + "linux1804-64/debug", + "linux1804-64-shippable/opt", + "linux1804-64-devedition/opt", + "linux1804-64-qr/opt", + "linux1804-64-qr/debug", + "linux1804-64-shippable-qr/opt", + "linux1804-64-asan-qr/opt", + "linux1804-64-tsan-qr/opt", + "windows10-32-qr/debug", + "windows10-32-qr/opt", + "windows10-32-shippable-qr/opt", + "windows10-32-2004-qr/debug", + "windows10-32-2004-qr/opt", + "windows10-32-2004-shippable-qr/opt", + "windows10-aarch64-qr/opt", + "windows10-64/debug", + "windows10-64/opt", + "windows10-64-shippable/opt", + "windows10-64-devedition/opt", + "windows10-64-qr/opt", + "windows10-64-qr/debug", + "windows10-64-shippable-qr/opt", + "windows10-64-devedition-qr/opt", + "windows10-64-asan-qr/opt", + "windows10-64-2004-qr/opt", + "windows10-64-2004-qr/debug", + "windows10-64-2004-shippable-qr/opt", + "windows10-64-2004-devedition-qr/opt", + "windows10-64-2004-asan-qr/opt", + "windows11-32-2009-qr/debug", + "windows11-32-2009-qr/opt", + "windows11-32-2009-shippable-qr/opt", + "windows11-64-2009-qr/opt", + "windows11-64-2009-qr/debug", + "windows11-64-2009-shippable-qr/opt", + "windows11-64-2009-devedition-qr/opt", + "windows11-64-2009-asan-qr/opt", + "macosx1015-64/opt", + "macosx1015-64/debug", + "macosx1015-64-shippable/opt", + "macosx1015-64-devedition/opt", + "macosx1015-64-devedition-qr/opt", + "macosx1015-64-qr/opt", + "macosx1015-64-shippable-qr/opt", + "macosx1015-64-qr/debug", + "macosx1100-64-shippable-qr/opt", + "macosx1100-64-qr/debug", + "macosx1400-64-shippable-qr/opt", + "macosx1400-64-qr/debug", + "android-em-7.0-x86_64-shippable/opt", + "android-em-7.0-x86_64-shippable-lite/opt", + "android-em-7.0-x86_64/debug", + "android-em-7.0-x86_64/debug-isolated-process", + "android-em-7.0-x86_64/opt", + "android-em-7.0-x86_64-lite/opt", + "android-em-7.0-x86-shippable/opt", + "android-em-7.0-x86-shippable-lite/opt", + "android-em-7.0-x86_64-shippable-qr/opt", + "android-em-7.0-x86_64-qr/debug", + "android-em-7.0-x86_64-qr/debug-isolated-process", + "android-em-7.0-x86_64-qr/opt", + "android-em-7.0-x86_64-shippable-lite-qr/opt", + "android-em-7.0-x86_64-lite-qr/debug", + "android-em-7.0-x86_64-lite-qr/opt", + ]: + task["tier"] = 1 + else: + task["tier"] = 2 + + yield task + + +@transforms.add +def apply_raptor_tier_optimization(config, tasks): + for task in tasks: + if task["suite"] != "raptor": + yield task + continue + + if "regression-tests" in task["test-name"]: + # Don't optimize the regression tests + yield task + continue + + if not task["test-platform"].startswith("android-hw"): + task["optimization"] = {"skip-unless-expanded": None} + if task["tier"] > 1: + task["optimization"] = {"skip-unless-backstop": None} + + if task["attributes"].get("unittest_variant"): + task["tier"] = max(task["tier"], 2) + yield task + + +@transforms.add +def disable_try_only_platforms(config, tasks): + """Turns off platforms that should only run on try.""" + try_only_platforms = () + for task in tasks: + if any(re.match(k + "$", task["test-platform"]) for k in try_only_platforms): + task["run-on-projects"] = [] + yield task + + +@transforms.add +def ensure_spi_disabled_on_all_but_spi(config, tasks): + for task in tasks: + variant = task["attributes"].get("unittest_variant", "") + has_no_setpref = ("gtest", "cppunit", "jittest", "junit", "raptor") + + if ( + all(s not in task["suite"] for s in has_no_setpref) + and "socketprocess" not in variant + ): + task["mozharness"]["extra-options"].append( + "--setpref=media.peerconnection.mtransport_process=false" + ) + task["mozharness"]["extra-options"].append( + "--setpref=network.process.enabled=false" + ) + + yield task + + +test_setting_description_schema = Schema( + { + Required("_hash"): str, + "platform": { + Required("arch"): Any("32", "64", "aarch64", "arm7", "x86", "x86_64"), + Required("os"): { + Required("name"): Any("android", "linux", "macosx", "windows"), + Required("version"): str, + Optional("build"): str, + }, + Optional("device"): str, + Optional("display"): "wayland", + Optional("machine"): Any("ref-hw-2017", "hw-ref"), + }, + "build": { + Required("type"): Any("opt", "debug", "debug-isolated-process"), + Any( + "asan", + "ccov", + "clang-trunk", + "devedition", + "domstreams", + "lite", + "mingwclang", + "nightlyasrelease", + "shippable", + "tsan", + ): bool, + }, + "runtime": {Any(*list(TEST_VARIANTS.keys()) + ["1proc"]): bool}, + }, + check=False, +) +"""Schema test settings must conform to. Validated by +:py:func:`~test.test_mozilla_central.test_test_setting`""" + + +@transforms.add +def set_test_setting(config, tasks): + """A test ``setting`` is the set of configuration that uniquely + distinguishes a test task from other tasks that run the same suite + (ignoring chunks). + + There are three different types of information that make up a setting: + + 1. Platform - Information describing the underlying platform tests run on, + e.g, OS, CPU architecture, etc. + + 2. Build - Information describing the build being tested, e.g build type, + ccov, asan/tsan, etc. + + 3. Runtime - Information describing which runtime parameters are enabled, + e.g, prefs, environment variables, etc. + + This transform adds a ``test-setting`` object to the ``extra`` portion of + all test tasks, of the form: + + .. code-block:: + + { + "platform": { ... }, + "build": { ... }, + "runtime": { ... } + } + + This information could be derived from the label, but consuming this + object is less brittle. + """ + # Some attributes have a dash in them which complicates parsing. Ensure we + # don't split them up. + # TODO Rename these so they don't have a dash. + dash_attrs = [ + "clang-trunk", + "ref-hw-2017", + "hw-ref", + ] + dash_token = "%D%" + platform_re = re.compile(r"(\D+)(\d*)") + + for task in tasks: + setting = { + "platform": { + "os": {}, + }, + "build": {}, + "runtime": {}, + } + + # parse platform and build information out of 'test-platform' + platform, build_type = task["test-platform"].split("/", 1) + + # ensure dashed attributes don't get split up + for attr in dash_attrs: + if attr in platform: + platform = platform.replace(attr, attr.replace("-", dash_token)) + + parts = platform.split("-") + + # restore dashes now that split is finished + for i, part in enumerate(parts): + if dash_token in part: + parts[i] = part.replace(dash_token, "-") + + match = platform_re.match(parts.pop(0)) + assert match + os_name, os_version = match.groups() + + device = machine = os_build = display = None + if os_name == "android": + device = parts.pop(0) + if device == "hw": + device = parts.pop(0) + else: + device = "emulator" + + os_version = parts.pop(0) + if parts[0].isdigit(): + os_version = f"{os_version}.{parts.pop(0)}" + + if parts[0] == "android": + parts.pop(0) + + arch = parts.pop(0) + + else: + arch = parts.pop(0) + if parts[0].isdigit(): + os_build = parts.pop(0) + + if parts and parts[0] == "ref-hw-2017": + machine = parts.pop(0) + + if parts and parts[0] == "hw-ref": + machine = parts.pop(0) + + if parts and parts[0] == "wayland": + display = parts.pop(0) + + if parts and parts[0] == "aarch64": + arch = parts.pop(0) + + # It's not always possible to glean the exact architecture used from + # the task, so sometimes this will just be set to "32" or "64". + setting["platform"]["arch"] = arch + setting["platform"]["os"] = { + "name": os_name, + "version": os_version, + } + + if os_build: + setting["platform"]["os"]["build"] = os_build + + if device: + setting["platform"]["device"] = device + + if machine: + setting["platform"]["machine"] = machine + + if display: + setting["platform"]["display"] = display + + # parse remaining parts as build attributes + setting["build"]["type"] = build_type + while parts: + attr = parts.pop(0) + if attr == "qr": + # all tasks are webrender now, no need to store it + continue + + setting["build"][attr] = True + + unittest_variant = task["attributes"].get("unittest_variant") + if unittest_variant: + for variant in unittest_variant.split("+"): + setting["runtime"][variant] = True + + # add a hash of the setting object for easy comparisons + setting["_hash"] = hashlib.sha256( + json.dumps(setting, sort_keys=True).encode("utf-8") + ).hexdigest()[:12] + + task["test-setting"] = ReadOnlyDict(**setting) + yield task + + +@transforms.add +def allow_software_gl_layers(config, tasks): + """ + Handle the "allow-software-gl-layers" property for platforms where it + applies. + """ + for task in tasks: + if task.get("allow-software-gl-layers"): + # This should be set always once bug 1296086 is resolved. + task["mozharness"].setdefault("extra-options", []).append( + "--allow-software-gl-layers" + ) + + yield task + + +@transforms.add +def enable_webrender(config, tasks): + """ + Handle the "webrender" property by passing a flag to mozharness if it is + enabled. + """ + for task in tasks: + # TODO: this was all conditionally in enable_webrender- do we still need this? + extra_options = task["mozharness"].setdefault("extra-options", []) + # We only want to 'setpref' on tests that have a profile + if not task["attributes"]["unittest_category"] in [ + "cppunittest", + "geckoview-junit", + "gtest", + "jittest", + "raptor", + ]: + extra_options.append("--setpref=layers.d3d11.enable-blacklist=false") + + yield task + + +@transforms.add +def set_schedules_for_webrender_android(config, tasks): + """android-hw has limited resources, we need webrender on phones""" + for task in tasks: + if task["suite"] in ["crashtest", "reftest"] and task[ + "test-platform" + ].startswith("android-hw"): + task["schedules-component"] = "android-hw-gfx" + yield task + + +@transforms.add +def set_retry_exit_status(config, tasks): + """Set the retry exit status to TBPL_RETRY, the value returned by mozharness + scripts to indicate a transient failure that should be retried.""" + for task in tasks: + # add in 137 as it is an error with GCP workers + task["retry-exit-status"] = [4, 137] + yield task + + +@transforms.add +def set_profile(config, tasks): + """Set profiling mode for tests.""" + ttconfig = config.params["try_task_config"] + profile = ttconfig.get("gecko-profile", False) + settings = ( + "gecko-profile-interval", + "gecko-profile-entries", + "gecko-profile-threads", + "gecko-profile-features", + ) + + for task in tasks: + if profile and task["suite"] in ["talos", "raptor"]: + extras = task["mozharness"]["extra-options"] + extras.append("--gecko-profile") + for setting in settings: + value = ttconfig.get(setting) + if value is not None: + # These values can contain spaces (eg the "DOM Worker" + # thread) and the command is constructed in different, + # incompatible ways on different platforms. + + if task["test-platform"].startswith("win"): + # Double quotes for Windows (single won't work). + extras.append("--" + setting + '="' + str(value) + '"') + else: + # Other platforms keep things as separate values, + # rather than joining with spaces. + extras.append("--" + setting + "=" + str(value)) + + yield task + + +@transforms.add +def set_tag(config, tasks): + """Set test for a specific tag.""" + tag = None + if config.params["try_mode"] == "try_option_syntax": + tag = config.params["try_options"]["tag"] + for task in tasks: + if tag: + task["mozharness"]["extra-options"].extend(["--tag", tag]) + yield task + + +@transforms.add +def set_test_type(config, tasks): + types = ["mochitest", "reftest", "talos", "raptor", "geckoview-junit", "gtest"] + for task in tasks: + for test_type in types: + if test_type in task["suite"] and "web-platform" not in task["suite"]: + task.setdefault("tags", {})["test-type"] = test_type + yield task + + +@transforms.add +def set_schedules_components(config, tasks): + for task in tasks: + if "optimization" in task or "when" in task: + yield task + continue + + category = task["attributes"]["unittest_category"] + schedules = task.get("schedules-component", category) + if isinstance(schedules, str): + schedules = [schedules] + + schedules = set(schedules) + if schedules & set(INCLUSIVE_COMPONENTS): + # if this is an "inclusive" test, then all files which might + # cause it to run are annotated with SCHEDULES in moz.build, + # so do not include the platform or any other components here + task["schedules-component"] = sorted(schedules) + yield task + continue + + schedules.add(category) + schedules.add(platform_family(task["build-platform"])) + + task["schedules-component"] = sorted(schedules) + yield task + + +@transforms.add +def enable_parallel_marking_in_tsan_tests(config, tasks): + """Enable parallel marking in TSAN tests""" + skip_list = ["cppunittest", "gtest"] + for task in tasks: + if "-tsan-" in task["test-platform"]: + if task["suite"] not in skip_list: + extra_options = task["mozharness"].setdefault("extra-options", []) + extra_options.append( + "--setpref=javascript.options.mem.gc_parallel_marking=true" + ) + + yield task |