diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /testing/web-platform/tests/tools/ci/tc/taskgraph.py | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/tools/ci/tc/taskgraph.py')
-rw-r--r-- | testing/web-platform/tests/tools/ci/tc/taskgraph.py | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/testing/web-platform/tests/tools/ci/tc/taskgraph.py b/testing/web-platform/tests/tools/ci/tc/taskgraph.py new file mode 100644 index 0000000000..d270dad460 --- /dev/null +++ b/testing/web-platform/tests/tools/ci/tc/taskgraph.py @@ -0,0 +1,171 @@ +# mypy: allow-untyped-defs + +import json +import os +import re +from collections import OrderedDict +from copy import deepcopy + +import yaml + +here = os.path.dirname(__file__) + + +def first(iterable): + # First item from a list or iterator + if not hasattr(iterable, "next"): + if hasattr(iterable, "__iter__"): + iterable = iter(iterable) + else: + raise ValueError("Object isn't iterable") + return next(iterable) + + +def load_task_file(path): + with open(path) as f: + return yaml.safe_load(f) + + +def update_recursive(data, update_data): + for key, value in update_data.items(): + if key not in data: + data[key] = value + else: + initial_value = data[key] + if isinstance(value, dict): + if not isinstance(initial_value, dict): + raise ValueError("Variable %s has inconsistent types " + "(expected object)" % key) + update_recursive(initial_value, value) + elif isinstance(value, list): + if not isinstance(initial_value, list): + raise ValueError("Variable %s has inconsistent types " + "(expected list)" % key) + initial_value.extend(value) + else: + data[key] = value + + +def resolve_use(task_data, templates): + rv = {} + if "use" in task_data: + for template_name in task_data["use"]: + update_recursive(rv, deepcopy(templates[template_name])) + update_recursive(rv, task_data) + rv.pop("use", None) + return rv + + +def resolve_name(task_data, default_name): + if "name" not in task_data: + task_data["name"] = default_name + return task_data + + +def resolve_chunks(task_data): + if "chunks" not in task_data: + return [task_data] + rv = [] + total_chunks = task_data["chunks"] + for i in range(1, total_chunks + 1): + chunk_data = deepcopy(task_data) + chunk_data["chunks"] = {"id": i, + "total": total_chunks} + rv.append(chunk_data) + return rv + + +def replace_vars(input_string, variables): + # TODO: support replacing as a non-string type? + variable_re = re.compile(r"(?<!\\)\${([^}]+)}") + + def replacer(m): + var = m.group(1).split(".") + repl = variables + for part in var: + try: + repl = repl[part] + except Exception: + # Don't substitute + return m.group(0) + return str(repl) + + return variable_re.sub(replacer, input_string) + + +def sub_variables(data, variables): + if isinstance(data, str): + return replace_vars(data, variables) + if isinstance(data, list): + return [sub_variables(item, variables) for item in data] + if isinstance(data, dict): + return {key: sub_variables(value, variables) + for key, value in data.items()} + return data + + +def substitute_variables(task): + variables = {"vars": task.get("vars", {}), + "chunks": task.get("chunks", {})} + + return sub_variables(task, variables) + + +def expand_maps(task): + name = first(task.keys()) + if name != "$map": + return [task] + + map_data = task["$map"] + if set(map_data.keys()) != {"for", "do"}: + raise ValueError("$map objects must have exactly two properties named 'for' " + "and 'do' (got %s)" % ("no properties" if not map_data.keys() + else ", ". join(map_data.keys()))) + rv = [] + for for_data in map_data["for"]: + do_items = map_data["do"] + if not isinstance(do_items, list): + do_items = expand_maps(do_items) + for do_data in do_items: + task_data = deepcopy(for_data) + if len(do_data.keys()) != 1: + raise ValueError("Each item in the 'do' list must be an object " + "with a single property") + name = first(do_data.keys()) + update_recursive(task_data, deepcopy(do_data[name])) + rv.append({name: task_data}) + return rv + + +def load_tasks(tasks_data): + map_resolved_tasks = OrderedDict() + tasks = [] + + for task in tasks_data["tasks"]: + if len(task.keys()) != 1: + raise ValueError("Each task must be an object with a single property") + for task in expand_maps(task): + if len(task.keys()) != 1: + raise ValueError("Each task must be an object with a single property") + name = first(task.keys()) + data = task[name] + new_name = sub_variables(name, {"vars": data.get("vars", {})}) + if new_name in map_resolved_tasks: + raise ValueError("Got duplicate task name %s" % new_name) + map_resolved_tasks[new_name] = substitute_variables(data) + + for task_default_name, data in map_resolved_tasks.items(): + task = resolve_use(data, tasks_data["components"]) + task = resolve_name(task, task_default_name) + tasks.extend(resolve_chunks(task)) + + tasks = [substitute_variables(task_data) for task_data in tasks] + return OrderedDict([(t["name"], t) for t in tasks]) + + +def load_tasks_from_path(path): + return load_tasks(load_task_file(path)) + + +def run(venv, **kwargs): + print(json.dumps(load_tasks_from_path(os.path.join(here, "tasks", "test.yml")), indent=2)) |