diff --git a/_pytest/mark/legacy.py b/_pytest/mark/legacy.py index ec45f12afdb..5c7b8d0013a 100644 --- a/_pytest/mark/legacy.py +++ b/_pytest/mark/legacy.py @@ -5,8 +5,6 @@ import attr import keyword -from . import MarkInfo, MarkDecorator - from _pytest.config import UsageError @@ -18,11 +16,8 @@ class MarkMapping(object): own_mark_names = attr.ib() @classmethod - def from_keywords(cls, keywords): - mark_names = set() - for key, value in keywords.items(): - if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator): - mark_names.add(key) + def from_item(cls, item): + mark_names = set(mark.name for mark in item.iter_markers()) return cls(mark_names) def __getitem__(self, name): @@ -70,7 +65,7 @@ def __getitem__(self, subname): def matchmark(colitem, markexpr): """Tries to match on any marker names, attached to the given colitem.""" - return eval(markexpr, {}, MarkMapping.from_keywords(colitem.keywords)) + return eval(markexpr, {}, MarkMapping.from_item(colitem)) def matchkeyword(colitem, keywordexpr): diff --git a/changelog/3441.bugfix b/changelog/3441.bugfix new file mode 100644 index 00000000000..6afeeab6245 --- /dev/null +++ b/changelog/3441.bugfix @@ -0,0 +1 @@ +Also use iter_marker for discovering the marks applying for marker expressions from the cli to avoid the bad data from the legacy mark storage. \ No newline at end of file diff --git a/testing/test_mark.py b/testing/test_mark.py index 9ec1ce75a99..31d3af3e52c 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -295,7 +295,7 @@ def test_mark_option_custom(spec, testdir): def pytest_collection_modifyitems(items): for item in items: if "interface" in item.nodeid: - item.keywords["interface"] = pytest.mark.interface + item.add_marker(pytest.mark.interface) """) testdir.makepyfile(""" def test_interface(): @@ -927,3 +927,35 @@ def test_parameterset_for_parametrize_marks(testdir, mark): def test_parameterset_for_parametrize_bad_markname(testdir): with pytest.raises(pytest.UsageError): test_parameterset_for_parametrize_marks(testdir, 'bad') + + +def test_mark_expressions_no_smear(testdir): + testdir.makepyfile(""" + import pytest + + class BaseTests(object): + def test_something(self): + pass + + @pytest.mark.FOO + class TestFooClass(BaseTests): + pass + + @pytest.mark.BAR + class TestBarClass(BaseTests): + pass + """) + + reprec = testdir.inline_run("-m", 'FOO') + passed, skipped, failed = reprec.countoutcomes() + dlist = reprec.getcalls("pytest_deselected") + assert passed == 1 + assert skipped == failed == 0 + deselected_tests = dlist[0].items + assert len(deselected_tests) == 1 + + # keywords smear - expected behaviour + reprec_keywords = testdir.inline_run("-k", 'FOO') + passed_k, skipped_k, failed_k = reprec_keywords.countoutcomes() + assert passed_k == 2 + assert skipped_k == failed_k == 0