2010-11-06 18:38:53 +08:00
|
|
|
"""
|
2011-05-27 02:15:21 +08:00
|
|
|
support for presenting detailed information in failing assertions.
|
2010-11-06 18:38:53 +08:00
|
|
|
"""
|
2009-08-27 23:26:02 +08:00
|
|
|
import py
|
2016-01-08 17:58:26 +08:00
|
|
|
import os
|
2009-08-29 01:16:15 +08:00
|
|
|
import sys
|
2016-06-20 20:45:13 +08:00
|
|
|
|
|
|
|
from _pytest.config import hookimpl
|
2016-07-12 09:03:53 +08:00
|
|
|
from _pytest.monkeypatch import MonkeyPatch
|
2011-06-29 10:13:12 +08:00
|
|
|
from _pytest.assertion import util
|
2009-08-27 23:26:02 +08:00
|
|
|
|
2014-04-03 00:16:37 +08:00
|
|
|
|
2009-08-27 23:26:02 +08:00
|
|
|
def pytest_addoption(parser):
|
|
|
|
group = parser.getgroup("debugconfig")
|
2014-04-03 00:16:37 +08:00
|
|
|
group.addoption('--assert',
|
|
|
|
action="store",
|
|
|
|
dest="assertmode",
|
2011-07-05 23:29:53 +08:00
|
|
|
choices=("rewrite", "reinterp", "plain",),
|
2014-04-03 00:16:37 +08:00
|
|
|
default="rewrite",
|
|
|
|
metavar="MODE",
|
|
|
|
help="""control assertion debugging tools. 'plain'
|
|
|
|
performs no assertion debugging. 'reinterp'
|
|
|
|
reinterprets assert statements after they failed
|
|
|
|
to provide assertion expression information.
|
|
|
|
'rewrite' (the default) rewrites assert
|
|
|
|
statements in test modules on import to
|
|
|
|
provide assert expression information. """)
|
|
|
|
|
2011-05-27 03:34:27 +08:00
|
|
|
|
2011-05-27 05:08:25 +08:00
|
|
|
class AssertionState:
|
|
|
|
"""State for the assertion plugin."""
|
|
|
|
|
|
|
|
def __init__(self, config, mode):
|
|
|
|
self.mode = mode
|
|
|
|
self.trace = config.trace.root.get("assertion")
|
2009-08-27 23:26:02 +08:00
|
|
|
|
2014-04-03 00:16:37 +08:00
|
|
|
|
2016-06-20 20:45:13 +08:00
|
|
|
@hookimpl(tryfirst=True)
|
|
|
|
def pytest_load_initial_conftests(early_config, parser, args):
|
|
|
|
ns, ns_unknown_args = parser.parse_known_and_unknown_args(args)
|
|
|
|
mode = ns.assertmode
|
2011-07-05 21:21:08 +08:00
|
|
|
if mode == "rewrite":
|
|
|
|
try:
|
2013-10-12 21:39:22 +08:00
|
|
|
import ast # noqa
|
2011-07-05 21:21:08 +08:00
|
|
|
except ImportError:
|
|
|
|
mode = "reinterp"
|
2011-09-21 12:45:40 +08:00
|
|
|
else:
|
2013-03-22 01:19:01 +08:00
|
|
|
# Both Jython and CPython 2.6.0 have AST bugs that make the
|
|
|
|
# assertion rewriting hook malfunction.
|
|
|
|
if (sys.platform.startswith('java') or
|
2014-04-03 00:16:37 +08:00
|
|
|
sys.version_info[:3] == (2, 6, 0)):
|
2011-09-21 12:45:40 +08:00
|
|
|
mode = "reinterp"
|
2016-06-20 20:45:13 +08:00
|
|
|
|
|
|
|
early_config._assertstate = AssertionState(early_config, mode)
|
|
|
|
warn_about_missing_assertion(mode, early_config.pluginmanager)
|
|
|
|
|
2011-07-05 23:29:53 +08:00
|
|
|
if mode != "plain":
|
2011-06-29 10:13:12 +08:00
|
|
|
_load_modules(mode)
|
2016-07-12 09:03:53 +08:00
|
|
|
m = MonkeyPatch()
|
2016-06-20 20:45:13 +08:00
|
|
|
early_config._cleanup.append(m.undo)
|
2011-05-26 06:54:02 +08:00
|
|
|
m.setattr(py.builtin.builtins, 'AssertionError',
|
2013-10-12 21:39:22 +08:00
|
|
|
reinterpret.AssertionError) # noqa
|
2016-06-20 20:45:13 +08:00
|
|
|
|
2011-06-29 10:13:12 +08:00
|
|
|
hook = None
|
2011-06-29 22:44:04 +08:00
|
|
|
if mode == "rewrite":
|
2016-06-21 15:28:10 +08:00
|
|
|
hook = rewrite.AssertionRewritingHook(early_config) # noqa
|
2012-05-06 05:31:05 +08:00
|
|
|
sys.meta_path.insert(0, hook)
|
2016-06-20 20:45:13 +08:00
|
|
|
|
|
|
|
early_config._assertstate.hook = hook
|
|
|
|
early_config._assertstate.trace("configured with mode set to %r" % (mode,))
|
2015-04-22 22:33:20 +08:00
|
|
|
def undo():
|
2016-06-20 20:45:13 +08:00
|
|
|
hook = early_config._assertstate.hook
|
2015-04-22 22:33:20 +08:00
|
|
|
if hook is not None and hook in sys.meta_path:
|
|
|
|
sys.meta_path.remove(hook)
|
2016-06-20 20:45:13 +08:00
|
|
|
early_config.add_cleanup(undo)
|
2011-05-25 06:48:56 +08:00
|
|
|
|
2014-04-03 00:16:37 +08:00
|
|
|
|
2011-07-06 00:01:31 +08:00
|
|
|
def pytest_collection(session):
|
|
|
|
# this hook is only called when test modules are collected
|
|
|
|
# so for example not in the master process of pytest-xdist
|
|
|
|
# (which does not collect test modules)
|
2011-06-29 10:13:12 +08:00
|
|
|
hook = session.config._assertstate.hook
|
|
|
|
if hook is not None:
|
|
|
|
hook.set_session(session)
|
2011-05-20 10:52:10 +08:00
|
|
|
|
2014-04-03 00:16:37 +08:00
|
|
|
|
2016-01-08 17:58:26 +08:00
|
|
|
def _running_on_ci():
|
|
|
|
"""Check if we're currently running on a CI system."""
|
|
|
|
env_vars = ['CI', 'BUILD_NUMBER']
|
|
|
|
return any(var in os.environ for var in env_vars)
|
|
|
|
|
|
|
|
|
2011-10-16 18:51:15 +08:00
|
|
|
def pytest_runtest_setup(item):
|
2014-04-03 00:35:22 +08:00
|
|
|
"""Setup the pytest_assertrepr_compare hook
|
|
|
|
|
|
|
|
The newinterpret and rewrite modules will use util._reprcompare if
|
|
|
|
it exists to use custom reporting via the
|
|
|
|
pytest_assertrepr_compare hook. This sets up this custom
|
|
|
|
comparison for the test.
|
|
|
|
"""
|
2011-10-16 18:51:15 +08:00
|
|
|
def callbinrepr(op, left, right):
|
2014-04-03 00:35:22 +08:00
|
|
|
"""Call the pytest_assertrepr_compare hook and prepare the result
|
|
|
|
|
|
|
|
This uses the first result from the hook and then ensures the
|
|
|
|
following:
|
2016-01-08 17:58:26 +08:00
|
|
|
* Overly verbose explanations are dropped unless -vv was used or
|
|
|
|
running on a CI.
|
2014-04-03 00:35:22 +08:00
|
|
|
* Embedded newlines are escaped to help util.format_explanation()
|
|
|
|
later.
|
|
|
|
* If the rewrite mode is used embedded %-characters are replaced
|
|
|
|
to protect later % formatting.
|
|
|
|
|
|
|
|
The result can be formatted by util.format_explanation() for
|
|
|
|
pretty printing.
|
|
|
|
"""
|
2011-10-16 18:51:15 +08:00
|
|
|
hook_result = item.ihook.pytest_assertrepr_compare(
|
|
|
|
config=item.config, op=op, left=left, right=right)
|
|
|
|
for new_expl in hook_result:
|
|
|
|
if new_expl:
|
2016-01-15 07:01:07 +08:00
|
|
|
if (sum(len(p) for p in new_expl[1:]) > 80*8 and
|
2016-01-23 04:32:45 +08:00
|
|
|
item.config.option.verbose < 2 and
|
|
|
|
not _running_on_ci()):
|
2015-08-28 09:20:13 +08:00
|
|
|
show_max = 10
|
|
|
|
truncated_lines = len(new_expl) - show_max
|
|
|
|
new_expl[show_max:] = [py.builtin._totext(
|
|
|
|
'Detailed information truncated (%d more lines)'
|
|
|
|
', use "-vv" to show' % truncated_lines)]
|
2014-04-03 00:35:22 +08:00
|
|
|
new_expl = [line.replace("\n", "\\n") for line in new_expl]
|
2014-04-03 00:16:37 +08:00
|
|
|
res = py.builtin._totext("\n~").join(new_expl)
|
2011-10-16 18:51:15 +08:00
|
|
|
if item.config.getvalue("assertmode") == "rewrite":
|
|
|
|
res = res.replace("%", "%%")
|
|
|
|
return res
|
|
|
|
util._reprcompare = callbinrepr
|
|
|
|
|
2014-04-03 00:16:37 +08:00
|
|
|
|
2011-10-16 18:51:15 +08:00
|
|
|
def pytest_runtest_teardown(item):
|
|
|
|
util._reprcompare = None
|
|
|
|
|
2014-04-03 00:16:37 +08:00
|
|
|
|
2011-06-29 10:13:12 +08:00
|
|
|
def pytest_sessionfinish(session):
|
|
|
|
hook = session.config._assertstate.hook
|
|
|
|
if hook is not None:
|
|
|
|
hook.session = None
|
2009-08-27 23:26:02 +08:00
|
|
|
|
2014-04-03 00:16:37 +08:00
|
|
|
|
2011-06-29 10:13:12 +08:00
|
|
|
def _load_modules(mode):
|
|
|
|
"""Lazily import assertion related code."""
|
|
|
|
global rewrite, reinterpret
|
2013-10-12 21:39:22 +08:00
|
|
|
from _pytest.assertion import reinterpret # noqa
|
2011-06-29 22:44:04 +08:00
|
|
|
if mode == "rewrite":
|
2013-10-12 21:39:22 +08:00
|
|
|
from _pytest.assertion import rewrite # noqa
|
2011-06-29 10:13:12 +08:00
|
|
|
|
2014-04-03 00:16:37 +08:00
|
|
|
|
2016-06-20 20:45:13 +08:00
|
|
|
def warn_about_missing_assertion(mode, pluginmanager):
|
2009-08-27 23:26:02 +08:00
|
|
|
try:
|
|
|
|
assert False
|
|
|
|
except AssertionError:
|
|
|
|
pass
|
|
|
|
else:
|
2011-06-29 22:44:04 +08:00
|
|
|
if mode == "rewrite":
|
2011-06-29 10:13:12 +08:00
|
|
|
specifically = ("assertions which are not in test modules "
|
|
|
|
"will be ignored")
|
|
|
|
else:
|
|
|
|
specifically = "failing tests may report as passing"
|
|
|
|
|
2016-06-20 20:45:13 +08:00
|
|
|
# temporarily disable capture so we can print our warning
|
|
|
|
capman = pluginmanager.getplugin('capturemanager')
|
|
|
|
try:
|
|
|
|
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)
|
2014-04-03 00:16:37 +08:00
|
|
|
|
2010-09-07 02:35:17 +08:00
|
|
|
|
2014-04-03 00:35:22 +08:00
|
|
|
# Expose this plugin's implementation for the pytest_assertrepr_compare hook
|
2011-05-27 01:01:34 +08:00
|
|
|
pytest_assertrepr_compare = util.assertrepr_compare
|