summaryrefslogtreecommitdiffstats
path: root/taskcluster/gecko_taskgraph
diff options
context:
space:
mode:
Diffstat (limited to 'taskcluster/gecko_taskgraph')
-rw-r--r--taskcluster/gecko_taskgraph/morph.py25
-rw-r--r--taskcluster/gecko_taskgraph/parameters.py4
-rw-r--r--taskcluster/gecko_taskgraph/target_tasks.py50
-rw-r--r--taskcluster/gecko_taskgraph/test/test_morph.py96
-rw-r--r--taskcluster/gecko_taskgraph/test/test_target_tasks.py15
-rw-r--r--taskcluster/gecko_taskgraph/test/test_util_partials.py32
-rw-r--r--taskcluster/gecko_taskgraph/transforms/artifact.py4
-rw-r--r--taskcluster/gecko_taskgraph/transforms/artifacts.yml4
-rw-r--r--taskcluster/gecko_taskgraph/transforms/bootstrap.py2
-rw-r--r--taskcluster/gecko_taskgraph/transforms/job/mozharness_test.py11
-rw-r--r--taskcluster/gecko_taskgraph/transforms/snap_test.py2
-rw-r--r--taskcluster/gecko_taskgraph/transforms/source_test.py29
-rw-r--r--taskcluster/gecko_taskgraph/transforms/test/other.py29
-rw-r--r--taskcluster/gecko_taskgraph/transforms/test/raptor.py23
-rw-r--r--taskcluster/gecko_taskgraph/util/partials.py10
-rw-r--r--taskcluster/gecko_taskgraph/util/perftest.py18
16 files changed, 292 insertions, 62 deletions
diff --git a/taskcluster/gecko_taskgraph/morph.py b/taskcluster/gecko_taskgraph/morph.py
index 1d03ddaab6..42fe4597fa 100644
--- a/taskcluster/gecko_taskgraph/morph.py
+++ b/taskcluster/gecko_taskgraph/morph.py
@@ -254,10 +254,31 @@ def add_eager_cache_index_tasks(taskgraph, label_to_taskid, parameters, graph_co
@register_morph
def add_try_task_duplicates(taskgraph, label_to_taskid, parameters, graph_config):
- try_config = parameters["try_task_config"]
+ return _add_try_task_duplicates(
+ taskgraph, label_to_taskid, parameters, graph_config
+ )
+
+
+# this shim function exists so we can call it from the unittests.
+# this works around an issue with
+# third_party/python/taskcluster_taskgraph/taskgraph/morph.py#40
+def _add_try_task_duplicates(taskgraph, label_to_taskid, parameters, graph_config):
+ try_config = parameters.get("try_task_config", {})
+ tasks = try_config.get("tasks", [])
+ glob_tasks = {x.strip("-*") for x in tasks if x.endswith("-*")}
+ tasks = set(tasks) - glob_tasks
+
rebuild = try_config.get("rebuild")
if rebuild:
for task in taskgraph.tasks.values():
- if task.label in try_config.get("tasks", []):
+ chunk_index = -1
+ if task.label.endswith("-cf"):
+ chunk_index = -2
+ label_parts = task.label.split("-")
+ label_no_chunk = "-".join(label_parts[:chunk_index])
+
+ if label_parts[chunk_index].isnumeric() and label_no_chunk in glob_tasks:
+ task.attributes["task_duplicates"] = rebuild
+ elif task.label in tasks:
task.attributes["task_duplicates"] = rebuild
return taskgraph, label_to_taskid
diff --git a/taskcluster/gecko_taskgraph/parameters.py b/taskcluster/gecko_taskgraph/parameters.py
index 2a61a71b96..7e3de1372f 100644
--- a/taskcluster/gecko_taskgraph/parameters.py
+++ b/taskcluster/gecko_taskgraph/parameters.py
@@ -74,6 +74,10 @@ gecko_parameters_schema = {
"worker-overrides",
description="Mapping of worker alias to worker pools to use for those aliases.",
): {str: str},
+ Optional(
+ "worker-types",
+ description="List of worker types that we will use to run tasks on.",
+ ): [str],
Optional("routes"): [str],
},
Required("version"): str,
diff --git a/taskcluster/gecko_taskgraph/target_tasks.py b/taskcluster/gecko_taskgraph/target_tasks.py
index 2f445d3f95..fcbfab4e17 100644
--- a/taskcluster/gecko_taskgraph/target_tasks.py
+++ b/taskcluster/gecko_taskgraph/target_tasks.py
@@ -4,6 +4,7 @@
import itertools
+import logging
import os
import re
from datetime import datetime, timedelta
@@ -21,6 +22,9 @@ from gecko_taskgraph.util.attributes import (
from gecko_taskgraph.util.hg import find_hg_revision_push_info, get_hg_commit_message
from gecko_taskgraph.util.platforms import platform_family
+logger = logging.getLogger(__name__)
+
+
# Some tasks show up in the target task set, but are possibly special cases,
# uncommon tasks, or tasks running against limited hardware set that they
# should only be selectable with --full.
@@ -247,9 +251,9 @@ def accept_raptor_android_build(platform):
if "p5" in platform and "aarch64" in platform:
return False
if "p6" in platform and "aarch64" in platform:
- return False
+ return True
if "s21" in platform and "aarch64" in platform:
- return False
+ return True
if "a51" in platform:
return True
return False
@@ -299,16 +303,30 @@ def _try_task_config(full_task_graph, parameters, graph_config):
pattern_tasks = [x for x in requested_tasks if x.endswith("-*")]
tasks = list(set(requested_tasks) - set(pattern_tasks))
matched_tasks = []
+ missing = set()
for pattern in pattern_tasks:
- matched_tasks.extend(
- [
- t
- for t in full_task_graph.graph.nodes
- if t.split(pattern.replace("*", ""))[-1].isnumeric()
- ]
- )
+ found = [
+ t
+ for t in full_task_graph.graph.nodes
+ if t.split(pattern.replace("*", ""))[-1].isnumeric()
+ ]
+ if found:
+ matched_tasks.extend(found)
+ else:
+ missing.add(pattern)
+
+ if "MOZHARNESS_TEST_PATHS" in parameters["try_task_config"].get("env", {}):
+ matched_tasks = [x for x in matched_tasks if x.endswith("-1")]
- return list(set(tasks) | set(matched_tasks))
+ selected_tasks = set(tasks) | set(matched_tasks)
+ missing.update(selected_tasks - set(full_task_graph.tasks))
+
+ if missing:
+ missing_str = "\n ".join(sorted(missing))
+ logger.warning(
+ f"The following tasks were requested but do not exist in the full task graph and will be skipped:\n {missing_str}"
+ )
+ return list(selected_tasks - missing)
def _try_option_syntax(full_task_graph, parameters, graph_config):
@@ -731,6 +749,7 @@ def target_tasks_larch(full_task_graph, parameters, graph_config):
"l10n" in task.kind
or "msix" in task.kind
or "android" in task.attributes.get("build_platform", "")
+ or (task.kind == "test" and "msix" in task.label)
):
return False
# otherwise reduce tests only
@@ -800,6 +819,8 @@ def target_tasks_custom_car_perf_testing(full_task_graph, parameters, graph_conf
if "browsertime" in try_name and (
"custom-car" in try_name or "cstm-car-m" in try_name
):
+ if "hw-s21" in platform and "speedometer3" not in try_name:
+ return False
return True
return False
@@ -853,6 +874,8 @@ def target_tasks_general_perf_testing(full_task_graph, parameters, graph_config)
return True
# Android selection
elif accept_raptor_android_build(platform):
+ if "hw-s21" in platform and "speedometer3" not in try_name:
+ return False
if "chrome-m" in try_name and (
("ebay" in try_name and "live" not in try_name)
or (
@@ -935,6 +958,8 @@ def target_tasks_speedometer_tests(full_task_graph, parameters, graph_config):
platform
):
try_name = attributes.get("raptor_try_name")
+ if "hw-s21" in platform and "speedometer3" not in try_name:
+ return False
if (
"browsertime" in try_name
and "speedometer" in try_name
@@ -950,7 +975,9 @@ def target_tasks_nightly_linux(full_task_graph, parameters, graph_config):
"""Select the set of tasks required for a nightly build of linux. The
nightly build process involves a pipeline of builds, signing,
and, eventually, uploading the tasks to balrog."""
- filter = make_desktop_nightly_filter({"linux64-shippable", "linux-shippable"})
+ filter = make_desktop_nightly_filter(
+ {"linux64-shippable", "linux-shippable", "linux-aarch64-shippable"}
+ )
return [l for l, t in full_task_graph.tasks.items() if filter(t, parameters)]
@@ -1060,6 +1087,7 @@ def target_tasks_searchfox(full_task_graph, parameters, graph_config):
"searchfox-macosx64-searchfox/debug",
"searchfox-win64-searchfox/debug",
"searchfox-android-armv7-searchfox/debug",
+ "searchfox-ios-searchfox/debug",
"source-test-file-metadata-bugzilla-components",
"source-test-file-metadata-test-info-all",
"source-test-wpt-metadata-summary",
diff --git a/taskcluster/gecko_taskgraph/test/test_morph.py b/taskcluster/gecko_taskgraph/test/test_morph.py
index c29fb58207..c3c499769c 100644
--- a/taskcluster/gecko_taskgraph/test/test_morph.py
+++ b/taskcluster/gecko_taskgraph/test/test_morph.py
@@ -26,6 +26,102 @@ def make_taskgraph():
return inner
+@pytest.mark.parametrize(
+ "params,expected",
+ (
+ pytest.param(
+ {
+ "try_mode": "try_task_config",
+ "try_task_config": {
+ "rebuild": 10,
+ "tasks": ["b"],
+ },
+ "project": "try",
+ },
+ {"b": 10},
+ id="duplicates no chunks",
+ ),
+ pytest.param(
+ {
+ "try_mode": "try_task_config",
+ "try_task_config": {
+ "rebuild": 10,
+ "tasks": ["a-*"],
+ },
+ "project": "try",
+ },
+ {"a-1": 10, "a-2": 10},
+ id="duplicates with chunks",
+ ),
+ pytest.param(
+ {
+ "try_mode": "try_task_config",
+ "try_task_config": {
+ "rebuild": 10,
+ "tasks": ["a-*", "b"],
+ },
+ "project": "try",
+ },
+ {"a-1": 10, "a-2": 10, "b": 10},
+ id="duplicates with and without chunks",
+ ),
+ pytest.param(
+ {
+ "try_mode": "try_task_config",
+ "try_task_config": {
+ "tasks": ["a-*"],
+ },
+ "project": "try",
+ },
+ {"a-1": -1, "a-2": -1},
+ id="no rebuild, no duplicates",
+ ),
+ pytest.param(
+ {
+ "try_mode": "try_task_config",
+ "try_task_config": {
+ "rebuild": 0,
+ "tasks": ["a-*"],
+ },
+ "project": "try",
+ },
+ {"a-1": -1, "a-2": -1},
+ id="rebuild of zero",
+ ),
+ pytest.param(
+ {
+ "try_mode": "try_task_config",
+ "try_task_config": {
+ "rebuild": 100,
+ "tasks": ["a-*"],
+ },
+ "project": "try",
+ },
+ {"a-1": 100, "a-2": 100},
+ id="rebuild 100",
+ ),
+ ),
+)
+def test_try_task_duplicates(make_taskgraph, graph_config, params, expected):
+ taskb = Task(kind="test", label="b", attributes={}, task={})
+ task1 = Task(kind="test", label="a-1", attributes={}, task={})
+ task2 = Task(kind="test", label="a-2", attributes={}, task={})
+ taskgraph, label_to_taskid = make_taskgraph(
+ {
+ taskb.label: taskb,
+ task1.label: task1,
+ task2.label: task2,
+ }
+ )
+
+ taskgraph, label_to_taskid = morph._add_try_task_duplicates(
+ taskgraph, label_to_taskid, params, graph_config
+ )
+ for label in expected:
+ task = taskgraph.tasks[label]
+ assert task.attributes.get("task_duplicates", -1) == expected[label]
+
+
def test_make_index_tasks(make_taskgraph, graph_config):
task_def = {
"routes": [
diff --git a/taskcluster/gecko_taskgraph/test/test_target_tasks.py b/taskcluster/gecko_taskgraph/test/test_target_tasks.py
index 2bbc57fcf3..22582f9040 100644
--- a/taskcluster/gecko_taskgraph/test/test_target_tasks.py
+++ b/taskcluster/gecko_taskgraph/test/test_target_tasks.py
@@ -210,6 +210,21 @@ class TestTargetTasks(unittest.TestCase):
}
self.assertEqual(sorted(method(tg, params, {})), ["ddd-1", "ddd-2"])
+ def test_try_task_config_regex_with_paths(self):
+ "try_mode = try_task_config uses the try config with regex instead of chunk numbers"
+ tg = self.make_task_graph()
+ method = target_tasks.get_method("try_tasks")
+ params = {
+ "try_mode": "try_task_config",
+ "try_task_config": {
+ "new-test-config": True,
+ "tasks": ["ddd-*"],
+ "env": {"MOZHARNESS_TEST_PATHS": "foo/bar"},
+ },
+ "project": "try",
+ }
+ self.assertEqual(sorted(method(tg, params, {})), ["ddd-1"])
+
def test_try_task_config_absolute(self):
"try_mode = try_task_config uses the try config with full task labels"
tg = self.make_task_graph()
diff --git a/taskcluster/gecko_taskgraph/test/test_util_partials.py b/taskcluster/gecko_taskgraph/test/test_util_partials.py
index 3630d7b0ec..4f8bea8662 100644
--- a/taskcluster/gecko_taskgraph/test/test_util_partials.py
+++ b/taskcluster/gecko_taskgraph/test/test_util_partials.py
@@ -30,18 +30,34 @@ release_blob = {
def nightly_blob(release):
- return {
- "platforms": {
- "WINNT_x86_64-msvc": {
- "locales": {
- "en-US": {
- "buildID": release[-14:],
- "completes": [{"fileUrl": release}],
+ # Added for bug 1883046, where we identified a case where a Balrog release
+ # that does not contain completes will throw an unnecessary exception.
+ if release == "Firefox-mozilla-central-nightly-20211001214601":
+ return {
+ "platforms": {
+ "WINNT_x86_64-msvc": {
+ "locales": {
+ "en-US": {
+ "buildID": release[-14:],
+ "partials": [{"fileUrl": release}],
+ }
+ }
+ }
+ }
+ }
+ else:
+ return {
+ "platforms": {
+ "WINNT_x86_64-msvc": {
+ "locales": {
+ "en-US": {
+ "buildID": release[-14:],
+ "completes": [{"fileUrl": release}],
+ }
}
}
}
}
- }
class TestReleaseHistory(unittest.TestCase):
diff --git a/taskcluster/gecko_taskgraph/transforms/artifact.py b/taskcluster/gecko_taskgraph/transforms/artifact.py
index 559148f7b4..b14f723d4a 100644
--- a/taskcluster/gecko_taskgraph/transforms/artifact.py
+++ b/taskcluster/gecko_taskgraph/transforms/artifact.py
@@ -85,9 +85,11 @@ def set_artifact_expiration(config, jobs):
art_dict = manifest["macos"]
elif plat.startswith("android"):
art_dict = manifest["android"]
+ elif plat.startswith("ios"):
+ art_dict = manifest["ios"]
else:
print(
- 'The platform name "{plat}" didn\'t start with',
+ f'The platform name "{plat}" didn\'t start with',
'"win", "mac", "android", or "linux".',
file=sys.stderr,
)
diff --git a/taskcluster/gecko_taskgraph/transforms/artifacts.yml b/taskcluster/gecko_taskgraph/transforms/artifacts.yml
index 26f06640ad..efcbc70f15 100644
--- a/taskcluster/gecko_taskgraph/transforms/artifacts.yml
+++ b/taskcluster/gecko_taskgraph/transforms/artifacts.yml
@@ -18,3 +18,7 @@ android:
target.crashreporter-symbols-full.tar.zst: shortest
sccache.log: shortest
sccache-stats.json: shortest
+
+ios:
+ sccache.log: shortest
+ sccache-stats.json: shortest
diff --git a/taskcluster/gecko_taskgraph/transforms/bootstrap.py b/taskcluster/gecko_taskgraph/transforms/bootstrap.py
index e4537cab01..9c02fc6819 100644
--- a/taskcluster/gecko_taskgraph/transforms/bootstrap.py
+++ b/taskcluster/gecko_taskgraph/transforms/bootstrap.py
@@ -59,7 +59,7 @@ def bootstrap_tasks(config, tasks):
f"python3 bootstrap.py --no-interactive --application-choice {app}",
"cd mozilla-unified",
# After bootstrap, configure should go through without its own auto-bootstrap.
- "./mach configure --disable-bootstrap",
+ "./mach configure --enable-bootstrap=no-update",
# Then a build should go through too.
"./mach build",
]
diff --git a/taskcluster/gecko_taskgraph/transforms/job/mozharness_test.py b/taskcluster/gecko_taskgraph/transforms/job/mozharness_test.py
index eb4aea609f..060f37c9c2 100644
--- a/taskcluster/gecko_taskgraph/transforms/job/mozharness_test.py
+++ b/taskcluster/gecko_taskgraph/transforms/job/mozharness_test.py
@@ -15,6 +15,7 @@ from gecko_taskgraph.transforms.job import configure_taskdesc_for_run, run_job_u
from gecko_taskgraph.transforms.job.common import get_expiration, support_vcs_checkout
from gecko_taskgraph.transforms.test import normpath, test_description_schema
from gecko_taskgraph.util.attributes import is_try
+from gecko_taskgraph.util.perftest import is_external_browser
VARIANTS = [
"shippable",
@@ -63,9 +64,13 @@ def test_packages_url(taskdesc):
)
# 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")
+ 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", ""))
):
head, tail = os.path.split(artifact_url)
artifact_url = os.path.join(head, "en-US", tail)
diff --git a/taskcluster/gecko_taskgraph/transforms/snap_test.py b/taskcluster/gecko_taskgraph/transforms/snap_test.py
index e6d879f225..d49276843d 100644
--- a/taskcluster/gecko_taskgraph/transforms/snap_test.py
+++ b/taskcluster/gecko_taskgraph/transforms/snap_test.py
@@ -43,6 +43,8 @@ def fill_template(config, tasks):
timeout = 10
if collection != "opt":
timeout = 60
+ task["task"]["payload"]["env"]["BUILD_IS_DEBUG"] = "1"
+
task["task"]["payload"]["env"]["TEST_TIMEOUT"] = "{}".format(timeout)
yield task
diff --git a/taskcluster/gecko_taskgraph/transforms/source_test.py b/taskcluster/gecko_taskgraph/transforms/source_test.py
index 5c561e8114..e3eeb7c819 100644
--- a/taskcluster/gecko_taskgraph/transforms/source_test.py
+++ b/taskcluster/gecko_taskgraph/transforms/source_test.py
@@ -10,7 +10,6 @@ treeherder configuration and attributes for that platform.
import copy
import os
-import taskgraph
from taskgraph.transforms.base import TransformSequence
from taskgraph.util.attributes import keymatch
from taskgraph.util.schema import Schema, optionally_keyed_by, resolve_keyed_by
@@ -18,7 +17,6 @@ from taskgraph.util.treeherder import join_symbol, split_symbol
from voluptuous import Any, Extra, Optional, Required
from gecko_taskgraph.transforms.job import job_description_schema
-from gecko_taskgraph.util.hg import get_json_automationrelevance
source_test_description_schema = Schema(
{
@@ -239,33 +237,6 @@ def set_code_review_env(config, jobs):
@transforms.add
-def set_base_revision_in_tgdiff(config, jobs):
- # Don't attempt to download 'json-automation' locally as the revision may
- # not exist in the repository.
- if not os.environ.get("MOZ_AUTOMATION") or taskgraph.fast:
- yield from jobs
- return
-
- data = get_json_automationrelevance(
- config.params["head_repository"], config.params["head_rev"]
- )
- for job in jobs:
- if job["name"] != "taskgraph-diff":
- yield job
- continue
-
- job["task-context"] = {
- "from-object": {
- "base_rev": data["changesets"][0]["parents"][0],
- },
- "substitution-fields": [
- "run.command",
- ],
- }
- yield job
-
-
-@transforms.add
def set_worker_exit_code(config, jobs):
for job in jobs:
worker = job["worker"]
diff --git a/taskcluster/gecko_taskgraph/transforms/test/other.py b/taskcluster/gecko_taskgraph/transforms/test/other.py
index dc258ef97a..b8cb95cff7 100644
--- a/taskcluster/gecko_taskgraph/transforms/test/other.py
+++ b/taskcluster/gecko_taskgraph/transforms/test/other.py
@@ -16,6 +16,7 @@ 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.perftest import is_external_browser
from gecko_taskgraph.util.platforms import platform_family
from gecko_taskgraph.util.templates import merge
@@ -260,6 +261,20 @@ def handle_keyed_by(config, tasks):
@transforms.add
+def setup_raptor_external_browser_platforms(config, tasks):
+ for task in tasks:
+ if task["suite"] != "raptor":
+ yield task
+ continue
+
+ if is_external_browser(task["try-name"]):
+ task["build-platform"] = "linux64/opt"
+ task["build-label"] = "build-linux64/opt"
+
+ yield task
+
+
+@transforms.add
def set_target(config, tasks):
for task in tasks:
build_platform = task["build-platform"]
@@ -308,25 +323,25 @@ def setup_browsertime(config, tasks):
ts = {
"by-test-platform": {
- "android.*": ["browsertime", "linux64-geckodriver", "linux64-node-16"],
- "linux.*": ["browsertime", "linux64-geckodriver", "linux64-node-16"],
+ "android.*": ["browsertime", "linux64-geckodriver", "linux64-node"],
+ "linux.*": ["browsertime", "linux64-geckodriver", "linux64-node"],
"macosx1015.*": [
"browsertime",
"macosx64-geckodriver",
- "macosx64-node-16",
+ "macosx64-node",
],
"macosx1400.*": [
"browsertime",
"macosx64-aarch64-geckodriver",
- "macosx64-aarch64-node-16",
+ "macosx64-aarch64-node",
],
"windows.*aarch64.*": [
"browsertime",
"win32-geckodriver",
- "win32-node-16",
+ "win32-node",
],
- "windows.*-32.*": ["browsertime", "win32-geckodriver", "win32-node-16"],
- "windows.*-64.*": ["browsertime", "win64-geckodriver", "win64-node-16"],
+ "windows.*-32.*": ["browsertime", "win32-geckodriver", "win32-node"],
+ "windows.*-64.*": ["browsertime", "win64-geckodriver", "win64-node"],
},
}
diff --git a/taskcluster/gecko_taskgraph/transforms/test/raptor.py b/taskcluster/gecko_taskgraph/transforms/test/raptor.py
index 0667d22bb2..18e21e6a1e 100644
--- a/taskcluster/gecko_taskgraph/transforms/test/raptor.py
+++ b/taskcluster/gecko_taskgraph/transforms/test/raptor.py
@@ -10,6 +10,7 @@ from voluptuous import Extra, Optional, Required
from gecko_taskgraph.transforms.test import test_description_schema
from gecko_taskgraph.util.copy_task import copy_task
+from gecko_taskgraph.util.perftest import is_external_browser
transforms = TransformSequence()
task_transforms = TransformSequence()
@@ -316,6 +317,28 @@ def add_extra_options(config, tests):
yield test
+@transforms.add
+def modify_mozharness_configs(config, tests):
+ for test in tests:
+ if not is_external_browser(test["app"]):
+ yield test
+ continue
+
+ test_platform = test["test-platform"]
+ mozharness = test.setdefault("mozharness", {})
+ if "mac" in test_platform:
+ mozharness["config"] = ["raptor/mac_external_browser_config.py"]
+ elif "windows" in test_platform:
+ mozharness["config"] = ["raptor/windows_external_browser_config.py"]
+ elif "linux" in test_platform:
+ mozharness["config"] = ["raptor/linux_external_browser_config.py"]
+ elif "android" in test_platform:
+ test["target"] = "target.tar.bz2"
+ mozharness["config"] = ["raptor/android_hw_external_browser_config.py"]
+
+ yield test
+
+
@task_transforms.add
def add_scopes_and_proxy(config, tasks):
for task in tasks:
diff --git a/taskcluster/gecko_taskgraph/util/partials.py b/taskcluster/gecko_taskgraph/util/partials.py
index 1a3affcc42..b04fc64e17 100644
--- a/taskcluster/gecko_taskgraph/util/partials.py
+++ b/taskcluster/gecko_taskgraph/util/partials.py
@@ -251,7 +251,17 @@ def _populate_nightly_history(product, branch, maxbuilds=4, maxsearch=10):
builds[platform][locale] = dict()
if len(builds[platform][locale]) >= maxbuilds:
continue
+ if "buildID" not in history["platforms"][platform]["locales"][locale]:
+ continue
buildid = history["platforms"][platform]["locales"][locale]["buildID"]
+ if (
+ "completes" not in history["platforms"][platform]["locales"][locale]
+ or len(
+ history["platforms"][platform]["locales"][locale]["completes"]
+ )
+ == 0
+ ):
+ continue
url = history["platforms"][platform]["locales"][locale]["completes"][0][
"fileUrl"
]
diff --git a/taskcluster/gecko_taskgraph/util/perftest.py b/taskcluster/gecko_taskgraph/util/perftest.py
new file mode 100644
index 0000000000..01b153be37
--- /dev/null
+++ b/taskcluster/gecko_taskgraph/util/perftest.py
@@ -0,0 +1,18 @@
+# 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/.
+
+
+def is_external_browser(label):
+ if any(
+ external_browser in label
+ for external_browser in (
+ "safari",
+ "chrome",
+ "custom-car",
+ "chrome-m",
+ "cstm-car-m",
+ )
+ ):
+ return True
+ return False