* add pytest_nose plugin
* have unittest functions always receive a fresh instance --HG-- branch : 1.0.x
This commit is contained in:
		
							parent
							
								
									a01e4769cc
								
							
						
					
					
						commit
						b552f6eb46
					
				|  | @ -1,6 +1,10 @@ | ||||||
| Changes between 1.0.0 and 1.0.1 | Changes between 1.0.0 and 1.0.1 | ||||||
| ===================================== | ===================================== | ||||||
| 
 | 
 | ||||||
|  | * added a 'pytest_nose' plugin which handles nose.SkipTest,  | ||||||
|  |   nose-style function/method/generator setup/teardown and  | ||||||
|  |   tries to report functions correctly.  | ||||||
|  | 
 | ||||||
| * unicode fixes: capturing and unicode writes to sys.stdout  | * unicode fixes: capturing and unicode writes to sys.stdout  | ||||||
|   (through e.g a print statement) now work within tests,  |   (through e.g a print statement) now work within tests,  | ||||||
|   they are encoded as "utf8" by default, also terminalwriting  |   they are encoded as "utf8" by default, also terminalwriting  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,64 @@ | ||||||
|  | 
 | ||||||
|  | pytest_nose plugin | ||||||
|  | ================== | ||||||
|  | 
 | ||||||
|  | nose-compatibility plugin: allow to run nose test suites natively. | ||||||
|  | 
 | ||||||
|  | .. contents:: | ||||||
|  |   :local: | ||||||
|  | 
 | ||||||
|  | This is an experimental plugin for allowing to run tests written  | ||||||
|  | in the 'nosetests' style with py.test.   | ||||||
|  | nosetests is a popular clone  | ||||||
|  | of py.test and thus shares some philosophy.  This plugin is an  | ||||||
|  | attempt to understand and neutralize differences.  It allows to  | ||||||
|  | run nosetests' own test suite and a number of other test suites  | ||||||
|  | without problems.  | ||||||
|  | 
 | ||||||
|  | Usage | ||||||
|  | ------------- | ||||||
|  | 
 | ||||||
|  | If you type:: | ||||||
|  | 
 | ||||||
|  |     py.test -p nose  | ||||||
|  | 
 | ||||||
|  | where you would type ``nosetests``, you can run your nose style tests.   | ||||||
|  | You might also try to run without the nose plugin to see where your test | ||||||
|  | suite is incompatible to the default py.test.  | ||||||
|  | 
 | ||||||
|  | To avoid the need for specifying a command line option you can set an environment  | ||||||
|  | variable:: | ||||||
|  | 
 | ||||||
|  |     PYTEST_PLUGINS=nose   | ||||||
|  | 
 | ||||||
|  | or create a ``conftest.py`` file in your test directory or below:: | ||||||
|  | 
 | ||||||
|  |     # conftest.py  | ||||||
|  |     pytest_plugins = "nose",  | ||||||
|  | 
 | ||||||
|  | If you find issues or have suggestions you may run::  | ||||||
|  | 
 | ||||||
|  |     py.test -p nose --pastebin=all  | ||||||
|  | 
 | ||||||
|  | to create a URL of a test run session and send it with comments to the issue | ||||||
|  | tracker or mailing list.  | ||||||
|  | 
 | ||||||
|  | Known issues  | ||||||
|  | ------------------ | ||||||
|  | 
 | ||||||
|  | - nose-style doctests are not collected and executed correctly, | ||||||
|  |   also fixtures don't work. | ||||||
|  | 
 | ||||||
|  | Start improving this plugin in 30 seconds | ||||||
|  | ========================================= | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Do you find the above documentation or the plugin itself lacking?  | ||||||
|  | 
 | ||||||
|  | 1. Download `pytest_nose.py`_ plugin source code  | ||||||
|  | 2. put it somewhere as ``pytest_nose.py`` into your import path  | ||||||
|  | 3. a subsequent ``py.test`` run will use your local version | ||||||
|  | 
 | ||||||
|  | Further information: extend_ documentation, other plugins_ or contact_.   | ||||||
|  | 
 | ||||||
