diff --git a/_py/test/plugin/pytest_nose.py b/_py/test/plugin/pytest_nose.py index bc46e6caa..0b930c3a5 100644 --- a/_py/test/plugin/pytest_nose.py +++ b/_py/test/plugin/pytest_nose.py @@ -1,7 +1,7 @@ """nose-compatibility plugin: allow to run nose test suites natively. This is an experimental plugin for allowing to run tests written -in 'nosetests' style with py.test. +in 'nosetests style with py.test. Usage ------------- @@ -10,26 +10,32 @@ type:: py.test # instead of 'nosetests' -and you should be able to run nose style tests. You will of course -get py.test style reporting and its feature set. +and you should be able to run nose style tests and at the same +time can make full use of py.test's capabilities. -Issues? ----------------- +Supported nose Idioms +---------------------- -If you find issues or have suggestions please run:: +* setup and teardown at module/class/method level +* SkipTest exceptions and markers +* setup/teardown decorators +* yield-based tests and their setup +* general usage of nose utilities - py.test --pastebin=all - -and send the resulting URL to a some contact channel. - -Known issues ------------------- +Unsupported idioms / issues +---------------------------------- - nose-style doctests are not collected and executed correctly, also fixtures don't work. - no nose-configuration is recognized +If you find other issues or have suggestions please run:: + + py.test --pastebin=all + +and send the resulting URL to a py.test contact channel, +at best to the mailing list. """ import py import inspect @@ -66,11 +72,14 @@ def pytest_runtest_setup(item): if isinstance(gen.parent, py.test.collect.Instance): call_optional(gen.parent.obj, 'setup') gen._nosegensetup = True - call_optional(item.obj, 'setup') + if not call_optional(item.obj, 'setup'): + # call module level setup if there is no object level one + call_optional(item.parent.obj, 'setup') def pytest_runtest_teardown(item): if isinstance(item, py.test.collect.Function): - call_optional(item.obj, 'teardown') + if not call_optional(item.obj, 'teardown'): + call_optional(item.parent.obj, 'teardown') #if hasattr(item.parent, '_nosegensetup'): # #call_optional(item._nosegensetup, 'teardown') # del item.parent._nosegensetup @@ -82,4 +91,9 @@ def pytest_make_collect_report(collector): def call_optional(obj, name): method = getattr(obj, name, None) if method: - method() + argspec = inspect.getargspec(method) + if argspec[0] == ['self']: + argspec = argspec[1:] + if not any(argspec): + method() + return True diff --git a/_py/test/pycollect.py b/_py/test/pycollect.py index 8e6fc4111..f2fbf324a 100644 --- a/_py/test/pycollect.py +++ b/_py/test/pycollect.py @@ -161,13 +161,24 @@ class Module(py.test.collect.File, PyCollectorMixin): def setup(self): if getattr(self.obj, 'disabled', 0): py.test.skip("%r is disabled" %(self.obj,)) - mod = self.obj - if hasattr(mod, 'setup_module'): - self.obj.setup_module(mod) + if hasattr(self.obj, 'setup_module'): + #XXX: nose compat hack, move to nose plugin + # if it takes a positional arg, its probably a py.test style one + # so we pass the current module object + if inspect.getargspec(self.obj.setup_module)[0]: + self.obj.setup_module(self.obj) + else: + self.obj.setup_module() def teardown(self): if hasattr(self.obj, 'teardown_module'): - self.obj.teardown_module(self.obj) + #XXX: nose compat hack, move to nose plugin + # if it takes a positional arg, its probably a py.test style one + # so we pass the current module object + if inspect.getargspec(self.obj.teardown_module)[0]: + self.obj.teardown_module(self.obj) + else: + self.obj.teardown_module() class Class(PyCollectorMixin, py.test.collect.Collector): diff --git a/bin-for-dist/makepluginlist.py b/bin-for-dist/makepluginlist.py index afe13083a..e4d6bb779 100644 --- a/bin-for-dist/makepluginlist.py +++ b/bin-for-dist/makepluginlist.py @@ -1,6 +1,5 @@ -import py -import sys +import os, sys WIDTH = 75 plugins = [ @@ -269,6 +268,9 @@ class PluginDoc(RestWriter): self.Print(opt.help, indent=4) if __name__ == "__main__": + if os.path.exists("py"): + sys.path.insert(0, os.getcwd()) + import py _config = py.test.config _config.parse([]) _config.pluginmanager.do_configure(_config) diff --git a/doc/changelog.txt b/doc/changelog.txt index f90420314..00a573189 100644 --- a/doc/changelog.txt +++ b/doc/changelog.txt @@ -1,6 +1,10 @@ Changes between 1.0.2 and '1.1.0b1' ===================================== +* merged Ronny's nose-compatibility hacks: now + nose-style setup_module() and setup() functions are + supported + * introduce generalized py.test.mark function marking * reshuffle / refine command line grouping diff --git a/doc/test/plugin/nose.txt b/doc/test/plugin/nose.txt index 3a969c27d..771dc0d18 100644 --- a/doc/test/plugin/nose.txt +++ b/doc/test/plugin/nose.txt @@ -8,7 +8,7 @@ nose-compatibility plugin: allow to run nose test suites natively. :local: This is an experimental plugin for allowing to run tests written -in 'nosetests' style with py.test. +in 'nosetests style with py.test. Usage ------------- @@ -17,25 +17,32 @@ type:: py.test # instead of 'nosetests' -and you should be able to run nose style tests. You will of course -get py.test style reporting and its feature set. +and you should be able to run nose style tests and at the same +time can make full use of py.test's capabilities. -Issues? ----------------- +Supported nose Idioms +---------------------- -If you find issues or have suggestions please run:: +* setup and teardown at module/class/method level +* SkipTest exceptions and markers +* setup/teardown decorators +* yield-based tests and their setup +* general usage of nose utilities - py.test --pastebin=all - -and send the resulting URL to a some contact channel. - -Known issues ------------------- +Unsupported idioms / issues +---------------------------------- - nose-style doctests are not collected and executed correctly, also fixtures don't work. -- no nose-configuration is recognized +- no nose-configuration is recognized + +If you find other issues or have suggestions please run:: + + py.test --pastebin=all + +and send the resulting URL to a py.test contact channel, +at best to the mailing list. Start improving this plugin in 30 seconds ========================================= diff --git a/testing/pytest/plugin/test_pytest_nose.py b/testing/pytest/plugin/test_pytest_nose.py index 4950095dd..fea4f0cf8 100644 --- a/testing/pytest/plugin/test_pytest_nose.py +++ b/testing/pytest/plugin/test_pytest_nose.py @@ -85,3 +85,54 @@ def test_nose_test_generator_fixtures(testdir): ]) + +def test_module_level_setup(testdir): + testdir.makepyfile(""" + from nose.tools import with_setup + items = {} + def setup(): + items[1]=1 + + def teardown(): + del items[1] + + def setup2(): + items[2] = 2 + + def teardown2(): + del items[2] + + def test_setup_module_setup(): + assert items[1] == 1 + + @with_setup(setup2, teardown2) + def test_local_setup(): + assert items[2] == 2 + assert 1 not in items + + """) + result = testdir.runpytest('-p', 'nose') + result.stdout.fnmatch_lines([ + "*2 passed*", + ]) + +def test_nose_style_setup_teardown(testdir): + testdir.makepyfile(""" + l = [] + def setup_module(): + l.append(1) + + def teardown_module(): + del l[0] + + def test_hello(): + assert l == [1] + + def test_world(): + assert l == [1] + """) + result = testdir.runpytest('-p', 'nose') + result.stdout.fnmatch_lines([ + "*2 passed*", + ]) +