diff --git a/projects/rocblas/clients/gtest/CMakeLists.txt b/projects/rocblas/clients/gtest/CMakeLists.txt index 369deda12ebb..bf4c82ec0553 100644 --- a/projects/rocblas/clients/gtest/CMakeLists.txt +++ b/projects/rocblas/clients/gtest/CMakeLists.txt @@ -242,8 +242,7 @@ if(ROCBLAS_HAS_CTEST_CATEGORIES) rocblas-test "${CMAKE_CURRENT_SOURCE_DIR}/test_categories.yaml" "${PROJECT_BINARY_DIR}/staging" - INSTALL_TEST_FILE "${ROCBLAS_CTEST_INSTALL_FILE}" - USE_RTEST_DRIVER + "${ROCBLAS_CTEST_INSTALL_FILE}" ) elseif(BUILD_CLIENTS_TESTS AND NOT ROCBLAS_ENABLE_CTEST) message(STATUS "rocBLAS: ROCBLAS_ENABLE_CTEST=OFF (CTest category labels not applied)") diff --git a/projects/rocblas/clients/gtest/test_categories.yaml b/projects/rocblas/clients/gtest/test_categories.yaml index e6b00f12b8b2..8d6d61a98d5c 100644 --- a/projects/rocblas/clients/gtest/test_categories.yaml +++ b/projects/rocblas/clients/gtest/test_categories.yaml @@ -3,10 +3,12 @@ # *quick* without *strided* is a proxy for smoke set until we use the smoke yaml. test_categories: quick: - description: "Smoke - rocblas_smoke.yaml with optional gtest filter refinements (timeout: 30 min)" - test_yaml: rocblas_smoke.yaml + description: "Smoke - not the same as rocblas_smoke.yaml TODO (timeout: 30 min)" test_patterns: + - "*quick*" exclude: + - "*strided*" + - "*known_bug*" labels: - "quick" - "smoke" diff --git a/projects/rocblas/rtest.py b/projects/rocblas/rtest.py index 441954aae6c8..a18350f92064 100644 --- a/projects/rocblas/rtest.py +++ b/projects/rocblas/rtest.py @@ -24,7 +24,6 @@ import sys import subprocess import shlex -import shutil import argparse import pathlib import platform @@ -49,8 +48,7 @@ def parse_args(): Checks build arguments """) parser.add_argument( '--ci_labels', type=str, required=False, default="", - help='Semicolon-separated labels that may modify test runs (e.g. "gfx12;TestLevel1Only"). ' - 'If omitted, the GITHUB_PR_LABELS environment variable is used when set (e.g. CI PR labels).') + help='Semi-colon seperated list of labels that may modify test runs (optional, e.g. "gfx12;TestLevel1Only")') parser.add_argument( '--ci_gfx', type=str, required=False, default="", help='Semi-colon seperated list of gfx targets expected on test runs (optional, e.g. "gfx1030;gfx1201")') parser.add_argument('-e', '--emulation', type=str, required=False, @@ -73,36 +71,24 @@ def arg_into_list(arg) -> list: arg = re.sub(r"['\"]|['\']",'', arg) return arg.split(';') -def rocm_executable(exe_name: str) -> str: - if shutil.which(exe_name): - return exe_name - bin_dir = os.environ.get("ROCM_PATH") or os.environ.get("HIP_PATH") - if bin_dir: - candidate = pathlib.Path(bin_dir) / "bin" / exe_name - if candidate.exists(): - return str(candidate) - return None - def vram_detect(): global OS_info OS_info["VRAM"] = 0 if os.name == "nt": - cmd = rocm_executable("hipinfo.exe") - if cmd is not None: - process = subprocess.run([cmd], stdout=subprocess.PIPE) - for line_in in process.stdout.decode().splitlines(): - if 'totalGlobalMem' in line_in: - OS_info["VRAM"] = float(line_in.split()[1]) - break + cmd = "hipinfo.exe" + process = subprocess.run([cmd], stdout=subprocess.PIPE) + for line_in in process.stdout.decode().splitlines(): + if 'totalGlobalMem' in line_in: + OS_info["VRAM"] = float(line_in.split()[1]) + break else: - cmd = rocm_executable("rocminfo") - if cmd is not None: - process = subprocess.run([cmd], stdout=subprocess.PIPE) - for line_in in process.stdout.decode().splitlines(): - match = re.search(r'.*Size:.*([0-9]+)\(.*\).*KB', line_in, re.IGNORECASE) - if match: - OS_info["VRAM"] = float(match.group(1))/(1024*1024) - break + cmd = "rocminfo" + process = subprocess.run([cmd], stdout=subprocess.PIPE) + for line_in in process.stdout.decode().splitlines(): + match = re.search(r'.*Size:.*([0-9]+)\(.*\).*KB', line_in, re.IGNORECASE) + if match: + OS_info["VRAM"] = float(match.group(1))/(1024*1024) + break def os_detect(): global OS_info @@ -419,13 +405,6 @@ def main(): os_detect() args = parse_args() - # PR / CI labels (e.g. GitHub Actions): semicolon-separated, same format as --ci_labels. - # When --ci_labels is omitted, use GITHUB_PR_LABELS from the environment (e.g. set by CI before ctest). - if not args.ci_labels: - env_labels = os.environ.get("GITHUB_PR_LABELS", "").strip() - if env_labels: - args.ci_labels = env_labels - if args.emulation: args.test = f'emulation_{args.emulation}' diff --git a/projects/rocblas/rtest.xml b/projects/rocblas/rtest.xml index 4213528db3dd..112530288667 100644 --- a/projects/rocblas/rtest.xml +++ b/projects/rocblas/rtest.xml @@ -1,20 +1,6 @@ - - - {TEST_BASE_OPTIONS} --yaml rocblas_smoke.yaml - - - {TEST_BASE_OPTIONS} --gtest_filter=*quick*:*pre_checkin*-*known_bug* - - - {TEST_BASE_OPTIONS} --gtest_filter=*nightly*-*known_bug* - - - {TEST_BASE_OPTIONS} --gtest_filter=*quick*:*pre_checkin*:*nightly*-*known_bug* - - {TEST_BASE_OPTIONS} --gtest_filter=*quick*:*pre_checkin*-*known_bug* diff --git a/shared/ctest/README.md b/shared/ctest/README.md index 3243398c5d9e..2bcecf5f5d32 100644 --- a/shared/ctest/README.md +++ b/shared/ctest/README.md @@ -33,7 +33,7 @@ Defines test organization: Python script that: - Parses YAML configuration files - Detects runtime environment (OS, GPU architecture) -- Builds gtest filter strings with positive and negative patterns (default), or emits commands to run an **rtest** driver when **`--use-rtest-driver`** is passed +- Builds gtest filter strings with positive and negative patterns - Generates CMake test registration code with proper exclusion syntax #### 3. **TestCategories.cmake** (Shared) @@ -135,19 +135,14 @@ execution_settings: **Environment (optional):** Under `execution_settings`, an `environment` map sets env vars for all category tests (e.g. `OPENBLAS_NUM_THREADS`, `OMP_NUM_THREADS`). Keys and values are strings; they are passed to CTest as `ENVIRONMENT "VAR1=val1;VAR2=val2"`. -**Test YAML (optional, per-category):** A category may set `test_yaml` to a basename (e.g. `rocblas_smoke.yaml`). For direct gtest invocations, the parser injects `--yaml ` **before** `--gtest_filter=...`. A category may define `test_yaml` only, `test_patterns` only, or both. When `--use-rtest-driver` is enabled, the project's rtest XML must still define matching `--yaml` / filter behavior for `ctest_` suites. - -**Extra arguments (optional, per-category):** A category may set `extra_args` to a list (or single string) of additional command-line arguments that the parser appends to the test command after `--gtest_filter=...` (or after `--yaml` when no filter is used). Useful for projects whose test binary accepts runtime flags beyond gtest filtering (for example, a flag to select a reduced subset of tests, or to scale a sampling/iteration parameter). Each entry is shell-quoted with `shlex.quote`, so values with spaces or shell metacharacters are preserved as a single argument by CTest. +**Extra arguments (optional, per-category):** A category may set `extra_args` to a list (or single string) of additional command-line arguments that the parser appends to the test command after `--gtest_filter=...`. Useful for projects whose test binary accepts runtime flags beyond gtest filtering (for example, a flag to select a reduced subset of tests, or to scale a sampling/iteration parameter). Each entry is shell-quoted with `shlex.quote`, so values with spaces or shell metacharacters are preserved as a single argument by CTest. ```yaml test_categories: quick: - test_yaml: rocblas_smoke.yaml test_patterns: - - "*quick*" - exclude: - - "*strided*" - extra_args: # appended after --gtest_filter (or after --yaml) + - "*" + extra_args: # appended after --gtest_filter - "--quick-subset" labels: - "quick" @@ -577,7 +572,7 @@ Each test gets labels that enable flexible CTest filtering: ## Function Reference -### `apply_test_category_labels(target_name yaml_file working_dir [install_test_file [resource_group [parser_extra]]])` +### `apply_test_category_labels(target_name yaml_file working_dir [install_test_file])` | Parameter | Required | Description | @@ -587,15 +582,8 @@ Each test gets labels that enable flexible CTest filtering: | `working_dir` | Yes | Working directory for test execution (must be a valid directory) | | `install_test_file` | No | Path to the generated install-time `CTestTestfile.cmake`. Not required for local builds, but | | | | required for install/package workflows (for example, TheRock). | -| `resource_group` | No | CTest `RESOURCE_GROUPS` token; when set, suite names include `_` after the target name. | -| `parser_extra` | No | Extra argument(s) forwarded to `parse_test_categories.py` (for example `--use-rtest-driver`). | - **Validation:** The function validates all inputs before execution and emits `WARNING` messages if any check fails, returning early without generating tests. -### Optional rtest driver (`--use-rtest-driver`) - -By default, generated CTest suites run the **gtest executable** with `--gtest_filter=...`. If the project passes **`--use-rtest-driver`** as the optional **`parser_extra`** argument to `apply_test_category_labels()`, the parser instead emits commands to run the **rtest** Python driver next to the binary. The driver basename is derived from the CTest name prefix (typically `mylib-test` → `mylib_rtest.py`). The driver is invoked as ` _rtest.py -t ctest_` using a platform-appropriate interpreter on `PATH` at CTest run time (`python3` on Linux/macOS, `python` on Windows—not configure-time `Python3_EXECUTABLE`, which may be absent on CI test nodes). **`--cmake-python3`** may be passed explicitly to the parser to override the install-tree interpreter. PR-style labels are read at rtest run time from the **`GITHUB_PR_LABELS`** environment variable (semicolon-separated, same format as rtest’s `--ci_labels`) when that variable is set and `--ci_labels` was not passed on the command line. CTest **`LABELS`** on the suite are unchanged (for `ctest -L`). The project’s rtest XML must define matching `` entries. GPU-specific exclusion suites still invoke the gtest binary directly. In **rocm-libraries**, only **rocBLAS** enables this via `USE_RTEST_DRIVER` in `projects/rocblas/clients/gtest/CMakeLists.txt`. - ## YAML Reference ### `test_categories` (required) @@ -663,11 +651,11 @@ When tests are installed (e.g. into `/opt/rocm/bin/`), the **build-tree** test d **How it works:** -1. **Project enables it** by passing an optional **4th argument** to `apply_test_category_labels()`: a path to a file (e.g. `install_CTestTestfile.cmake`) that the parser will create or append to. The parser writes `add_test(...)` and `set_tests_properties(...)` lines into that file using a **relative** command (e.g. `"../my_project_gtest"`), so the test binary is found relative to the directory where the file will live after install. +1. **Project enables it** by passing an optional **4th argument** to `apply_test_category_labels()`: a path to a file (e.g. `install_CTestTestfile.cmake`) that the parser will create or append to. The parser writes `add_test(...)` and `set_tests_properties(...)` lines into that file using a **relative** command (e.g. `"../rocblas-test"`), so the test binary is found relative to the directory where the file will live after install. -2. **Project installs that file** to a fixed location under the install prefix, typically a **project-specific subdirectory** of the bin dir (e.g. `bin/my_project/`) and renames it to `CTestTestfile.cmake`. The test executable is installed in the parent `bin/` directory, so from `bin/my_project/` the path `../my_project_gtest` correctly points at the installed binary. +2. **Project installs that file** to a fixed location under the install prefix, typically a **project-specific subdirectory** of the bin dir (e.g. `bin/rocblas/` or `bin/MIOpen/`) and renames it to `CTestTestfile.cmake`. The test executable is installed in the parent `bin/` directory, so from `bin/rocblas/` the path `../rocblas-test` correctly points at the installed binary. -3. **TheRock (or any consumer)** runs CTest **from that installed directory** (e.g. `cd /opt/rocm/bin/my_project && ctest -L quick`). CTest reads the local `CTestTestfile.cmake`, runs the tests with the same labels and timeouts as in the build tree, and no build tree is required. +3. **TheRock (or any consumer)** runs CTest **from that installed directory** (e.g. `cd /opt/rocm/bin/rocblas && ctest -L quick`). CTest reads the local `CTestTestfile.cmake`, runs the tests with the same labels and timeouts as in the build tree, and no build tree is required. **Example (rocBLAS):** diff --git a/shared/ctest/TestCategories.cmake b/shared/ctest/TestCategories.cmake index 77383f852ad8..9302026c15af 100644 --- a/shared/ctest/TestCategories.cmake +++ b/shared/ctest/TestCategories.cmake @@ -11,7 +11,7 @@ find_package(Python3 COMPONENTS Interpreter) function(_parse_test_category_optional_args) cmake_parse_arguments( ARG - "USE_RTEST_DRIVER" + "" "INSTALL_TEST_FILE;RESOURCE_GROUP;TEST_NAME_PREFIX;INSTALL_EXECUTABLE" "COMMAND_ARGS;INSTALL_COMMAND_ARGS;ADDITIONAL_LABELS;ENVIRONMENT" ${ARGN} @@ -19,7 +19,6 @@ function(_parse_test_category_optional_args) set(_install_test_file "${ARG_INSTALL_TEST_FILE}") set(_resource_group "${ARG_RESOURCE_GROUP}") - set(_use_rtest_driver "${ARG_USE_RTEST_DRIVER}") if(ARG_UNPARSED_ARGUMENTS) list(LENGTH ARG_UNPARSED_ARGUMENTS _arg_count) if(NOT _install_test_file AND _arg_count GREATER 0) @@ -28,17 +27,10 @@ function(_parse_test_category_optional_args) if(NOT _resource_group AND _arg_count GREATER 1) list(GET ARG_UNPARSED_ARGUMENTS 1 _resource_group) endif() - if(NOT _use_rtest_driver) - list(FIND ARG_UNPARSED_ARGUMENTS "--use-rtest-driver" _rtest_idx) - if(NOT _rtest_idx EQUAL -1) - set(_use_rtest_driver TRUE) - endif() - endif() endif() set(_TEST_CATEGORY_INSTALL_FILE "${_install_test_file}" PARENT_SCOPE) set(_TEST_CATEGORY_RESOURCE_GROUP "${_resource_group}" PARENT_SCOPE) - set(_TEST_CATEGORY_USE_RTEST_DRIVER "${_use_rtest_driver}" PARENT_SCOPE) set(_TEST_CATEGORY_NAME_PREFIX "${ARG_TEST_NAME_PREFIX}" PARENT_SCOPE) set(_TEST_CATEGORY_INSTALL_EXECUTABLE "${ARG_INSTALL_EXECUTABLE}" PARENT_SCOPE) set(_TEST_CATEGORY_COMMAND_ARGS "${ARG_COMMAND_ARGS}" PARENT_SCOPE) @@ -75,9 +67,6 @@ function(_build_test_category_parser_args out_var) foreach(extra_env_kv IN LISTS _TEST_CATEGORY_ENVIRONMENT) list(APPEND extra_args "--environment" "${extra_env_kv}") endforeach() - if(_TEST_CATEGORY_USE_RTEST_DRIVER) - list(APPEND extra_args "--use-rtest-driver") - endif() set(${out_var} "${extra_args}" PARENT_SCOPE) endfunction() @@ -134,8 +123,6 @@ endfunction() # generated suite, merged with execution_settings.environment from the # YAML (this list wins on key conflicts). Use to forward CMake-side # TEST_ENVIRONMENT (ASAN symbolizer path, coverage LLVM_PROFILE_FILE). -# USE_RTEST_DRIVER - Run the project's _rtest.py driver with -# -t ctest_ instead of invoking the gtest binary directly. # ~~~ function(apply_test_category_labels target_name yaml_file working_dir) _parse_test_category_optional_args(${ARGN}) diff --git a/shared/ctest/parse_test_categories.py b/shared/ctest/parse_test_categories.py index 6c4596b6f5f8..2fc0600fb541 100644 --- a/shared/ctest/parse_test_categories.py +++ b/shared/ctest/parse_test_categories.py @@ -49,112 +49,6 @@ def _dedupe_preserve_order(values): return result -def _rtest_script_basename(name_prefix: str) -> str: - """Basename of the rtest driver script next to the gtest binary. - - ``name_prefix`` is normally the gtest executable name used in CTest (for - example ``mylib-test``). The co-installed rtest driver is expected to be - ``_rtest.py`` where ``stem`` is ``name_prefix`` with a trailing - ``-test`` removed (``mylib-test`` -> ``mylib_rtest.py``). If there is no - ``-test`` suffix, hyphens in ``name_prefix`` are turned into underscores and - ``_rtest.py`` is appended. - """ - if name_prefix.endswith("-test"): - return name_prefix[: -len("-test")] + "_rtest.py" - stem = name_prefix.replace("-", "_") - return stem + "_rtest.py" - - -def _format_gtest_command_tail( - executable, - pattern_string, - extra_args_string, - command_args_string="", - test_yaml=None, -): - """Gtest binary invocation: optional args, --yaml, --gtest_filter, then extra_args.""" - tail = f"{executable}{command_args_string}" - if test_yaml: - tail += f" --yaml {shlex.quote(test_yaml)}" - if pattern_string: - tail += f" --gtest_filter={pattern_string}" - return f"{tail}{extra_args_string}" - - -def _ctest_python_command(is_windows): - """Python executable name for CTest rtest invocations at run time. - - Windows Python installs typically expose ``python`` on PATH, not ``python3``. - """ - return "python" if is_windows else "python3" - - -def _format_category_command( - use_rtest_driver, - name_prefix, - target_name, - pattern_string, - extra_args_string, - category_name, - test_yaml=None, - command_args_string="", - is_windows=False, -): - """Return the COMMAND tail for add_test (everything after COMMAND).""" - if use_rtest_driver: - rtest_script = _rtest_script_basename(name_prefix) - rtest_set = f"ctest_{category_name}" - py = _ctest_python_command(is_windows) - return f"{py} {rtest_script} -t {rtest_set}{extra_args_string}" - return _format_gtest_command_tail( - target_name, - pattern_string, - extra_args_string, - command_args_string, - test_yaml, - ) - - -def _format_install_add_test_line( - use_rtest_driver, - name_prefix, - category_name, - target_name, - pattern_string, - extra_args_string, - cmake_python3, - test_yaml=None, - gpu_arch=None, - install_executable=None, - install_command_args_string="", - is_windows=False, -): - """One-line add_test(...) for install-time CTestTestfile fragments.""" - suffix = f"_{gpu_arch}" if gpu_arch else "" - test_name = f"{name_prefix}_{category_name}{suffix}_suite" - if use_rtest_driver: - py = ( - shlex.quote(cmake_python3) - if cmake_python3 - else _ctest_python_command(is_windows) - ) - rtest_script = _rtest_script_basename(name_prefix) - rtest_set = f"ctest_{category_name}" - return ( - f'add_test({test_name} {py} "../{rtest_script}" ' - f"-t {rtest_set}{extra_args_string})\n" - ) - exe = install_executable if install_executable is not None else f"../{target_name}" - gtest_tail = _format_gtest_command_tail( - _cmake_quote(exe), - pattern_string, - extra_args_string, - install_command_args_string, - test_yaml, - ) - return f"add_test({test_name} {gtest_tail})\n" - - # Allowlist patterns for YAML-sourced values _IDENTIFIER_RE = re.compile(r"^[\w\-\.]+$") _GTEST_PATTERN_RE = re.compile(r"^[\w\*\.\-/]+$") @@ -236,17 +130,6 @@ def validate_config(categories, exclude_gpu_config, is_windows, is_linux): if err is not None: errors.append(f"category {category_name!r} label: {err}") - test_yaml = category_info.get("test_yaml") - if test_yaml is not None: - err = validate_identifier(test_yaml) - if err is not None: - errors.append(f"category {category_name!r} test_yaml: {err}") - - if not patterns and not test_yaml: - errors.append( - f"category {category_name!r}: must define test_patterns and/or test_yaml" - ) - if exclude_gpu_config is None: return errors if not isinstance(exclude_gpu_config, dict): @@ -416,24 +299,6 @@ def main(): "same-keyed execution_settings.environment entry from the YAML." ), ) - parser.add_argument( - "--use-rtest-driver", - action="store_true", - dest="use_rtest_driver", - help=( - "Generate CTest commands that run the rtest driver script " - "(basename derived from name_prefix; typically mylib-test -> " - "mylib_rtest.py) with -t ctest_ instead of invoking the " - "gtest binary with --gtest_filter=... " - '(requires matching entries in the ' - "project's rtest XML)." - ), - ) - parser.add_argument( - "--cmake-python3", - default=None, - help="Absolute path to Python3 interpreter (for install-tree add_test lines).", - ) args = parser.parse_args() @@ -442,8 +307,6 @@ def main(): working_dir = args.working_dir install_test_file = args.install_test_file resource_group = args.resource_group - use_rtest_driver = args.use_rtest_driver - cmake_python3 = args.cmake_python3 if args.test_name_prefix is not None: err = validate_identifier(args.test_name_prefix) if err is not None: @@ -545,11 +408,10 @@ def main(): category_data = {} for category_name, category_info in categories.items(): - patterns = category_info.get("test_patterns", []) or [] - test_yaml = category_info.get("test_yaml") - if not patterns and not test_yaml: + patterns = category_info.get("test_patterns", []) + if not patterns: print( - f"Warning: Category '{category_name}' has no test_patterns or test_yaml defined, skipping.", + f"Warning: Category '{category_name}' has no test_patterns defined, skipping.", file=sys.stderr, ) continue @@ -585,7 +447,6 @@ def main(): "exclude_string": exclude_string, "labels": labels[:], # Make a copy "timeout": timeout, - "test_yaml": test_yaml, "extra_args": ( list(extra_args) if isinstance(extra_args, list) else extra_args ), @@ -606,18 +467,9 @@ def main(): # ======================================================================= print("add_test(") print(f" NAME {name_prefix}_{category_name}_suite") - cmd_tail = _format_category_command( - use_rtest_driver, - name_prefix, - target_name, - pattern_string, - extra_args_string, - category_name, - test_yaml, - command_args_string, - is_windows=is_windows, + print( + f" COMMAND {target_name}{command_args_string} --gtest_filter={pattern_string}{extra_args_string}" ) - print(f" COMMAND {cmd_tail}") print(f" WORKING_DIRECTORY {working_dir}") print(")") @@ -637,19 +489,7 @@ def main(): if install_file_handle: try: install_file_handle.write( - _format_install_add_test_line( - use_rtest_driver, - name_prefix, - category_name, - target_name, - pattern_string, - extra_args_string, - cmake_python3, - test_yaml, - install_executable=install_executable, - install_command_args_string=install_command_args_string, - is_windows=is_windows, - ) + f"add_test({name_prefix}_{category_name}_suite {_cmake_quote(install_executable)}{install_command_args_string} --gtest_filter={pattern_string}{extra_args_string})\n" ) env_prop = f' ENVIRONMENT "{env_string}"' if env_string else "" install_file_handle.write( @@ -751,7 +591,6 @@ def main(): cat_labels = cat_data["labels"] timeout = cat_data["timeout"] cat_extra_args_string = _format_extra_args(cat_data.get("extra_args")) - cat_test_yaml = cat_data.get("test_yaml") # Build combined pattern string: positive - category_excludes:gpu_excludes combined_exclude_string = "" @@ -775,19 +614,9 @@ def main(): print(f"# GPU exclusion for {gpu_arch} - {category_name} category") print("add_test(") print(f" NAME {name_prefix}_{category_name}_{gpu_arch}_suite") - # GPU-specific gtest slices are not represented in rtest XML; always use the binary. - cmd_tail = _format_category_command( - False, - name_prefix, - target_name, - pattern_string, - cat_extra_args_string, - category_name, - cat_test_yaml, - command_args_string, - is_windows=is_windows, + print( + f" COMMAND {target_name}{command_args_string} --gtest_filter={pattern_string}{cat_extra_args_string}" ) - print(f" COMMAND {cmd_tail}") print(f" WORKING_DIRECTORY {working_dir}") print(")") @@ -807,20 +636,7 @@ def main(): if install_file_handle: try: install_file_handle.write( - _format_install_add_test_line( - False, - name_prefix, - category_name, - target_name, - pattern_string, - cat_extra_args_string, - cmake_python3, - cat_test_yaml, - gpu_arch=gpu_arch, - install_executable=install_executable, - install_command_args_string=install_command_args_string, - is_windows=is_windows, - ) + f"add_test({name_prefix}_{category_name}_{gpu_arch}_suite {_cmake_quote(install_executable)}{install_command_args_string} --gtest_filter={pattern_string}{cat_extra_args_string})\n" ) env_prop = f' ENVIRONMENT "{env_string}"' if env_string else "" install_file_handle.write( diff --git a/shared/ctest/tests/CMakeLists.txt b/shared/ctest/tests/CMakeLists.txt index 2b1f9f584fc1..30cfd9ce7077 100644 --- a/shared/ctest/tests/CMakeLists.txt +++ b/shared/ctest/tests/CMakeLists.txt @@ -3,8 +3,6 @@ get_filename_component(_rocm_libraries_root "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) -find_package(Python3 COMPONENTS Interpreter) - add_test( NAME shared_ctest_optional_args_scope COMMAND @@ -12,13 +10,3 @@ add_test( "${CMAKE_CURRENT_BINARY_DIR}/optional_args_scope" -DROCM_LIBRARIES_ROOT=${_rocm_libraries_root} ) - -if(Python3_FOUND) - add_test( - NAME shared_ctest_parse_test_categories_unit - COMMAND - ${Python3_EXECUTABLE} -m unittest discover -v - -s "${CMAKE_CURRENT_LIST_DIR}" - -p "test_parse_test_categories.py" - ) -endif() diff --git a/shared/ctest/tests/test_parse_test_categories.py b/shared/ctest/tests/test_parse_test_categories.py deleted file mode 100644 index da78b3635179..000000000000 --- a/shared/ctest/tests/test_parse_test_categories.py +++ /dev/null @@ -1,364 +0,0 @@ -# Copyright Advanced Micro Devices, Inc., or its affiliates. -# SPDX-License-Identifier: MIT - -import subprocess -import sys -import tempfile -import unittest -from pathlib import Path -import platform - -_TESTS_DIR = Path(__file__).resolve().parent -_CTEST_DIR = _TESTS_DIR.parent -_PARSER = _CTEST_DIR / "parse_test_categories.py" - -sys.path.insert(0, str(_CTEST_DIR)) -import parse_test_categories as ptc # noqa: E402 - -MINIMAL_YAML = """ -test_categories: - quick: - test_yaml: smoke.yaml - test_patterns: - - "*quick*" - exclude: - - "*known_bug*" - labels: - - quick -execution_settings: - category_timeouts: - quick: 60 -""" - -PATTERNS_ONLY_YAML = """ -test_categories: - quick: - test_patterns: - - "*quick*" - labels: - - quick -execution_settings: - category_timeouts: - quick: 60 - environment: - OMP_NUM_THREADS: "4" -""" - - -class TestDevelopHelperFunctions(unittest.TestCase): - def test_cmake_quote_simple(self): - self.assertEqual(ptc._cmake_quote("foo"), "[[foo]]") - self.assertEqual(ptc._cmake_quote("../mylib-test"), "[[../mylib-test]]") - - def test_cmake_quote_escapes_brackets(self): - self.assertEqual(ptc._cmake_quote("a]]b"), "[=[a]]b]=]") - - def test_format_cmake_args_empty(self): - self.assertEqual(ptc._format_cmake_args([]), "") - - def test_format_cmake_args_multiple(self): - out = ptc._format_cmake_args(["--test-article", "my.article"]) - self.assertEqual(out, " [[--test-article]] [[my.article]]") - - def test_dedupe_preserve_order(self): - self.assertEqual( - ptc._dedupe_preserve_order(["quick", "ci", "quick", "extra"]), - ["quick", "ci", "extra"], - ) - - def test_format_gtest_command_tail_with_command_args(self): - tail = ptc._format_gtest_command_tail( - "mylib-test", - "*quick*", - "", - command_args_string=" [[--test-article]] [[article1]]", - ) - self.assertEqual( - tail, - "mylib-test [[--test-article]] [[article1]] --gtest_filter=*quick*", - ) - - def test_format_install_add_test_line_custom_executable_and_args(self): - line = ptc._format_install_add_test_line( - False, - "prefix", - "quick", - "mylib-test", - "*quick*", - "", - "/usr/bin/python3", - install_executable="../custom/binary", - install_command_args_string=" [[--test-config]] [[cfg.json]]", - ) - self.assertIn("add_test(prefix_quick_suite [[../custom/binary]]", line) - self.assertIn("[[--test-config]] [[cfg.json]]", line) - self.assertIn("--gtest_filter=*quick*", line) - - -class TestHelperFunctions(unittest.TestCase): - def test_format_extra_args_empty(self): - self.assertEqual(ptc._format_extra_args([]), "") - self.assertEqual(ptc._format_extra_args(None), "") - - def test_format_extra_args_quotes_spaces(self): - out = ptc._format_extra_args(["--flag", "value with spaces"]) - self.assertIn("'value with spaces'", out) - self.assertTrue(out.startswith(" ")) - - def test_rtest_script_basename(self): - self.assertEqual(ptc._rtest_script_basename("rocblas-test"), "rocblas_rtest.py") - self.assertEqual(ptc._rtest_script_basename("hipblas-test"), "hipblas_rtest.py") - self.assertEqual(ptc._rtest_script_basename("my-lib-test"), "my-lib_rtest.py") - self.assertEqual( - ptc._rtest_script_basename("custom_gtest"), "custom_gtest_rtest.py" - ) - - def test_ctest_python_command_platform(self): - self.assertEqual(ptc._ctest_python_command(False), "python3") - self.assertEqual(ptc._ctest_python_command(True), "python") - - def test_format_gtest_command_tail_yaml_and_filter(self): - tail = ptc._format_gtest_command_tail( - "rocblas-test", - "*quick*-*known_bug*", - "", - test_yaml="rocblas_smoke.yaml", - ) - self.assertEqual( - tail, - "rocblas-test --yaml rocblas_smoke.yaml --gtest_filter=*quick*-*known_bug*", - ) - - def test_format_gtest_command_tail_yaml_only(self): - tail = ptc._format_gtest_command_tail( - "rocblas-test", "", "", test_yaml="rocblas_smoke.yaml" - ) - self.assertEqual(tail, "rocblas-test --yaml rocblas_smoke.yaml") - - def test_format_category_command_rtest_driver(self): - cmd = ptc._format_category_command( - True, - "rocblas-test", - "rocblas-test", - "*quick*", - "", - "quick", - ) - self.assertEqual(cmd, "python3 rocblas_rtest.py -t ctest_quick") - - def test_format_category_command_rtest_driver_windows(self): - cmd = ptc._format_category_command( - True, - "rocblas-test", - "rocblas-test", - "*quick*", - "", - "quick", - is_windows=True, - ) - self.assertEqual(cmd, "python rocblas_rtest.py -t ctest_quick") - - def test_format_install_add_test_line_rtest_windows(self): - line = ptc._format_install_add_test_line( - True, - "rocblas-test", - "quick", - "rocblas-test", - "*quick*", - "", - None, - is_windows=True, - ) - self.assertIn( - 'add_test(rocblas-test_quick_suite python "../rocblas_rtest.py"', line - ) - - def test_format_category_command_direct_gtest(self): - cmd = ptc._format_category_command( - False, - "rocblas-test", - "rocblas-test", - "*quick*-*known_bug*", - " --iterations 2", - "quick", - test_yaml="smoke.yaml", - ) - self.assertIn("--yaml smoke.yaml", cmd) - self.assertIn("--gtest_filter=*quick*-*known_bug*", cmd) - self.assertIn(" --iterations 2", cmd) - - def test_format_install_add_test_line_direct(self): - line = ptc._format_install_add_test_line( - False, - "rocblas-test", - "quick", - "rocblas-test", - "*quick*", - "", - "/usr/bin/python3", - test_yaml="smoke.yaml", - ) - self.assertIn("add_test(rocblas-test_quick_suite [[../rocblas-test]]", line) - self.assertIn("--yaml smoke.yaml", line) - - -class TestValidation(unittest.TestCase): - def test_validate_identifier_rejects_unsafe(self): - self.assertIsNotNone(ptc.validate_identifier("bad;name")) - - def test_validate_gtest_pattern_accepts_wildcards(self): - self.assertIsNone(ptc.validate_gtest_pattern("*quick*")) - self.assertIsNone(ptc.validate_gtest_pattern("*pre_checkin*")) - - def test_validate_config_requires_patterns_or_yaml(self): - errors = ptc.validate_config( - {"quick": {"labels": ["quick"]}}, None, False, True - ) - self.assertTrue(any("test_patterns and/or test_yaml" in e for e in errors)) - - def test_validate_config_accepts_test_yaml_only(self): - errors = ptc.validate_config( - {"quick": {"test_yaml": "smoke.yaml", "labels": ["quick"]}}, - None, - False, - True, - ) - self.assertEqual(errors, []) - - def test_parse_exclude_gpu_key(self): - self.assertEqual( - ptc.parse_exclude_gpu_key("exclude_gpu_gfx942"), ("gfx942", None) - ) - self.assertEqual( - ptc.parse_exclude_gpu_key("exclude_gpu_gfx942_linux"), ("gfx942", "linux") - ) - self.assertEqual(ptc.parse_exclude_gpu_key("invalid"), (None, None)) - - def test_exclude_gpu_key_applies(self): - self.assertTrue(ptc.exclude_gpu_key_applies(None, False, True)) - self.assertTrue(ptc.exclude_gpu_key_applies("linux", False, True)) - self.assertFalse(ptc.exclude_gpu_key_applies("windows", False, True)) - - def test_gpu_arch_matches(self): - self.assertTrue(ptc.gpu_arch_matches("gfx1150", "gfx1150")) - self.assertTrue(ptc.gpu_arch_matches("gfx1150", "gfx115X")) - self.assertFalse(ptc.gpu_arch_matches("gfx1150", "gfx942")) - - -class TestCliIntegration(unittest.TestCase): - def _run_parser(self, yaml_text, *extra_args, install_file=None): - with tempfile.TemporaryDirectory() as tmp: - yaml_path = Path(tmp) / "test_categories.yaml" - yaml_path.write_text(yaml_text, encoding="utf-8") - install_path = None - cmd = [ - sys.executable, - str(_PARSER), - str(yaml_path), - "rocblas-test", - tmp, - ] - if install_file is not None: - install_path = Path(tmp) / install_file - cmd.append(str(install_path)) - cmd.extend(extra_args) - result = subprocess.run(cmd, capture_output=True, text=True, check=False) - install_contents = ( - install_path.read_text(encoding="utf-8") if install_path else None - ) - return result, install_contents - - def test_cli_direct_gtest_emits_yaml_and_filter(self): - result, _ = self._run_parser(MINIMAL_YAML) - self.assertEqual(result.returncode, 0, msg=result.stderr) - self.assertIn( - "COMMAND rocblas-test --yaml smoke.yaml --gtest_filter=*quick*-*known_bug*", - result.stdout, - ) - self.assertIn('LABELS "quick"', result.stdout) - - def test_cli_rtest_driver_emits_rtest_command(self): - result, _ = self._run_parser(MINIMAL_YAML, "--use-rtest-driver") - self.assertEqual(result.returncode, 0, msg=result.stderr) - expected_py = ptc._ctest_python_command(platform.system() == "Windows") - self.assertIn( - f"COMMAND {expected_py} rocblas_rtest.py -t ctest_quick", - result.stdout, - ) - - def test_cli_command_arg_injected_before_filter(self): - result, _ = self._run_parser( - PATTERNS_ONLY_YAML, - "--command-arg=--test-article", - "--command-arg=article1", - ) - self.assertEqual(result.returncode, 0, msg=result.stderr) - self.assertIn( - "COMMAND rocblas-test [[--test-article]] [[article1]] --gtest_filter=*quick*", - result.stdout, - ) - - def test_cli_test_name_prefix(self): - result, _ = self._run_parser( - PATTERNS_ONLY_YAML, "--test-name-prefix", "provider_gtest" - ) - self.assertEqual(result.returncode, 0, msg=result.stderr) - self.assertIn("NAME provider_gtest_quick_suite", result.stdout) - - def test_cli_additional_label_merged_and_deduped(self): - result, _ = self._run_parser( - PATTERNS_ONLY_YAML, - "--additional-label", - "ci", - "--additional-label", - "quick", - ) - self.assertEqual(result.returncode, 0, msg=result.stderr) - self.assertIn('LABELS "quick;ci"', result.stdout) - - def test_cli_environment_override(self): - result, _ = self._run_parser( - PATTERNS_ONLY_YAML, "--environment", "OMP_NUM_THREADS=24" - ) - self.assertEqual(result.returncode, 0, msg=result.stderr) - self.assertIn('ENVIRONMENT "OMP_NUM_THREADS=24"', result.stdout) - - def test_cli_install_command_arg_and_executable(self): - result, install_contents = self._run_parser( - PATTERNS_ONLY_YAML, - "--install-command-arg=--test-config", - "--install-command-arg=config.json", - "--install-executable", - "../provider_gtest", - install_file="install_CTestTestfile.cmake", - ) - self.assertEqual(result.returncode, 0, msg=result.stderr) - self.assertIsNotNone(install_contents) - self.assertIn( - "add_test(rocblas-test_quick_suite [[../provider_gtest]]", install_contents - ) - self.assertIn("[[--test-config]] [[config.json]]", install_contents) - self.assertIn("--gtest_filter=*quick*", install_contents) - - def test_cli_invalid_yaml_exits_nonzero(self): - result, _ = self._run_parser("test_categories:\n bad:\n labels: [quick]\n") - self.assertNotEqual(result.returncode, 0) - self.assertIn("validation error", result.stderr) - - def test_cli_invalid_environment_exits_nonzero(self): - result, _ = self._run_parser( - PATTERNS_ONLY_YAML, "--environment", "not-a-kv-pair" - ) - self.assertNotEqual(result.returncode, 0) - self.assertIn("invalid --environment value", result.stderr) - - def test_cli_invalid_additional_label_exits_nonzero(self): - result, _ = self._run_parser( - PATTERNS_ONLY_YAML, "--additional-label", "bad;label" - ) - self.assertNotEqual(result.returncode, 0) - self.assertIn("invalid --additional-label value", result.stderr) - - -if __name__ == "__main__": - unittest.main()