-
Notifications
You must be signed in to change notification settings - Fork 239
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
Automated gtest formatting checks #3370
base: develop
Are you sure you want to change the base?
Changes from all commits
625127e
f1303f6
34b5365
f6aea6d
68bdf71
c5e7bc5
792a89f
a72f2db
7c8309c
6d2862a
55b691d
857353b
8639afe
a0ca1a6
6370f44
28eaf21
5c730bb
5bac5d3
7f75e55
63265c4
e60c943
afe09d5
a9dd0a3
331ffbc
36f73c8
35bccf2
0e5f825
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import os | ||
import re | ||
import logging | ||
|
||
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') | ||
|
||
FOLDER_PATH = "../../test/gtest" | ||
|
||
# Ignore list: Add test names or file paths you want to exclude | ||
IGNORE_LIST = { | ||
"CPU_MIOpenDriverRegressionBigTensorTest_FP32", | ||
"../../test/gtest/reduce_custom_fp32.cpp", | ||
"../../test/gtest/binary_tensor_ops.cpp", | ||
"../../test/gtest/layout_transpose.cpp", | ||
"../../test/gtest/graphapi_conv_bias_res_add_activ_fwd.cpp", | ||
"../../test/gtest/unary_tensor_ops.cpp", | ||
"../../test/gtest/graphapi_operation_rng.cpp" | ||
} | ||
|
||
# Valid enums and Regex for validation | ||
VALID_HW_TYPES = {"CPU", "GPU"} | ||
VALID_DATATYPES = {"FP8", "FP16", "FP32", "FP64", "BFP16", "BFP8", "I64", "I32", "I16", "I8", "NONE"} | ||
TESTSUITE_REGEX = re.compile( | ||
r"^(CPU|GPU)_[A-Za-z0-9]+(?:_[A-Za-z0-9]+)*_(" + "|".join(VALID_DATATYPES) + r")$" | ||
) | ||
Comment on lines
+21
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't it duplicate #3218 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one extends on it + CI integration that runs upon adding new gtest There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need separate python scripts? |
||
TEST_P_REGEX = re.compile(r"\bTEST_P\(\s*([^,]+),\s*([^)]+)\)") | ||
INSTANTIATE_TEST_REGEX = re.compile(r"\bINSTANTIATE_TEST_SUITE_P\(\s*([^,]+),\s*([^,]+),") | ||
ALLOW_UNINSTANTIATED_REGEX = re.compile(r"\bGTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST\(\s*([^)]+)\)") | ||
TEST_TYPE_REGEX = re.compile(r"^(Smoke|Full|Perf|Unit)([A-Za-z0-9]*)?$") | ||
|
||
|
||
def analyze_tests(folder_path): | ||
errors = [] | ||
|
||
for root, _, files in os.walk(folder_path): | ||
for file in files: | ||
if file.endswith(".cpp"): | ||
file_path = os.path.join(root, file) | ||
|
||
if file_path in IGNORE_LIST: | ||
logging.info(f"Skipping ignored file: {file_path}") | ||
continue | ||
|
||
with open(file_path, "r") as f: | ||
lines = f.readlines() | ||
|
||
# Store all content in a single string for regex matching | ||
content = "".join(lines) | ||
|
||
# Extract TEST_P, INSTANTIATE_TEST_SUITE_P, and GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST | ||
test_p_matches = [(m.start(), m.groups()) for m in TEST_P_REGEX.finditer(content)] | ||
instantiate_matches = [(m.start(), m.groups()) for m in INSTANTIATE_TEST_REGEX.finditer(content)] | ||
allow_uninstantiated_matches = [(m.start(), m.group(1)) for m in ALLOW_UNINSTANTIATED_REGEX.finditer(content)] | ||
|
||
# Map line numbers to errors | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line numbers, nice! They aren't quite right though. In all the instances I've seen, they are too small by 1. I also injected an error on line 1 and it printed as line 0, which suggests a counting or indexing issue. The image also shows another problem I found--if a line is an exact copy of a previous line, one of them is ignored. In this example, line 86 was copied and pasted at line 91 and line 86 was missed. I've also seen cases when 91 was the missing one. You'll probably need to do something along the lines of generating a hash and use that for the key for each line instead of the text itself. If a comma is missed, the regex consumes everything until the next comma is hit--including CR's. See the error prints for line 88 (printed as line 87). This needs to be fixed in this PR. This also demonstrates other checks we should add in the future:
|
||
def get_line_number(position): | ||
return sum(1 for i, line in enumerate(lines) if position >= len("".join(lines[:i + 1]))) | ||
|
||
test_p_suites = {suite: (info, get_line_number(pos)) for pos, (suite, info) in test_p_matches} | ||
instantiated_suites = {suite: (test_type, get_line_number(pos)) for pos, (test_type, suite) in instantiate_matches} | ||
allowed_uninstantiated_suites = {(suite, get_line_number(pos)) for pos, suite in allow_uninstantiated_matches} | ||
|
||
# Validate TEST_P suites | ||
for suite, (info, line) in test_p_suites.items(): | ||
if suite 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.") | ||
|
||
if suite not in instantiated_suites and suite not in [s[0] for s in allowed_uninstantiated_suites]: | ||
errors.append( | ||
f"{file_path}:{line}: Test '{suite}.{info}' does not have a matching " | ||
f"INSTANTIATE_TEST_SUITE_P or GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST." | ||
) | ||
|
||
# Validate instantiated suites | ||
for suite, (test_type, line) in instantiated_suites.items(): | ||
normalized_test_type = test_type.replace("\\", "").strip() | ||
|
||
if suite in IGNORE_LIST: | ||
logging.info(f"Skipping ignored instantiated suite: {suite}") | ||
continue | ||
|
||
if suite not in test_p_suites: | ||
errors.append(f"{file_path}:{line}: INSTANTIATE_TEST_SUITE_P references non-existent TESTSUITE_NAME '{suite}'.") | ||
if not TEST_TYPE_REGEX.match(normalized_test_type): | ||
errors.append(f"{file_path}:{line}: Invalid TEST_TYPE '{test_type}' in INSTANTIATE_TEST_SUITE_P.") | ||
|
||
# Validate allowed uninstantiated suites | ||
for suite, line in allowed_uninstantiated_suites: | ||
if suite in IGNORE_LIST: | ||
logging.info(f"Skipping ignored allowed uninstantiated suite: {suite}") | ||
continue | ||
|
||
if suite not in test_p_suites: | ||
errors.append(f"{file_path}:{line}: GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST references non-existent TESTSUITE_NAME '{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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it excluded? It's not an
UNINSTANTIATED_PARAMETERIZED_TEST
- it is perfectly instantiated.I guess the problem is in the parsing, since you have to run preprocessor prior to get proper code, but preproceesor will eliminate all
INSTANTIATE_TEST_SUITE_P
macros.I would suggest keeping "hard to parse" cases separated from "UNINSTANTIATED_PARAMETERIZED_TEST" cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests have formatting names that aren't to standard for gtest folder - some are in the macro section, there are regexes like ##SRC_TYPE## in the tests name:
We can either accommodate them or modify them with the standard in different PR: