From 8dfca22252d8ac0a5f7465ba64f730f23031309d Mon Sep 17 00:00:00 2001 From: Marco Castelluccio Date: Sat, 19 Sep 2026 02:45:56 +0200 Subject: [PATCH 1/2] Don't walk the whole repository for root-level files in test selection heuristics For a root-level file (e.g. `mach`), the parent directory is the repository root, so the sibling search walked the entire tree and scheduled every test manifest. Skip the sibling search for such files. --- bugbug/test_scheduling.py | 8 +++++++- tests/test_test_scheduling.py | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/bugbug/test_scheduling.py b/bugbug/test_scheduling.py index e8b0ebed1f..22bb989161 100644 --- a/bugbug/test_scheduling.py +++ b/bugbug/test_scheduling.py @@ -1009,7 +1009,9 @@ def collect_support_files(value): # If a manifest, a test, or a support file is modified, run the manifest that includes it. if path in manifest_by_path: manifests.update(manifest_by_path[path]) - else: + # Skip root-level files, otherwise we'd walk the whole repository and + # schedule every manifest. + elif (repo_dir / path).parent != repo_dir: # Find manifests that are in test subfolders close to a modified file (e.g. if dom/battery/BatteryManager.cpp is modified, we should run dom/battery/test/chrome.toml and dom/battery/test/mochitest.toml). for sibling in (repo_dir / path).parent.rglob("*"): if sibling.is_dir() and repository.is_test(f"{str(sibling)}/"): @@ -1101,6 +1103,10 @@ def find_tasks_for_paths( # Any file in a folder close to a gtest folder is modified (e.g. dom/media/CubebUtils.cpp and we have dom/media/gtest/). if not select_gtest: for path in paths: + # Skip root-level files, otherwise we'd walk the whole repository. + if (repo_dir / path).parent == repo_dir: + continue + for sibling in (repo_dir / path).parent.rglob("*"): if sibling.is_dir() and any( part in _GTEST_FOLDERS for part in sibling.parts diff --git a/tests/test_test_scheduling.py b/tests/test_test_scheduling.py index a22143fcb3..5524831883 100644 --- a/tests/test_test_scheduling.py +++ b/tests/test_test_scheduling.py @@ -1078,6 +1078,11 @@ def test_find_manifests_for_paths(tmp_path) -> None: "test/chrome.toml" } + # A root-level file that is not referenced by any manifest must not + # schedule every manifest in the repository. + (tmp_path / "mach").touch() + assert test_scheduling.find_manifests_for_paths(str(tmp_path), ["mach"]) == set() + assert test_scheduling.find_manifests_for_paths( str(tmp_path), ["test/test_resolve_uris_ipc.js"] ) == { From 238a807b0d4fac9dcf19675f19189b20e4d3fb9f Mon Sep 17 00:00:00 2001 From: Marco Castelluccio Date: Sat, 19 Sep 2026 02:46:20 +0200 Subject: [PATCH 2/2] Don't schedule manifests close to a file when there are too many of them Files directly inside large directories (e.g. dom/moz.build, browser/moz.build) are close to hundreds of test manifests, which makes the sibling heuristic uninformative and schedules far too much. Cap the number of manifests the sibling search may return for a single path at 42; above that, leave the decision to the model. --- bugbug/test_scheduling.py | 11 ++++++++++- tests/test_test_scheduling.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/bugbug/test_scheduling.py b/bugbug/test_scheduling.py index 22bb989161..05715c0486 100644 --- a/bugbug/test_scheduling.py +++ b/bugbug/test_scheduling.py @@ -914,6 +914,11 @@ def get_test_info(date: datetime) -> dict[str, Any]: manifest_by_path: dict[str, set[str]] | None = None +# If a modified file is close to more manifests than this, it is too broad +# (e.g. dom/moz.build) for the sibling heuristic to be informative, so we +# don't schedule any of them and leave the decision to the model. +MAX_SIBLING_MANIFESTS = 42 + def find_manifests_for_paths(repo_dir_str: str, paths: list[str]) -> set[str]: global manifest_by_path @@ -1013,14 +1018,18 @@ def collect_support_files(value): # schedule every manifest. elif (repo_dir / path).parent != repo_dir: # Find manifests that are in test subfolders close to a modified file (e.g. if dom/battery/BatteryManager.cpp is modified, we should run dom/battery/test/chrome.toml and dom/battery/test/mochitest.toml). + sibling_manifests: set[str] = set() for sibling in (repo_dir / path).parent.rglob("*"): if sibling.is_dir() and repository.is_test(f"{str(sibling)}/"): - manifests.update( + sibling_manifests.update( str(f.relative_to(repo_dir)) for f in sibling.rglob("*.toml") if f.is_file() ) + if len(sibling_manifests) <= MAX_SIBLING_MANIFESTS: + manifests.update(sibling_manifests) + # If a web-platform test or meta is modified, run the relevant web-platform folder. if not any(path.endswith(ignore) for ignore in ("/META.yml", "/README.md")): for base in ("testing/web-platform/mozilla", "testing/web-platform"): diff --git a/tests/test_test_scheduling.py b/tests/test_test_scheduling.py index 5524831883..b5efe3d9bd 100644 --- a/tests/test_test_scheduling.py +++ b/tests/test_test_scheduling.py @@ -1083,6 +1083,36 @@ def test_find_manifests_for_paths(tmp_path) -> None: (tmp_path / "mach").touch() assert test_scheduling.find_manifests_for_paths(str(tmp_path), ["mach"]) == set() + # A file close to too many manifests (e.g. dom/moz.build) must not + # schedule all of them. + (tmp_path / "hub" / "moz.build").parent.mkdir(parents=True) + (tmp_path / "hub" / "moz.build").touch() + for i in range(test_scheduling.MAX_SIBLING_MANIFESTS): + (tmp_path / "hub" / f"component{i}" / "test").mkdir(parents=True) + (tmp_path / "hub" / f"component{i}" / "test" / "mochitest.toml").touch() + + assert ( + len(test_scheduling.find_manifests_for_paths(str(tmp_path), ["hub/moz.build"])) + == test_scheduling.MAX_SIBLING_MANIFESTS + ) + + (tmp_path / "hub" / "one_more" / "test").mkdir(parents=True) + (tmp_path / "hub" / "one_more" / "test" / "mochitest.toml").touch() + + assert ( + test_scheduling.find_manifests_for_paths(str(tmp_path), ["hub/moz.build"]) + == set() + ) + + # The cap applies per path, so a narrow file is still scheduled when + # modified together with a broad one. + assert test_scheduling.find_manifests_for_paths( + str(tmp_path), ["hub/moz.build", "dom/battery/BatteryManager.cpp"] + ) == { + "dom/battery/test/mochitest.toml", + "dom/battery/test/chrome.toml", + } + assert test_scheduling.find_manifests_for_paths( str(tmp_path), ["test/test_resolve_uris_ipc.js"] ) == {