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
258 lines
11 KiB
Python
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()
|