from unittest.mock import MagicMock import pytest from test_fixtures.pytest_markers import deselect_items_by_tag def make_item(tag: str | None) -> MagicMock: item = MagicMock(spec=pytest.Item) if tag is None: item.get_closest_marker.return_value = None else: marker = MagicMock() marker.args = (tag,) item.get_closest_marker.return_value = marker return item def make_config() -> MagicMock: config = MagicMock(spec=pytest.Config) config.hook = MagicMock() return config # --- Marker present but no positional args raises clearly --- def test_marker_without_args_raises_value_error(): config = make_config() item = MagicMock(spec=pytest.Item) item.nodeid = 'test_module.py::test_foo' marker = MagicMock() marker.args = () item.get_closest_marker.return_value = marker with pytest.raises(ValueError, match='requires a positional tag argument'): deselect_items_by_tag(config, [item], 'lambda_name', active_tags={'foo'}) # --- Items without the marker are always kept --- def test_items_without_marker_always_kept(): config = make_config() unmarked = make_item(None) items = [unmarked] deselect_items_by_tag(config, items, 'lambda_name', active_tags={'foo'}) assert items == [unmarked] config.hook.pytest_deselected.assert_not_called() # --- Normal tags: empty active_tags means run everything --- def test_empty_active_tags_runs_all_normal_tags(): config = make_config() items = [make_item('foo'), make_item('bar')] deselect_items_by_tag(config, items, 'lambda_name', active_tags=set()) assert len(items) == 2 config.hook.pytest_deselected.assert_not_called() def test_active_tags_keeps_matching_items(): config = make_config() foo = make_item('foo') items = [foo] deselect_items_by_tag(config, items, 'lambda_name', active_tags={'foo'}) assert items == [foo] config.hook.pytest_deselected.assert_not_called() def test_active_tags_deselects_non_matching_items(): config = make_config() foo, bar = make_item('foo'), make_item('bar') items = [foo, bar] deselect_items_by_tag(config, items, 'lambda_name', active_tags={'foo'}) assert items == [foo] config.hook.pytest_deselected.assert_called_once_with(items=[bar]) def test_multiple_active_tags_keeps_all_matching(): config = make_config() foo, bar, baz = make_item('foo'), make_item('bar'), make_item('baz') items = [foo, bar, baz] deselect_items_by_tag(config, items, 'lambda_name', active_tags={'foo', 'bar'}) assert set(items) == {foo, bar} config.hook.pytest_deselected.assert_called_once_with(items=[baz]) # --- always_deselect_when_missing: deselected even when active_tags is empty --- def test_always_deselect_when_missing_and_active_tags_empty(): config = make_config() sfn = make_item('state-machine') items = [sfn] deselect_items_by_tag( config, items, 'lambda_name', active_tags=set(), always_deselect_when_missing={'state-machine'}, ) assert items == [] config.hook.pytest_deselected.assert_called_once_with(items=[sfn]) def test_always_deselect_when_missing_kept_when_in_active_tags(): config = make_config() sfn = make_item('state-machine') items = [sfn] deselect_items_by_tag( config, items, 'lambda_name', active_tags={'state-machine'}, always_deselect_when_missing={'state-machine'}, ) assert items == [sfn] config.hook.pytest_deselected.assert_not_called() # --- Mixed: normal tags + always_deselect_when_missing --- def test_mixed_normal_and_always_deselect(): config = make_config() foo, bar, sfn = make_item('foo'), make_item('bar'), make_item('state-machine') items = [foo, bar, sfn] deselect_items_by_tag( config, items, 'lambda_name', active_tags={'foo'}, always_deselect_when_missing={'state-machine'}, ) assert items == [foo] config.hook.pytest_deselected.assert_called_once_with(items=[bar, sfn]) def test_mixed_empty_active_tags_still_deselects_always_deselect(): config = make_config() foo, sfn = make_item('foo'), make_item('state-machine') items = [foo, sfn] deselect_items_by_tag( config, items, 'lambda_name', active_tags=set(), always_deselect_when_missing={'state-machine'}, ) assert items == [foo] config.hook.pytest_deselected.assert_called_once_with(items=[sfn]) # --- No deselection when nothing to deselect --- def test_no_deselected_hook_called_when_nothing_deselected(): config = make_config() items = [make_item('foo'), make_item('bar')] deselect_items_by_tag(config, items, 'lambda_name', active_tags={'foo', 'bar'}) config.hook.pytest_deselected.assert_not_called()