Initial patch as sent to py-dev
With a small but disasterous typo fixed though. --HG-- branch : trunk
This commit is contained in:
@@ -162,10 +162,7 @@ class DebugInterpreter(ast.NodeVisitor):
|
||||
def visit_Compare(self, comp):
|
||||
left = comp.left
|
||||
left_explanation, left_result = self.visit(left)
|
||||
got_result = False
|
||||
for op, next_op in zip(comp.ops, comp.comparators):
|
||||
if got_result and not result:
|
||||
break
|
||||
next_explanation, next_result = self.visit(next_op)
|
||||
op_symbol = operator_map[op.__class__]
|
||||
explanation = "%s %s %s" % (left_explanation, op_symbol,
|
||||
@@ -177,9 +174,16 @@ class DebugInterpreter(ast.NodeVisitor):
|
||||
__exprinfo_right=next_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
else:
|
||||
got_result = True
|
||||
if not result:
|
||||
break
|
||||
left_explanation, left_result = next_explanation, next_result
|
||||
hook_result = py.test.config.hook.pytest_assert_compare(
|
||||
op=op_symbol, left=left_result, right=next_result)
|
||||
if hook_result:
|
||||
for new_expl in hook_result:
|
||||
if new_expl:
|
||||
explanation = '\n~'.join(new_expl)
|
||||
break
|
||||
return explanation, result
|
||||
|
||||
def visit_BoolOp(self, boolop):
|
||||
|
||||
@@ -5,12 +5,20 @@ BuiltinAssertionError = py.builtin.builtins.AssertionError
|
||||
|
||||
|
||||
def _format_explanation(explanation):
|
||||
# uck! See CallFunc for where \n{ and \n} escape sequences are used
|
||||
"""This formats an explanation
|
||||
|
||||
Normally all embedded newlines are escaped, however there are
|
||||
three exceptions: \n{, \n} and \n~. The first two are intended
|
||||
cover nested explanations, see function and attribute explanations
|
||||
for examples (.visit_Call(), visit_Attribute()). The last one is
|
||||
for when one explanation needs to span multiple lines, e.g. when
|
||||
displaying diffs.
|
||||
"""
|
||||
raw_lines = (explanation or '').split('\n')
|
||||
# escape newlines not followed by { and }
|
||||
# escape newlines not followed by {, } and ~
|
||||
lines = [raw_lines[0]]
|
||||
for l in raw_lines[1:]:
|
||||
if l.startswith('{') or l.startswith('}'):
|
||||
if l.startswith('{') or l.startswith('}') or l.startswith('~'):
|
||||
lines.append(l)
|
||||
else:
|
||||
lines[-1] += '\\n' + l
|
||||
@@ -28,11 +36,14 @@ def _format_explanation(explanation):
|
||||
stackcnt[-1] += 1
|
||||
stackcnt.append(0)
|
||||
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
|
||||
else:
|
||||
elif line.startswith('}'):
|
||||
assert line.startswith('}')
|
||||
stack.pop()
|
||||
stackcnt.pop()
|
||||
result[stack[-1]] += line[1:]
|
||||
else:
|
||||
assert line.startswith('~')
|
||||
result.append(' '*len(stack) + line[1:])
|
||||
assert len(stack) == 1
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
@@ -123,6 +123,19 @@ def pytest_sessionstart(session):
|
||||
def pytest_sessionfinish(session, exitstatus):
|
||||
""" whole test run finishes. """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# hooks for customising the assert methods
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def pytest_assert_compare(op, left, right):
|
||||
"""Customise compare assertion
|
||||
|
||||
Return None or an empty list for no custom compare, otherwise
|
||||
return a list of strings. The strings will be joined by newlines
|
||||
but any newlines *in* as string will be escaped. Note that all
|
||||
but the first line will be indented sligthly.
|
||||
"""
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# hooks for influencing reporting (invoked from pytest_terminal)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import difflib
|
||||
import pprint
|
||||
|
||||
import py
|
||||
import sys
|
||||
|
||||
@@ -26,3 +29,49 @@ def warn_about_missing_assertion():
|
||||
else:
|
||||
py.std.warnings.warn("Assertions are turned off!"
|
||||
" (are you using python -O?)")
|
||||
|
||||
|
||||
def pytest_assert_compare(op, left, right):
|
||||
"""Make a specialised explanation for comapare equal"""
|
||||
if op != '==' or type(left) != type(right):
|
||||
return None
|
||||
explanation = []
|
||||
left_repr = py.io.saferepr(left, maxsize=30)
|
||||
right_repr = py.io.saferepr(right, maxsize=30)
|
||||
explanation += ['%s == %s' % (left_repr, right_repr)]
|
||||
issquence = lambda x: isinstance(x, (list, tuple))
|
||||
istext = lambda x: isinstance(x, basestring)
|
||||
isdict = lambda x: isinstance(x, dict)
|
||||
if istext(left):
|
||||
explanation += [line.strip('\n') for line in
|
||||
difflib.ndiff(left.splitlines(), right.splitlines())]
|
||||
elif issquence(left):
|
||||
explanation += _compare_eq_sequence(left, right)
|
||||
elif isdict(left):
|
||||
explanation += _pprint_diff(left, right)
|
||||
else:
|
||||
return None # No specialised knowledge
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_sequence(left, right):
|
||||
explanation = []
|
||||
for i in xrange(min(len(left), len(right))):
|
||||
if left[i] != right[i]:
|
||||
explanation += ['First differing item %s: %s != %s' %
|
||||
(i, left[i], right[i])]
|
||||
break
|
||||
if len(left) > len(right):
|
||||
explanation += ['Left contains more items, '
|
||||
'first extra item: %s' % left[len(right)]]
|
||||
elif len(left) < len(right):
|
||||
explanation += ['Right contains more items, '
|
||||
'first extra item: %s' % right[len(right)]]
|
||||
return explanation + _pprint_diff(left, right)
|
||||
|
||||
|
||||
def _pprint_diff(left, right):
|
||||
"""Make explanation using pprint and difflib"""
|
||||
return [line.strip('\n') for line in
|
||||
difflib.ndiff(pprint.pformat(left).splitlines(),
|
||||
pprint.pformat(right).splitlines())]
|
||||
|
||||
Reference in New Issue
Block a user