|  | .. include:: links.txt | ||||||
|  | @ -7,7 +7,7 @@ plugins = [ | ||||||
|     ('Plugins related to Python test functions and programs',  |     ('Plugins related to Python test functions and programs',  | ||||||
|             'xfail figleaf monkeypatch capture recwarn',), |             'xfail figleaf monkeypatch capture recwarn',), | ||||||
|     ('Plugins for other testing styles and languages',  |     ('Plugins for other testing styles and languages',  | ||||||
|             'unittest doctest oejskit restdoc'), |             'oejskit unittest nose doctest restdoc'), | ||||||
|     ('Plugins for generic reporting and failure logging',  |     ('Plugins for generic reporting and failure logging',  | ||||||
|             'pastebin resultlog terminal',), |             'pastebin resultlog terminal',), | ||||||
|     ('internal plugins / core functionality',  |     ('internal plugins / core functionality',  | ||||||
|  |  | ||||||
|  | @ -395,7 +395,12 @@ class Directory(FSCollector): | ||||||
| 
 | 
 | ||||||
|     def _ignore(self, path): |     def _ignore(self, path): | ||||||
|         ignore_paths = self.config.getconftest_pathlist("collect_ignore", path=path) |         ignore_paths = self.config.getconftest_pathlist("collect_ignore", path=path) | ||||||
|         return ignore_paths and path in ignore_paths  |         return ignore_paths and path in ignore_paths | ||||||
|  |         # XXX more refined would be:  | ||||||
|  |         if ignore_paths: | ||||||
|  |             for p in ignore_paths: | ||||||
|  |                 if path == p or path.relto(p): | ||||||
|  |                     return True | ||||||
| 
 | 
 | ||||||
|     def consider(self, path): |     def consider(self, path): | ||||||
|         if self._ignore(path): |         if self._ignore(path): | ||||||
|  |  | ||||||
|  | @ -15,10 +15,15 @@ def pytest_funcarg__testdir(request): | ||||||
|     #        testdir.plugins.append(obj.testplugin) |     #        testdir.plugins.append(obj.testplugin) | ||||||
|     #        break |     #        break | ||||||
|     #else: |     #else: | ||||||
|     basename = request.module.__name__.split(".")[-1]  |     modname = request.module.__name__.split(".")[-1]  | ||||||
|     if basename.startswith("pytest_"): |     if modname.startswith("pytest_"): | ||||||
|         testdir.plugins.append(vars(request.module)) |         testdir.plugins.append(vars(request.module)) | ||||||
|         testdir.plugins.append(basename)  |         testdir.plugins.append(modname)  | ||||||
|  |     #elif modname.startswith("test_pytest"): | ||||||
|  |     #    pname = modname[5:] | ||||||
|  |     #    assert pname not in testdir.plugins | ||||||
|  |     #    testdir.plugins.append(pname)  | ||||||
|  |     #    #testdir.plugins.append(vars(request.module)) | ||||||
|     else: |     else: | ||||||
|         pass # raise ValueError("need better support code") |         pass # raise ValueError("need better support code") | ||||||
|     return testdir |     return testdir | ||||||
|  |  | ||||||
|  | @ -0,0 +1,97 @@ | ||||||
|  | """nose-compatibility plugin: allow to run nose test suites natively.  | ||||||
|  | 
 | ||||||
|  | This is an experimental plugin for allowing to run tests written  | ||||||
|  | in the 'nosetests' style with py.test.   | ||||||
|  | nosetests is a popular clone  | ||||||
|  | of py.test and thus shares some philosophy.  This plugin is an  | ||||||
|  | attempt to understand and neutralize differences.  It allows to  | ||||||
|  | run nosetests' own test suite and a number of other test suites  | ||||||
|  | without problems.  | ||||||
|  | 
 | ||||||
|  | Usage | ||||||
|  | ------------- | ||||||
|  | 
 | ||||||
|  | If you type:: | ||||||
|  | 
 | ||||||
|  |     py.test -p nose  | ||||||
|  | 
 | ||||||
|  | where you would type ``nosetests``, you can run your nose style tests.   | ||||||
|  | You might also try to run without the nose plugin to see where your test | ||||||
|  | suite is incompatible to the default py.test.  | ||||||
|  | 
 | ||||||
|  | To avoid the need for specifying a command line option you can set an environment  | ||||||
|  | variable:: | ||||||
|  | 
 | ||||||
|  |     PYTEST_PLUGINS=nose   | ||||||
|  | 
 | ||||||
|  | or create a ``conftest.py`` file in your test directory or below:: | ||||||
|  | 
 | ||||||
|  |     # conftest.py  | ||||||
|  |     pytest_plugins = "nose",  | ||||||
|  | 
 | ||||||
|  | If you find issues or have suggestions you may run::  | ||||||
|  | 
 | ||||||
|  |     py.test -p nose --pastebin=all  | ||||||
|  | 
 | ||||||
|  | to create a URL of a test run session and send it with comments to the issue | ||||||
|  | tracker or mailing list.  | ||||||
|  | 
 | ||||||
|  | Known issues  | ||||||
|  | ------------------ | ||||||
|  | 
 | ||||||
|  | - nose-style doctests are not collected and executed correctly, | ||||||
|  |   also fixtures don't work.  | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | import py | ||||||
|  | import inspect | ||||||
|  | import sys | ||||||
|  | 
 | ||||||
|  | def pytest_runtest_makereport(__call__, item, call): | ||||||
|  |     SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None) | ||||||
|  |     if SkipTest: | ||||||
|  |         if call.excinfo and call.excinfo.errisinstance(SkipTest): | ||||||
|  |             # let's substitute the excinfo with a py.test.skip one  | ||||||
|  |             call2 = call.__class__(lambda: py.test.skip(str(call.excinfo.value)), call.when) | ||||||
|  |             call.excinfo = call2.excinfo  | ||||||
|  | 
 | ||||||
|  | def pytest_report_iteminfo(item): | ||||||
|  |     # nose 0.11.1 uses decorators for "raises" and other helpers.  | ||||||
|  |     # for reporting progress by filename we fish for the filename  | ||||||
|  |     if isinstance(item, py.test.collect.Function): | ||||||
|  |         obj = item.obj | ||||||
|  |         if hasattr(obj, 'compat_co_firstlineno'): | ||||||
|  |             fn = sys.modules[obj.__module__].__file__  | ||||||
|  |             if fn.endswith(".pyc"): | ||||||
|  |                 fn = fn[:-1] | ||||||
|  |             #assert 0 | ||||||
|  |             #fn = inspect.getsourcefile(obj) or inspect.getfile(obj) | ||||||
|  |             lineno = obj.compat_co_firstlineno     | ||||||
|  |             return py.path.local(fn), lineno, obj.__module__ | ||||||
|  |      | ||||||
|  | def pytest_runtest_setup(item): | ||||||
|  |     if isinstance(item, (py.test.collect.Function)): | ||||||
|  |         if isinstance(item.parent, py.test.collect.Generator): | ||||||
|  |             gen = item.parent  | ||||||
|  |             if not hasattr(gen, '_nosegensetup'): | ||||||
|  |                 call_optional(gen.obj, 'setup') | ||||||
|  |                 if isinstance(gen.parent, py.test.collect.Instance): | ||||||
|  |                     call_optional(gen.parent.obj, 'setup') | ||||||
|  |                 gen._nosegensetup = True | ||||||
|  |         call_optional(item.obj, 'setup') | ||||||
|  | 
 | ||||||
|  | def pytest_runtest_teardown(item): | ||||||
|  |     if isinstance(item, py.test.collect.Function): | ||||||
|  |         call_optional(item.obj, 'teardown') | ||||||
|  |         #if hasattr(item.parent, '_nosegensetup'): | ||||||
|  |         #    #call_optional(item._nosegensetup, 'teardown') | ||||||
|  |         #    del item.parent._nosegensetup | ||||||
|  | 
 | ||||||
|  | def pytest_make_collect_report(collector): | ||||||
|  |     if isinstance(collector, py.test.collect.Generator): | ||||||
|  |         call_optional(collector.obj, 'setup') | ||||||
|  | 
 | ||||||
|  | def call_optional(obj, name): | ||||||
|  |     method = getattr(obj, name, None) | ||||||
|  |     if method: | ||||||
|  |         method() | ||||||
|  | @ -241,15 +241,10 @@ class TerminalReporter: | ||||||
|         if self.config.option.traceconfig: |         if self.config.option.traceconfig: | ||||||
|             plugins = [] |             plugins = [] | ||||||
|             for plugin in self.config.pluginmanager.comregistry: |             for plugin in self.config.pluginmanager.comregistry: | ||||||
|                 name = plugin.__class__.__name__ |                 name = getattr(plugin, '__name__', None) | ||||||
|                 if name.endswith("Plugin"): |                 if name is None: | ||||||
|                     name = name[:-6] |                     name = plugin.__class__.__name__ | ||||||
|                     #if name == "Conftest": |                 plugins.append(name) | ||||||
|                     #    XXX get filename  |  | ||||||
|                     plugins.append(name) |  | ||||||
|                 else: |  | ||||||
|                     plugins.append(str(plugin)) |  | ||||||
| 
 |  | ||||||
|             plugins = ", ".join(plugins)  |             plugins = ", ".join(plugins)  | ||||||
|             self.write_line("active plugins: %s" %(plugins,)) |             self.write_line("active plugins: %s" %(plugins,)) | ||||||
|         for i, testarg in py.builtin.enumerate(self.config.args): |         for i, testarg in py.builtin.enumerate(self.config.args): | ||||||
|  |  | ||||||
|  | @ -55,6 +55,9 @@ class UnitTestFunction(py.test.collect.Function): | ||||||
|         if obj is not _dummy: |         if obj is not _dummy: | ||||||
|             self._obj = obj |             self._obj = obj | ||||||
|         self._sort_value = sort_value |         self._sort_value = sort_value | ||||||
|  |         if hasattr(self.parent, 'newinstance'): | ||||||
|  |             self.parent.newinstance() | ||||||
|  |             self.obj = self._getobj() | ||||||
| 
 | 
 | ||||||
|     def runtest(self): |     def runtest(self): | ||||||
|         target = self.obj |         target = self.obj | ||||||
|  | @ -87,7 +90,6 @@ def test_simple_unittest(testdir): | ||||||
| def test_setup(testdir): | def test_setup(testdir): | ||||||
|     testpath = testdir.makepyfile(test_two=""" |     testpath = testdir.makepyfile(test_two=""" | ||||||
|         import unittest |         import unittest | ||||||
|         pytest_plugins = "pytest_unittest" # XXX  |  | ||||||
|         class MyTestCase(unittest.TestCase): |         class MyTestCase(unittest.TestCase): | ||||||
|             def setUp(self): |             def setUp(self): | ||||||
|                 self.foo = 1 |                 self.foo = 1 | ||||||
|  | @ -98,6 +100,18 @@ def test_setup(testdir): | ||||||
|     rep = reprec.matchreport("test_setUp") |     rep = reprec.matchreport("test_setUp") | ||||||
|     assert rep.passed |     assert rep.passed | ||||||
| 
 | 
 | ||||||
|  | def test_new_instances(testdir): | ||||||
|  |     testpath = testdir.makepyfile(""" | ||||||
|  |         import unittest | ||||||
|  |         class MyTestCase(unittest.TestCase): | ||||||
|  |             def test_func1(self): | ||||||
|  |                 self.x = 2 | ||||||
|  |             def test_func2(self): | ||||||
|  |                 assert not hasattr(self, 'x') | ||||||
|  |     """) | ||||||
|  |     reprec = testdir.inline_run(testpath) | ||||||
|  |     reprec.assertoutcome(passed=2) | ||||||
|  | 
 | ||||||
| def test_teardown(testdir): | def test_teardown(testdir): | ||||||
|     testpath = testdir.makepyfile(test_three=""" |     testpath = testdir.makepyfile(test_three=""" | ||||||
|         import unittest |         import unittest | ||||||
|  |  | ||||||
|  | @ -0,0 +1,87 @@ | ||||||
|  | import py | ||||||
|  | py.test.importorskip("nose") | ||||||
|  | 
 | ||||||
|  | def test_nose_setup(testdir): | ||||||
|  |     p = testdir.makepyfile(""" | ||||||
|  |         l = [] | ||||||
|  | 
 | ||||||
|  |         def test_hello(): | ||||||
|  |             assert l == [1] | ||||||
|  |         def test_world(): | ||||||
|  |             assert l == [1,2] | ||||||
|  |         test_hello.setup = lambda: l.append(1) | ||||||
|  |         test_hello.teardown = lambda: l.append(2) | ||||||
|  |     """) | ||||||
|  |     result = testdir.runpytest(p, '-p', 'nose') | ||||||
|  |     result.stdout.fnmatch_lines([ | ||||||
|  |         "*2 passed*" | ||||||
|  |     ]) | ||||||
|  | 
 | ||||||
|  | def test_nose_test_generator_fixtures(testdir): | ||||||
|  |     p = testdir.makepyfile(""" | ||||||
|  |         # taken from nose-0.11.1 unit_tests/test_generator_fixtures.py | ||||||
|  |         from nose.tools import eq_ | ||||||
|  |         called = [] | ||||||
|  | 
 | ||||||
|  |         def outer_setup(): | ||||||
|  |             called.append('outer_setup') | ||||||
|  | 
 | ||||||
|  |         def outer_teardown(): | ||||||
|  |             called.append('outer_teardown') | ||||||
|  | 
 | ||||||
|  |         def inner_setup(): | ||||||
|  |             called.append('inner_setup') | ||||||
|  | 
 | ||||||
|  |         def inner_teardown(): | ||||||
|  |             called.append('inner_teardown') | ||||||
|  | 
 | ||||||
|  |         def test_gen(): | ||||||
|  |             called[:] = [] | ||||||
|  |             for i in range(0, 5): | ||||||
|  |                 yield check, i | ||||||
|  |                  | ||||||
|  |         def check(i): | ||||||
|  |             expect = ['outer_setup'] | ||||||
|  |             for x in range(0, i): | ||||||
|  |                 expect.append('inner_setup') | ||||||
|  |                 expect.append('inner_teardown') | ||||||
|  |             expect.append('inner_setup') | ||||||
|  |             eq_(called, expect) | ||||||
|  | 
 | ||||||
|  |              | ||||||
|  |         test_gen.setup = outer_setup | ||||||
|  |         test_gen.teardown = outer_teardown | ||||||
|  |         check.setup = inner_setup | ||||||
|  |         check.teardown = inner_teardown | ||||||
|  | 
 | ||||||
|  |         class TestClass(object): | ||||||
|  |             def setup(self): | ||||||
|  |                 print "setup called in", self | ||||||
|  |                 self.called = ['setup'] | ||||||
|  | 
 | ||||||
|  |             def teardown(self): | ||||||
|  |                 print "teardown called in", self | ||||||
|  |                 eq_(self.called, ['setup']) | ||||||
|  |                 self.called.append('teardown') | ||||||
|  | 
 | ||||||
|  |             def test(self): | ||||||
|  |                 print "test called in", self | ||||||
|  |                 for i in range(0, 5): | ||||||
|  |                     yield self.check, i | ||||||
|  | 
 | ||||||
|  |             def check(self, i): | ||||||
|  |                 print "check called in", self | ||||||
|  |                 expect = ['setup'] | ||||||
|  |                 #for x in range(0, i): | ||||||
|  |                 #    expect.append('setup') | ||||||
|  |                 #    expect.append('teardown') | ||||||
|  |                 #expect.append('setup') | ||||||
|  |                 eq_(self.called, expect) | ||||||
|  | 
 | ||||||
|  |     """) | ||||||
|  |     result = testdir.runpytest(p, '-p', 'nose') | ||||||
|  |     result.stdout.fnmatch_lines([ | ||||||
|  |         "*10 passed*" | ||||||
|  |     ]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
		Loading…
	
		Reference in New Issue