aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest/py3/_pytest/assertion/util.py
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2025-05-05 12:31:52 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2025-05-05 12:41:33 +0300
commit6ff49ec58061f642c3a2f83c61eba12820787dfc (patch)
treec733ec9bdb15ed280080d31dea8725bfec717acd /contrib/python/pytest/py3/_pytest/assertion/util.py
parenteefca8305c6a545cc6b16dca3eb0d91dcef2adcd (diff)
downloadydb-6ff49ec58061f642c3a2f83c61eba12820787dfc.tar.gz
Intermediate changes
commit_hash:8b3bb826b17db8329ed1221f545c0645f12c552d
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/assertion/util.py')
-rw-r--r--contrib/python/pytest/py3/_pytest/assertion/util.py221
1 files changed, 154 insertions, 67 deletions
diff --git a/contrib/python/pytest/py3/_pytest/assertion/util.py b/contrib/python/pytest/py3/_pytest/assertion/util.py
index 39ca5403e04..a7074115d65 100644
--- a/contrib/python/pytest/py3/_pytest/assertion/util.py
+++ b/contrib/python/pytest/py3/_pytest/assertion/util.py
@@ -1,4 +1,5 @@
"""Utilities for assertion debugging."""
+
import collections.abc
import os
import pprint
@@ -7,18 +8,21 @@ from typing import Any
from typing import Callable
from typing import Iterable
from typing import List
+from typing import Literal
from typing import Mapping
from typing import Optional
+from typing import Protocol
from typing import Sequence
from unicodedata import normalize
-import _pytest._code
from _pytest import outcomes
-from _pytest._io.saferepr import _pformat_dispatch
+import _pytest._code
+from _pytest._io.pprint import PrettyPrinter
from _pytest._io.saferepr import saferepr
from _pytest._io.saferepr import saferepr_unlimited
from _pytest.config import Config
+
# The _reprcompare attribute on the util module is used by the new assertion
# interpretation code and assertion rewriter to detect this plugin was
# loaded and in turn call the hooks defined here as part of the
@@ -33,6 +37,11 @@ _assertion_pass: Optional[Callable[[int, str, str], None]] = None
_config: Optional[Config] = None
+class _HighlightFunc(Protocol):
+ def __call__(self, source: str, lexer: Literal["diff", "python"] = "python") -> str:
+ """Apply highlighting to the given source."""
+
+
def format_explanation(explanation: str) -> str:
r"""Format an explanation.
@@ -161,7 +170,7 @@ def assertrepr_compare(
config, op: str, left: Any, right: Any, use_ascii: bool = False
) -> Optional[List[str]]:
"""Return specialised explanations for some operators/operands."""
- verbose = config.getoption("verbose")
+ verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
# Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier.
# See issue #3246.
@@ -185,14 +194,31 @@ def assertrepr_compare(
right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii)
summary = f"{left_repr} {op} {right_repr}"
+ highlighter = config.get_terminal_writer()._highlight
explanation = None
try:
if op == "==":
- explanation = _compare_eq_any(left, right, verbose)
+ explanation = _compare_eq_any(left, right, highlighter, verbose)
elif op == "not in":
if istext(left) and istext(right):
explanation = _notin_text(left, right, verbose)
+ elif op == "!=":
+ if isset(left) and isset(right):
+ explanation = ["Both sets are equal"]
+ elif op == ">=":
+ if isset(left) and isset(right):
+ explanation = _compare_gte_set(left, right, highlighter, verbose)
+ elif op == "<=":
+ if isset(left) and isset(right):
+ explanation = _compare_lte_set(left, right, highlighter, verbose)
+ elif op == ">":
+ if isset(left) and isset(right):
+ explanation = _compare_gt_set(left, right, highlighter, verbose)
+ elif op == "<":
+ if isset(left) and isset(right):
+ explanation = _compare_lt_set(left, right, highlighter, verbose)
+
except outcomes.Exit:
raise
except Exception:
@@ -206,10 +232,14 @@ def assertrepr_compare(
if not explanation:
return None
+ if explanation[0] != "":
+ explanation = [""] + explanation
return [summary] + explanation
-def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
+def _compare_eq_any(
+ left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0
+) -> List[str]:
explanation = []
if istext(left) and istext(right):
explanation = _diff_text(left, right, verbose)
@@ -222,23 +252,23 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
other_side = right if isinstance(left, ApproxBase) else left
explanation = approx_side._repr_compare(other_side)
- elif type(left) == type(right) and (
+ elif type(left) is type(right) and (
isdatacls(left) or isattrs(left) or isnamedtuple(left)
):
# Note: unlike dataclasses/attrs, namedtuples compare only the
# field values, not the type or field names. But this branch
# intentionally only handles the same-type case, which was often
# used in older code bases before dataclasses/attrs were available.
- explanation = _compare_eq_cls(left, right, verbose)
+ explanation = _compare_eq_cls(left, right, highlighter, verbose)
elif issequence(left) and issequence(right):
- explanation = _compare_eq_sequence(left, right, verbose)
+ explanation = _compare_eq_sequence(left, right, highlighter, verbose)
elif isset(left) and isset(right):
- explanation = _compare_eq_set(left, right, verbose)
+ explanation = _compare_eq_set(left, right, highlighter, verbose)
elif isdict(left) and isdict(right):
- explanation = _compare_eq_dict(left, right, verbose)
+ explanation = _compare_eq_dict(left, right, highlighter, verbose)
if isiterable(left) and isiterable(right):
- expl = _compare_eq_iterable(left, right, verbose)
+ expl = _compare_eq_iterable(left, right, highlighter, verbose)
explanation.extend(expl)
return explanation
@@ -273,8 +303,8 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
if i > 42:
i -= 10 # Provide some context
explanation += [
- "Skipping {} identical trailing "
- "characters in diff, use -v to show".format(i)
+ f"Skipping {i} identical trailing "
+ "characters in diff, use -v to show"
]
left = left[:-i]
right = right[:-i]
@@ -292,51 +322,40 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
return explanation
-def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
- """Move opening/closing parenthesis/bracket to own lines."""
- opening = lines[0][:1]
- if opening in ["(", "[", "{"]:
- lines[0] = " " + lines[0][1:]
- lines[:] = [opening] + lines
- closing = lines[-1][-1:]
- if closing in [")", "]", "}"]:
- lines[-1] = lines[-1][:-1] + ","
- lines[:] = lines + [closing]
-
-
def _compare_eq_iterable(
- left: Iterable[Any], right: Iterable[Any], verbose: int = 0
+ left: Iterable[Any],
+ right: Iterable[Any],
+ highligher: _HighlightFunc,
+ verbose: int = 0,
) -> List[str]:
if verbose <= 0 and not running_on_ci():
return ["Use -v to get more diff"]
# dynamic import to speedup pytest
import difflib
- left_formatting = pprint.pformat(left).splitlines()
- right_formatting = pprint.pformat(right).splitlines()
+ left_formatting = PrettyPrinter().pformat(left).splitlines()
+ right_formatting = PrettyPrinter().pformat(right).splitlines()
- # Re-format for different output lengths.
- lines_left = len(left_formatting)
- lines_right = len(right_formatting)
- if lines_left != lines_right:
- left_formatting = _pformat_dispatch(left).splitlines()
- right_formatting = _pformat_dispatch(right).splitlines()
-
- if lines_left > 1 or lines_right > 1:
- _surrounding_parens_on_own_lines(left_formatting)
- _surrounding_parens_on_own_lines(right_formatting)
-
- explanation = ["Full diff:"]
+ explanation = ["", "Full diff:"]
# "right" is the expected base against which we compare "left",
# see https://github.com/pytest-dev/pytest/issues/3333
explanation.extend(
- line.rstrip() for line in difflib.ndiff(right_formatting, left_formatting)
+ highligher(
+ "\n".join(
+ line.rstrip()
+ for line in difflib.ndiff(right_formatting, left_formatting)
+ ),
+ lexer="diff",
+ ).splitlines()
)
return explanation
def _compare_eq_sequence(
- left: Sequence[Any], right: Sequence[Any], verbose: int = 0
+ left: Sequence[Any],
+ right: Sequence[Any],
+ highlighter: _HighlightFunc,
+ verbose: int = 0,
) -> List[str]:
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
explanation: List[str] = []
@@ -359,7 +378,10 @@ def _compare_eq_sequence(
left_value = left[i]
right_value = right[i]
- explanation += [f"At index {i} diff: {left_value!r} != {right_value!r}"]
+ explanation.append(
+ f"At index {i} diff:"
+ f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}"
+ )
break
if comparing_bytes:
@@ -379,34 +401,91 @@ def _compare_eq_sequence(
extra = saferepr(right[len_left])
if len_diff == 1:
- explanation += [f"{dir_with_more} contains one more item: {extra}"]
+ explanation += [
+ f"{dir_with_more} contains one more item: {highlighter(extra)}"
+ ]
else:
explanation += [
"%s contains %d more items, first extra item: %s"
- % (dir_with_more, len_diff, extra)
+ % (dir_with_more, len_diff, highlighter(extra))
]
return explanation
def _compare_eq_set(
- left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0
+ left: AbstractSet[Any],
+ right: AbstractSet[Any],
+ highlighter: _HighlightFunc,
+ verbose: int = 0,
+) -> List[str]:
+ explanation = []
+ explanation.extend(_set_one_sided_diff("left", left, right, highlighter))
+ explanation.extend(_set_one_sided_diff("right", right, left, highlighter))
+ return explanation
+
+
+def _compare_gt_set(
+ left: AbstractSet[Any],
+ right: AbstractSet[Any],
+ highlighter: _HighlightFunc,
+ verbose: int = 0,
+) -> List[str]:
+ explanation = _compare_gte_set(left, right, highlighter)
+ if not explanation:
+ return ["Both sets are equal"]
+ return explanation
+
+
+def _compare_lt_set(
+ left: AbstractSet[Any],
+ right: AbstractSet[Any],
+ highlighter: _HighlightFunc,
+ verbose: int = 0,
+) -> List[str]:
+ explanation = _compare_lte_set(left, right, highlighter)
+ if not explanation:
+ return ["Both sets are equal"]
+ return explanation
+
+
+def _compare_gte_set(
+ left: AbstractSet[Any],
+ right: AbstractSet[Any],
+ highlighter: _HighlightFunc,
+ verbose: int = 0,
+) -> List[str]:
+ return _set_one_sided_diff("right", right, left, highlighter)
+
+
+def _compare_lte_set(
+ left: AbstractSet[Any],
+ right: AbstractSet[Any],
+ highlighter: _HighlightFunc,
+ verbose: int = 0,
+) -> List[str]:
+ return _set_one_sided_diff("left", left, right, highlighter)
+
+
+def _set_one_sided_diff(
+ posn: str,
+ set1: AbstractSet[Any],
+ set2: AbstractSet[Any],
+ highlighter: _HighlightFunc,
) -> List[str]:
explanation = []
- diff_left = left - right
- diff_right = right - left
- if diff_left:
- explanation.append("Extra items in the left set:")
- for item in diff_left:
- explanation.append(saferepr(item))
- if diff_right:
- explanation.append("Extra items in the right set:")
- for item in diff_right:
- explanation.append(saferepr(item))
+ diff = set1 - set2
+ if diff:
+ explanation.append(f"Extra items in the {posn} set:")
+ for item in diff:
+ explanation.append(highlighter(saferepr(item)))
return explanation
def _compare_eq_dict(
- left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0
+ left: Mapping[Any, Any],
+ right: Mapping[Any, Any],
+ highlighter: _HighlightFunc,
+ verbose: int = 0,
) -> List[str]:
explanation: List[str] = []
set_left = set(left)
@@ -417,12 +496,16 @@ def _compare_eq_dict(
explanation += ["Omitting %s identical items, use -vv to show" % len(same)]
elif same:
explanation += ["Common items:"]
- explanation += pprint.pformat(same).splitlines()
+ explanation += highlighter(pprint.pformat(same)).splitlines()
diff = {k for k in common if left[k] != right[k]}
if diff:
explanation += ["Differing items:"]
for k in diff:
- explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
+ explanation += [
+ highlighter(saferepr({k: left[k]}))
+ + " != "
+ + highlighter(saferepr({k: right[k]}))
+ ]
extra_left = set_left - set_right
len_extra_left = len(extra_left)
if len_extra_left:
@@ -431,7 +514,7 @@ def _compare_eq_dict(
% (len_extra_left, "" if len_extra_left == 1 else "s")
)
explanation.extend(
- pprint.pformat({k: left[k] for k in extra_left}).splitlines()
+ highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines()
)
extra_right = set_right - set_left
len_extra_right = len(extra_right)
@@ -441,12 +524,14 @@ def _compare_eq_dict(
% (len_extra_right, "" if len_extra_right == 1 else "s")
)
explanation.extend(
- pprint.pformat({k: right[k] for k in extra_right}).splitlines()
+ highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines()
)
return explanation
-def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
+def _compare_eq_cls(
+ left: Any, right: Any, highlighter: _HighlightFunc, verbose: int
+) -> List[str]:
if not has_default_eq(left):
return []
if isdatacls(left):
@@ -478,21 +563,23 @@ def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
explanation.append("Omitting %s identical items, use -vv to show" % len(same))
elif same:
explanation += ["Matching attributes:"]
- explanation += pprint.pformat(same).splitlines()
+ explanation += highlighter(pprint.pformat(same)).splitlines()
if diff:
explanation += ["Differing attributes:"]
- explanation += pprint.pformat(diff).splitlines()
+ explanation += highlighter(pprint.pformat(diff)).splitlines()
for field in diff:
field_left = getattr(left, field)
field_right = getattr(right, field)
explanation += [
"",
- "Drill down into differing attribute %s:" % field,
- ("%s%s: %r != %r") % (indent, field, field_left, field_right),
+ f"Drill down into differing attribute {field}:",
+ f"{indent}{field}: {highlighter(repr(field_left))} != {highlighter(repr(field_right))}",
]
explanation += [
indent + line
- for line in _compare_eq_any(field_left, field_right, verbose)
+ for line in _compare_eq_any(
+ field_left, field_right, highlighter, verbose
+ )
]
return explanation