parent
54872e94b4
commit
3a81d2e012
|
@ -4,6 +4,8 @@ support for presenting detailed information in failing assertions.
|
||||||
import py
|
import py
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from _pytest.config import hookimpl
|
||||||
from _pytest.monkeypatch import monkeypatch
|
from _pytest.monkeypatch import monkeypatch
|
||||||
from _pytest.assertion import util
|
from _pytest.assertion import util
|
||||||
|
|
||||||
|
@ -42,9 +44,13 @@ class AssertionState:
|
||||||
self.trace = config.trace.root.get("assertion")
|
self.trace = config.trace.root.get("assertion")
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
@hookimpl(tryfirst=True)
|
||||||
mode = config.getvalue("assertmode")
|
def pytest_load_initial_conftests(early_config, parser, args):
|
||||||
if config.getvalue("noassert") or config.getvalue("nomagic"):
|
ns, ns_unknown_args = parser.parse_known_and_unknown_args(args)
|
||||||
|
mode = ns.assertmode
|
||||||
|
no_assert = ns.noassert
|
||||||
|
no_magic = ns.nomagic
|
||||||
|
if no_assert or no_magic:
|
||||||
mode = "plain"
|
mode = "plain"
|
||||||
if mode == "rewrite":
|
if mode == "rewrite":
|
||||||
try:
|
try:
|
||||||
|
@ -57,25 +63,30 @@ def pytest_configure(config):
|
||||||
if (sys.platform.startswith('java') or
|
if (sys.platform.startswith('java') or
|
||||||
sys.version_info[:3] == (2, 6, 0)):
|
sys.version_info[:3] == (2, 6, 0)):
|
||||||
mode = "reinterp"
|
mode = "reinterp"
|
||||||
|
|
||||||
|
early_config._assertstate = AssertionState(early_config, mode)
|
||||||
|
warn_about_missing_assertion(mode, early_config.pluginmanager)
|
||||||
|
|
||||||
if mode != "plain":
|
if mode != "plain":
|
||||||
_load_modules(mode)
|
_load_modules(mode)
|
||||||
m = monkeypatch()
|
m = monkeypatch()
|
||||||
config._cleanup.append(m.undo)
|
early_config._cleanup.append(m.undo)
|
||||||
m.setattr(py.builtin.builtins, 'AssertionError',
|
m.setattr(py.builtin.builtins, 'AssertionError',
|
||||||
reinterpret.AssertionError) # noqa
|
reinterpret.AssertionError) # noqa
|
||||||
|
|
||||||
hook = None
|
hook = None
|
||||||
if mode == "rewrite":
|
if mode == "rewrite":
|
||||||
hook = rewrite.AssertionRewritingHook() # noqa
|
hook = rewrite.AssertionRewritingHook() # noqa
|
||||||
|
hook.set_config(early_config)
|
||||||
sys.meta_path.insert(0, hook)
|
sys.meta_path.insert(0, hook)
|
||||||
warn_about_missing_assertion(mode)
|
|
||||||
config._assertstate = AssertionState(config, mode)
|
early_config._assertstate.hook = hook
|
||||||
config._assertstate.hook = hook
|
early_config._assertstate.trace("configured with mode set to %r" % (mode,))
|
||||||
config._assertstate.trace("configured with mode set to %r" % (mode,))
|
|
||||||
def undo():
|
def undo():
|
||||||
hook = config._assertstate.hook
|
hook = early_config._assertstate.hook
|
||||||
if hook is not None and hook in sys.meta_path:
|
if hook is not None and hook in sys.meta_path:
|
||||||
sys.meta_path.remove(hook)
|
sys.meta_path.remove(hook)
|
||||||
config.add_cleanup(undo)
|
early_config.add_cleanup(undo)
|
||||||
|
|
||||||
|
|
||||||
def pytest_collection(session):
|
def pytest_collection(session):
|
||||||
|
@ -154,7 +165,8 @@ def _load_modules(mode):
|
||||||
from _pytest.assertion import rewrite # noqa
|
from _pytest.assertion import rewrite # noqa
|
||||||
|
|
||||||
|
|
||||||
def warn_about_missing_assertion(mode):
|
def warn_about_missing_assertion(mode, pluginmanager):
|
||||||
|
print('got here')
|
||||||
try:
|
try:
|
||||||
assert False
|
assert False
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
|
@ -166,10 +178,18 @@ def warn_about_missing_assertion(mode):
|
||||||
else:
|
else:
|
||||||
specifically = "failing tests may report as passing"
|
specifically = "failing tests may report as passing"
|
||||||
|
|
||||||
sys.stderr.write("WARNING: " + specifically +
|
# temporarily disable capture so we can print our warning
|
||||||
" because assert statements are not executed "
|
capman = pluginmanager.getplugin('capturemanager')
|
||||||
"by the underlying Python interpreter "
|
try:
|
||||||
"(are you using python -O?)\n")
|
out, err = capman.suspendcapture()
|
||||||
|
sys.stderr.write("WARNING: " + specifically +
|
||||||
|
" because assert statements are not executed "
|
||||||
|
"by the underlying Python interpreter "
|
||||||
|
"(are you using python -O?)\n")
|
||||||
|
finally:
|
||||||
|
capman.resumecapture()
|
||||||
|
sys.stdout.write(out)
|
||||||
|
sys.stderr.write(err)
|
||||||
|
|
||||||
|
|
||||||
# Expose this plugin's implementation for the pytest_assertrepr_compare hook
|
# Expose this plugin's implementation for the pytest_assertrepr_compare hook
|
||||||
|
|
|
@ -50,14 +50,14 @@ class AssertionRewritingHook(object):
|
||||||
self._register_with_pkg_resources()
|
self._register_with_pkg_resources()
|
||||||
|
|
||||||
def set_session(self, session):
|
def set_session(self, session):
|
||||||
self.fnpats = session.config.getini("python_files")
|
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
|
def set_config(self, config):
|
||||||
|
self.config = config
|
||||||
|
self.fnpats = config.getini("python_files")
|
||||||
|
|
||||||
def find_module(self, name, path=None):
|
def find_module(self, name, path=None):
|
||||||
if self.session is None:
|
state = self.config._assertstate
|
||||||
return None
|
|
||||||
sess = self.session
|
|
||||||
state = sess.config._assertstate
|
|
||||||
state.trace("find_module called for: %s" % name)
|
state.trace("find_module called for: %s" % name)
|
||||||
names = name.rsplit(".", 1)
|
names = name.rsplit(".", 1)
|
||||||
lastname = names[-1]
|
lastname = names[-1]
|
||||||
|
@ -86,24 +86,11 @@ class AssertionRewritingHook(object):
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
|
fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
|
||||||
|
|
||||||
fn_pypath = py.path.local(fn)
|
fn_pypath = py.path.local(fn)
|
||||||
# Is this a test file?
|
if not self._should_rewrite(fn_pypath, state):
|
||||||
if not sess.isinitpath(fn):
|
return
|
||||||
# We have to be very careful here because imports in this code can
|
|
||||||
# trigger a cycle.
|
|
||||||
self.session = None
|
|
||||||
try:
|
|
||||||
for pat in self.fnpats:
|
|
||||||
if fn_pypath.fnmatch(pat):
|
|
||||||
state.trace("matched test file %r" % (fn,))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
finally:
|
|
||||||
self.session = sess
|
|
||||||
else:
|
|
||||||
state.trace("matched test file (was specified on cmdline): %r" %
|
|
||||||
(fn,))
|
|
||||||
# The requested module looks like a test file, so rewrite it. This is
|
# The requested module looks like a test file, so rewrite it. This is
|
||||||
# the most magical part of the process: load the source, rewrite the
|
# the most magical part of the process: load the source, rewrite the
|
||||||
# asserts, and load the rewritten source. We also cache the rewritten
|
# asserts, and load the rewritten source. We also cache the rewritten
|
||||||
|
@ -151,6 +138,32 @@ class AssertionRewritingHook(object):
|
||||||
self.modules[name] = co, pyc
|
self.modules[name] = co, pyc
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def _should_rewrite(self, fn_pypath, state):
|
||||||
|
# always rewrite conftest files
|
||||||
|
fn = str(fn_pypath)
|
||||||
|
if fn_pypath.basename == 'conftest.py':
|
||||||
|
state.trace("rewriting conftest file: %r" % (fn,))
|
||||||
|
return True
|
||||||
|
elif self.session is not None:
|
||||||
|
if self.session.isinitpath(fn):
|
||||||
|
state.trace("matched test file (was specified on cmdline): %r" %
|
||||||
|
(fn,))
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# modules not passed explicitly on the command line are only
|
||||||
|
# rewritten if they match the naming convention for test files
|
||||||
|
session = self.session # avoid a cycle here
|
||||||
|
self.session = None
|
||||||
|
try:
|
||||||
|
for pat in self.fnpats:
|
||||||
|
if fn_pypath.fnmatch(pat):
|
||||||
|
state.trace("matched test file %r" % (fn,))
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
self.session = session
|
||||||
|
del session
|
||||||
|
return False
|
||||||
|
|
||||||
def load_module(self, name):
|
def load_module(self, name):
|
||||||
# If there is an existing module object named 'fullname' in
|
# If there is an existing module object named 'fullname' in
|
||||||
# sys.modules, the loader must use that existing module. (Otherwise,
|
# sys.modules, the loader must use that existing module. (Otherwise,
|
||||||
|
|
|
@ -694,6 +694,40 @@ class TestAssertionRewriteHookDetails(object):
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines('*1 passed*')
|
result.stdout.fnmatch_lines('*1 passed*')
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('initial_conftest', [True, False])
|
||||||
|
@pytest.mark.parametrize('mode', ['plain', 'rewrite', 'reinterp'])
|
||||||
|
def test_conftest_assertion_rewrite(self, testdir, initial_conftest, mode):
|
||||||
|
"""Test that conftest files are using assertion rewrite on import.
|
||||||
|
(#1619)
|
||||||
|
"""
|
||||||
|
testdir.tmpdir.join('foo/tests').ensure(dir=1)
|
||||||
|
conftest_path = 'conftest.py' if initial_conftest else 'foo/conftest.py'
|
||||||
|
contents = {
|
||||||
|
conftest_path: """
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture
|
||||||
|
def check_first():
|
||||||
|
def check(values, value):
|
||||||
|
assert values.pop(0) == value
|
||||||
|
return check
|
||||||
|
""",
|
||||||
|
'foo/tests/test_foo.py': """
|
||||||
|
def test(check_first):
|
||||||
|
check_first([10, 30], 30)
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
testdir.makepyfile(**contents)
|
||||||
|
result = testdir.runpytest_subprocess('--assert=%s' % mode)
|
||||||
|
if mode == 'plain':
|
||||||
|
expected = 'E AssertionError'
|
||||||
|
elif mode == 'rewrite':
|
||||||
|
expected = '*assert 10 == 30*'
|
||||||
|
elif mode == 'reinterp':
|
||||||
|
expected = '*AssertionError:*was re-run*'
|
||||||
|
else:
|
||||||
|
assert 0
|
||||||
|
result.stdout.fnmatch_lines([expected])
|
||||||
|
|
||||||
|
|
||||||
def test_issue731(testdir):
|
def test_issue731(testdir):
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
|
|
|
@ -485,9 +485,14 @@ def test_load_initial_conftest_last_ordering(testdir):
|
||||||
pm.register(m)
|
pm.register(m)
|
||||||
hc = pm.hook.pytest_load_initial_conftests
|
hc = pm.hook.pytest_load_initial_conftests
|
||||||
l = hc._nonwrappers + hc._wrappers
|
l = hc._nonwrappers + hc._wrappers
|
||||||
assert l[-1].function.__module__ == "_pytest.capture"
|
expected = [
|
||||||
assert l[-2].function == m.pytest_load_initial_conftests
|
"_pytest.config",
|
||||||
assert l[-3].function.__module__ == "_pytest.config"
|
'test_config',
|
||||||
|
'_pytest.assertion',
|
||||||
|
'_pytest.capture',
|
||||||
|
]
|
||||||
|
assert [x.function.__module__ for x in l] == expected
|
||||||
|
|
||||||
|
|
||||||
class TestWarning:
|
class TestWarning:
|
||||||
def test_warn_config(self, testdir):
|
def test_warn_config(self, testdir):
|
||||||
|
|
Loading…
Reference in New Issue