[py-dev] Advanced assert equal
holger krekel
holger at merlinux.eu
Mon Aug 16 09:10:16 CEST 2010
Hey Floris,
nice. i also thought about improving reporting for particular types
of assert-expressions. Will take a look at your code after
holiday and maybe Benjamin can also take a look or make a suggestion
on how to best make assert expression-reporting customizable.
cheers,
holger
On Mon, Aug 16, 2010 at 01:25 +0100, Floris Bruynooghe wrote:
> Hi
>
> Ever since unittest grew it's .assertSequenceEqual() and
> .assertMultilineEqual() I've been jealous of it. So this weekend I've
> looked into the py.test code and made an attempt at getting this into
> my favourite testing tool.
>
> The attached patch makes compare equal a special case and checks if
> the two arguments to it are both a list, text or dict and tries to
> generate a nicer explanation text for them. The patch is more like a
> proof of concept then a final implementation, I may have done some
> very strange or silly things as I'm not familiar with the code. It
> would be great to get feedback, both on the general concept and the
> actual implementation (particularly note the way I had to hack
> _format_explanation() in assertion.py).
>
> Some of the rough edges I can think off right now: (i) no idea how
> comparisons and nested calls work together, (ii) no attempt is made to
> limit the output from difflib so the screen doesn't get flooded.
> There's probably many more.
>
> I hope this can be useful
> Floris
>
> --
> Debian GNU/Linux -- The Power of Freedom
> www.debian.org | www.gnu.org | www.kernel.org
> diff --git a/py/_code/_assertionnew.py b/py/_code/_assertionnew.py
> --- a/py/_code/_assertionnew.py
> +++ b/py/_code/_assertionnew.py
> @@ -5,6 +5,8 @@ This should replace _assertionold.py eve
>
> import sys
> import ast
> +import difflib
> +import pprint
>
> import py
> from py._code.assertion import _format_explanation, BuiltinAssertionError
> @@ -164,8 +166,6 @@ class DebugInterpreter(ast.NodeVisitor):
> 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,11 +177,56 @@ 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
> + if op_symbol == "==":
> + new_expl = self._explain_equal(left_result, next_result,
> + left_explanation, next_explanation)
> + if new_expl:
> + explanation = new_expl
> return explanation, result
>
> + def _explain_equal(self, left, right, left_repr, right_repr):
> + """Make a specialised explanation for comapare equal"""
> + if type(left) != type(right):
> + return None
> + explanation = []
> + if len(left_repr) > 30:
> + left_repr = left_repr[:27] + '...'
> + if len(right_repr) > 30:
> + right_repr = right_repr[:27] + '...'
> + 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 issquence(left):
> + 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)]]
> + explanation += [line.strip('\n') for line in
> + difflib.ndiff(pprint.pformat(left).splitlines(),
> + pprint.pformat(right).splitlines())]
> + elif istext(left):
> + explanation += [line.strip('\n') for line in
> + difflib.ndiff(left.splitlines(),
> + right.splitlines())]
> + elif isdict(left):
> + explanation += [line.strip('\n') for line in
> + difflib.ndiff(pprint.pformat(left).splitlines(),
> + pprint.pformat(right).splitlines())]
> + else:
> + return None # No specialised knowledge
> + return '\n=='.join(explanation)
> +
> def visit_BoolOp(self, boolop):
> is_or = isinstance(boolop.op, ast.Or)
> explanations = []
> diff --git a/py/_code/assertion.py b/py/_code/assertion.py
> --- a/py/_code/assertion.py
> +++ b/py/_code/assertion.py
> @@ -10,7 +10,7 @@ def _format_explanation(explanation):
> # 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 +28,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(' ' + line.strip('=='))
> assert len(stack) == 1
> return '\n'.join(result)
>
> diff --git a/testing/code/test_assertionnew.py b/testing/code/test_assertionnew.py
> new file mode 100644
> --- /dev/null
> +++ b/testing/code/test_assertionnew.py
> @@ -0,0 +1,74 @@
> +import sys
> +
> +import py
> +from py._code._assertionnew import interpret
> +
> +
> +def getframe():
> + """Return the frame of the caller as a py.code.Frame object"""
> + return py.code.Frame(sys._getframe(1))
> +
> +
> +def setup_module(mod):
> + py.code.patch_builtins(assertion=True, compile=False)
> +
> +
> +def teardown_module(mod):
> + py.code.unpatch_builtins(assertion=True, compile=False)
> +
> +
> +def test_assert_simple():
> + # Simply test that this way of testing works
> + a = 0
> + b = 1
> + r = interpret('assert a == b', getframe())
> + assert r == 'assert 0 == 1'
> +
> +
> +def test_assert_list():
> + r = interpret('assert [0, 1] == [0, 2]', getframe())
> + msg = ('assert [0, 1] == [0, 2]\n'
> + ' First differing item 1: 1 != 2\n'
> + ' - [0, 1]\n'
> + ' ? ^\n'
> + ' + [0, 2]\n'
> + ' ? ^')
> + print r
> + assert r == msg
> +
> +
> +def test_assert_string():
> + r = interpret('assert "foo and bar" == "foo or bar"', getframe())
> + msg = ("assert 'foo and bar' == 'foo or bar'\n"
> + " - foo and bar\n"
> + " ? ^^^\n"
> + " + foo or bar\n"
> + " ? ^^")
> + print r
> + assert r == msg
> +
> +
> +def test_assert_multiline_string():
> + a = 'foo\nand bar\nbaz'
> + b = 'foo\nor bar\nbaz'
> + r = interpret('assert a == b', getframe())
> + msg = ("assert 'foo\\nand bar\\nbaz' == 'foo\\nor bar\\nbaz'\n"
> + ' foo\n'
> + ' - and bar\n'
> + ' + or bar\n'
> + ' baz')
> + print r
> + assert r == msg
> +
> +
> +def test_assert_dict():
> + a = {'a': 0, 'b': 1}
> + b = {'a': 0, 'c': 2}
> + r = interpret('assert a == b', getframe())
> + msg = ("assert {'a': 0, 'b': 1} == {'a': 0, 'c': 2}\n"
> + " - {'a': 0, 'b': 1}\n"
> + " ? ^ ^\n"
> + " + {'a': 0, 'c': 2}\n"
> + " ? ^ ^")
> + print r
> + assert r == msg
> _______________________________________________
> py-dev mailing list
> py-dev at codespeak.net
> http://codespeak.net/mailman/listinfo/py-dev
--
More information about the Pytest-dev
mailing list