diff --git a/.github/scripts/tests/therock_matrix_test.py b/.github/scripts/tests/therock_matrix_test.py index 223c3ebd970..8bea16e1ff8 100644 --- a/.github/scripts/tests/therock_matrix_test.py +++ b/.github/scripts/tests/therock_matrix_test.py @@ -1,3 +1,4 @@ +import copy from pathlib import Path import os import sys @@ -13,6 +14,11 @@ def test_collect_projects_to_run_without_additional_option(self): project_to_run = therock_matrix.collect_projects_to_run(subtrees) self.assertEqual(len(project_to_run), 1) + blas_entry = project_to_run[0] + self.assertIn( + "hipsparselt", + blas_entry["projects_to_test"].split(","), + ) def test_collect_projects_to_run(self): subtrees = ["projects/rocsparse", "projects/hipblaslt"] @@ -36,7 +42,31 @@ def test_collect_projects_to_run_dependency_graph_diff_projects(self): subtrees = ["projects/miopen", "projects/rocwmma"] project_to_run = therock_matrix.collect_projects_to_run(subtrees) - self.assertEqual(len(project_to_run), 2) + # rocwmma only contributes via blas under additional_options; miopen absorbs blas. + self.assertEqual(len(project_to_run), 1) + combined = project_to_run[0] + self.assertIn("rocwmma", combined["projects_to_test"].split(",")) + self.assertIn("miopen", combined["projects_to_test"].split(",")) + + def test_collect_projects_to_run_does_not_mutate_module_state(self): + # Snapshot module-level dicts, run a series of representative calls, and + # confirm the originals are untouched. This guards against the + # mutate-globals regression that previously required importlib.reload + # between tests. + project_map_before = copy.deepcopy(therock_matrix.project_map) + additional_options_before = copy.deepcopy(therock_matrix.additional_options) + + therock_matrix.collect_projects_to_run(["projects/hipblaslt"]) + therock_matrix.collect_projects_to_run( + ["projects/rocsparse", "projects/hipblaslt"] + ) + therock_matrix.collect_projects_to_run( + ["projects/miopen", "projects/hipblaslt"] + ) + therock_matrix.collect_projects_to_run(["projects/miopen", "projects/rocwmma"]) + + self.assertEqual(therock_matrix.project_map, project_map_before) + self.assertEqual(therock_matrix.additional_options, additional_options_before) if __name__ == "__main__": diff --git a/.github/scripts/therock_matrix.py b/.github/scripts/therock_matrix.py index 013ae6ba491..4769d1d4cfb 100644 --- a/.github/scripts/therock_matrix.py +++ b/.github/scripts/therock_matrix.py @@ -2,6 +2,7 @@ This dictionary is used to map specific file directory changes to the corresponding build flag and tests """ +import copy import os subtree_to_project_map = { @@ -152,36 +153,53 @@ "miopen": ["blas", "rand", "fusilli-provider"], } +# When these subtrees change, also activate the given optional matrix project so +# its additional_options merge into the parent job (e.g. hipSPARSELt depends on hipBLASLt). +SUBTREE_EXTRA_MATRIX_PROJECTS = { + "projects/hipblaslt": "sparselt", +} + def collect_projects_to_run(subtrees): platform = os.getenv("PLATFORM") projects = set() + # Work on per-call deep copies so module-level state stays immutable across calls. + # Without this, the function would extend lists, replace values, and delete keys + # in `project_map` / `additional_options`, breaking any second call in the same + # process (and forcing tests to `importlib.reload` between cases). + local_project_map = copy.deepcopy(project_map) + local_additional_options = copy.deepcopy(additional_options) + # collect the associated subtree to project for subtree in subtrees: if subtree in subtree_to_project_map: projects.add(subtree_to_project_map.get(subtree)) + extra_matrix = SUBTREE_EXTRA_MATRIX_PROJECTS.get(subtree) + if extra_matrix: + projects.add(extra_matrix) + for project in list(projects): # Check if an optional math component was included. - if project in additional_options: - project_options_to_add = additional_options[project] + if project in local_additional_options: + project_options_to_add = local_additional_options[project] project_to_add = project_options_to_add["project_to_add"] # If `project_to_add` is in included, add options to the existing `project_map` entry if project_to_add in projects: - project_map[project_to_add]["cmake_options"].extend( + local_project_map[project_to_add]["cmake_options"].extend( project_options_to_add["cmake_options"] ) - project_map[project_to_add]["projects_to_test"].extend( + local_project_map[project_to_add]["projects_to_test"].extend( project_options_to_add["projects_to_test"] ) # If `project_to_add` is not included, only run build and tests for the optional project else: projects.add(project_to_add) - project_map[project_to_add]["cmake_options"] = project_options_to_add[ - "cmake_options" - ] - project_map[project_to_add]["projects_to_test"] = ( + local_project_map[project_to_add]["cmake_options"] = ( + project_options_to_add["cmake_options"] + ) + local_project_map[project_to_add]["projects_to_test"] = ( project_options_to_add["projects_to_test"] ) @@ -193,24 +211,24 @@ def collect_projects_to_run(subtrees): for dependency in dependency_graph[project]: # If the dependency is also included, let's combine to avoid overlap if dependency in projects: - project_map[project]["cmake_options"].extend( - project_map[dependency]["cmake_options"] + local_project_map[project]["cmake_options"].extend( + local_project_map[dependency]["cmake_options"] ) - project_map[project]["projects_to_test"].extend( - project_map[dependency]["projects_to_test"] + local_project_map[project]["projects_to_test"].extend( + local_project_map[dependency]["projects_to_test"] ) to_remove_from_project_map.append(dependency) # if dependency is included in projects and parent is found, we delete the dependency as the parent will build and test for to_remove_item in to_remove_from_project_map: projects.remove(to_remove_item) - del project_map[to_remove_item] + del local_project_map[to_remove_item] # retrieve the subtrees to checkout, cmake options to build, and projects to test project_to_run = [] for project in projects: - if project in project_map: - project_map_data = project_map.get(project) + if project in local_project_map: + project_map_data = local_project_map.get(project) # Check if platform-based additional flags are needed if (