From f673e1fe99dca5b7526c65eb70dce81f907c20ab Mon Sep 17 00:00:00 2001 From: "Randy J. Spaulding" Date: Mon, 2 Feb 2026 21:20:28 -0500 Subject: [PATCH] implement fixturemap --- script/dependency-parser/main.py | 5 ++ .../src/all_gtest_fixtures.py | 76 +++++++++++++++++++ .../src/selective_test_filter.py | 39 +++++++++- 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 script/dependency-parser/src/all_gtest_fixtures.py diff --git a/script/dependency-parser/main.py b/script/dependency-parser/main.py index f345362b26..4f4dbe22ed 100644 --- a/script/dependency-parser/main.py +++ b/script/dependency-parser/main.py @@ -66,6 +66,9 @@ def main(): parser_test.add_argument( "--output", help="Output JSON file", default="tests_to_run.json" ) + parser_test.add_argument( + "--fixturemap", help="Optional path to file containing the test <-> gtest fixture mapping", default="" + ) # Code auditing parser_audit = subparsers.add_parser( @@ -95,6 +98,8 @@ def main(): filter_args.append("--all") if args.output: filter_args += ["--output", args.output] + if args.fixturemap: + filter_args += ["--fixturemap", args.fixturemap] run_selective_test_filter(filter_args) elif args.command == "audit": run_selective_test_filter([args.depmap_json, "--audit"]) diff --git a/script/dependency-parser/src/all_gtest_fixtures.py b/script/dependency-parser/src/all_gtest_fixtures.py new file mode 100644 index 0000000000..020f40dc4c --- /dev/null +++ b/script/dependency-parser/src/all_gtest_fixtures.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +import os +import stat +import subprocess +import json +import sys +from pathlib import Path + +def is_executable(file_path: Path) -> bool: + """Check if a file is an executable (not a directory).""" + return ( + file_path.is_file() + and os.access(file_path, os.X_OK) + and not file_path.name.startswith('.') # skip hidden files + ) + +def list_gtest_fixtures(executable: Path): + """Run the executable with --gtest_list_tests and return fixture names.""" + try: + # Run the command and capture output + result = subprocess.run( + [str(executable), "--gtest_list_tests"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=10 # prevent hanging + ) + if result.returncode != 0: + print(f"Warning: {executable} returned non-zero exit code. Assuming it is not a gtest.", file=sys.stderr) + return [] + + fixtures = [] + for line in result.stdout.splitlines(): + if line.strip() and not line.startswith(" ") and line.endswith("."): # non-indented = fixture name + fixtures.append(line.strip()) + return fixtures + + except subprocess.TimeoutExpired: + print(f"Error: {executable} timed out", file=sys.stderr) + return [] + except Exception as e: + print(f"Error running {executable}: {e}", file=sys.stderr) + return [] + +def main(directory: str, output_file: str): + dir_path = Path(directory) + if not dir_path.is_dir(): + print(f"Error: {directory} is not a valid directory", file=sys.stderr) + sys.exit(1) + + results = {} + fixture_count = 0 + for file in dir_path.iterdir(): + if is_executable(file): + fixtures = list_gtest_fixtures(file) + if fixtures: +# src_file = file.name[5:] + ".cpp" # For MIOpen gtests, src_file.cpp <-> test_src_file + src_file = file.name # however, we're currently using the exe's name + results[src_file] = fixtures + fixture_count += len(fixtures) + + # Write results to JSON + try: + with open(output_file, "w", encoding="utf-8") as f: + json.dump(results, f, indent=4) + print(f"List of {fixture_count} fixtures from {len(results)} files written to {output_file}") + except Exception as e: + print(f"Error writing JSON file: {e}", file=sys.stderr) + +if __name__ == "__main__": + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ", file=sys.stderr) + sys.exit(1) + + main(sys.argv[1], sys.argv[2]) + diff --git a/script/dependency-parser/src/selective_test_filter.py b/script/dependency-parser/src/selective_test_filter.py index 83f7f7eebe..1be5851d6d 100644 --- a/script/dependency-parser/src/selective_test_filter.py +++ b/script/dependency-parser/src/selective_test_filter.py @@ -57,6 +57,13 @@ def load_depmap(depmap_json): return data +def load_fixturemap(fixture_file): + """Load the dependency mapping JSON.""" + with open(fixture_file, "r") as f: + data = json.load(f) + return data + + def select_tests(file_to_executables, changed_files, filter_mode): """Return a set of test executables affected by changed files.""" affected = set() @@ -70,6 +77,18 @@ def select_tests(file_to_executables, changed_files, filter_mode): return sorted(affected) +def get_gtest_filter(tests, fixturemap): + """Maps the set of texts to be executed to a gtest_filter""" + gtest_filter = "" + for t in tests: + if t in fixturemap: + for f in t: + gtest_filter += fixturemap[t][f] + "*:" + if gtest_filter: + gtest_filter = gtest_filter[:-1] + return gtest_filter + + def main(): if "--audit" in sys.argv: if len(sys.argv) < 2: @@ -127,7 +146,10 @@ def main(): idx = sys.argv.index("--output") if idx + 1 < len(sys.argv): output_json = sys.argv[idx + 1] - + if "--fixturemap" in sys.argv: + idx = sys.argv.index("--fixturemap") + if idx + 1 < len(sys.argv): + fixture_file = sys.argv[idx + 1] if not os.path.exists(depmap_json): print(f"Dependency map JSON not found: {depmap_json}") sys.exit(1) @@ -139,11 +161,20 @@ def main(): else: file_to_executables = load_depmap(depmap_json) tests = select_tests(file_to_executables, changed_files, filter_mode) + gtest_filter = "" + if tests and os.path.exists(fixture_file): + tests_to_fixtures = load_fixturemap(fixture_file) + gtest_filter = get_gtest_filter(tests, tests_to_fixtures) with open(output_json, "w") as f: - json.dump( - {"tests_to_run": tests, "changed_files": sorted(changed_files)}, f, indent=2 - ) + if gtest_filter: + json.dump( + {"tests_to_run": tests, "gtest_filter": gtest_filter, "changed_files": sorted(changed_files)}, f, indent=2 + ) + else: + json.dump( + {"tests_to_run": tests, "changed_files": sorted(changed_files)}, f, indent=2 + ) print(f"Exported {len(tests)} tests to run to {output_json}")