feat(verify): pass parameters into verifications (Bug 1748929), r=releng-reviewers,gabriel
authorAndrew Halberstadt <ahal@pm.me>
Thu, 31 Mar 2022 13:48:54 +0000
changeset 394 b875b88f21bbb1c7f2b500432268292ae831c6f7
parent 393 eacf990177b3ca313b35b2158fb8e00a6e4c86db
child 395 4783a2ce84ac670c0e1b00b4abaa64f93949e8f7
push id219
push userahalberstadt@mozilla.com
push dateThu, 31 Mar 2022 13:50:59 +0000
treeherdertaskgraph@b875b88f21bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersreleng-reviewers, gabriel
bugs1748929
feat(verify): pass parameters into verifications (Bug 1748929), r=releng-reviewers,gabriel This syncs a feature from Gecko. Jira: RELENG-781 Differential Revision: https://phabricator.services.mozilla.com/D142235
src/taskgraph/generator.py
src/taskgraph/test/conftest.py
src/taskgraph/util/verify.py
--- a/src/taskgraph/generator.py
+++ b/src/taskgraph/generator.py
@@ -312,47 +312,51 @@ class TaskGraphGenerator:
                 logger.exception(f"Error loading tasks for kind {kind_name}:")
                 raise
             for task in new_tasks:
                 if task.label in all_tasks:
                     raise Exception("duplicate tasks with label " + task.label)
                 all_tasks[task.label] = task
             logger.info(f"Generated {len(new_tasks)} tasks for kind {kind_name}")
         full_task_set = TaskGraph(all_tasks, Graph(set(all_tasks), set()))
-        yield verifications("full_task_set", full_task_set, graph_config)
+        yield verifications("full_task_set", full_task_set, graph_config, parameters)
 
         logger.info("Generating full task graph")
         edges = set()
         for t in full_task_set:
             for depname, dep in t.dependencies.items():
                 edges.add((t.label, dep, depname))
 
         full_task_graph = TaskGraph(all_tasks, Graph(full_task_set.graph.nodes, edges))
         logger.info(
             "Full task graph contains %d tasks and %d dependencies"
             % (len(full_task_set.graph.nodes), len(edges))
         )
-        yield verifications("full_task_graph", full_task_graph, graph_config)
+        yield verifications(
+            "full_task_graph", full_task_graph, graph_config, parameters
+        )
 
         logger.info("Generating target task set")
         target_task_set = TaskGraph(
             dict(all_tasks), Graph(set(all_tasks.keys()), set())
         )
         for fltr in filters:
             old_len = len(target_task_set.graph.nodes)
             target_tasks = set(fltr(target_task_set, parameters, graph_config))
             target_task_set = TaskGraph(
                 {l: all_tasks[l] for l in target_tasks}, Graph(target_tasks, set())
             )
             logger.info(
                 "Filter %s pruned %d tasks (%d remain)"
                 % (fltr.__name__, old_len - len(target_tasks), len(target_tasks))
             )
 
-        yield verifications("target_task_set", target_task_set, graph_config)
+        yield verifications(
+            "target_task_set", target_task_set, graph_config, parameters
+        )
 
         logger.info("Generating target task graph")
         # include all docker-image build tasks here, in case they are needed for a graph morph
         docker_image_tasks = {
             t.label
             for t in full_task_graph.tasks.values()
             if t.attributes["kind"] == "docker-image"
         }
@@ -367,39 +371,45 @@ class TaskGraphGenerator:
             % (len(always_target_tasks) - len(always_target_tasks & target_tasks))
         )
         target_graph = full_task_graph.graph.transitive_closure(
             target_tasks | docker_image_tasks | always_target_tasks
         )
         target_task_graph = TaskGraph(
             {l: all_tasks[l] for l in target_graph.nodes}, target_graph
         )
-        yield verifications("target_task_graph", target_task_graph, graph_config)
+        yield verifications(
+            "target_task_graph", target_task_graph, graph_config, parameters
+        )
 
         logger.info("Generating optimized task graph")
         existing_tasks = parameters.get("existing_tasks")
         do_not_optimize = set(parameters.get("do_not_optimize", []))
         if not parameters.get("optimize_target_tasks", True):
             do_not_optimize = set(target_task_set.graph.nodes).union(do_not_optimize)
         optimized_task_graph, label_to_taskid = optimize_task_graph(
             target_task_graph,
             parameters,
             do_not_optimize,
             self._decision_task_id,
             existing_tasks=existing_tasks,
         )
 
-        yield verifications("optimized_task_graph", optimized_task_graph, graph_config)
+        yield verifications(
+            "optimized_task_graph", optimized_task_graph, graph_config, parameters
+        )
 
         morphed_task_graph, label_to_taskid = morph(
             optimized_task_graph, label_to_taskid, parameters, graph_config
         )
 
         yield "label_to_taskid", label_to_taskid
-        yield verifications("morphed_task_graph", morphed_task_graph, graph_config)
+        yield verifications(
+            "morphed_task_graph", morphed_task_graph, graph_config, parameters
+        )
 
     def _run_until(self, name):
         while name not in self._run_results:
             try:
                 k, v = next(self._run)
             except StopIteration:
                 raise AttributeError(f"No such run result {name}")
             self._run_results[k] = v
