# 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 os import re from typing import Literal, Optional from taskgraph.util import json from taskgraph.util.schema import Schema from taskgraph.util.taskcluster import get_artifact_path from gecko_taskgraph.transforms.job import configure_taskdesc_for_run, run_job_using from gecko_taskgraph.transforms.job.common import ( docker_worker_add_artifacts, generic_worker_add_artifacts, get_expiration, support_vcs_checkout, ) from gecko_taskgraph.transforms.test import TestDescriptionSchema, normpath from gecko_taskgraph.util.attributes import is_try from gecko_taskgraph.util.chunking import get_test_tags from gecko_taskgraph.util.perftest import is_external_browser VARIANTS = [ "shippable", "shippable-qr", "shippable-lite", "shippable-lite-qr", "devedition", "pgo", "asan", "stylo", "qr", "ccov", ] def get_variant(test_platform): for v in VARIANTS: if f"-{v}/" in test_platform: return v return "" class MozharnessTestSchema(Schema, forbid_unknown_fields=False, kw_only=True): test_platform: str mozharness: TestDescriptionSchema.__annotations__["mozharness"] # noqa: F821 docker_image: TestDescriptionSchema.__annotations__[ "docker_image" # noqa: F821 ] loopback_video: TestDescriptionSchema.__annotations__["loopback_video"] # noqa: F821 loopback_audio: TestDescriptionSchema.__annotations__["loopback_audio"] # noqa: F821 max_run_time: TestDescriptionSchema.__annotations__["max_run_time"] # noqa: F821 retry_exit_status: TestDescriptionSchema.__annotations__["retry_exit_status"] = None class MozharnessTestRunSchema(Schema, kw_only=True): using: Literal["mozharness-test"] test: MozharnessTestSchema # noqa: F821 # Base work directory used to set up the task. workdir: Optional[str] = None def test_packages_url(taskdesc): """Account for different platforms that name their test packages differently""" artifact_path = "target.test_packages.json" # for android shippable we need to add 'en-US' to the artifact url test = taskdesc["run"]["test"] if ( "android" in test["test-platform"] and ( get_variant(test["test-platform"]) in ("shippable", "shippable-qr", "shippable-lite", "shippable-lite-qr") ) and not is_external_browser(test.get("try-name", "")) ): artifact_path = os.path.join("en-US", artifact_path) return f"" def installer_url(taskdesc): test = taskdesc["run"]["test"] mozharness = test["mozharness"] if "installer-url" in mozharness: return mozharness["installer-url"] upstream_task = "build-signing" if mozharness["requires-signed-builds"] else "build" return f"<{upstream_task}/{mozharness['build-artifact-name']}>" @run_job_using("docker-worker", "mozharness-test", schema=MozharnessTestRunSchema) def mozharness_test_on_docker(config, job, taskdesc): run = job["run"] test = taskdesc["run"]["test"] mozharness = test["mozharness"] worker = taskdesc["worker"] = job["worker"] # apply some defaults worker["docker-image"] = test["docker-image"] worker["allow-ptrace"] = True # required for all tests, for crashreporter worker["loopback-video"] = test["loopback-video"] worker["loopback-audio"] = test["loopback-audio"] worker["max-run-time"] = test["max-run-time"] worker["retry-exit-status"] = test["retry-exit-status"] if "android-em-" in test["test-platform"]: worker["kvm"] = True artifacts = [ # (artifact name prefix, in-image path) ("public/logs", "{workdir}/workspace/logs/".format(**run)), ("public/test", "{workdir}/artifacts/".format(**run)), ( "public/test_info", "{workdir}/workspace/build/blobber_upload_dir/".format(**run), ), ] installer = installer_url(taskdesc) worker.setdefault("artifacts", []) worker["artifacts"].extend([ { "name": prefix, "path": os.path.join("{workdir}/workspace".format(**run), path), "type": "directory", "expires-after": get_expiration(config, "default"), } for (prefix, path) in artifacts ]) worker["artifacts"].append({ "name": "public/xsession-errors.log", "path": "{workdir}/.xsession-errors".format(**run), "type": "file", "expires-after": get_expiration(config, "default"), }) docker_worker_add_artifacts(config, job, taskdesc) env = worker.setdefault("env", {}) env.update({ "MOZHARNESS_CONFIG": " ".join(mozharness["config"]), "MOZHARNESS_SCRIPT": mozharness["script"], "MOZILLA_BUILD_URL": {"artifact-reference": installer}, "NEED_WINDOW_MANAGER": "true", "ENABLE_E10S": str(bool(test.get("e10s"))).lower(), "WORKING_DIR": "/builds/worker", }) env["PYTHON"] = "python3" if test.get("docker-image", {}).get("in-tree") == "ubuntu1804-test": env["NEED_PULSEAUDIO"] = "true" # Bug 1602701/1601828 - use compiz on ubuntu1804 due to GTK asynchiness # when manipulating windows. if "wdspec" in job["run"]["test"]["suite"] or ( "marionette" in job["run"]["test"]["suite"] and "headless" not in job["label"] ): env.update({"NEED_COMPIZ": "true"}) if test.get("docker-image", {}).get("in-tree") == "ubuntu2404-test": env["NEED_PIPEWIRE"] = "true" # Set MOZ_ENABLE_WAYLAND env variables to enable Wayland backend. if "wayland" in job["label"]: env["MOZ_ENABLE_WAYLAND"] = "1" else: assert "MOZ_ENABLE_WAYLAND" not in env if mozharness.get("mochitest-flavor"): env["MOCHITEST_FLAVOR"] = mozharness["mochitest-flavor"] if mozharness["set-moz-node-path"]: env["MOZ_NODE_PATH"] = "/usr/local/bin/node" if "actions" in mozharness: env["MOZHARNESS_ACTIONS"] = " ".join(mozharness["actions"]) if is_try(config.params): env["TRY_COMMIT_MSG"] = config.params["message"] # handle some of the mozharness-specific options if test["reboot"]: raise Exception( "reboot: {} not supported on generic-worker".format(test["reboot"]) ) if not test["checkout"]: # Support vcs checkouts regardless of whether the task runs from # source or not in case it is needed on an interactive loaner. support_vcs_checkout(config, job, taskdesc, config.repo_configs) # If we have a source checkout, run mozharness from it instead of # downloading a zip file with the same content. if test["checkout"]: env["MOZHARNESS_PATH"] = "{workdir}/checkouts/gecko/testing/mozharness".format( **run ) else: mozharness_url = f"" env["MOZHARNESS_URL"] = {"artifact-reference": mozharness_url} extra_config = { "installer_url": installer, "test_packages_url": test_packages_url(taskdesc), } env["EXTRA_MOZHARNESS_CONFIG"] = { "artifact-reference": json.dumps(extra_config, sort_keys=True) } # Bug 1634554 - pass in decision task artifact URL to mozharness for WPT. # Bug 1645974 - test-verify-wpt and test-coverage-wpt need artifact URL. if "web-platform-tests" in test["suite"] or re.match( "test-(coverage|verify)-wpt", test["suite"] ): env["TESTS_BY_MANIFEST_URL"] = { "artifact-reference": "" } command = [ "{workdir}/bin/test-linux.sh".format(**run), ] command.extend(mozharness.get("extra-options", [])) if test.get("test-manifests"): env["MOZHARNESS_TEST_PATHS"] = json.dumps( {test["suite"]: test["test-manifests"]}, sort_keys=True ) test_tags = get_test_tags(config, env) if test_tags: env["MOZHARNESS_TEST_TAG"] = json.dumps(test_tags) command.extend([f"--tag={x}" for x in test_tags]) # TODO: remove the need for run['chunked'] elif mozharness.get("chunked") or test["chunks"] > 1: command.append("--total-chunk={}".format(test["chunks"])) command.append("--this-chunk={}".format(test["this-chunk"])) if test.get("timeoutfactor"): command.append("--timeout-factor={}".format(test["timeoutfactor"])) if "download-symbols" in mozharness: download_symbols = mozharness["download-symbols"] download_symbols = {True: "true", False: "false"}.get( download_symbols, download_symbols ) command.append("--download-symbols=" + download_symbols) use_caches = test.get("use-caches", ["checkout", "pip", "uv"]) job["run"] = { "workdir": run["workdir"], "tooltool-downloads": mozharness["tooltool-downloads"], "checkout": test["checkout"], "command": command, "use-caches": use_caches, "using": "run-task", } configure_taskdesc_for_run(config, job, taskdesc, worker["implementation"]) @run_job_using("generic-worker", "mozharness-test", schema=MozharnessTestRunSchema) def mozharness_test_on_generic_worker(config, job, taskdesc): test = taskdesc["run"]["test"] mozharness = test["mozharness"] worker = taskdesc["worker"] = job["worker"] bitbar_script = "test-linux.sh" is_macosx = worker["os"] == "macosx" is_windows = worker["os"] == "windows" is_linux = worker["os"] == "linux" or worker["os"] in [ "linux-bitbar", "linux-lambda", ] is_bitbar = worker["os"] == "linux-bitbar" is_lambda = worker["os"] == "linux-lambda" assert is_macosx or is_windows or is_linux artifacts = [ { "name": "public/logs", "path": "logs", "type": "directory", "expires-after": get_expiration(config, "default"), } ] generic_worker_add_artifacts(config, job, taskdesc) # jittest doesn't have blob_upload_dir if test["test-name"] != "jittest": artifacts.append({ "name": "public/test_info", "path": "build/blobber_upload_dir", "type": "directory", "expires-after": get_expiration(config, "default"), }) if is_bitbar or is_lambda: artifacts = [ { "name": "public/test/", "path": "artifacts/public", "type": "directory", "expires-after": get_expiration(config, "default"), }, { "name": "public/logs/", "path": "workspace/logs", "type": "directory", "expires-after": get_expiration(config, "default"), }, { "name": "public/test_info/", "path": "workspace/build/blobber_upload_dir", "type": "directory", "expires-after": get_expiration(config, "default"), }, ] installer = installer_url(taskdesc) worker["os-groups"] = test["os-groups"] # run-as-administrator is a feature for workers with UAC enabled and as such should not be # included in tasks on workers that have UAC disabled. Currently UAC is only enabled on # gecko Windows 10 workers, however this may be subject to change. Worker type # environment definitions can be found in https://github.com/mozilla-releng/OpenCloudConfig # See https://docs.microsoft.com/en-us/windows/desktop/secauthz/user-account-control # for more information about UAC. if test.get("run-as-administrator", False): if job["worker-type"].startswith("win10-64") or job["worker-type"].startswith( "win11-64" ): worker["run-as-administrator"] = True else: raise Exception( "run-as-administrator not supported on {}".format(job["worker-type"]) ) if test["reboot"]: raise Exception( "reboot: {} not supported on generic-worker".format(test["reboot"]) ) worker["max-run-time"] = test["max-run-time"] worker["retry-exit-status"] = test["retry-exit-status"] worker.setdefault("artifacts", []) worker["artifacts"].extend(artifacts) env = worker.setdefault("env", {}) env["GECKO_HEAD_REPOSITORY"] = config.params["head_repository"] env["GECKO_HEAD_REV"] = config.params["head_rev"] # this list will get cleaned up / reduced / removed in bug 1354088 if is_macosx: env.update({ "LC_ALL": "en_US.UTF-8", "LANG": "en_US.UTF-8", "MOZ_NODE_PATH": "/usr/local/bin/node", "PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin", "SHELL": "/bin/bash", }) elif is_bitbar or is_lambda: env.update({ "LANG": "en_US.UTF-8", "MOZHARNESS_CONFIG": " ".join(mozharness["config"]), "MOZHARNESS_SCRIPT": mozharness["script"], "MOZHARNESS_URL": { "artifact-reference": "" }, "MOZILLA_BUILD_URL": {"artifact-reference": installer}, "NEED_XVFB": "false", "XPCOM_DEBUG_BREAK": "warn", "NO_FAIL_ON_TEST_ERRORS": "1", "MOZ_HIDE_RESULTS_TABLE": "1", "MOZ_NODE_PATH": "/usr/local/bin/node", "TASKCLUSTER_WORKER_TYPE": job["worker-type"], }) extra_config = { "installer_url": installer, "test_packages_url": test_packages_url(taskdesc), } env["EXTRA_MOZHARNESS_CONFIG"] = { "artifact-reference": json.dumps(extra_config, sort_keys=True) } # Bug 1634554 - pass in decision task artifact URL to mozharness for WPT. # Bug 1645974 - test-verify-wpt and test-coverage-wpt need artifact URL. if "web-platform-tests" in test["suite"] or re.match( "test-(coverage|verify)-wpt", test["suite"] ): env["TESTS_BY_MANIFEST_URL"] = { "artifact-reference": "" } if is_windows: py_binary = "c:\\mozilla-build\\{python}\\{python}.exe".format(python="python3") mh_command = [ py_binary, "-u", "mozharness\\scripts\\" + normpath(mozharness["script"]), ] elif is_bitbar or is_lambda: py_binary = "python3" mh_command = ["bash", f"./{bitbar_script}"] elif is_macosx: py_binary = "/usr/local/bin/{}".format("python3") mh_command = [ py_binary, "-u", "mozharness/scripts/" + mozharness["script"], ] else: # is_linux py_binary = "/usr/bin/{}".format("python3") mh_command = [ # Using /usr/bin/python2.7 rather than python2.7 because # /usr/local/bin/python2.7 is broken on the mac workers. # See bug #1547903. py_binary, "-u", "mozharness/scripts/" + mozharness["script"], ] env["PYTHON"] = py_binary for mh_config in mozharness["config"]: cfg_path = "mozharness/configs/" + mh_config if is_windows: cfg_path = normpath(cfg_path) mh_command.extend(["--cfg", cfg_path]) mh_command.extend(mozharness.get("extra-options", [])) if mozharness.get("download-symbols"): if isinstance(mozharness["download-symbols"], str): mh_command.extend(["--download-symbols", mozharness["download-symbols"]]) else: mh_command.extend(["--download-symbols", "true"]) if mozharness.get("include-blob-upload-branch"): mh_command.append("--blob-upload-branch=" + config.params["project"]) if test.get("test-manifests"): env["MOZHARNESS_TEST_PATHS"] = json.dumps( {test["suite"]: test["test-manifests"]}, sort_keys=True ) test_tags = get_test_tags(config, env) if test_tags: # do not add --tag for perf tests if test["suite"] not in ["talos", "raptor"]: env["MOZHARNESS_TEST_TAG"] = json.dumps(test_tags) mh_command.extend([f"--tag={x}" for x in test_tags]) # TODO: remove the need for run['chunked'] elif mozharness.get("chunked") or test["chunks"] > 1: mh_command.append("--total-chunk={}".format(test["chunks"])) mh_command.append("--this-chunk={}".format(test["this-chunk"])) if test.get("timeoutfactor"): mh_command.append("--timeout-factor={}".format(test["timeoutfactor"])) if is_try(config.params): env["TRY_COMMIT_MSG"] = config.params["message"] worker["mounts"] = [ { "directory": "mozharness", "content": { "artifact": get_artifact_path(taskdesc, "mozharness.zip"), "task-id": {"task-reference": ""}, }, "format": "zip", } ] if is_bitbar or is_lambda: a_url = config.params.file_url( f"taskcluster/scripts/tester/{bitbar_script}", ) worker["mounts"] = [ { "file": bitbar_script, "content": { "url": a_url, }, } ] use_caches = test.get("use-caches", ["checkout", "pip", "uv"]) job["run"] = { "tooltool-downloads": mozharness["tooltool-downloads"], "checkout": test["checkout"], "command": mh_command, "use-caches": use_caches, "using": "run-task", } if is_bitbar or is_lambda: job["run"]["run-as-root"] = True configure_taskdesc_for_run(config, job, taskdesc, worker["implementation"])