From 63ef46dd91d8e320a0e1db226da0b32912e0c8c5 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 3 May 2018 16:28:47 +0200 Subject: [PATCH 1/3] fix #3441 - use iter-marks to populate markexpr namespace --- _pytest/mark/legacy.py | 11 +++-------- changelog/3441.bugfix | 1 + 2 files changed, 4 insertions(+), 8 deletions(-) create mode 100644 changelog/3441.bugfix diff --git a/_pytest/mark/legacy.py b/_pytest/mark/legacy.py index ec45f12af..5c7b8d001 100644 --- a/_pytest/mark/legacy.py +++ b/_pytest/mark/legacy.py @@ -5,8 +5,6 @@ we hope ot remove 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 @@ python_keywords_allowed_list = ["or", "and", "not"] 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 000000000..6afeeab62 --- /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 From a5cf55dd4a5237ad4b5d6e93d9822558bbc8cd28 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 3 May 2018 16:33:16 +0200 Subject: [PATCH 2/3] fix test_mark_option_custom - it used the legacy keyword storage for addign markers --- testing/test_mark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_mark.py b/testing/test_mark.py index 9ec1ce75a..520712895 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(): From e6a86e0f4c2c5b1b600e86eda8d1ba545142ac72 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 3 May 2018 17:01:47 +0200 Subject: [PATCH 3/3] add tests for #3441 --- testing/test_mark.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/testing/test_mark.py b/testing/test_mark.py index 520712895..31d3af3e5 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -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