--- a/src/taskgraph/test/conftest.py
+++ b/src/taskgraph/test/conftest.py
@@ -107,16 +107,17 @@ def maketgg(monkeypatch):
         target_tasks_mod._target_task_methods["test_method"] = target_tasks_method
         monkeypatch.setattr(
             optimize_mod, "_make_default_strategies", make_fake_strategies
         )
 
         parameters = FakeParameters(
             {
                 "_kinds": kinds,
+                "project": "",
                 "target_tasks_method": "test_method",
                 "try_mode": None,
                 "tasks_for": "hg-push",
             }
         )
         parameters.update(params)
 
         monkeypatch.setattr(generator, "load_graph_config", fake_load_graph_config)
--- a/src/taskgraph/util/verify.py
+++ b/src/taskgraph/util/verify.py
@@ -3,55 +3,76 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
 import logging
 import sys
 
 import attr
 
+from taskgraph.util.attributes import match_run_on_projects
+
 logger = logging.getLogger(__name__)
 
 
 @attr.s(frozen=True)
+class Verification:
+    verify = attr.ib()
+    run_on_projects = attr.ib()
+
+
+@attr.s(frozen=True)
 class VerificationSequence:
     """
     Container for a sequence of verifications over a TaskGraph. Each
     verification is represented as a callable taking (task, taskgraph,
     scratch_pad), called for each task in the taskgraph, and one more
     time with no task but with the taskgraph and the same scratch_pad
     that was passed for each task.
     """
 
     _verifications = attr.ib(factory=dict)
 
-    def __call__(self, graph_name, graph, graph_config):
+    def __call__(self, graph_name, graph, graph_config, parameters):
         for verification in self._verifications.get(graph_name, []):
+            if not match_run_on_projects(
+                parameters["project"], verification.run_on_projects
+            ):
+                continue
             scratch_pad = {}
             graph.for_each_task(
-                verification, scratch_pad=scratch_pad, graph_config=graph_config
+                verification.verify,
+                scratch_pad=scratch_pad,
+                graph_config=graph_config,
+                parameters=parameters,
             )
-            verification(
-                None, graph, scratch_pad=scratch_pad, graph_config=graph_config
+            verification.verify(
+                None,
+                graph,
+                scratch_pad=scratch_pad,
+                graph_config=graph_config,
+                parameters=parameters,
             )
         return graph_name, graph
 
-    def add(self, graph_name):
+    def add(self, graph_name, run_on_projects={"all"}):
         def wrap(func):
-            self._verifications.setdefault(graph_name, []).append(func)
+            self._verifications.setdefault(graph_name, []).append(
+                Verification(func, run_on_projects)
+            )
             return func
 
         return wrap
 
 
 verifications = VerificationSequence()
 
 
 @verifications.add("full_task_graph")
-def verify_task_graph_symbol(task, taskgraph, scratch_pad, graph_config):
+def verify_task_graph_symbol(task, taskgraph, scratch_pad, graph_config, parameters):
     """
     This function verifies that tuple
     (collection.keys(), machine.platform, groupSymbol, symbol) is unique
     for a target task graph.
     """
     if task is None:
         return
     task_dict = task.task
@@ -72,17 +93,19 @@ def verify_task_graph_symbol(task, taskg
                         task.label, scratch_pad[key], key
                     )
                 )
             else:
                 scratch_pad[key] = task.label
 
 
 @verifications.add("full_task_graph")
-def verify_trust_domain_v2_routes(task, taskgraph, scratch_pad, graph_config):
+def verify_trust_domain_v2_routes(
+    task, taskgraph, scratch_pad, graph_config, parameters
+):
     """
     This function ensures that any two tasks have distinct ``index.{trust-domain}.v2`` routes.
     """
     if task is None:
         return
     route_prefix = "index.{}.v2".format(graph_config["trust-domain"])
     task_dict = task.task
     routes = task_dict.get("routes", [])
@@ -95,17 +118,19 @@ def verify_trust_domain_v2_routes(task, 
                         task.label, scratch_pad[route], route
                     )
                 )
             else:
                 scratch_pad[route] = task.label
 
 
 @verifications.add("full_task_graph")
-def verify_routes_notification_filters(task, taskgraph, scratch_pad, graph_config):
+def verify_routes_notification_filters(
+    task, taskgraph, scratch_pad, graph_config, parameters
+):
     """
     This function ensures that only understood filters for notifications are
     specified.
 
     See: https://docs.taskcluster.net/reference/core/taskcluster-notify/docs/usage
     """
     if task is None:
         return
@@ -122,17 +147,17 @@ def verify_routes_notification_filters(t
                 raise Exception(
                     "{} has invalid notification filter ({})".format(
                         task.label, route_filter
                     )
                 )
 
 
 @verifications.add("full_task_graph")
-def verify_dependency_tiers(task, taskgraph, scratch_pad, graph_config):
+def verify_dependency_tiers(task, taskgraph, scratch_pad, graph_config, parameters):
     tiers = scratch_pad
     if task is not None:
         tiers[task.label] = (
             task.task.get("extra", {}).get("treeherder", {}).get("tier", sys.maxsize)
         )
     else:
 
         def printable_tier(tier):
@@ -154,16 +179,16 @@ def verify_dependency_tiers(task, taskgr
                             printable_tier(tier),
                             d,
                             printable_tier(tiers[d]),
                         )
                     )
 
 
 @verifications.add("optimized_task_graph")
-def verify_always_optimized(task, taskgraph, scratch_pad, graph_config):
+def verify_always_optimized(task, taskgraph, scratch_pad, graph_config, parameters):
     """
     This function ensures that always-optimized tasks have been optimized.
     """
     if task is None:
         return
     if task.task.get("workerType") == "always-optimized":
         raise Exception(f"Could not optimize the task {task.label!r}")