Files
TheRock/rocm-libraries/test/therock/test_runner.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

274 lines
9.0 KiB
Python

#!/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.
TEST_TYPE: Test category to run - one of "quick", "standard", "comprehensive", or "full".
Defaults to "quick". Invalid values fall back to "quick" with an error message.
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 logging
import os
import re
import shlex
import subprocess
import sys
from pathlib import Path
THEROCK_BIN_DIR = os.getenv("THEROCK_BIN_DIR")
SCRIPT_DIR = Path(__file__).resolve().parent
THEROCK_DIR = Path(
os.environ.get("THEROCK_DIR") or SCRIPT_DIR.parent.parent.parent
).resolve()
VALID_TEST_CATEGORIES = {"quick", "standard", "comprehensive", "full"}
TEST_TYPE = os.getenv("TEST_TYPE", "quick")
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",
"rocblas": "rocblas",
"hipblas": "hipblas",
"rocrand": "rocRAND",
"hiprand": "hipRAND",
"rocthrust": "rocthrust",
"rocprim": "rocprim",
"rocwmma": "rocwmma",
"hipcub": "hipcub",
"hipdnn": "hipdnn",
"hipdnn-samples": "hipdnn_samples",
"miopen_plugin": "miopen_legacy_plugin",
"hiptensor": "hiptensor",
# 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
# CTest per-test timeout (default 2 hours, in seconds)
# There should be a timeout set from component level, but this can be used as an override
ctest_timeout_seconds = 7200
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 find_matching_gpu_arch(gpu_arch: str, available_gpu_archs: set[str]) -> str | None:
"""
Find the most specific GPU architecture in the set that matches the given GPU.
Tries in order from most specific to least specific:
# Example:
# find_matching_gpu_arch('gfx1151', {'gfx1151', 'gfx115X', 'gfx11X'}) gives 'gfx1151'
# find_matching_gpu_arch('gfx1151', {'gfx1150', 'gfx94X', 'gfx11X'}) gives 'gfx11X'
- Wildcard matches (gfx115X, gfx11X, etc.)
Returns the matching architecture string or None if no match found.
"""
if gpu_arch in available_gpu_archs:
return gpu_arch
# Start matching from the end (gfx115X) and go back till the 5th character (gfx11X)
# Return the top matching pattern
for i in range(len(gpu_arch) - 1, 4, -1):
pattern = gpu_arch[:i] + "X"
if pattern in available_gpu_archs:
return pattern
return None
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}",
"--timeout",
str(ctest_timeout_seconds),
"--test-dir",
f"{THEROCK_BIN_DIR}/{TEST_COMPONENT}",
"-V",
"--tests-information",
f"{SHARD_INDEX},,{TOTAL_SHARDS}",
]
)
if gpu_arch.lower() in ["generic", "none", ""]:
cmd.extend(["-LE", "ex_gpu"])
return cmd
matching_arch = find_matching_gpu_arch(gpu_arch, available_gpu_archs)
if matching_arch:
gpu_label = f"ex_gpu_{matching_arch}"
cmd.extend(["-L", gpu_label])
print(f"# Using GPU suite label: {gpu_label}")
else:
cmd.extend(["-LE", "ex_gpu"])
print(f"# No GPU suite found for {gpu_arch}, excluding all ex_gpu tests")
return cmd
def main():
category = TEST_TYPE.lower() if TEST_TYPE else "quick"
if category not in VALID_TEST_CATEGORIES:
print(
f"ERROR: Invalid TEST_TYPE '{TEST_TYPE}'. "
f"Must be one of: {', '.join(sorted(VALID_TEST_CATEGORIES))}. "
f"Falling back to 'quick'.",
file=sys.stderr,
)
category = "quick"
gpu_arch = ""
if AMDGPU_FAMILIES:
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()
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()
cmd = build_ctest_command(category, gpu_arch, available_gpu_archs)
print(f"# Running: {' '.join(cmd)}")
print()
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())