diff --git a/_pytest/assertion/util.py b/_pytest/assertion/util.py index 06d60a6fc..1df32fa9e 100644 --- a/_pytest/assertion/util.py +++ b/_pytest/assertion/util.py @@ -171,10 +171,22 @@ def _diff_text(left, right, verbose=False): """ from difflib import ndiff explanation = [] + + def to_unicode_text(binary_text): + """ + This ensures that the internal string is always valid unicode, converting any bytes safely to valid unicode. + This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape + newlines and carriage returns (#429). + """ + r = six.text_type(repr(binary_text)[1:-1]) + r = r.replace(r'\n', '\n') + r = r.replace(r'\r', '\r') + return r + if isinstance(left, six.binary_type): - left = u(repr(left)[1:-1]).replace(r'\n', '\n') + left = to_unicode_text(left) if isinstance(right, six.binary_type): - right = u(repr(right)[1:-1]).replace(r'\n', '\n') + right = to_unicode_text(right) if not verbose: i = 0 # just in case left or right has zero length for i in range(min(len(left), len(right))): @@ -197,6 +209,10 @@ def _diff_text(left, right, verbose=False): left = left[:-i] right = right[:-i] keepends = True + if left.isspace() or right.isspace(): + left = repr(str(left)) + right = repr(str(right)) + explanation += [u'Strings contain only whitespace, escaping them using repr()'] explanation += [line.strip('\n') for line in ndiff(left.splitlines(keepends), right.splitlines(keepends))] diff --git a/changelog/3443.bugfix.rst b/changelog/3443.bugfix.rst new file mode 100644 index 000000000..e8bdcdf1d --- /dev/null +++ b/changelog/3443.bugfix.rst @@ -0,0 +1 @@ +When showing diffs of failed assertions where the contents contain only whitespace, escape them using ``repr()`` first to make it easy to spot the differences. diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 328fe7fa9..341090580 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -746,6 +746,18 @@ def test_reprcompare_notin(mock_config): assert detail == ["'foo' is contained here:", ' aaafoobbb', '? +++'] +def test_reprcompare_whitespaces(mock_config): + detail = plugin.pytest_assertrepr_compare( + mock_config, '==', '\r\n', '\n') + assert detail == [ + r"'\r\n' == '\n'", + r"Strings contain only whitespace, escaping them using repr()", + r"- '\r\n'", + r"? --", + r"+ '\n'", + ] + + def test_pytest_assertrepr_compare_integration(testdir): testdir.makepyfile(""" def test_hello():