Escape newlines in result from assertrepr hook

The result from the pytest_assertrepr_compare hook should not include
any newlines since that will confuse the mini-formatting language used
by assertion.util.format_explanation.  So simply escape the included
newlines, this way hook writers do not have to worry about this at
all.

Fixes issue 453.
This commit is contained in:
Floris Bruynooghe 2014-04-02 17:35:22 +01:00
parent 844c141d10
commit adb12d0d4f
2 changed files with 38 additions and 5 deletions

View File

@ -88,22 +88,38 @@ def pytest_collection(session):
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
"""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.
"""
def callbinrepr(op, left, right): def callbinrepr(op, left, right):
"""Call the pytest_assertrepr_compare hook and prepare the result
This uses the first result from the hook and then ensures the
following:
* Overly verbose explanations are dropped unles -vv was used.
* 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.
"""
hook_result = item.ihook.pytest_assertrepr_compare( hook_result = item.ihook.pytest_assertrepr_compare(
config=item.config, op=op, left=left, right=right) config=item.config, op=op, left=left, right=right)
for new_expl in hook_result: for new_expl in hook_result:
if new_expl: if new_expl:
# Don't include pageloads of data unless we are very
# verbose (-vv)
if (sum(len(p) for p in new_expl[1:]) > 80*8 if (sum(len(p) for p in new_expl[1:]) > 80*8
and item.config.option.verbose < 2): and item.config.option.verbose < 2):
new_expl[1:] = [py.builtin._totext( new_expl[1:] = [py.builtin._totext(
'Detailed information truncated, use "-vv" to show')] 'Detailed information truncated, use "-vv" to show')]
new_expl = [line.replace("\n", "\\n") for line in new_expl]
res = py.builtin._totext("\n~").join(new_expl) res = py.builtin._totext("\n~").join(new_expl)
if item.config.getvalue("assertmode") == "rewrite": if item.config.getvalue("assertmode") == "rewrite":
# The result will be fed back a python % formatting
# operation, which will fail if there are extraneous
# '%'s in the string. Escape them here.
res = res.replace("%", "%%") res = res.replace("%", "%%")
return res return res
util._reprcompare = callbinrepr util._reprcompare = callbinrepr
@ -145,4 +161,5 @@ def warn_about_missing_assertion(mode):
"(are you using python -O?)\n") "(are you using python -O?)\n")
# Expose this plugin's implementation for the pytest_assertrepr_compare hook
pytest_assertrepr_compare = util.assertrepr_compare pytest_assertrepr_compare = util.assertrepr_compare

View File

@ -4,6 +4,7 @@ import sys
import py, pytest import py, pytest
import _pytest.assertion as plugin import _pytest.assertion as plugin
from _pytest.assertion import reinterpret from _pytest.assertion import reinterpret
from _pytest.assertion import util
needsnewassert = pytest.mark.skipif("sys.version_info < (2,6)") needsnewassert = pytest.mark.skipif("sys.version_info < (2,6)")
@ -199,6 +200,21 @@ class TestAssert_reprcompare:
assert msg assert msg
class TestFormatExplanation:
def test_speical_chars_full(self, testdir):
# Issue 453, for the bug this would raise IndexError
testdir.makepyfile("""
def test_foo():
assert u'\\n}' == u''
""")
result = testdir.runpytest()
assert result.ret == 1
result.stdout.fnmatch_lines([
"*AssertionError*",
])
def test_python25_compile_issue257(testdir): def test_python25_compile_issue257(testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def test_rewritten(): def test_rewritten():