diff --git a/doc/example/assertion/failure_demo.py b/doc/example/assertion/failure_demo.py index f0266b82c..7817ed123 100644 --- a/doc/example/assertion/failure_demo.py +++ b/doc/example/assertion/failure_demo.py @@ -152,6 +152,9 @@ class TestSpecialisedExplanations(object): def test_eq_set(self): assert set([0, 10, 11, 12]) == set([0, 20, 21]) + def test_eq_longer_list(self): + assert [1,2] == [1,2,3] + def test_in_list(self): assert 1 in [0, 2, 3, 4, 5] diff --git a/doc/example/assertion/test_failures.py b/doc/example/assertion/test_failures.py index 2f223d0d2..e925d6c1f 100644 --- a/doc/example/assertion/test_failures.py +++ b/doc/example/assertion/test_failures.py @@ -10,6 +10,6 @@ def test_failure_demo_fails_properly(testdir): failure_demo.copy(testdir.tmpdir.join(failure_demo.basename)) result = testdir.runpytest(target) result.stdout.fnmatch_lines([ - "*20 failed*" + "*31 failed*" ]) assert result.ret != 0 diff --git a/py/_plugin/pytest_assertion.py b/py/_plugin/pytest_assertion.py index e9a04975f..af380369e 100644 --- a/py/_plugin/pytest_assertion.py +++ b/py/_plugin/pytest_assertion.py @@ -61,18 +61,22 @@ def pytest_assertrepr_compare(op, left, right): isset = lambda x: isinstance(x, set) explanation = None - if op == '==': - if istext(left) and istext(right): - explanation = _diff_text(left, right) - elif issequence(left) and issequence(right): - explanation = _compare_eq_sequence(left, right) - elif isset(left) and isset(right): - explanation = _compare_eq_set(left, right) - elif isdict(left) and isdict(right): - explanation = _diff_text(py.std.pprint.pformat(left), - py.std.pprint.pformat(right)) - elif op == 'in': - pass # XXX + try: + if op == '==': + if istext(left) and istext(right): + explanation = _diff_text(left, right) + elif issequence(left) and issequence(right): + explanation = _compare_eq_sequence(left, right) + elif isset(left) and isset(right): + explanation = _compare_eq_set(left, right) + elif isdict(left) and isdict(right): + explanation = _diff_text(py.std.pprint.pformat(left), + py.std.pprint.pformat(right)) + except py.builtin._sysex: + raise + except: + explanation = ['(pytest_assertion plugin: representation of ' + 'details failed. Probably an object has a faulty __repr__.)'] if not explanation: return None @@ -120,17 +124,17 @@ def _compare_eq_sequence(left, right): explanation = [] for i in range(min(len(left), len(right))): if left[i] != right[i]: - explanation += ['First differing item %s: %s != %s' % + explanation += ['At index %s diff: %r != %r' % (i, left[i], right[i])] break if len(left) > len(right): explanation += ['Left contains more items, ' - 'first extra item: %s' % left[len(right)]] + 'first extra item: %s' % py.io.saferepr(left[len(right)],)] elif len(left) < len(right): explanation += ['Right contains more items, ' - 'first extra item: %s' % right[len(left)]] - return explanation + _diff_text(py.std.pprint.pformat(left), - py.std.pprint.pformat(right)) + 'first extra item: %s' % py.io.saferepr(right[len(left)],)] + return explanation # + _diff_text(py.std.pprint.pformat(left), + # py.std.pprint.pformat(right)) def _compare_eq_set(left, right): diff --git a/testing/plugin/test_pytest_assertion.py b/testing/plugin/test_pytest_assertion.py index 1b98c8ae8..6812abc47 100644 --- a/testing/plugin/test_pytest_assertion.py +++ b/testing/plugin/test_pytest_assertion.py @@ -47,44 +47,62 @@ class TestBinReprIntegration: plugin.pytest_unconfigure(config) assert hook == py.code._reprcompare +def callequal(left, right): + return plugin.pytest_assertrepr_compare('==', left, right) + class TestAssert_reprcompare: def test_different_types(self): - assert plugin.pytest_assertrepr_compare('==', [0, 1], 'foo') is None + assert callequal([0, 1], 'foo') is None def test_summary(self): - summary = plugin.pytest_assertrepr_compare('==', [0, 1], [0, 2])[0] + summary = callequal([0, 1], [0, 2])[0] assert len(summary) < 65 def test_text_diff(self): - diff = plugin.pytest_assertrepr_compare('==', 'spam', 'eggs')[1:] + diff = callequal('spam', 'eggs')[1:] assert '- spam' in diff assert '+ eggs' in diff def test_multiline_text_diff(self): left = 'foo\nspam\nbar' right = 'foo\neggs\nbar' - diff = plugin.pytest_assertrepr_compare('==', left, right) + diff = callequal(left, right) assert '- spam' in diff assert '+ eggs' in diff def test_list(self): - expl = plugin.pytest_assertrepr_compare('==', [0, 1], [0, 2]) + expl = callequal([0, 1], [0, 2]) assert len(expl) > 1 def test_list_different_lenghts(self): - expl = plugin.pytest_assertrepr_compare('==', [0, 1], [0, 1, 2]) + expl = callequal([0, 1], [0, 1, 2]) assert len(expl) > 1 - expl = plugin.pytest_assertrepr_compare('==', [0, 1, 2], [0, 1]) + expl = callequal([0, 1, 2], [0, 1]) assert len(expl) > 1 def test_dict(self): - expl = plugin.pytest_assertrepr_compare('==', {'a': 0}, {'a': 1}) + expl = callequal({'a': 0}, {'a': 1}) assert len(expl) > 1 def test_set(self): - expl = plugin.pytest_assertrepr_compare('==', set([0, 1]), set([0, 2])) + expl = callequal(set([0, 1]), set([0, 2])) assert len(expl) > 1 + def test_list_tuples(self): + expl = callequal([], [(1,2)]) + assert len(expl) > 1 + expl = callequal([(1,2)], []) + assert len(expl) > 1 + + def test_list_bad_repr(self): + class A: + def __repr__(self): + raise ValueError(42) + expl = callequal([], [A()]) + assert 'ValueError' in "".join(expl) + expl = callequal({}, {'1': A()}) + assert 'faulty' in "".join(expl) + @needsnewassert def test_pytest_assertrepr_compare_integration(testdir): testdir.makepyfile(""" @@ -102,6 +120,25 @@ def test_pytest_assertrepr_compare_integration(testdir): "*E*50*", ]) +@needsnewassert +def test_sequence_comparison_uses_repr(testdir): + testdir.makepyfile(""" + def test_hello(): + x = set("hello x") + y = set("hello y") + assert x == y + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "*def test_hello():*", + "*assert x == y*", + "*E*Extra items*left*", + "*E*'x'*", + "*E*Extra items*right*", + "*E*'y'*", + ]) + + def test_functional(testdir): testdir.makepyfile(""" def test_hello():