diff --git a/.github/scripts/tests/therock_nightly_test_scope_test.py b/.github/scripts/tests/therock_nightly_test_scope_test.py new file mode 100644 index 000000000000..ec317857491a --- /dev/null +++ b/.github/scripts/tests/therock_nightly_test_scope_test.py @@ -0,0 +1,59 @@ +import json +import os +import sys +import tempfile +import unittest +from pathlib import Path +from typing import List + +sys.path.insert(0, os.fspath(Path(__file__).parent.parent)) +import therock_nightly_test_scope as scope + + +def write_config(tmpdir: str, names: List[tuple]) -> Path: + entries = [ + { + "name": name, + "url": f"ROCm/{name}", + "branch": "develop", + "category": category, + "auto_subtree_pull": False, + "auto_subtree_push": True, + "monorepo_source_of_truth": True, + } + for category, name in names + ] + config_path = Path(tmpdir) / "repos-config.json" + config_path.write_text(json.dumps({"repositories": entries}), encoding="utf-8") + return config_path + + +class NightlyTestScopeTest(unittest.TestCase): + def test_returns_sorted_category_name_pairs(self): + with tempfile.TemporaryDirectory() as tmpdir: + config_path = write_config( + tmpdir, + [ + ("projects", "rocsparse"), + ("shared", "hipblas-common"), + ("projects", "rocblas"), + ], + ) + self.assertEqual( + scope.get_test_projects(config_path), + ["projects/rocblas", "projects/rocsparse", "shared/hipblas-common"], + ) + + def test_empty_config_fails_instead_of_testing_everything(self): + # An empty changed_projects means "test everything" to TheRock. + with tempfile.TemporaryDirectory() as tmpdir: + config_path = write_config(tmpdir, []) + with self.assertRaises(SystemExit): + scope.main(["--config", os.fspath(config_path)]) + + def test_real_config_is_non_empty(self): + self.assertTrue(scope.get_test_projects(scope.DEFAULT_CONFIG_PATH)) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/scripts/therock_nightly_test_scope.py b/.github/scripts/therock_nightly_test_scope.py new file mode 100644 index 000000000000..6cd5471307e3 --- /dev/null +++ b/.github/scripts/therock_nightly_test_scope.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +"""Emits the subtrees the multi-arch nightly CI should test, as `category/name`. + +TEMPORARY: a scheduled run has no diff to narrow testing against, so TheRock +tests every component it builds, including ROCm system components (hip-tests, +rocrtst, rocgdb, amdsmi, ...) that no change here can affect. Passing every +subtree in repos-config.json as `changed_projects` scopes it back to the code +this repo owns. Delete this script, its test, and the `configure` job in +therock-multi-arch-ci-nightly.yml once TheRock scopes schedule runs natively. +""" + +import argparse +import logging +from pathlib import Path +from typing import List, Optional + +from ci_utils import append_step_summary, set_github_output +from config_loader import load_repo_config +from pr_detect_changed_subtrees import get_valid_prefixes + +DEFAULT_CONFIG_PATH = Path(__file__).resolve().parent.parent / "repos-config.json" + + +def get_test_projects(config_path: Path) -> List[str]: + """Return every subtree in repos-config.json as a sorted `category/name` list.""" + return sorted(get_valid_prefixes(load_repo_config(str(config_path)))) + + +def format_step_summary(test_projects: List[str]) -> str: + """Render the test scope as a markdown step summary.""" + subtrees = "\n".join(test_projects) + return ( + "### Nightly test scope\n\n" + "Testing rocm-libraries subtrees and their direct consumers only:\n\n" + f"```\n{subtrees}\n```\n" + ) + + +def main(argv: Optional[List[str]] = None) -> None: + logging.basicConfig(level=logging.INFO) + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--config", + type=Path, + default=DEFAULT_CONFIG_PATH, + help="Path to the repos-config.json file", + ) + args = parser.parse_args(argv) + + test_projects = get_test_projects(args.config) + if not test_projects: + # TheRock reads an empty `changed_projects` as "test everything". + raise SystemExit(f"No subtrees found in {args.config}") + + set_github_output({"test_projects": ",".join(test_projects)}) + append_step_summary(format_step_summary(test_projects)) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/therock-multi-arch-ci-nightly.yml b/.github/workflows/therock-multi-arch-ci-nightly.yml index b32752dbf10d..9d68a6d74e47 100644 --- a/.github/workflows/therock-multi-arch-ci-nightly.yml +++ b/.github/workflows/therock-multi-arch-ci-nightly.yml @@ -9,6 +9,12 @@ # test hardware exists for that family yet (see amdgpu_family_matrix.py in # TheRock, family key "gfx125x": test-runs-on is empty everywhere). # +# Testing is scoped to the subtrees listed in .github/repos-config.json rather +# than to all of ROCm. A schedule run has no diff to narrow against, so TheRock +# would otherwise test every component it builds, including system components +# (hip-tests, rocrtst, rocgdb, rocprofiler-*, amdsmi, rccl, ...) that this +# repository cannot affect. +# # For manual triggering with custom GPU families, use therock-multi-arch-ci.yml. name: TheRock Multi-Arch Nightly CI @@ -33,6 +39,30 @@ concurrency: cancel-in-progress: true jobs: + configure: + name: Configure Test Scope + runs-on: ubuntu-26.04 + outputs: + test_projects: ${{ steps.scope.outputs.test_projects }} + steps: + - name: Checkout CI config + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: .github + sparse-checkout-cone-mode: true + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + + - name: Install python dependencies + run: pip install pydantic requests + + - name: Collect rocm-libraries subtrees + id: scope + run: python .github/scripts/therock_nightly_test_scope.py + setup: uses: ROCm/TheRock/.github/workflows/setup_multi_arch.yml@47434387de730d51788b705e5600602dd6064997 # 2026-10-16 with: @@ -85,7 +115,7 @@ jobs: linux_build_and_test: name: Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} - needs: [setup] + needs: [configure, setup] if: >- ${{ needs.setup.outputs.linux_build_config != '' && @@ -101,8 +131,12 @@ jobs: rocm_rpm_package_version: ${{ needs.setup.outputs.rocm_rpm_package_version }} test_type: ${{ needs.setup.outputs.test_type }} external_repo_config: ${{ needs.setup.outputs.external_repo_config }} - # Empty string triggers full test run in downstream workflow - changed_projects: '' + # Scope testing to the subtrees this repository owns (plus the consumers + # TheRock's dependency graph pulls in). An empty string would mean "test + # everything", which on a schedule run spends GPU runner time on ROCm + # system components (hip-tests, rocrtst, rocgdb, rocprofiler-*, amdsmi, + # ...) that no rocm-libraries change can affect. + changed_projects: ${{ needs.configure.outputs.test_projects }} repository: ROCm/TheRock ref: 47434387de730d51788b705e5600602dd6064997 # 2026-10-16 permissions: @@ -111,7 +145,7 @@ jobs: windows_build_and_test: name: Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} - needs: [setup] + needs: [configure, setup] if: >- ${{ needs.setup.outputs.windows_build_config != '' && @@ -125,8 +159,8 @@ jobs: rocm_package_version: ${{ needs.setup.outputs.rocm_package_version }} test_type: ${{ needs.setup.outputs.test_type }} external_repo_config: ${{ needs.setup.outputs.external_repo_config }} - # Empty string triggers full test run in downstream workflow - changed_projects: '' + # See the Linux job above: scope testing to rocm-libraries-owned subtrees. + changed_projects: ${{ needs.configure.outputs.test_projects }} repository: ROCm/TheRock ref: 47434387de730d51788b705e5600602dd6064997 # 2026-10-16 permissions: @@ -137,6 +171,7 @@ jobs: name: Multi-Arch CI Summary if: always() needs: + - configure - setup - linux_build_and_test - windows_build_and_test