/
niceSOFT
/
python3-pytest
Обзор
Документация
Войти
/
niceSOFT
/
python3-pytest
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/_pytest/assertion/util.py
191 строка
7 KB
Pierre Sassoulas
Register assertion_text_diff_style with its Literal alias
01 авг 2026, 08:22
01 авг 2026, 08:22
184c168
Код
Авторство
О чём код?
# mypy: allow-untyped-defs """Utilities for assertion debugging.""" from __future__ import annotations from collections.abc import Callable from collections.abc import Iterator from collections.abc import Sequence from unicodedata import normalize from _pytest import outcomes import _pytest._code from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr_unlimited from _pytest.assertion._compare_any import _compare_eq_any from _pytest.assertion._compare_set import SET_COMPARISON_FUNCTIONS from _pytest.assertion._guards import isset from _pytest.assertion._guards import istext from _pytest.assertion._typing import _AssertionTextDiffStyle from _pytest.assertion._typing import _HighlightFunc from _pytest.assertion._typing import NO_TRUNCATION_BUDGET from _pytest.assertion._typing import TruncationBudget from _pytest.assertion.compare_text import _notin_text from _pytest.assertion.highlight import dummy_highlighter as dummy_highlighter 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 # DebugInterpreter. _reprcompare: Callable[[str, object, object], str | None] | None = None # Works similarly as _reprcompare attribute. Is populated with the hook call # when pytest_runtest_setup is called. _assertion_pass: Callable[[int, str, str], None] | None = None # Config object which is assigned during pytest_runtest_protocol. _config: Config | None = None def format_explanation(explanation: str) -> str: r"""Format an explanation. Normally all embedded newlines are escaped, however there are three exceptions: \n{, \n} and \n~. The first two are intended cover nested explanations, see function and attribute explanations for examples (.visit_Call(), visit_Attribute()). The last one is for when one explanation needs to span multiple lines, e.g. when displaying diffs. """ lines = _split_explanation(explanation) result = _format_lines(lines) return "\n".join(result) def _split_explanation(explanation: str) -> list[str]: r"""Return a list of individual lines in the explanation. This will return a list of lines split on '\n{', '\n}' and '\n~'. Any other newlines will be escaped and appear in the line as the literal '\n' characters. """ raw_lines = (explanation or "").split("\n") lines = [raw_lines[0]] for values in raw_lines[1:]: if values and values[0] in ["{", "}", "~", ">"]: lines.append(values) else: lines[-1] += "\\n" + values return lines def _format_lines(lines: Sequence[str]) -> list[str]: """Format the individual lines. This will replace the '{', '}' and '~' characters of our mini formatting language with the proper 'where ...', 'and ...' and ' + ...' text, taking care of indentation along the way. Return a list of formatted lines. """ result = list(lines[:1]) stack = [0] stackcnt = [0] for line in lines[1:]: if line.startswith("{"): if stackcnt[-1]: s = "and " else: s = "where " stack.append(len(result)) stackcnt[-1] += 1 stackcnt.append(0) result.append(" +" + " " * (len(stack) - 1) + s + line[1:]) elif line.startswith("}"): stack.pop() stackcnt.pop() result[stack[-1]] += line[1:] else: assert line[0] in ["~", ">"] stack[-1] += 1 indent = len(stack) if line.startswith("~") else len(stack) - 1 result.append(" " * indent + line[1:]) assert len(stack) == 1 return result def assertrepr_compare( op: str, left: object, right: object, *, verbose: int, highlighter: _HighlightFunc, assertion_text_diff_style: _AssertionTextDiffStyle, truncation_budget: TruncationBudget = NO_TRUNCATION_BUDGET, ) -> Iterator[str]: """Yield specialised explanations for some operators/operands. The first line yielded is always the summary (``left op right``); subsequent lines are the detailed explanation. Yields nothing when no specialised explanation applies, which lets consumers map an empty iterator to "no explanation" without materialising anything. The iterator is lazy on purpose: a streaming consumer can stop pulling lines as soon as it has enough to show, so an enormous diff doesn't have to be built in full just to be thrown away. """ # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. # See issue #3246. use_ascii = ( isinstance(left, str) and isinstance(right, str) and normalize("NFD", left) == normalize("NFD", right) ) if verbose > 1: left_repr = saferepr_unlimited(left, use_ascii=use_ascii) right_repr = saferepr_unlimited(right, use_ascii=use_ascii) else: # XXX: "15 chars indentation" is wrong # ("E AssertionError: assert "); should use term width. maxsize = ( 80 - 15 - len(op) - 2 ) // 2 # 15 chars indentation, 1 space around op left_repr = saferepr(left, maxsize=maxsize, use_ascii=use_ascii) right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii) summary = f"{left_repr} {op} {right_repr}" try: if op == "==": source = _compare_eq_any( left, right, highlighter, verbose, assertion_text_diff_style, truncation_budget, ) elif op == "not in" and istext(left) and istext(right): source = _notin_text(left, right, verbose, truncation_budget) elif op in {"!=", ">=", "<=", ">", "<"} and isset(left) and isset(right): source = SET_COMPARISON_FUNCTIONS[op](left, right, highlighter, verbose) else: source = iter(()) # Only yield the summary if there is a detailed explanation. # Make sure there's a separating empty line after the summary. summary_yielded = False for line in source: if not summary_yielded: yield summary if line != "": yield "" summary_yielded = True yield line except outcomes.Exit: raise except Exception: repr_crash = _pytest._code.ExceptionInfo.from_current()._getreprcrash() if not summary_yielded: yield summary yield "" summary_yielded = True yield ( f"(pytest_assertion plugin: representation of details failed: {repr_crash}." ) yield " Probably an object has a faulty __repr__.)"