diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 0c629a2ac45..652c93c5cc7 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -498,21 +498,31 @@ def _compare_eq_cls( def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]: + from wcwidth import wcwidth + index = text.find(term) head = text[:index] tail = text[index + len(term) :] correct_text = head + tail - diff = _diff_text(correct_text, text, verbose) + newdiff = ["%s is contained here:" % saferepr(term, maxsize=42)] - for line in diff: - if line.startswith("Skipping"): - continue - if line.startswith("- "): - continue - if line.startswith("+ "): - newdiff.append(" " + line[2:]) - else: - newdiff.append(line) + if any( + wcwidth(ch) <= 0 + for ch in [ch for lines in [term, correct_text] for ch in lines] + ): + newdiff = [ + "NOTE: Strings contain non-printable characters. Escaping them using repr()." + ] + newdiff + text = repr(text) + indent = " " * (index + 1) + marker = "+" * (len(repr(term)) - 2) + else: + indent = " " * index + marker = "+" * len(term) + newdiff += [ + " " + text, + "? " + indent + marker, + ] return newdiff diff --git a/testing/test_assertion.py b/testing/test_assertion.py index dac0a49ed2b..77d2b9e78d4 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1200,6 +1200,16 @@ def test_reprcompare_notin() -> None: ] +def test_reprcompare_notin_newline() -> None: + assert callop("not in", "foo\n", "aaafoo\nbbb") == [ + r"'foo\n' not in 'aaafoo\nbbb'", + r"NOTE: Strings contain non-printable characters. Escaping them using repr().", + r"'foo\n' is contained here:", + r" 'aaafoo\nbbb'", + r"? +++++", + ] + + def test_reprcompare_whitespaces(): config = mock_config() detail = plugin.pytest_assertrepr_compare(config, "==", "\r\n", "\n")