Files
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

258 lines
11 KiB
Python

# *******************************************************************************
#
# MIT License
#
# Copyright (c) 2025 Advanced Micro Devices, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# *******************************************************************************
import os
import re
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
FOLDER_PATH = os.path.join(_SCRIPT_DIR, "..", "gtest")
# Ignore list: Add test names or file paths you want to exclude
# (keep the list sorted to min. git conflicts)
IGNORE_LIST = {
"binary_tensor_ops.cpp",
# bn_activ_infer / bn_fwd_infer instantiate their suites through the
# BN_*_INFER_TIERS macro, so the INSTANTIATE_TEST_SUITE_P calls are hidden
# behind line continuations the regex cannot parse (same reason as the
# *_tensor_ops.cpp entries).
"bn_activ_infer.cpp",
"bn_fwd_infer.cpp",
"graphapi_conv_bias_res_add_activ_fwd.cpp",
"graphapi_operation_rng.cpp",
"layout_transpose.cpp",
"reduce_custom_fp32.cpp",
"unary_tensor_ops.cpp",
"CPU_MIOpenDriverRegressionBigTensorTest_FP32",
"GPU_UnitTestConvSolverAsmBwdWrW3x3Wrw_FP32",
}
# Valid enums and Regex for validation
VALID_HW_TYPES = {"CPU", "GPU"}
VALID_DATATYPES = {
"FP8",
"FP16",
"FP32",
"TF32",
"FP64",
"BFP16",
"BFP8",
"I64",
"I32",
"I16",
"I8",
"NONE",
}
# Our suite (or fixture) naming convention: must start with CPU or GPU, followed by one or more alphanum groups, and end with a valid datatype.
TESTSUITE_REGEX = re.compile(
r"^(CPU|GPU)_[A-Za-z0-9]+(?:_[A-Za-z0-9]+)*_(" + "|".join(VALID_DATATYPES) + r")$"
)
# Test type for instantiations must be one of these words, optionally with extra alphanum characters.
TEST_TYPE_REGEX = re.compile(r"^(Smoke|Standard|Full|Perf|Unit)([A-Za-z0-9]*)?$")
# Updated regexes that do not allow newlines in the macro arguments
TEST_P_REGEX = re.compile(r"\bTEST_P\(\s*([^\n,]+?)\s*,\s*([^\n\)]+?)\s*\)")
INSTANTIATE_TEST_REGEX = re.compile(
r"\bINSTANTIATE_TEST_SUITE_P\(\s*([^\n,]+?)\s*,\s*([^\n,]+?)\s*,"
)
ALLOW_UNINSTANTIATED_REGEX = re.compile(
r"\bGTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST\(\s*([^\n\)]+?)\s*\)"
)
TEST_REGEX = re.compile(r"\bTEST\(\s*([^\n,]+?)\s*,\s*([^\n\)]+?)\s*\)")
TEST_F_REGEX = re.compile(r"\bTEST_F\(\s*([^\n,]+?)\s*,\s*([^\n\)]+?)\s*\)")
def analyze_tests(folder_path):
errors = []
# Walk over all files in the folder
for root, _, files in os.walk(folder_path):
for file in files:
if not file.endswith(".cpp"):
continue
file_path = os.path.join(root, file)
# Skip file if it is in the ignore list
if os.path.basename(file_path) in IGNORE_LIST:
logging.info(f"Skipping ignored file: {file_path}")
continue
with open(file_path, "r") as f:
content = f.read()
# Use the content for proper line-number computation
def get_line_number(position):
# Count the number of newline characters before the position; add 1 so that numbering starts at 1.
return content.count("\n", 0, position) + 1
# Dictionaries to record macro definitions.
# For TEST_P, TEST, and TEST_F we key on (suite_or_fixture, test_name)
test_p_definitions = {} # key: (suite, test_name) -> line number
test_definitions = {} # key: (suite, test_name) -> line number
test_f_definitions = {} # key: (fixture, test_name) -> line number
# For INSTANTIATE_TEST_SUITE_P we group by test suite; for each suite, instantiation names must be unique.
instantiations = (
{}
) # key: suite -> dict of {instantiation_name: line number}
# For GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST, we store a list of line numbers per suite.
allowed_uninstantiated = {} # key: suite -> list of line numbers
# === Process TEST_P macros ===
for m in TEST_P_REGEX.finditer(content):
suite, test_name = m.groups()
line = get_line_number(m.start())
if suite in IGNORE_LIST or test_name in IGNORE_LIST:
logging.info(f"Skipping ignored test suite: {suite}")
continue
if not TESTSUITE_REGEX.match(suite):
errors.append(
f"{file_path}:{line}: Invalid TESTSUITE_NAME '{suite}' in TEST_P."
)
key = (suite, test_name)
if key in test_p_definitions:
prev_line = test_p_definitions[key]
errors.append(
f"{file_path}:{line}: Duplicate TEST_P for '{suite}.{test_name}' (previously defined at line {prev_line})."
)
else:
test_p_definitions[key] = line
# === Process TEST macros ===
for m in TEST_REGEX.finditer(content):
suite, test_name = m.groups()
line = get_line_number(m.start())
if suite in IGNORE_LIST or test_name in IGNORE_LIST:
logging.info(f"Skipping ignored test suite: {suite}")
continue
if not TESTSUITE_REGEX.match(suite):
errors.append(
f"{file_path}:{line}: Invalid TEST suite name '{suite}' in TEST."
)
key = (suite, test_name)
if key in test_definitions:
prev_line = test_definitions[key]
errors.append(
f"{file_path}:{line}: Duplicate TEST for '{suite}.{test_name}' (previously defined at line {prev_line})."
)
else:
test_definitions[key] = line
# === Process TEST_F macros ===
for m in TEST_F_REGEX.finditer(content):
fixture, test_name = m.groups()
line = get_line_number(m.start())
if fixture in IGNORE_LIST or test_name in IGNORE_LIST:
logging.info(f"Skipping ignored test fixture: {fixture}")
continue
if not TESTSUITE_REGEX.match(fixture):
errors.append(
f"{file_path}:{line}: Invalid TEST_F fixture name '{fixture}'."
)
key = (fixture, test_name)
if key in test_f_definitions:
prev_line = test_f_definitions[key]
errors.append(
f"{file_path}:{line}: Duplicate TEST_F for '{fixture}.{test_name}' (previously defined at line {prev_line})."
)
else:
test_f_definitions[key] = line
# === Process INSTANTIATE_TEST_SUITE_P macros ===
for m in INSTANTIATE_TEST_REGEX.finditer(content):
instantiation_name, suite = m.groups()
line = get_line_number(m.start())
if suite in IGNORE_LIST or instantiation_name in IGNORE_LIST:
logging.info(f"Skipping ignored instantiation for suite: {suite}")
continue
normalized_instantiation = instantiation_name.replace("\\", "").strip()
if not TEST_TYPE_REGEX.match(normalized_instantiation):
errors.append(
f"{file_path}:{line}: Invalid TEST_TYPE '{instantiation_name}' in INSTANTIATE_TEST_SUITE_P."
)
# For each suite, the instantiation names must be unique.
if suite not in instantiations:
instantiations[suite] = {}
if instantiation_name in instantiations[suite]:
prev_line = instantiations[suite][instantiation_name]
errors.append(
f"{file_path}:{line}: Duplicate INSTANTIATE_TEST_SUITE_P instantiation name '{instantiation_name}' for suite '{suite}' (previously defined at line {prev_line})."
)
else:
instantiations[suite][instantiation_name] = line
# === Process GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST macros ===
for m in ALLOW_UNINSTANTIATED_REGEX.finditer(content):
suite = m.group(1).strip()
line = get_line_number(m.start())
if suite in IGNORE_LIST:
logging.info(
f"Skipping ignored allowed uninstantiated suite: {suite}"
)
continue
allowed_uninstantiated.setdefault(suite, []).append(line)
# === Validate TEST_P definitions have a corresponding instantiation or allowed exception ===
for (suite, test_name), line in test_p_definitions.items():
if suite not in instantiations and suite not in allowed_uninstantiated:
errors.append(
f"{file_path}:{line}: TEST_P '{suite}.{test_name}' does not have a matching INSTANTIATE_TEST_SUITE_P or GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST."
)
# === Validate INSTANTIATE_TEST_SUITE_P references an existing TEST_P suite ===
for suite, inst_map in instantiations.items():
# If there is no TEST_P for this suite, then flag an error for each instantiation occurrence.
if not any(
suite == tp_suite for (tp_suite, _) in test_p_definitions.keys()
):
for instantiation_name, line in inst_map.items():
errors.append(
f"{file_path}:{line}: INSTANTIATE_TEST_SUITE_P references non-existent TEST_P suite '{suite}'."
)
return errors
def main():
errors = analyze_tests(FOLDER_PATH)
if errors:
logging.error("The following issues were found:")
for error in errors:
logging.error(f" {error}")
raise ValueError("Validation failed. See the errors above.")
else:
logging.info("All tests meet the criteria.")
if __name__ == "__main__":
main()