diff --git a/python/gvtest/runner.py b/python/gvtest/runner.py index 49fb3f7..ea235c9 100644 --- a/python/gvtest/runner.py +++ b/python/gvtest/runner.py @@ -131,6 +131,7 @@ def __init__( self.properties: dict[str, str] = {} self.test_list: list[str] | None = test_list self.target_names: list[str] = targets if targets is not None else ['default'] + self._cli_targets_explicit: bool = targets is not None self.platform: str = platform if targets is None: self.default_target: Target = Target('default') @@ -417,7 +418,7 @@ def stop(self) -> None: @property def _cli_targets_specified(self) -> bool: """True when the user explicitly passed --target.""" - return self.target_names != ['default'] + return self._cli_targets_explicit def add_testset(self, file: str) -> None: if not os.path.isabs(file): @@ -480,10 +481,18 @@ def _resolve_targets_for_dir( loader, '_targets_config_dir', None ) targets: list[Target] = [] + # Filter by --target names, but only filter out + # YAML targets that aren't requested. 'default' + # in target_names means "untargeted tests" and + # doesn't affect YAML target resolution. + cli_real_targets = ( + [n for n in self.target_names if n != 'default'] + if self._cli_targets_specified + else [] + ) for name, cfg in yaml_targets.items(): - # If CLI specifies targets, filter - if (self.target_names != ['default'] - and name not in self.target_names): + if (cli_real_targets + and name not in cli_real_targets): continue t = Target.from_dict(name, cfg) t.config_dir = config_dir diff --git a/python/gvtest/tests.py b/python/gvtest/tests.py index 13bce4c..3d1cce2 100644 --- a/python/gvtest/tests.py +++ b/python/gvtest/tests.py @@ -427,17 +427,30 @@ def enqueue(self) -> None: else self.runner.get_config() ) - # When --target is specified, skip tests whose target - # is just the fallback (not from a gvtest.yaml - # targets section). This ensures only tests - # belonging to a real target are executed. - if (self.runner._cli_targets_specified - and self.target is not None - and getattr( - self.target, '_is_fallback', False - )): - # Don't count or show — silently excluded - return + # Target filtering when --target is specified: + is_fallback = ( + self.target is not None + and getattr(self.target, '_is_fallback', False) + ) + if self.runner._cli_targets_specified: + want_default = 'default' in self.runner.target_names + if is_fallback: + # Untargeted test — only run if 'default' + # is in the requested targets + if not want_default: + return + else: + # Targeted test — only run if its target + # name is in the requested targets + target_name = ( + self.target.name + if self.target is not None + else None + ) + if (target_name is not None + and target_name + not in self.runner.target_names): + return self.runner.count_test() if self.runner.tui is not None: diff --git a/tests/test_runner.py b/tests/test_runner.py index 95b4452..a53c43c 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -916,6 +916,86 @@ def testset_build(testset): # Both sub-testsets should run (1 test each) assert r.stats.stats['passed'] == 2 + def test_target_default_runs_only_untargeted( + self, tmp_path + ): + """--target default should run only tests without + a target definition, skipping targeted tests.""" + # Sub with real targets + sub = tmp_path / 'sub' + sub.mkdir() + (sub / 'gvtest.yaml').write_text( + 'targets:\n target_a: {}\n' + ) + (sub / 'testset.cfg').write_text(''' +from gvtest.testsuite import * + +def testset_build(testset): + testset.set_name('sub') + test = testset.new_test('targeted') + test.add_command(Shell('run', 'echo targeted')) +''') + # Root with untargeted test + import of sub + root = tmp_path / 'testset.cfg' + root.write_text(''' +from gvtest.testsuite import * + +def testset_build(testset): + testset.set_name('root') + test = testset.new_test('untargeted') + test.add_command(Shell('run', 'echo untargeted')) + testset.import_testset('sub/testset.cfg') +''') + r = Runner( + properties=[], flags=[], nb_threads=1, + targets=['default'] + ) + r.add_testset(str(root)) + r.start() + r.run() + r.stop() + # Only the untargeted test should run + assert r.stats.stats['passed'] == 1 + + def test_target_default_and_named_together( + self, tmp_path + ): + """--target default --target X should run both + untargeted tests and tests for target X.""" + sub = tmp_path / 'sub' + sub.mkdir() + (sub / 'gvtest.yaml').write_text( + 'targets:\n target_a: {}\n' + ) + (sub / 'testset.cfg').write_text(''' +from gvtest.testsuite import * + +def testset_build(testset): + testset.set_name('sub') + test = testset.new_test('targeted') + test.add_command(Shell('run', 'echo targeted')) +''') + root = tmp_path / 'testset.cfg' + root.write_text(''' +from gvtest.testsuite import * + +def testset_build(testset): + testset.set_name('root') + test = testset.new_test('untargeted') + test.add_command(Shell('run', 'echo untargeted')) + testset.import_testset('sub/testset.cfg') +''') + r = Runner( + properties=[], flags=[], nb_threads=1, + targets=['default', 'target_a'] + ) + r.add_testset(str(root)) + r.start() + r.run() + r.stop() + # Both should run + assert r.stats.stats['passed'] == 2 + def test_no_cli_target_runs_all_yaml_targets( self, tmp_path ):