From c97c50e2edf298bb23e0189c6a24db2cec0c11b6 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 10 Feb 2020 06:07:33 +0100 Subject: [PATCH 01/13] _diff_text: use repr with escape characters --- src/_pytest/assertion/util.py | 17 ++++++-- testing/test_assertion.py | 73 +++++++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 67f8d46185e..de1eddf3d12 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -193,6 +193,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: characters which are identical to keep the diff minimal. """ from difflib import ndiff + from wcwidth import wcswidth explanation = [] # type: List[str] @@ -225,10 +226,18 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: left = repr(str(left)) right = repr(str(right)) explanation += ["Strings contain only whitespace, escaping them using repr()"] - explanation += [ - line.strip("\n") - for line in ndiff(left.splitlines(keepends), right.splitlines(keepends)) - ] + + left_lines = left.splitlines(keepends) + right_lines = right.splitlines(keepends) + + if any(wcswidth(x) == -1 for x in left_lines + right_lines): + left_lines = [repr(x) for x in left_lines] + right_lines = [repr(x) for x in right_lines] + explanation += [ + "Strings contain non-printable/escape characters, escaping them using repr()" + ] + + explanation += [line.strip("\n") for line in ndiff(left_lines, right_lines)] return explanation diff --git a/testing/test_assertion.py b/testing/test_assertion.py index e975a3fea2b..3f97f71b765 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -334,8 +334,14 @@ def test_multiline_text_diff(self): left = "foo\nspam\nbar" right = "foo\neggs\nbar" diff = callequal(left, right) - assert "- spam" in diff - assert "+ eggs" in diff + assert diff == [ + "'foo\\nspam\\nbar' == 'foo\\neggs\\nbar'", + "Strings contain non-printable/escape characters, escaping them using repr()", + " 'foo\\n'", + "- 'spam\\n'", + "+ 'eggs\\n'", + " 'bar'", + ] def test_bytes_diff_normal(self): """Check special handling for bytes diff (#5260)""" @@ -989,7 +995,7 @@ def test_full_output_truncated(self, monkeypatch, testdir): line_count = 7 line_len = 100 - expected_truncated_lines = 2 + expected_truncated_lines = 3 testdir.makepyfile( r""" def test_many_lines(): @@ -1007,19 +1013,26 @@ def test_many_lines(): # without -vv, truncate the message showing a few diff lines only result.stdout.fnmatch_lines( [ - "*- 1*", - "*- 3*", - "*- 5*", + "> assert a == b", + "E AssertionError: assert '000000000000...6666666666666' == '000000000000...6666666666666'", + "E Skipping 91 identical leading characters in diff, use -v to show", + "E Strings contain non-printable/escape characters, escaping them using repr()", + "E '000000000\\n'", + "E - '1*\\n'", + "E '2*\\n'", + "E - '3*\\n'", + "E '4*\\...", + "E ", "*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines, ] ) result = testdir.runpytest("-vv") - result.stdout.fnmatch_lines(["* 6*"]) + result.stdout.fnmatch_lines(["* '6*"]) monkeypatch.setenv("CI", "1") result = testdir.runpytest() - result.stdout.fnmatch_lines(["* 6*"]) + result.stdout.fnmatch_lines(["* '6*"]) def test_python25_compile_issue257(testdir): @@ -1068,6 +1081,43 @@ def test_reprcompare_whitespaces(): ] +def test_reprcompare_escape_sequences(): + config = mock_config() + detail = plugin.pytest_assertrepr_compare( + config, "==", "\x1b[31mred", "\x1b[31mgreen" + ) + assert detail == [ + "'\\x1b[31mred' == '\\x1b[31mgreen'", + "Strings contain non-printable/escape characters, escaping them using repr()", + "- '\\x1b[31mred'", + "? ^", + "+ '\\x1b[31mgreen'", + "? + ^^", + ] + + detail = plugin.pytest_assertrepr_compare( + config, "==", ["\x1b[31mred"], ["\x1b[31mgreen"] + ) + assert detail == [ + "['\\x1b[31mred'] == ['\\x1b[31mgreen']", + "At index 0 diff: '\\x1b[31mred' != '\\x1b[31mgreen'", + "Use -v to get the full diff", + ] + config = mock_config(verbose=2) + detail = plugin.pytest_assertrepr_compare( + config, "==", ["\x1b[31mred"], ["\x1b[31mgreen"] + ) + assert detail == [ + "['\\x1b[31mred'] == ['\\x1b[31mgreen']", + "At index 0 diff: '\\x1b[31mred' != '\\x1b[31mgreen'", + "Full diff:", + "- ['\\x1b[31mred']", + "? ^", + "+ ['\\x1b[31mgreen']", + "? + ^^", + ] + + def test_pytest_assertrepr_compare_integration(testdir): testdir.makepyfile( """ @@ -1311,9 +1361,10 @@ def test_diff(): result.stdout.fnmatch_lines( r""" *assert 'asdf' == 'asdf\n' - * - asdf - * + asdf - * ? + + E Strings contain non-printable/escape characters, escaping them using repr() + * - 'asdf' + * + 'asdf\n' + * ? ++ """ ) From 0e153a0da560715a1db0f8def40772234ee57f3d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 10 Feb 2020 07:49:07 +0100 Subject: [PATCH 02/13] fixup! _diff_text: use repr with escape characters --- testing/test_assertion.py | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 3f97f71b765..e8ddaf39539 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1081,12 +1081,8 @@ def test_reprcompare_whitespaces(): ] -def test_reprcompare_escape_sequences(): - config = mock_config() - detail = plugin.pytest_assertrepr_compare( - config, "==", "\x1b[31mred", "\x1b[31mgreen" - ) - assert detail == [ +def test_reprcompare_escape_sequences_strings(): + assert callequal("\x1b[31mred", "\x1b[31mgreen") == [ "'\\x1b[31mred' == '\\x1b[31mgreen'", "Strings contain non-printable/escape characters, escaping them using repr()", "- '\\x1b[31mred'", @@ -1095,28 +1091,6 @@ def test_reprcompare_escape_sequences(): "? + ^^", ] - detail = plugin.pytest_assertrepr_compare( - config, "==", ["\x1b[31mred"], ["\x1b[31mgreen"] - ) - assert detail == [ - "['\\x1b[31mred'] == ['\\x1b[31mgreen']", - "At index 0 diff: '\\x1b[31mred' != '\\x1b[31mgreen'", - "Use -v to get the full diff", - ] - config = mock_config(verbose=2) - detail = plugin.pytest_assertrepr_compare( - config, "==", ["\x1b[31mred"], ["\x1b[31mgreen"] - ) - assert detail == [ - "['\\x1b[31mred'] == ['\\x1b[31mgreen']", - "At index 0 diff: '\\x1b[31mred' != '\\x1b[31mgreen'", - "Full diff:", - "- ['\\x1b[31mred']", - "? ^", - "+ ['\\x1b[31mgreen']", - "? + ^^", - ] - def test_pytest_assertrepr_compare_integration(testdir): testdir.makepyfile( From 9318b236ff35fc6a0734ee91bff2011afb93971a Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 14 Feb 2020 17:10:03 +0100 Subject: [PATCH 03/13] raw strings, zero-width, note --- src/_pytest/assertion/util.py | 10 ++++--- testing/test_assertion.py | 49 ++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index de1eddf3d12..4560fe936e7 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -1,5 +1,6 @@ """Utilities for assertion debugging""" import collections.abc +import itertools import pprint from typing import AbstractSet from typing import Any @@ -193,7 +194,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: characters which are identical to keep the diff minimal. """ from difflib import ndiff - from wcwidth import wcswidth + from wcwidth import wcwidth explanation = [] # type: List[str] @@ -230,11 +231,14 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: left_lines = left.splitlines(keepends) right_lines = right.splitlines(keepends) - if any(wcswidth(x) == -1 for x in left_lines + right_lines): + if any( + wcwidth(ch) <= -1 + for ch in itertools.chain.from_iterable([x for x in left_lines + right_lines]) + ): left_lines = [repr(x) for x in left_lines] right_lines = [repr(x) for x in right_lines] explanation += [ - "Strings contain non-printable/escape characters, escaping them using repr()" + "NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr()." ] explanation += [line.strip("\n") for line in ndiff(left_lines, right_lines)] diff --git a/testing/test_assertion.py b/testing/test_assertion.py index e8ddaf39539..2e26fbd1a73 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -335,12 +335,12 @@ def test_multiline_text_diff(self): right = "foo\neggs\nbar" diff = callequal(left, right) assert diff == [ - "'foo\\nspam\\nbar' == 'foo\\neggs\\nbar'", - "Strings contain non-printable/escape characters, escaping them using repr()", - " 'foo\\n'", - "- 'spam\\n'", - "+ 'eggs\\n'", - " 'bar'", + r"'foo\nspam\nbar' == 'foo\neggs\nbar'", + r"NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr().", + r" 'foo\n'", + r"- 'spam\n'", + r"+ 'eggs\n'", + r" 'bar'", ] def test_bytes_diff_normal(self): @@ -1013,17 +1013,17 @@ def test_many_lines(): # without -vv, truncate the message showing a few diff lines only result.stdout.fnmatch_lines( [ - "> assert a == b", - "E AssertionError: assert '000000000000...6666666666666' == '000000000000...6666666666666'", - "E Skipping 91 identical leading characters in diff, use -v to show", - "E Strings contain non-printable/escape characters, escaping them using repr()", - "E '000000000\\n'", - "E - '1*\\n'", - "E '2*\\n'", - "E - '3*\\n'", - "E '4*\\...", - "E ", - "*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines, + r"> assert a == b", + r"E AssertionError: assert '000000000000...6666666666666' == '000000000000...6666666666666'", + r"E Skipping 91 identical leading characters in diff, use -v to show", + r"E NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr().", + r"E '000000000\n'", + r"E - '1*\n'", + r"E '2*\n'", + r"E - '3*\n'", + r"E '4*", + r"E ", + r"*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines, ] ) @@ -1081,12 +1081,12 @@ def test_reprcompare_whitespaces(): ] -def test_reprcompare_escape_sequences_strings(): - assert callequal("\x1b[31mred", "\x1b[31mgreen") == [ - "'\\x1b[31mred' == '\\x1b[31mgreen'", - "Strings contain non-printable/escape characters, escaping them using repr()", - "- '\\x1b[31mred'", - "? ^", +def test_reprcompare_zerowidth_and_non_printable(): + assert callequal("\x00\x1b[31mred", "\x1b[31mgreen") == [ + "'\\x00\\x1b[31mred' == '\\x1b[31mgreen'", + "NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr().", + "- '\\x00\\x1b[31mred'", + "? ---- ^", "+ '\\x1b[31mgreen'", "? + ^^", ] @@ -1335,7 +1335,8 @@ def test_diff(): result.stdout.fnmatch_lines( r""" *assert 'asdf' == 'asdf\n' - E Strings contain non-printable/escape characters, escaping them using repr() + E AssertionError: assert 'asdf' == 'asdf\n' + E NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr(). * - 'asdf' * + 'asdf\n' * ? ++ From 079e0f9fe16a4e94dce86c35509901c3b979d420 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Feb 2020 13:36:15 +0100 Subject: [PATCH 04/13] test_reprcompare_zerowidth_and_non_printable --- testing/test_assertion.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 2e26fbd1a73..aa05b1edf2e 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1083,12 +1083,12 @@ def test_reprcompare_whitespaces(): def test_reprcompare_zerowidth_and_non_printable(): assert callequal("\x00\x1b[31mred", "\x1b[31mgreen") == [ - "'\\x00\\x1b[31mred' == '\\x1b[31mgreen'", - "NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr().", - "- '\\x00\\x1b[31mred'", - "? ---- ^", - "+ '\\x1b[31mgreen'", - "? + ^^", + r"'\x00\x1b[31mred' == '\x1b[31mgreen'", + r"NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr().", + r"- '\x00\x1b[31mred'", + r"? ---- ^", + r"+ '\x1b[31mgreen'", + r"? + ^^", ] From 989bdd78db12d2d9ad597eeeb7b3f8707deb0a50 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Feb 2020 13:40:43 +0100 Subject: [PATCH 05/13] simplify itertools.chain.from_iterable --- src/_pytest/assertion/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 4560fe936e7..9c64826cdf8 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -232,8 +232,8 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: right_lines = right.splitlines(keepends) if any( - wcwidth(ch) <= -1 - for ch in itertools.chain.from_iterable([x for x in left_lines + right_lines]) + wcwidth(ch) <= 0 + for ch in itertools.chain.from_iterable(x for x in left_lines + right_lines) ): left_lines = [repr(x) for x in left_lines] right_lines = [repr(x) for x in right_lines] From 527450430e7a5dd4e47e3b5f488ba6ec45d27cd7 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Feb 2020 13:42:04 +0100 Subject: [PATCH 06/13] remove zero-width --- src/_pytest/assertion/util.py | 2 +- testing/test_assertion.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 9c64826cdf8..60351c57081 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -238,7 +238,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: left_lines = [repr(x) for x in left_lines] right_lines = [repr(x) for x in right_lines] explanation += [ - "NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr()." + "NOTE: Strings contain non-printable characters. Escaping them using repr()." ] explanation += [line.strip("\n") for line in ndiff(left_lines, right_lines)] diff --git a/testing/test_assertion.py b/testing/test_assertion.py index aa05b1edf2e..c9a58214e6d 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -336,7 +336,7 @@ def test_multiline_text_diff(self): diff = callequal(left, right) assert diff == [ r"'foo\nspam\nbar' == 'foo\neggs\nbar'", - r"NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr().", + r"NOTE: Strings contain non-printable characters. Escaping them using repr().", r" 'foo\n'", r"- 'spam\n'", r"+ 'eggs\n'", @@ -1016,7 +1016,7 @@ def test_many_lines(): r"> assert a == b", r"E AssertionError: assert '000000000000...6666666666666' == '000000000000...6666666666666'", r"E Skipping 91 identical leading characters in diff, use -v to show", - r"E NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr().", + r"E NOTE: Strings contain non-printable characters. Escaping them using repr().", r"E '000000000\n'", r"E - '1*\n'", r"E '2*\n'", @@ -1084,7 +1084,7 @@ def test_reprcompare_whitespaces(): def test_reprcompare_zerowidth_and_non_printable(): assert callequal("\x00\x1b[31mred", "\x1b[31mgreen") == [ r"'\x00\x1b[31mred' == '\x1b[31mgreen'", - r"NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr().", + r"NOTE: Strings contain non-printable characters. Escaping them using repr().", r"- '\x00\x1b[31mred'", r"? ---- ^", r"+ '\x1b[31mgreen'", @@ -1336,7 +1336,7 @@ def test_diff(): r""" *assert 'asdf' == 'asdf\n' E AssertionError: assert 'asdf' == 'asdf\n' - E NOTE: Strings contain non-printable/zero-width characters. Escaping them using repr(). + E NOTE: Strings contain non-printable characters. Escaping them using repr(). * - 'asdf' * + 'asdf\n' * ? ++ From 2e81042a01507e745e3b154cc36f9ccfe1ef4d81 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Feb 2020 14:42:32 +0100 Subject: [PATCH 07/13] handle different line-endings --- doc/en/example/assertion/failure_demo.py | 3 +- src/_pytest/assertion/util.py | 35 +++++++++++++++-- testing/test_assertion.py | 50 +++++++++++++++--------- 3 files changed, 66 insertions(+), 22 deletions(-) diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index 26454e48d76..86bdaeac048 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -45,7 +45,8 @@ def test_eq_text(self): assert "spam" == "eggs" def test_eq_similar_text(self): - assert "foo 1 bar" == "foo 2 bar" + x = "foo\n1 bar\n" + assert x == "foo\n1 bar" def test_eq_multiline_text(self): assert "foo\nspam\nbar" == "foo\neggs\nbar" diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 60351c57081..b08775b383c 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -2,6 +2,7 @@ import collections.abc import itertools import pprint +import re from typing import AbstractSet from typing import Any from typing import Callable @@ -222,14 +223,15 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: ] left = left[:-i] right = right[:-i] - keepends = True if left.isspace() or right.isspace(): left = repr(str(left)) right = repr(str(right)) explanation += ["Strings contain only whitespace, escaping them using repr()"] - left_lines = left.splitlines(keepends) - right_lines = right.splitlines(keepends) + left_split = len(left) and re.split("(\r?\n)", left) or [] + left_lines = left_split[::2] + right_split = len(right) and re.split("(\r?\n)", right) or [] + right_lines = right_split[::2] if any( wcwidth(ch) <= 0 @@ -240,6 +242,33 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: explanation += [ "NOTE: Strings contain non-printable characters. Escaping them using repr()." ] + else: + max_lines = max(len(left_lines), len(right_lines)) + left_ends = left_split[1:max_lines:2] + right_ends = right_split[1:max_lines:2] + if left_ends != right_ends: + explanation += [ + "NOTE: Strings contain different line-endings. Escaping them using repr()." + ] + left_lines = [line for line in left_lines] + right_lines = [line for line in right_lines] + + for idx, (left_line, right_line) in enumerate( + itertools.zip_longest(left_lines, right_lines, fillvalue="") + ): + try: + left_end = left_ends[idx] + except IndexError: + left_end = "" + try: + right_end = right_ends[idx] + except IndexError: + right_end = "" + if left_end != right_end: + left_lines[idx] += repr(left_end)[1:-1] + right_lines[idx] += repr(right_end)[1:-1] + if not left_end or not right_end: + break explanation += [line.strip("\n") for line in ndiff(left_lines, right_lines)] return explanation diff --git a/testing/test_assertion.py b/testing/test_assertion.py index c9a58214e6d..97103bdf922 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -336,11 +336,11 @@ def test_multiline_text_diff(self): diff = callequal(left, right) assert diff == [ r"'foo\nspam\nbar' == 'foo\neggs\nbar'", - r"NOTE: Strings contain non-printable characters. Escaping them using repr().", - r" 'foo\n'", - r"- 'spam\n'", - r"+ 'eggs\n'", - r" 'bar'", + # r"NOTE: Strings contain different line-endings. Escaping them using repr().", + r" foo", + r"- spam", + r"+ eggs", + r" bar", ] def test_bytes_diff_normal(self): @@ -995,7 +995,7 @@ def test_full_output_truncated(self, monkeypatch, testdir): line_count = 7 line_len = 100 - expected_truncated_lines = 3 + expected_truncated_lines = 2 testdir.makepyfile( r""" def test_many_lines(): @@ -1016,23 +1016,37 @@ def test_many_lines(): r"> assert a == b", r"E AssertionError: assert '000000000000...6666666666666' == '000000000000...6666666666666'", r"E Skipping 91 identical leading characters in diff, use -v to show", - r"E NOTE: Strings contain non-printable characters. Escaping them using repr().", - r"E '000000000\n'", - r"E - '1*\n'", - r"E '2*\n'", - r"E - '3*\n'", - r"E '4*", + r"E 000000000", + r"E - 1*", + r"E 2*", + r"E - 3*", + r"E 4*", r"E ", r"*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines, ] ) result = testdir.runpytest("-vv") - result.stdout.fnmatch_lines(["* '6*"]) + result.stdout.fnmatch_lines( + [ + r"> assert a == b", + r"E AssertionError: assert ('0*0\n'\n * '5*5\n'\n '6*6')" + r" == ('0*0\n'\n '2*2\n'\n '4*4\n'\n '6*6')", + r"E 0*0", + r"E - 1*1", + r"E 2*2", + r"E - 3*3", + r"E 4*4", + r"E - 5*5", + r"E 6*6", + r"", + ], + consecutive=True, + ) monkeypatch.setenv("CI", "1") result = testdir.runpytest() - result.stdout.fnmatch_lines(["* '6*"]) + result.stdout.fnmatch_lines(["* 6*"]) def test_python25_compile_issue257(testdir): @@ -1336,10 +1350,10 @@ def test_diff(): r""" *assert 'asdf' == 'asdf\n' E AssertionError: assert 'asdf' == 'asdf\n' - E NOTE: Strings contain non-printable characters. Escaping them using repr(). - * - 'asdf' - * + 'asdf\n' - * ? ++ + E NOTE: Strings contain different line-endings. Escaping them using repr(). + * - asdf + * + asdf\n + * ? ++ """ ) From 2ba59a136008ad8935878ba98ffd2786db17f5fc Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Feb 2020 14:43:52 +0100 Subject: [PATCH 08/13] replace itertools.chain.from_iterable --- doc/en/example/assertion/failure_demo.py | 3 +-- src/_pytest/assertion/util.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index 86bdaeac048..26454e48d76 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -45,8 +45,7 @@ def test_eq_text(self): assert "spam" == "eggs" def test_eq_similar_text(self): - x = "foo\n1 bar\n" - assert x == "foo\n1 bar" + assert "foo 1 bar" == "foo 2 bar" def test_eq_multiline_text(self): assert "foo\nspam\nbar" == "foo\neggs\nbar" diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index b08775b383c..f8c646dff3e 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -235,7 +235,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: if any( wcwidth(ch) <= 0 - for ch in itertools.chain.from_iterable(x for x in left_lines + right_lines) + for ch in [ch for lines in left_lines + right_lines for ch in lines] ): left_lines = [repr(x) for x in left_lines] right_lines = [repr(x) for x in right_lines] From e567e049bfa3d32a1399bb23862ec00431ac05a2 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Feb 2020 15:40:07 +0100 Subject: [PATCH 09/13] test_diff_different_line_endings: more coverage --- testing/test_assertion.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 97103bdf922..db31d1ee240 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1358,6 +1358,26 @@ def test_diff(): ) +def test_diff_different_line_endings(): + assert callequal("asdf\n", "asdf", verbose=2) == [ + r"'asdf\n' == 'asdf'", + r"NOTE: Strings contain different line-endings. Escaping them using repr().", + r"- asdf\n", + r"? --", + r"+ asdf", + r"- ", + ] + + assert callequal("line1\r\nline2", "line1\nline2", verbose=2) == [ + r"'line1\r\nline2' == 'line1\nline2'", + r"NOTE: Strings contain different line-endings. Escaping them using repr().", + r"- line1\r\n", + r"? --", + r"+ line1\n", + r" line2", + ] + + @pytest.mark.filterwarnings("default") def test_assert_tuple_warning(testdir): msg = "assertion is always true" From 3e6c7343b912a47eb9842e1d65e14f0921207b20 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 19 Feb 2020 13:58:36 +0100 Subject: [PATCH 10/13] revisit, handle more on only one side --- src/_pytest/assertion/util.py | 31 +++++++++++-------------------- testing/test_assertion.py | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index f8c646dff3e..c6c7df79f14 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -243,32 +243,23 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: "NOTE: Strings contain non-printable characters. Escaping them using repr()." ] else: - max_lines = max(len(left_lines), len(right_lines)) - left_ends = left_split[1:max_lines:2] - right_ends = right_split[1:max_lines:2] + max_split = max(len(left_split), len(right_split)) + left_ends = left_split[1:max_split:2] + right_ends = right_split[1:max_split:2] if left_ends != right_ends: explanation += [ "NOTE: Strings contain different line-endings. Escaping them using repr()." ] - left_lines = [line for line in left_lines] - right_lines = [line for line in right_lines] - - for idx, (left_line, right_line) in enumerate( - itertools.zip_longest(left_lines, right_lines, fillvalue="") + for idx, (left_line, right_line, left_end, right_end) in enumerate( + itertools.zip_longest( + left_lines, right_lines, left_ends, right_ends, fillvalue=None + ) ): - try: - left_end = left_ends[idx] - except IndexError: - left_end = "" - try: - right_end = right_ends[idx] - except IndexError: - right_end = "" if left_end != right_end: - left_lines[idx] += repr(left_end)[1:-1] - right_lines[idx] += repr(right_end)[1:-1] - if not left_end or not right_end: - break + if left_end is not None: + left_lines[idx] += repr(left_end)[1:-1] + if right_end is not None: + right_lines[idx] += repr(right_end)[1:-1] explanation += [line.strip("\n") for line in ndiff(left_lines, right_lines)] return explanation diff --git a/testing/test_assertion.py b/testing/test_assertion.py index db31d1ee240..93852db1a10 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1377,6 +1377,29 @@ def test_diff_different_line_endings(): r" line2", ] + # Only '\r' is considered non-printable + assert callequal("line1\r\nline2", "line1\nline2\r", verbose=2) == [ + r"'line1\r\nline2' == 'line1\nline2\r'", + r"NOTE: Strings contain non-printable characters. Escaping them using repr().", + r" 'line1'", + r"- 'line2'", + r"+ 'line2\r'", + r"? ++", + ] + + # More on left: escapes them also. + assert callequal("line1\r\nline2\r\nline3\r\n", "line1\nline2", verbose=2) == [ + r"'line1\r\nline2\r\nline3\r\n' == 'line1\nline2'", + r"NOTE: Strings contain different line-endings. Escaping them using repr().", + r"- line1\r\n", + r"? --", + r"+ line1\n", + r"+ line2", + r"- line2\r\n", + r"- line3\r\n", + r"- ", + ] + @pytest.mark.filterwarnings("default") def test_assert_tuple_warning(testdir): From 3a7c810831fd32b1eae1b5fe753b9b65b44bb8b8 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 19 Feb 2020 14:02:33 +0100 Subject: [PATCH 11/13] fix "Avoid deeply nested control flow statements" --- src/_pytest/assertion/util.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index c6c7df79f14..7fa54a6142f 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -255,11 +255,12 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: left_lines, right_lines, left_ends, right_ends, fillvalue=None ) ): - if left_end != right_end: - if left_end is not None: - left_lines[idx] += repr(left_end)[1:-1] - if right_end is not None: - right_lines[idx] += repr(right_end)[1:-1] + if left_end == right_end: + continue + if left_end is not None: + left_lines[idx] += repr(left_end)[1:-1] + if right_end is not None: + right_lines[idx] += repr(right_end)[1:-1] explanation += [line.strip("\n") for line in ndiff(left_lines, right_lines)] return explanation From 48c9061b723ad2d8f53090d2bf2c4faa9a32368d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 19 Feb 2020 22:06:36 +0100 Subject: [PATCH 12/13] do not repr extra newlines --- src/_pytest/assertion/util.py | 2 +- testing/test_assertion.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 7fa54a6142f..ef42100546c 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -243,7 +243,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: "NOTE: Strings contain non-printable characters. Escaping them using repr()." ] else: - max_split = max(len(left_split), len(right_split)) + max_split = min(len(left_split), len(right_split)) + 1 left_ends = left_split[1:max_split:2] right_ends = right_split[1:max_split:2] if left_ends != right_ends: diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 93852db1a10..3570a90c279 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1387,16 +1387,18 @@ def test_diff_different_line_endings(): r"? ++", ] - # More on left: escapes them also. + # More on left. assert callequal("line1\r\nline2\r\nline3\r\n", "line1\nline2", verbose=2) == [ r"'line1\r\nline2\r\nline3\r\n' == 'line1\nline2'", r"NOTE: Strings contain different line-endings. Escaping them using repr().", r"- line1\r\n", r"? --", r"+ line1\n", - r"+ line2", r"- line2\r\n", - r"- line3\r\n", + r"- line3", + r"? ^", + r"+ line2", + r"? ^", r"- ", ] From 5f92be8a997e1ea0f4c04835d4a1bdc9e9f9bfc9 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 19 Feb 2020 22:28:18 +0100 Subject: [PATCH 13/13] fix max_split --- src/_pytest/assertion/util.py | 2 +- testing/test_assertion.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index ef42100546c..5a746da0f80 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -243,7 +243,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: "NOTE: Strings contain non-printable characters. Escaping them using repr()." ] else: - max_split = min(len(left_split), len(right_split)) + 1 + max_split = min(len(left_lines), len(right_lines)) + 1 left_ends = left_split[1:max_split:2] right_ends = right_split[1:max_split:2] if left_ends != right_ends: diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 3570a90c279..d5e28070815 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1394,11 +1394,8 @@ def test_diff_different_line_endings(): r"- line1\r\n", r"? --", r"+ line1\n", - r"- line2\r\n", + r" line2", r"- line3", - r"? ^", - r"+ line2", - r"? ^", r"- ", ]