Merge pull request #921 from tomviner/issue562-noseistest
Issue #562 - Ensure @nose.tools.istest is respected
This commit is contained in:
commit
eb989c8257
|
@ -1,6 +1,8 @@
|
||||||
2.8.0.dev (compared to 2.7.X)
|
2.8.0.dev (compared to 2.7.X)
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
- Fix #562: @nose.tools.istest now fully respected.
|
||||||
|
|
||||||
- parametrize now also generates meaningful test IDs for enum, regex and class
|
- parametrize now also generates meaningful test IDs for enum, regex and class
|
||||||
objects (as opposed to class instances).
|
objects (as opposed to class instances).
|
||||||
Thanks to Florian Bruhin for the PR.
|
Thanks to Florian Bruhin for the PR.
|
||||||
|
|
|
@ -37,7 +37,7 @@ def filter_traceback(entry):
|
||||||
|
|
||||||
|
|
||||||
def get_real_func(obj):
|
def get_real_func(obj):
|
||||||
"""gets the real function object of the (possibly) wrapped object by
|
""" gets the real function object of the (possibly) wrapped object by
|
||||||
functools.wraps or functools.partial.
|
functools.wraps or functools.partial.
|
||||||
"""
|
"""
|
||||||
while hasattr(obj, "__wrapped__"):
|
while hasattr(obj, "__wrapped__"):
|
||||||
|
@ -64,6 +64,17 @@ def getimfunc(func):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
def safe_getattr(object, name, default):
|
||||||
|
""" Like getattr but return default upon any Exception.
|
||||||
|
|
||||||
|
Attribute access can potentially fail for 'evil' Python objects.
|
||||||
|
See issue214
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return getattr(object, name, default)
|
||||||
|
except Exception:
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
class FixtureFunctionMarker:
|
class FixtureFunctionMarker:
|
||||||
def __init__(self, scope, params,
|
def __init__(self, scope, params,
|
||||||
|
@ -257,11 +268,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
# nothing was collected elsewhere, let's do it here
|
# nothing was collected elsewhere, let's do it here
|
||||||
if isclass(obj):
|
if isclass(obj):
|
||||||
if collector.classnamefilter(name):
|
if collector.istestclass(obj, name):
|
||||||
Class = collector._getcustomclass("Class")
|
Class = collector._getcustomclass("Class")
|
||||||
outcome.force_result(Class(name, parent=collector))
|
outcome.force_result(Class(name, parent=collector))
|
||||||
elif collector.funcnamefilter(name) and hasattr(obj, "__call__") and\
|
elif collector.istestfunction(obj, name):
|
||||||
getfixturemarker(obj) is None:
|
|
||||||
# mock seems to store unbound methods (issue473), normalize it
|
# mock seems to store unbound methods (issue473), normalize it
|
||||||
obj = getattr(obj, "__func__", obj)
|
obj = getattr(obj, "__func__", obj)
|
||||||
if not isfunction(obj):
|
if not isfunction(obj):
|
||||||
|
@ -347,9 +357,24 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
||||||
def funcnamefilter(self, name):
|
def funcnamefilter(self, name):
|
||||||
return self._matches_prefix_or_glob_option('python_functions', name)
|
return self._matches_prefix_or_glob_option('python_functions', name)
|
||||||
|
|
||||||
|
def isnosetest(self, obj):
|
||||||
|
""" Look for the __test__ attribute, which is applied by the
|
||||||
|
@nose.tools.istest decorator
|
||||||
|
"""
|
||||||
|
return safe_getattr(obj, '__test__', False)
|
||||||
|
|
||||||
def classnamefilter(self, name):
|
def classnamefilter(self, name):
|
||||||
return self._matches_prefix_or_glob_option('python_classes', name)
|
return self._matches_prefix_or_glob_option('python_classes', name)
|
||||||
|
|
||||||
|
def istestfunction(self, obj, name):
|
||||||
|
return (
|
||||||
|
(self.funcnamefilter(name) or self.isnosetest(obj))
|
||||||
|
and safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None
|
||||||
|
)
|
||||||
|
|
||||||
|
def istestclass(self, obj, name):
|
||||||
|
return self.classnamefilter(name) or self.isnosetest(obj)
|
||||||
|
|
||||||
def _matches_prefix_or_glob_option(self, option_name, name):
|
def _matches_prefix_or_glob_option(self, option_name, name):
|
||||||
"""
|
"""
|
||||||
checks if the given name matches the prefix or glob-pattern defined
|
checks if the given name matches the prefix or glob-pattern defined
|
||||||
|
@ -494,7 +519,7 @@ class FuncFixtureInfo:
|
||||||
|
|
||||||
|
|
||||||
def _marked(func, mark):
|
def _marked(func, mark):
|
||||||
"""Returns True if :func: is already marked with :mark:, False orherwise.
|
""" Returns True if :func: is already marked with :mark:, False otherwise.
|
||||||
This can happen if marker is applied to class and the test file is
|
This can happen if marker is applied to class and the test file is
|
||||||
invoked more than once.
|
invoked more than once.
|
||||||
"""
|
"""
|
||||||
|
@ -1130,9 +1155,9 @@ def raises(expected_exception, *args, **kwargs):
|
||||||
" derived from BaseException, not %s")
|
" derived from BaseException, not %s")
|
||||||
if isinstance(expected_exception, tuple):
|
if isinstance(expected_exception, tuple):
|
||||||
for exc in expected_exception:
|
for exc in expected_exception:
|
||||||
if not inspect.isclass(exc):
|
if not isclass(exc):
|
||||||
raise TypeError(msg % type(exc))
|
raise TypeError(msg % type(exc))
|
||||||
elif not inspect.isclass(expected_exception):
|
elif not isclass(expected_exception):
|
||||||
raise TypeError(msg % type(expected_exception))
|
raise TypeError(msg % type(expected_exception))
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
|
@ -1379,7 +1404,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
return self._pyfuncitem.session
|
return self._pyfuncitem.session
|
||||||
|
|
||||||
def addfinalizer(self, finalizer):
|
def addfinalizer(self, finalizer):
|
||||||
"""add finalizer/teardown function to be called after the
|
""" add finalizer/teardown function to be called after the
|
||||||
last test within the requesting test context finished
|
last test within the requesting test context finished
|
||||||
execution. """
|
execution. """
|
||||||
# XXX usually this method is shadowed by fixturedef specific ones
|
# XXX usually this method is shadowed by fixturedef specific ones
|
||||||
|
|
|
@ -347,3 +347,49 @@ def test_SkipTest_in_test(testdir):
|
||||||
""")
|
""")
|
||||||
reprec = testdir.inline_run()
|
reprec = testdir.inline_run()
|
||||||
reprec.assertoutcome(skipped=1)
|
reprec.assertoutcome(skipped=1)
|
||||||
|
|
||||||
|
def test_istest_function_decorator(testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
import nose.tools
|
||||||
|
@nose.tools.istest
|
||||||
|
def not_test_prefix():
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.assert_outcomes(passed=1)
|
||||||
|
|
||||||
|
def test_nottest_function_decorator(testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import nose.tools
|
||||||
|
@nose.tools.nottest
|
||||||
|
def test_prefix():
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
assert not reprec.getfailedcollections()
|
||||||
|
calls = reprec.getreports("pytest_runtest_logreport")
|
||||||
|
assert not calls
|
||||||
|
|
||||||
|
def test_istest_class_decorator(testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
import nose.tools
|
||||||
|
@nose.tools.istest
|
||||||
|
class NotTestPrefix:
|
||||||
|
def test_method(self):
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.assert_outcomes(passed=1)
|
||||||
|
|
||||||
|
def test_nottest_class_decorator(testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import nose.tools
|
||||||
|
@nose.tools.nottest
|
||||||
|
class TestPrefix:
|
||||||
|
def test_method(self):
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
assert not reprec.getfailedcollections()
|
||||||
|
calls = reprec.getreports("pytest_runtest_logreport")
|
||||||
|
assert not calls
|
||||||
|
|
Loading…
Reference in New Issue