Repository navigation
Changes to integrate Test Filter Standardization for miopen in TheRock #3595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 12 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
c833173
Changes to integrate Test Filter Standardization for miopen in TheRock
8e681bc
Check for the presence of ctest test file path and exit if not found
6547fbd
Merge branch 'main' into users/dravindr/miopen_test_filters
dileepr1 8de7262
Merge branch 'main' into users/dravindr/miopen_test_filters
dileepr1 8b25708
Removing # since the spaces between can vary. For example Test #1 an…
85c0b01
Move find_matching_gpu_arch() function to github_actions_utils.py so …
24c4195
Move ctest parallel count to a variable
04bccb7
Update the sys.path so that import github_actions_utils works
0b2fa8b
Change file name - test_filters.py to test_runner.py
61aa13c
Exit immediately if no tests are found.Add a better logic to identify…
0fd6551
Add documentation for test runner
c2f6376
Fixing pre-commit errors in README
1bc1ff9
Adding a timeout variable to ctest
b2b943c
Correct README.md filename
bf98091
Updates to documentation
479f891
Merge branch 'main' into users/dravindr/miopen_test_filters
tony-davis 84c4799
Correcting the test entry name in documentation
6b3b744
Merge branch 'main' into users/dravindr/miopen_test_filters
dileepr1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
build_tools/github_actions/test_executable_scripts/README_test_runner.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| # Test Runner | ||
|
|
||
| `test_runner.py` is a **generic test runner** used by GitHub Actions to run component tests (e.g. MIOpen). It relies on a standardized CTest naming and labeling scheme so that only tests valid for the current scenario are run. | ||
|
|
||
| This document describes how the mechanism works, what the component must provide, and how it ties into CI. | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## Overview | ||
|
|
||
| 1. The script is invoked by the **Test component** workflow with env vars set (component name, GPU arch, test type, sharding). | ||
| 1. The script expects the component under test to have a ctest based interface, with labels corresponding to test categories like quick, standard, comprehensive and full(which can be run for scenarios like smoke test, pre-commit etc) | ||
| 1. The component can opt to always exclude some tests or based on condition like OS and GPU where the test is getting run | ||
| 1. The script discovers which **GPU-specific test suites** exist by running `ctest --print-labels` and parsing labels of the form `ex_gpu_{gpu_arch}`. | ||
| 1. It builds and runs a **ctest** command with the right labels and options (category, parallelism, sharding, etc.). | ||
|
|
||
| No component-specific logic is required beyond using the expected test names and CTest labels. | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## Test Naming and Labels (Component Contract) | ||
|
|
||
| For this runner to work, the component’s CTest configuration must follow this convention. | ||
|
|
||
| ### CTest labels (discovery) | ||
|
|
||
| The script runs `ctest --print-labels --test-dir <component>` and collects every label that starts with **`ex_gpu_`**. The suffix is treated as the GPU architecture (e.g. `ex_gpu_gfx110X` → `gfx110X`, `ex_gpu_gfx950` → `gfx950`). Only labels whose suffix starts with `gfx` are used. | ||
|
|
||
| ### CTest labels (selection) | ||
|
|
||
| - **Category:** Tests are selected by **label** for the run (e.g. “run all tests with label `quick`” or `standard`). The script uses `-L <category>`. | ||
| - **GPU-specific suites:** For a given `gpu_arch`, the script looks for a label `ex_gpu_{gpu_arch}` (e.g. `ex_gpu_gfx1150`, `ex_gpu_gfx11X`). It uses `-L ex_gpu_<arch>` when it finds a matching label, or `-LE ex_gpu` to exclude all GPU-specific tests when no match is found or when no GPU is specified. | ||
|
|
||
| So the component must assign GPU-specific tests the label `ex_gpu_{gpu_arch}` (and typically a category label like `quick` or `standard`). | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## Execution Flow | ||
|
|
||
| 1. **Resolve component directory** | ||
| Map `TEST_COMPONENT` (e.g. `miopen`) to the test directory name (e.g. `MIOpen`) via `COMPONENT_DIR_MAPPING`. Fail if the test directory does not exist under `THEROCK_BIN_DIR`. | ||
| 1. **Discover GPU suites** | ||
| Run `ctest --print-labels --test-dir {THEROCK_BIN_DIR}/{TEST_COMPONENT}`. Collect every label that starts with `ex_gpu_` and whose suffix starts with `gfx` (e.g. `ex_gpu_gfx110X` → `gfx110X`). This yields the set of **available GPU architectures** for which a label exists. | ||
| 1. **Choose category** | ||
| From `TEST_TYPE`: `smoke` → `quick`, else → `standard`. | ||
| 1. **Resolve GPU arch** | ||
| Parse `AMDGPU_FAMILIES` for the first `gfx...` token (e.g. `gfx1151`). If missing or generic, the script will exclude all GPU-specific tests (`-LE ex_gpu`). | ||
| 1. **Match GPU to suite** | ||
| Using `find_matching_gpu_arch()` (from `github_actions_utils`): | ||
|
|
||
| - Prefer **exact** match in the discovered set (e.g. `gfx1151`). | ||
| - Else try **wildcard** patterns from most to least specific (e.g. for `gfx1151`: `gfx115X`, then `gfx11X`). | ||
| - If a match is found, add `-L ex_gpu_{matching_arch}`; otherwise add `-LE ex_gpu`. | ||
|
|
||
| 6. **Build ctest command** | ||
|
|
||
| - `ctest -L <category>` (and optionally `-L ex_gpu_<arch>` or `-LE ex_gpu`). | ||
| - Common options: `--output-on-failure`, `--parallel <N>`, `--test-dir`, `-V`, `--tests-information <SHARD_INDEX>,<TOTAL_SHARDS>`. | ||
| - Parallelism: default 8; can be adjusted according to `AMDGPU_FAMILIES` | ||
|
|
||
| 7. **Run ctest** | ||
| Execute the command in `THEROCK_DIR` with the environment that includes `ROCM_PATH`, `GTEST_SHARD_INDEX`, and `GTEST_TOTAL_SHARDS`. | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## CI Integration | ||
|
|
||
| - **Workflow:** `.github/workflows/test_component.yml` runs the test step with env vars such as `TEST_COMPONENT`, `TEST_TYPE`, `AMDGPU_FAMILIES`, `SHARD_INDEX`, `TOTAL_SHARDS`. | ||
| - **Test script:** For components that use this mechanism, the test script is set to `python .../test_runner.py` in `build_tools/github_actions/fetch_test_configurations.py` (e.g. MIOpen). | ||
| - **Sharding:** The workflow matrix uses `shard_arr` from the same config; the script passes sharding to ctest via `--tests-information` and to GTest via `GTEST_SHARD_INDEX` / `GTEST_TOTAL_SHARDS`. | ||
|
|
||
| ______________________________________________________________________ | ||
|
|
||
| ## Adding a New Component to This Runner | ||
|
|
||
| 1. **In the component (e.g. CMake/CTest):** | ||
|
|
||
| - Name GPU-specific tests `{target}-{category}-{gpu_arch}-suite`. | ||
| - Assign labels `ex_gpu_{gpu_arch}` and the category label (`quick` / `standard` or equivalent). | ||
|
|
||
| 2. **In TheRock:** | ||
|
|
||
| - In `test_filters.py`, add the job name → directory mapping in `COMPONENT_DIR_MAPPING`. | ||
| - In `fetch_test_configurations.py`, set the component’s `test_script` to `python .../test_filters.py` and set `job_name` (and shards, timeout, etc.) as needed. | ||
|
|
||
| After that, the generic flow (discovery → match GPU → run ctest with the right labels) applies without further changes to `test_filters.py` for that component. |
246 changes: 246 additions & 0 deletions
246
build_tools/github_actions/test_executable_scripts/test_runner.py
|
dileepr1 marked this conversation as resolved.
Resolved
dileepr1 marked this conversation as resolved.
Resolved
dileepr1 marked this conversation as resolved.
Resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| This is a generic test runner that can test multiple components. | ||
| This works on components in rocm-libraries/rocm-systems which use test_categories.yml for test categorization. | ||
|
|
||
| Environment variables used: | ||
| - TEST_COMPONENT: Job name of the component to test (e.g., "miopen", "rocrand", "hiprand") | ||
| This is automatically set by the GitHub Actions workflow from the job_name field. | ||
| The script maps these job names to actual test directory names (e.g., "miopen" -> "MIOpen") | ||
| Defaults to "miopen" if not set. | ||
|
dileepr1 marked this conversation as resolved.
Resolved
|
||
| - TEST_TYPE: "smoke" runs tests with "quick" category, otherwise runs "standard" category | ||
| - AMDGPU_FAMILIES: Parsed to extract GPU architecture (e.g., "gfx1151") | ||
|
|
||
| The script discovers GPU-specific labels via ctest --print-labels and runs the appropriate tests for the current GPU architecture. | ||
| """ | ||
|
|
||
| import sys | ||
| import subprocess | ||
| import re | ||
| import os | ||
|
|
||
| import logging | ||
| import shlex | ||
| from pathlib import Path | ||
|
|
||
| sys.path.insert(0, os.fspath(Path(__file__).resolve().parent.parent)) | ||
| from github_actions_utils import find_matching_gpu_arch | ||
|
|
||
| THEROCK_BIN_DIR = os.getenv("THEROCK_BIN_DIR") | ||
| SCRIPT_DIR = Path(__file__).resolve().parent | ||
| THEROCK_DIR = SCRIPT_DIR.parent.parent.parent | ||
| TEST_TYPE = os.getenv("TEST_TYPE", "smoke") | ||
| AMDGPU_FAMILIES = os.getenv("AMDGPU_FAMILIES") | ||
|
|
||
| # Map job names to actual test directory names | ||
| # The job names come from TEST_COMPONENT env var (set by GitHub Actions workflow) | ||
| # and need to be mapped to the actual directory names in THEROCK_BIN_DIR | ||
| COMPONENT_DIR_MAPPING = { | ||
| "miopen": "MIOpen", | ||
| "rocrand": "rocRAND", | ||
| "hiprand": "hipRAND", | ||
| "rocthrust": "rocthrust", | ||
| "rocprim": "rocprim", | ||
| "rocwmma": "rocwmma", | ||
| "hipcub": "hipcub", | ||
| "hipdnn": "hipdnn", | ||
| "hipdnn-samples": "hipdnn_samples", | ||
| "miopen_plugin": "miopen_legacy_plugin", | ||
| # Add more mappings as needed | ||
| } | ||
|
|
||
| # Get the test component from environment (required - no default) | ||
| test_component_job_name = os.getenv("TEST_COMPONENT") | ||
| if not test_component_job_name: | ||
| print( | ||
| "ERROR: TEST_COMPONENT environment variable is required but not set.", | ||
| file=sys.stderr, | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
| TEST_COMPONENT = COMPONENT_DIR_MAPPING.get( | ||
| test_component_job_name, test_component_job_name | ||
| ) | ||
|
|
||
| # GTest sharding | ||
| SHARD_INDEX = os.getenv("SHARD_INDEX", 1) | ||
| TOTAL_SHARDS = os.getenv("TOTAL_SHARDS", 1) | ||
|
|
||
| # CTest parallel jobs (use fewer in less capable platforms) | ||
| ctest_parallel_count = 8 | ||
| if AMDGPU_FAMILIES and "gfx1152" in AMDGPU_FAMILIES: | ||
| ctest_parallel_count = 4 | ||
| elif AMDGPU_FAMILIES and "gfx1153" in AMDGPU_FAMILIES: | ||
| ctest_parallel_count = 4 | ||
| environ_vars = os.environ.copy() | ||
| # Set the GTEST env vars for Gtest based tests | ||
| # Set ROCM_PATH for tests that rely on it | ||
| environ_vars["GTEST_SHARD_INDEX"] = str(int(SHARD_INDEX) - 1) | ||
| environ_vars["GTEST_TOTAL_SHARDS"] = str(TOTAL_SHARDS) | ||
| ROCM_PATH = Path(THEROCK_BIN_DIR).resolve().parent | ||
| environ_vars["ROCM_PATH"] = str(ROCM_PATH) | ||
|
|
||
| logging.basicConfig(level=logging.INFO) | ||
| ############################################## | ||
|
|
||
|
|
||
| def get_available_gpu_suite_tests(): | ||
| """ | ||
| Get all available GPU architecture labels from ctest --print-labels. | ||
|
|
||
| Parses labels of the form ex_gpu_{gpu_arch} (e.g. ex_gpu_gfx110X, ex_gpu_gfx950). | ||
| Returns a set of gpu_arch strings (e.g., 'gfx110X', 'gfx115X', 'gfx950'). | ||
| """ | ||
| test_dir = Path(THEROCK_BIN_DIR) / TEST_COMPONENT | ||
| if not test_dir.exists() or not test_dir.is_dir(): | ||
| print(f"Error: Test directory does not exist: {test_dir}", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| try: | ||
| # Ensure the component has at least one test | ||
| list_result = subprocess.run( | ||
| ["ctest", "-N", "--test-dir", str(test_dir)], | ||
| capture_output=True, | ||
| text=True, | ||
| check=True, | ||
| ) | ||
| total_tests = sum( | ||
| 1 | ||
| for line in list_result.stdout.splitlines() | ||
| if re.search(r"Test\s+#\d+:", line) | ||
| ) | ||
| if total_tests == 0: | ||
| print( | ||
| f"Error: No tests found in {test_dir}. Cannot run test suite.", | ||
| file=sys.stderr, | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
| result = subprocess.run( | ||
| ["ctest", "--print-labels", "--test-dir", str(test_dir)], | ||
| capture_output=True, | ||
| text=True, | ||
| check=True, | ||
| ) | ||
|
|
||
| gpu_archs = set() | ||
| prefix = "ex_gpu_" | ||
| for line in result.stdout.splitlines(): | ||
| label = line.strip() | ||
| if label.startswith(prefix): | ||
| gpu_arch = label[len(prefix) :] | ||
| if gpu_arch.startswith("gfx"): | ||
| gpu_archs.add(gpu_arch) | ||
|
|
||
| return gpu_archs | ||
| except subprocess.CalledProcessError as e: | ||
| print(f"Error running ctest --print-labels: {e}", file=sys.stderr) | ||
| sys.exit(1) | ||
| except FileNotFoundError: | ||
| print( | ||
| "Error: ctest not found. Make sure CMake/CTest is installed.", | ||
| file=sys.stderr, | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
|
|
||
| def build_ctest_command(category, gpu_arch, available_gpu_archs): | ||
| """ | ||
| Build the appropriate ctest command based on the category and GPU architecture. | ||
|
|
||
| Returns a list of command arguments suitable for subprocess.run() | ||
| """ | ||
| cmd = ["ctest", "-L", category] | ||
|
|
||
| # Add common ctest parameters | ||
| cmd.extend( | ||
| [ | ||
| "--output-on-failure", | ||
| "--parallel", | ||
| f"{ctest_parallel_count}", | ||
| "--test-dir", | ||
| f"{THEROCK_BIN_DIR}/{TEST_COMPONENT}", | ||
| "-V", # Always run in verbose mode | ||
| # Shards the tests by running a specific set of tests based on starting test (shard_index) and stride (total_shards) | ||
| "--tests-information", | ||
| f"{SHARD_INDEX},,{TOTAL_SHARDS}", | ||
|
dileepr1 marked this conversation as resolved.
Resolved
|
||
| ] | ||
| ) | ||
|
|
||
| if gpu_arch.lower() in ["generic", "none", ""]: | ||
| # For generic/unspecified GPU, exclude all GPU-specific suite tests | ||
| cmd.extend(["-LE", "ex_gpu"]) | ||
| return cmd | ||
|
|
||
| # Find the appropriate GPU suite | ||
| matching_arch = find_matching_gpu_arch(gpu_arch, available_gpu_archs) | ||
|
|
||
| if matching_arch: | ||
| # Run the specific GPU suite using the ex_gpu label | ||
| gpu_label = f"ex_gpu_{matching_arch}" | ||
| cmd.extend(["-L", gpu_label]) | ||
| print(f"# Using GPU suite label: {gpu_label}") | ||
| else: | ||
| # No specific GPU suite found, run standard tests excluding all GPU-specific ones | ||
| cmd.extend(["-LE", "ex_gpu"]) | ||
| print(f"# No GPU suite found for {gpu_arch}, excluding all ex_gpu tests") | ||
|
|
||
| return cmd | ||
|
|
||
|
|
||
| def main(): | ||
| # Use only two categories for now - quick and standard - depending on TEST_TYPE. | ||
| if TEST_TYPE and TEST_TYPE.lower() == "smoke": | ||
| category = "quick" | ||
| else: | ||
| category = "standard" | ||
|
|
||
| # Use AMDGPU_FAMILIES from environment variable, extract gfx<xxx> part | ||
| gpu_arch = "" | ||
| if AMDGPU_FAMILIES: | ||
| # Extract gfx<xxx> pattern from AMDGPU_FAMILIES string | ||
| # Pattern matches: gfx followed by alphanumeric characters (e.g., gfx1151, gfx950, gfx11X) | ||
| match = re.search(r"gfx[0-9a-zA-Z]+", AMDGPU_FAMILIES) | ||
| if match: | ||
| gpu_arch = match.group(0) | ||
| else: | ||
| print( | ||
| f"# Warning: Could not extract GPU architecture from AMDGPU_FAMILIES='{AMDGPU_FAMILIES}', using default '{gpu_arch}'" | ||
| ) | ||
|
|
||
| print( | ||
| f"# TEST_COMPONENT: {test_component_job_name} -> Test Directory: {TEST_COMPONENT}" | ||
| ) | ||
| print(f"# TEST_TYPE: {TEST_TYPE} -> Category: {category}") | ||
| print(f"# AMDGPU_FAMILIES: {AMDGPU_FAMILIES} -> GPU Architecture: {gpu_arch}") | ||
| print() | ||
|
|
||
| # Get available GPU suite tests from ctest | ||
| print("# Discovering available GPU suite tests...") | ||
| available_gpu_archs = get_available_gpu_suite_tests() | ||
|
|
||
| if available_gpu_archs: | ||
| print(f"# Found {len(available_gpu_archs)} GPU suite test(s)") | ||
| print(f"# Available GPU architectures: {sorted(available_gpu_archs)}") | ||
| else: | ||
| print("# Warning: No GPU specific test suites available") | ||
| print() | ||
|
|
||
| # Build the ctest command | ||
| cmd = build_ctest_command(category, gpu_arch, available_gpu_archs) | ||
|
|
||
| print(f"# Running: {' '.join(cmd)}") | ||
| print() | ||
|
|
||
| # Execute the command | ||
| try: | ||
| logging.info(f"++ Exec [{THEROCK_DIR}]$ {shlex.join(cmd)}") | ||
| result = subprocess.run(cmd, cwd=THEROCK_DIR, env=environ_vars, check=False) | ||
| return result.returncode | ||
| except Exception as e: | ||
| print(f"Error running ctest: {e}", file=sys.stderr) | ||
| return 1 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.