* 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',
|
||||||
|
|
|
@ -396,6 +396,11 @@ 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 = getattr(plugin, '__name__', None)
|
||||||
|
if name is None:
|
||||||
name = plugin.__class__.__name__
|
name = plugin.__class__.__name__
|
||||||
if name.endswith("Plugin"):
|
|
||||||
name = name[:-6]
|
|
||||||
#if name == "Conftest":
|
|
||||||
# XXX get filename
|
|
||||||
plugins.append(name)
|
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