Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/scripts/tests/therock_nightly_test_scope_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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 make_entry(name: str, category: str) -> dict:
return {
"name": name,
"url": f"ROCm/{name}",
"branch": "develop",
"category": category,
"auto_subtree_pull": False,
"auto_subtree_push": True,
"monorepo_source_of_truth": True,
}


def write_config(tmpdir: str, entries: List[dict]) -> Path:
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,
[
make_entry("rocblas", "projects"),
make_entry("rocsparse", "projects"),
make_entry("hipblas-common", "shared"),
],
)
self.assertEqual(
scope.get_test_projects(config_path),
[
"projects/rocblas",
"projects/rocsparse",
"shared/hipblas-common",
],
)

def test_includes_entries_regardless_of_subtree_sync_flags(self):
# The nightly scope is "everything this repo owns", so entries are not
# filtered by the auto_subtree_pull/push flags that the sync tooling uses.
with tempfile.TemporaryDirectory() as tmpdir:
entry = make_entry("rocprim", "projects")
entry["auto_subtree_push"] = False
entry["monorepo_source_of_truth"] = False
config_path = write_config(tmpdir, [entry])
self.assertEqual(scope.get_test_projects(config_path), ["projects/rocprim"])

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):
projects = scope.get_test_projects(scope.DEFAULT_CONFIG_PATH)
self.assertTrue(projects)
for project in projects:
self.assertIn("/", project)

def test_step_summary_lists_each_subtree_on_its_own_line(self):
summary = scope.format_step_summary(["projects/rocblas", "shared/rocroller"])
self.assertIn("### Nightly test scope", summary)
self.assertIn("projects/rocblas\nshared/rocroller", summary)


if __name__ == "__main__":
unittest.main()
92 changes: 92 additions & 0 deletions .github/scripts/therock_nightly_test_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3

"""
TheRock Nightly Test Scope
--------------------------
Computes the set of projects the multi-arch nightly CI workflow should test.

A scheduled run has no diff to narrow testing against, so TheRock would
otherwise test every component it builds, including ROCm system components
(hip-tests, rocrtst, rocgdb, rocprofiler-*, amdsmi, rccl, ...) that no change
in this repository can affect. This script emits every subtree registered in
.github/repos-config.json in `category/name` form, which the workflow passes to
TheRock as `changed_projects` so testing stays scoped to the code this
repository owns (plus the consumers TheRock's dependency graph pulls in).
Comment on lines +8 to +14

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See this issue please:

We can build something into the scheduled CI that limits which builds/tests to run to those affected by specific submodules only. Consider that there are 11 submodules in https://github.com/ROCm/TheRock/blob/main/.gitmodules and each may want to run some tests (filtered following https://github.com/ROCm/TheRock/blob/main/docs/rfcs/RFC0013-Consumer-Based-Test-Selection.md). As implemented here, each of up to those 11 repositories may want to fork this 200 lines of code. We should teach the common code how to handle this. Each repository shouldn't need to maintain its own list of repos-config.json files for the CI system to understand how to test changes in it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point! that's a better approach than this (hacky for sure)


Arguments:
--config : OPTIONAL, path to the repos-config.json file.

Outputs:
Writes 'test_projects' to the GitHub Actions $GITHUB_OUTPUT file as a
comma-separated list of subtrees in `category/name` format, and appends a
human-readable scope listing to $GITHUB_STEP_SUMMARY. Both are printed to
stdout when run outside of GitHub Actions.

Example Usage:
python therock_nightly_test_scope.py
"""

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

logger = logging.getLogger(__name__)

SCRIPT_DIR = Path(__file__).resolve().parent
DEFAULT_CONFIG_PATH = SCRIPT_DIR.parent / "repos-config.json"


def parse_arguments(argv: Optional[List[str]] = None) -> argparse.Namespace:
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(
description="Compute the nightly multi-arch CI test scope."
)
parser.add_argument(
"--config",
type=Path,
default=DEFAULT_CONFIG_PATH,
help="Path to the repos-config.json file",
)
return parser.parse_args(argv)


def get_test_projects(config_path: Path) -> List[str]:
"""Return every subtree in repos-config.json as a sorted `category/name` list."""
config = load_repo_config(str(config_path))
return sorted(get_valid_prefixes(config))


def format_step_summary(test_projects: List[str]) -> str:
"""Render the test scope as a markdown step summary."""
subtree_list = "\n".join(test_projects)
return (
"### Nightly test scope\n"
"\n"
"Testing rocm-libraries subtrees and their direct consumers only:\n"
"\n"
f"```\n{subtree_list}\n```\n"
)


def main(argv: Optional[List[str]] = None) -> None:
"""Emit the nightly test scope for the multi-arch CI workflow."""
logging.basicConfig(level=logging.INFO)
args = parse_arguments(argv)

test_projects = get_test_projects(args.config)
if not test_projects:
# TheRock reads an empty `changed_projects` as "test everything", which
# is the opposite of this job's purpose, so fail instead of emitting it.
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()
49 changes: 43 additions & 6 deletions .github/workflows/therock-multi-arch-ci-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,6 +39,32 @@ concurrency:
cancel-in-progress: true

jobs:
configure:
name: Configure Test Scope
runs-on: ubuntu-24.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: |
python -m pip install --upgrade pip
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:
Expand Down Expand Up @@ -85,7 +117,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 != '' &&
Expand All @@ -101,8 +133,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:
Expand All @@ -111,7 +147,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 != '' &&
Expand All @@ -125,8 +161,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:
Expand All @@ -137,6 +173,7 @@ jobs:
name: Multi-Arch CI Summary
if: always()
needs:
- configure
- setup
- linux_build_and_test
- windows_build_and_test
Expand Down