From 494be731e30af59a8206f6c693e47a130bfad03d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 10 Apr 2014 12:46:27 +0200 Subject: [PATCH 1/4] support nose-style ``__test__`` attribute on modules, classes and functions, including unittest-style Classes. If set to True, the test will not be collected. --HG-- branch : nose_test_attr --- CHANGELOG | 4 +++ _pytest/python.py | 3 ++ _pytest/unittest.py | 4 ++- doc/en/nose.txt | 1 + testing/python/integration.py | 55 ++++++++++++++++++++++++++++++++--- 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4daca0687..c07807732 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -64,6 +64,10 @@ NEXT (2.6) - fix issue443: fix skip examples to use proper comparison. Thanks Alex Groenholm. +- support nose-style ``__test__`` attribute on modules, classes and + functions, including unittest-style Classes. If set to True, the + test will not be collected. + 2.5.2 ----------------------------------- diff --git a/_pytest/python.py b/_pytest/python.py index 7c43c8019..76fa2ddf7 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -314,6 +314,9 @@ class PyCollector(PyobjMixin, pytest.Collector): return True def collect(self): + if not getattr(self.obj, "__test__", True): + return [] + # NB. we avoid random getattrs and peek in the __dict__ instead # (XXX originally introduced from a PyPy need, still true?) dicts = [getattr(self.obj, '__dict__', {})] diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 12c1cfd53..435a93514 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -41,10 +41,12 @@ class UnitTestCase(pytest.Class): super(UnitTestCase, self).setup() def collect(self): + cls = self.obj + if not getattr(cls, "__test__", True): + return self.session._fixturemanager.parsefactories(self, unittest=True) loader = py.std.unittest.TestLoader() module = self.getparent(pytest.Module).obj - cls = self.obj foundsomething = False for name in loader.getTestCaseNames(self.obj): x = getattr(self.obj, name) diff --git a/doc/en/nose.txt b/doc/en/nose.txt index 61d101723..065803558 100644 --- a/doc/en/nose.txt +++ b/doc/en/nose.txt @@ -25,6 +25,7 @@ Supported nose Idioms * SkipTest exceptions and markers * setup/teardown decorators * yield-based tests and their setup +* ``__test__`` attribute on modules/classes/functions * general usage of nose utilities Unsupported idioms / known issues diff --git a/testing/python/integration.py b/testing/python/integration.py index 8798f643b..f8feb865c 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -164,20 +164,21 @@ class TestMockDecoration: names = [x.nodeid.split("::")[-1] for x in calls] assert names == ["test_one", "test_two", "test_three"] - def test_mock_double_patch_issue473(self, testdir): + def test_mock_and_mark_issue473(self, testdir): + pytest.importorskip("mock", "1.0.1") testdir.makepyfile(""" from mock import patch from pytest import mark @patch('os.getcwd') @patch('os.path') - @mark.slow + #@mark.slow class TestSimple: def test_simple_thing(self, mock_path, mock_getcwd): pass """) - res = testdir.inline_run() - res.assertoutcome(passed=1) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) class TestReRunTests: @@ -214,3 +215,49 @@ class TestReRunTests: def test_pytestconfig_is_session_scoped(): from _pytest.python import pytestconfig assert pytestconfig._pytestfixturefunction.scope == "session" + + +class TestNoselikeTestAttribute: + def test_module(self, testdir): + testdir.makepyfile(""" + __test__ = False + def test_hello(): + pass + """) + reprec = testdir.inline_run() + calls = reprec.getreports("pytest_runtest_logreport") + assert not calls + + def test_class_and_method(self, testdir): + testdir.makepyfile(""" + __test__ = True + def test_func(): + pass + test_hello.__test__ = False + + class TestSome: + __test__ = False + def test_method(self): + pass + """) + reprec = testdir.inline_run() + calls = reprec.getreports("pytest_runtest_logreport") + assert not calls + + def test_unittest_class(self, testdir): + testdir.makepyfile(""" + import unittest + class TC(unittest.TestCase): + def test_1(self): + pass + class TC2(unittest.TestCase): + __test__ = False + def test_2(self): + pass + """) + reprec = testdir.inline_run() + call = reprec.getcalls("pytest_collection_modifyitems")[0] + assert len(call.items) == 1 + assert call.items[0].cls.__name__ == "TC" + + From 5e26e6e553aa30d4cf5c890f7db2b3be7cd547da Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 10 Apr 2014 12:56:14 +0200 Subject: [PATCH 2/4] fix typo in changelog --HG-- branch : nose_test_attr --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c07807732..e228a948f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -65,7 +65,7 @@ NEXT (2.6) Groenholm. - support nose-style ``__test__`` attribute on modules, classes and - functions, including unittest-style Classes. If set to True, the + functions, including unittest-style Classes. If set to False, the test will not be collected. From e42cbc714f237ccfcf796b535e905cfdb7299d98 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 10 Apr 2014 12:58:10 +0200 Subject: [PATCH 3/4] fix wrong merge --HG-- branch : nose_test_attr --- testing/python/integration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/python/integration.py b/testing/python/integration.py index f8feb865c..e5764df9c 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -164,7 +164,7 @@ class TestMockDecoration: names = [x.nodeid.split("::")[-1] for x in calls] assert names == ["test_one", "test_two", "test_three"] - def test_mock_and_mark_issue473(self, testdir): + def test_mock_double_patch_issue473(self, testdir): pytest.importorskip("mock", "1.0.1") testdir.makepyfile(""" from mock import patch @@ -172,7 +172,7 @@ class TestMockDecoration: @patch('os.getcwd') @patch('os.path') - #@mark.slow + @mark.slow class TestSimple: def test_simple_thing(self, mock_path, mock_getcwd): pass From 15af7e16621254194b0a2a1a1097c6888cd5062d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 10 Apr 2014 13:37:39 +0200 Subject: [PATCH 4/4] fix tests to properly fail on failed collectiosn (which was hiding an error) and also implement __test__=False for test functions properly. --HG-- branch : nose_test_attr --- _pytest/python.py | 9 +++++---- testing/python/integration.py | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index 76fa2ddf7..d541dd4d3 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -229,10 +229,11 @@ def pytest_pycollect_makeitem(__multicall__, collector, name, obj): "cannot collect %r because it is not a function." % name, ) return - if is_generator(obj): - return Generator(name, parent=collector) - else: - return list(collector._genfunctions(name, obj)) + if getattr(obj, "__test__", True): + if is_generator(obj): + return Generator(name, parent=collector) + else: + return list(collector._genfunctions(name, obj)) def is_generator(func): try: diff --git a/testing/python/integration.py b/testing/python/integration.py index e5764df9c..e27f27d52 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -225,6 +225,7 @@ class TestNoselikeTestAttribute: pass """) reprec = testdir.inline_run() + assert not reprec.getfailedcollections() calls = reprec.getreports("pytest_runtest_logreport") assert not calls @@ -233,7 +234,7 @@ class TestNoselikeTestAttribute: __test__ = True def test_func(): pass - test_hello.__test__ = False + test_func.__test__ = False class TestSome: __test__ = False @@ -241,6 +242,7 @@ class TestNoselikeTestAttribute: pass """) reprec = testdir.inline_run() + assert not reprec.getfailedcollections() calls = reprec.getreports("pytest_runtest_logreport") assert not calls @@ -256,6 +258,7 @@ class TestNoselikeTestAttribute: pass """) reprec = testdir.inline_run() + assert not reprec.getfailedcollections() call = reprec.getcalls("pytest_collection_modifyitems")[0] assert len(call.items) == 1 assert call.items[0].cls.__name__ == "TC"