Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pytest.mark in classes marks methods defined in the class body only #848

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
all directories that do not contain tests.
Thanks to Adrian for idea (#694) and Bruno Oliveira for the PR.

- fix issue725: pytest.mark in class decorators will mark methods defined
in the class body only, instead of propagating marks to the superclass'
test methods.
Thanks to foobarbazquux for the report and Bruno Oliveira for the PR.

- fix issue713: JUnit XML reports for doctest failures.
Thanks Punyashloka Biswal.

Expand Down
19 changes: 13 additions & 6 deletions _pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,16 +483,23 @@ def __init__(self, argnames, names_closure, name2fixturedefs):
def transfer_markers(funcobj, cls, mod):
# XXX this should rather be code in the mark plugin or the mark
# plugin should merge with the python plugin.
for holder in (cls, mod):
def transfer(holder):
try:
pytestmark = holder.pytestmark
except AttributeError:
continue
if isinstance(pytestmark, list):
for mark in pytestmark:
mark(funcobj)
pass
else:
pytestmark(funcobj)
if isinstance(pytestmark, list):
for mark in pytestmark:
mark(funcobj)
else:
pytestmark(funcobj)

# transfer markers from class decorators only if the method is defined
# in the class itself
if cls is not None and funcobj.__name__ in cls.__dict__:
transfer(cls)
transfer(mod)

class Module(pytest.File, PyCollector):
""" Collector for test classes and functions. """
Expand Down
33 changes: 33 additions & 0 deletions testing/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,39 @@ def assert_markers(self, items, **expected):
if isinstance(v, MarkInfo)])
assert marker_names == set(expected_markers)

def test_class_makker_applies_to_its_own_methods_only(self, testdir):
"""Check that markers applied using class decorators apply markers
only to methods defined in the class itself but not to methods
in super classes (#725).
"""
testdir.makepyfile("""
import pytest

class Base:
def test_base(self):
pass

@pytest.mark.a
class Test1(Base):
def test_foo(self):
pass

@pytest.mark.b
class Test2(Base):
def test_bar(self):
pass
""")
result = testdir.runpytest('--collect-only', '-m', 'a')
result.stdout.fnmatch_lines([
'*test_foo*',
'*3 deselected*'
])
result = testdir.runpytest('--collect-only', '-m', 'b')
result.stdout.fnmatch_lines([
'*test_bar*',
'*3 deselected*'
])


class TestKeywordSelection:
def test_select_simple(self, testdir):
Expand Down
13 changes: 6 additions & 7 deletions testing/test_nose.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,20 +300,19 @@ def test_fun(self):
result = testdir.runpytest()
result.assert_outcomes(passed=1)

@pytest.mark.skipif("sys.version_info < (2,6)")

def test_setup_teardown_linking_issue265(testdir):
# we accidentally didnt integrate nose setupstate with normal setupstate
# this test ensures that won't happen again
testdir.makepyfile('''
import pytest
class TestGeneric(object):
def test_nothing(self):
"""Tests the API of the implementation (for generic and specialized)."""
@pytest.mark.skipif("True", reason=
"Skip tests to check if teardown is skipped as well.")
class TestSkipTeardown(TestGeneric):
class TestSkipTeardown(object):
def test_nothing(self):
pass
def setup(self):
"""Sets up my specialized implementation for $COOL_PLATFORM."""
Expand All @@ -324,7 +323,7 @@ def teardown(self):
raise Exception("should not call teardown for skipped tests")
''')
reprec = testdir.runpytest()
reprec.assert_outcomes(passed=1, skipped=1)
reprec.assert_outcomes(passed=0, skipped=1)


def test_SkipTest_during_collection(testdir):
Expand Down