Skip to content

Commit e7213f6

Browse files
committed
WIP: --changed
1 parent 82be52c commit e7213f6

File tree

4 files changed

+63
-15
lines changed

4 files changed

+63
-15
lines changed

tests/unit/test_project.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1942,7 +1942,7 @@ def hash_file_name_of(self, source_file):
19421942
"""
19431943
Get the hash file name of a source_file
19441944
"""
1945-
return self.project._hash_file_name_of(source_file) # pylint: disable=protected-access
1945+
return self.project.hash_file_name_of(source_file)
19461946

19471947
def update(self, source_file):
19481948
"""

vunit/project.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ def get_compile_timestamps(self, files):
481481
# Cache timestamps to avoid duplicate file operations
482482
timestamps = {}
483483
for source_file in files:
484-
hash_file_name = self._hash_file_name_of(source_file)
484+
hash_file_name = self.hash_file_name_of(source_file)
485485
if not ostools.file_exists(hash_file_name):
486486
timestamps[source_file] = None
487487
else:
@@ -614,7 +614,7 @@ def _needs_recompile(self, dependency_graph, source_file, timestamps):
614614
"""
615615
timestamp = timestamps[source_file]
616616

617-
content_hash_file_name = self._hash_file_name_of(source_file)
617+
content_hash_file_name = self.hash_file_name_of(source_file)
618618
if timestamp is None:
619619
LOGGER.debug(
620620
"%s has no vunit_hash file at %s and must be recompiled",
@@ -649,7 +649,7 @@ def _needs_recompile(self, dependency_graph, source_file, timestamps):
649649

650650
return False
651651

652-
def _hash_file_name_of(self, source_file):
652+
def hash_file_name_of(self, source_file):
653653
"""
654654
Returns the name of the hash file associated with the source_file
655655
"""
@@ -663,5 +663,5 @@ def update(self, source_file):
663663
to update the timestamp
664664
"""
665665
new_content_hash = source_file.content_hash
666-
ostools.write_file(self._hash_file_name_of(source_file), new_content_hash)
666+
ostools.write_file(self.hash_file_name_of(source_file), new_content_hash)
667667
LOGGER.debug("Wrote %s content_hash=%s", source_file.name, new_content_hash)

vunit/ui/__init__.py

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from ..test.bench_list import TestBenchList
4040
from ..test.report import TestReport
4141
from ..test.runner import TestRunner
42+
from ..test.list import TestList
4243
from ..dependency_graph import CircularDependencyException
4344

4445
from .common import LOGGER, TEST_OUTPUT_PATH, select_vhdl_standard, check_not_empty
@@ -173,8 +174,10 @@ def __init__(
173174
self._builtins = Builtins(self, self._vhdl_standard, simulator_class)
174175

175176
self._dependency_graph = None
176-
self._include_in_test_pattern: Optional[List[Union[str, Path]]] = []
177-
self._exclude_from_test_pattern: Optional[List[Union[str, Path]]] = []
177+
self._include_in_test_pattern: Optional[List[Union[str, Path]]] = None
178+
self._exclude_from_test_pattern: Optional[List[Union[str, Path]]] = None
179+
self._latest_dependency_updates = None
180+
self._test_history = None
178181

179182
def _create_database(self):
180183
"""
@@ -803,14 +806,14 @@ def get_dependent_files(dependencies):
803806

804807
dependent_files = get_dependent_files(include_dependencies) - get_dependent_files(exclude_dependencies)
805808

806-
# Extract testbenches from dependent files and create corresponding test patterns:
807-
# lib_name.tb_name*
808-
test_patterns = []
809-
for dependent_file in dependent_files:
810-
library_name = dependent_file.library.name
811-
for testbench in self._test_bench_list.get_test_benches_in_library(library_name):
812-
if testbench.design_unit.source_file == dependent_file:
813-
test_patterns.append(f"{library_name}.{testbench.name}*")
809+
# Extract testbenches from dependent files
810+
dependent_testbenches = []
811+
for testbench in self._test_bench_list.get_test_benches():
812+
if testbench.design_unit.source_file in dependent_files:
813+
dependent_testbenches.append(testbench)
814+
815+
# Create test patterns for remaining testbenches: lib_name.tb_name*
816+
test_patterns = [f"{testbench.library_name}.{testbench.name}*" for testbench in dependent_testbenches]
814817

815818
# Update test filter to match test patterns
816819
if isinstance(self._args.test_patterns, list):
@@ -874,6 +877,12 @@ def _get_latest_dependency_updates(self):
874877

875878
source_files_in_order = self._dependency_graph.toposort()
876879
source_file_timestamps = self._project.get_compile_timestamps(source_files_in_order)
880+
for source_file in source_files_in_order:
881+
hash_file_name = self._project.hash_file_name_of(source_file)
882+
if not ostools.file_exists(hash_file_name) or (
883+
ostools.read_file(hash_file_name) != source_file.content_hash
884+
):
885+
source_file_timestamps[source_file] = 10e9
877886

878887
for source_file, timestamp in source_file_timestamps.items():
879888
dependency_time_stamps = [
@@ -975,12 +984,41 @@ def _update_test_history(self, report, simulator_if):
975984

976985
self._database[b"test_history"] = test_history
977986

987+
def _get_test_list_depending_on_change(self, test_list):
988+
if self._latest_dependency_updates is None:
989+
self._latest_dependency_updates = self._get_latest_dependency_updates()
990+
991+
if self._test_history is None:
992+
self._test_history = self._get_test_history(simulator_if=None)
993+
994+
def depending_on_change(test_suite):
995+
latest_dependency_update = self._latest_dependency_updates[test_suite.file_name]
996+
test_suite_history = self._test_history.get(test_suite.name, None)
997+
if test_suite_history:
998+
test_start_times = [test_data["start_time"] for test_data in test_suite_history.values()]
999+
if None in test_start_times:
1000+
return True
1001+
1002+
return min(test_start_times) < latest_dependency_update
1003+
else:
1004+
return True
1005+
1006+
new_test_list = TestList()
1007+
for test_suite in test_list:
1008+
if depending_on_change(test_suite):
1009+
new_test_list.add_suite(test_suite)
1010+
1011+
return new_test_list
1012+
9781013
def _main_run(self, post_run):
9791014
"""
9801015
Main with running tests
9811016
"""
9821017
simulator_if = self._create_simulator_if()
9831018
test_list = self._create_tests(simulator_if)
1019+
if self._args.changed:
1020+
test_list = self._get_test_list_depending_on_change(test_list)
1021+
9841022
self._compile(simulator_if)
9851023
print()
9861024

@@ -1015,6 +1053,9 @@ def _main_list_only(self):
10151053
Main function when only listing test cases
10161054
"""
10171055
test_list = self._create_tests(simulator_if=None)
1056+
if self._args.changed:
1057+
test_list = self._get_test_list_depending_on_change(test_list)
1058+
10181059
for test_name in test_list.test_names:
10191060
print(test_name)
10201061
print(f"Listed {test_list.num_tests} tests")

vunit/vunit_cli.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ def _create_argument_parser(description=None, for_documentation=False):
166166
help="Output path for compilation and simulation artifacts",
167167
)
168168

169+
parser.add_argument(
170+
"--changed",
171+
action="store_true",
172+
default=False,
173+
help="Include only tests that depend on file changes since the last recorded test run",
174+
)
175+
169176
parser.add_argument(
170177
"--test-prio",
171178
choices=["opt", "ordered"],

0 commit comments

Comments
 (0)