updates
This commit is contained in:
@@ -1,54 +1,36 @@
|
||||
# mypy: allow-untyped-defs
|
||||
"""Utilities for assertion debugging."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Mapping
|
||||
from collections.abc import Sequence
|
||||
from collections.abc import Set as AbstractSet
|
||||
import os
|
||||
import pprint
|
||||
from typing import AbstractSet
|
||||
from typing import Any
|
||||
from typing import Literal
|
||||
from typing import Protocol
|
||||
from typing import Callable
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from unicodedata import normalize
|
||||
|
||||
from _pytest import outcomes
|
||||
import _pytest._code
|
||||
from _pytest._io.pprint import PrettyPrinter
|
||||
from _pytest import outcomes
|
||||
from _pytest._io.saferepr import _pformat_dispatch
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest._io.saferepr import saferepr_unlimited
|
||||
from _pytest.compat import running_on_ci
|
||||
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
|
||||
_reprcompare: Optional[Callable[[str, object, object], Optional[str]]] = 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
|
||||
_assertion_pass: Optional[Callable[[int, str, str], None]] = None
|
||||
|
||||
# Config object which is assigned during pytest_runtest_protocol.
|
||||
_config: Config | None = None
|
||||
|
||||
|
||||
class _HighlightFunc(Protocol):
|
||||
def __call__(self, source: str, lexer: Literal["diff", "python"] = "python") -> str:
|
||||
"""Apply highlighting to the given source."""
|
||||
|
||||
|
||||
def dummy_highlighter(source: str, lexer: Literal["diff", "python"] = "python") -> str:
|
||||
"""Dummy highlighter that returns the text unprocessed.
|
||||
|
||||
Needed for _notin_text, as the diff gets post-processed to only show the "+" part.
|
||||
"""
|
||||
return source
|
||||
_config: Optional[Config] = None
|
||||
|
||||
|
||||
def format_explanation(explanation: str) -> str:
|
||||
@@ -66,7 +48,7 @@ def format_explanation(explanation: str) -> str:
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def _split_explanation(explanation: str) -> list[str]:
|
||||
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~'.
|
||||
@@ -83,7 +65,7 @@ def _split_explanation(explanation: str) -> list[str]:
|
||||
return lines
|
||||
|
||||
|
||||
def _format_lines(lines: Sequence[str]) -> list[str]:
|
||||
def _format_lines(lines: Sequence[str]) -> List[str]:
|
||||
"""Format the individual lines.
|
||||
|
||||
This will replace the '{', '}' and '~' characters of our mini formatting
|
||||
@@ -131,7 +113,7 @@ def isdict(x: Any) -> bool:
|
||||
|
||||
|
||||
def isset(x: Any) -> bool:
|
||||
return isinstance(x, set | frozenset)
|
||||
return isinstance(x, (set, frozenset))
|
||||
|
||||
|
||||
def isnamedtuple(obj: Any) -> bool:
|
||||
@@ -150,7 +132,7 @@ def isiterable(obj: Any) -> bool:
|
||||
try:
|
||||
iter(obj)
|
||||
return not istext(obj)
|
||||
except Exception:
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
@@ -169,7 +151,7 @@ def has_default_eq(
|
||||
code_filename = obj.__eq__.__code__.co_filename
|
||||
|
||||
if isattrs(obj):
|
||||
return "attrs generated " in code_filename
|
||||
return "attrs generated eq" in code_filename
|
||||
|
||||
return code_filename == "<string>" # data class
|
||||
return True
|
||||
@@ -177,9 +159,9 @@ def has_default_eq(
|
||||
|
||||
def assertrepr_compare(
|
||||
config, op: str, left: Any, right: Any, use_ascii: bool = False
|
||||
) -> list[str] | None:
|
||||
) -> Optional[List[str]]:
|
||||
"""Return specialised explanations for some operators/operands."""
|
||||
verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
|
||||
verbose = config.getoption("verbose")
|
||||
|
||||
# Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier.
|
||||
# See issue #3246.
|
||||
@@ -203,54 +185,34 @@ 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, highlighter, verbose)
|
||||
explanation = _compare_eq_any(left, right, 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:
|
||||
repr_crash = _pytest._code.ExceptionInfo.from_current()._getreprcrash()
|
||||
explanation = [
|
||||
f"(pytest_assertion plugin: representation of details failed: {repr_crash}.",
|
||||
"(pytest_assertion plugin: representation of details failed: {}.".format(
|
||||
_pytest._code.ExceptionInfo.from_current()._getreprcrash()
|
||||
),
|
||||
" Probably an object has a faulty __repr__.)",
|
||||
]
|
||||
|
||||
if not explanation:
|
||||
return None
|
||||
|
||||
if explanation[0] != "":
|
||||
explanation = ["", *explanation]
|
||||
return [summary, *explanation]
|
||||
return [summary] + explanation
|
||||
|
||||
|
||||
def _compare_eq_any(
|
||||
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0
|
||||
) -> list[str]:
|
||||
def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
|
||||
explanation = []
|
||||
if istext(left) and istext(right):
|
||||
explanation = _diff_text(left, right, highlighter, verbose)
|
||||
explanation = _diff_text(left, right, verbose)
|
||||
else:
|
||||
from _pytest.python_api import ApproxBase
|
||||
|
||||
@@ -260,31 +222,29 @@ def _compare_eq_any(
|
||||
other_side = right if isinstance(left, ApproxBase) else left
|
||||
|
||||
explanation = approx_side._repr_compare(other_side)
|
||||
elif type(left) is type(right) and (
|
||||
elif type(left) == 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, highlighter, verbose)
|
||||
explanation = _compare_eq_cls(left, right, verbose)
|
||||
elif issequence(left) and issequence(right):
|
||||
explanation = _compare_eq_sequence(left, right, highlighter, verbose)
|
||||
explanation = _compare_eq_sequence(left, right, verbose)
|
||||
elif isset(left) and isset(right):
|
||||
explanation = _compare_eq_set(left, right, highlighter, verbose)
|
||||
explanation = _compare_eq_set(left, right, verbose)
|
||||
elif isdict(left) and isdict(right):
|
||||
explanation = _compare_eq_dict(left, right, highlighter, verbose)
|
||||
explanation = _compare_eq_dict(left, right, verbose)
|
||||
|
||||
if isiterable(left) and isiterable(right):
|
||||
expl = _compare_eq_iterable(left, right, highlighter, verbose)
|
||||
expl = _compare_eq_iterable(left, right, verbose)
|
||||
explanation.extend(expl)
|
||||
|
||||
return explanation
|
||||
|
||||
|
||||
def _diff_text(
|
||||
left: str, right: str, highlighter: _HighlightFunc, verbose: int = 0
|
||||
) -> list[str]:
|
||||
def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
|
||||
"""Return the explanation for the diff between text.
|
||||
|
||||
Unless --verbose is used this will skip leading and trailing
|
||||
@@ -292,7 +252,7 @@ def _diff_text(
|
||||
"""
|
||||
from difflib import ndiff
|
||||
|
||||
explanation: list[str] = []
|
||||
explanation: List[str] = []
|
||||
|
||||
if verbose < 1:
|
||||
i = 0 # just in case left or right has zero length
|
||||
@@ -302,7 +262,7 @@ def _diff_text(
|
||||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation = [
|
||||
f"Skipping {i} identical leading characters in diff, use -v to show"
|
||||
"Skipping %s identical leading characters in diff, use -v to show" % i
|
||||
]
|
||||
left = left[i:]
|
||||
right = right[i:]
|
||||
@@ -313,8 +273,8 @@ def _diff_text(
|
||||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation += [
|
||||
f"Skipping {i} identical trailing "
|
||||
"characters in diff, use -v to show"
|
||||
"Skipping {} identical trailing "
|
||||
"characters in diff, use -v to show".format(i)
|
||||
]
|
||||
left = left[:-i]
|
||||
right = right[:-i]
|
||||
@@ -325,55 +285,61 @@ def _diff_text(
|
||||
explanation += ["Strings contain only whitespace, escaping them using repr()"]
|
||||
# "right" is the expected base against which we compare "left",
|
||||
# see https://github.com/pytest-dev/pytest/issues/3333
|
||||
explanation.extend(
|
||||
highlighter(
|
||||
"\n".join(
|
||||
line.strip("\n")
|
||||
for line in ndiff(right.splitlines(keepends), left.splitlines(keepends))
|
||||
),
|
||||
lexer="diff",
|
||||
).splitlines()
|
||||
)
|
||||
explanation += [
|
||||
line.strip("\n")
|
||||
for line in ndiff(right.splitlines(keepends), left.splitlines(keepends))
|
||||
]
|
||||
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],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> list[str]:
|
||||
left: Iterable[Any], right: Iterable[Any], 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 = PrettyPrinter().pformat(left).splitlines()
|
||||
right_formatting = PrettyPrinter().pformat(right).splitlines()
|
||||
left_formatting = pprint.pformat(left).splitlines()
|
||||
right_formatting = pprint.pformat(right).splitlines()
|
||||
|
||||
explanation = ["", "Full diff:"]
|
||||
# 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:"]
|
||||
# "right" is the expected base against which we compare "left",
|
||||
# see https://github.com/pytest-dev/pytest/issues/3333
|
||||
explanation.extend(
|
||||
highlighter(
|
||||
"\n".join(
|
||||
line.rstrip()
|
||||
for line in difflib.ndiff(right_formatting, left_formatting)
|
||||
),
|
||||
lexer="diff",
|
||||
).splitlines()
|
||||
line.rstrip() for line in difflib.ndiff(right_formatting, left_formatting)
|
||||
)
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_sequence(
|
||||
left: Sequence[Any],
|
||||
right: Sequence[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> list[str]:
|
||||
left: Sequence[Any], right: Sequence[Any], verbose: int = 0
|
||||
) -> List[str]:
|
||||
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
|
||||
explanation: list[str] = []
|
||||
explanation: List[str] = []
|
||||
len_left = len(left)
|
||||
len_right = len(right)
|
||||
for i in range(min(len_left, len_right)):
|
||||
@@ -393,10 +359,7 @@ def _compare_eq_sequence(
|
||||
left_value = left[i]
|
||||
right_value = right[i]
|
||||
|
||||
explanation.append(
|
||||
f"At index {i} diff:"
|
||||
f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}"
|
||||
)
|
||||
explanation += [f"At index {i} diff: {left_value!r} != {right_value!r}"]
|
||||
break
|
||||
|
||||
if comparing_bytes:
|
||||
@@ -416,134 +379,74 @@ def _compare_eq_sequence(
|
||||
extra = saferepr(right[len_left])
|
||||
|
||||
if len_diff == 1:
|
||||
explanation += [
|
||||
f"{dir_with_more} contains one more item: {highlighter(extra)}"
|
||||
]
|
||||
explanation += [f"{dir_with_more} contains one more item: {extra}"]
|
||||
else:
|
||||
explanation += [
|
||||
f"{dir_with_more} contains {len_diff} more items, first extra item: {highlighter(extra)}"
|
||||
"%s contains %d more items, first extra item: %s"
|
||||
% (dir_with_more, len_diff, extra)
|
||||
]
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_set(
|
||||
left: AbstractSet[Any],
|
||||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> list[str]:
|
||||
left: AbstractSet[Any], right: AbstractSet[Any], 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 = set1 - set2
|
||||
if diff:
|
||||
explanation.append(f"Extra items in the {posn} set:")
|
||||
for item in diff:
|
||||
explanation.append(highlighter(saferepr(item)))
|
||||
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))
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_dict(
|
||||
left: Mapping[Any, Any],
|
||||
right: Mapping[Any, Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> list[str]:
|
||||
explanation: list[str] = []
|
||||
left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0
|
||||
) -> List[str]:
|
||||
explanation: List[str] = []
|
||||
set_left = set(left)
|
||||
set_right = set(right)
|
||||
common = set_left.intersection(set_right)
|
||||
same = {k: left[k] for k in common if left[k] == right[k]}
|
||||
if same and verbose < 2:
|
||||
explanation += [f"Omitting {len(same)} identical items, use -vv to show"]
|
||||
explanation += ["Omitting %s identical items, use -vv to show" % len(same)]
|
||||
elif same:
|
||||
explanation += ["Common items:"]
|
||||
explanation += highlighter(pprint.pformat(same)).splitlines()
|
||||
explanation += 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 += [
|
||||
highlighter(saferepr({k: left[k]}))
|
||||
+ " != "
|
||||
+ highlighter(saferepr({k: right[k]}))
|
||||
]
|
||||
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
|
||||
extra_left = set_left - set_right
|
||||
len_extra_left = len(extra_left)
|
||||
if len_extra_left:
|
||||
explanation.append(
|
||||
f"Left contains {len_extra_left} more item{'' if len_extra_left == 1 else 's'}:"
|
||||
"Left contains %d more item%s:"
|
||||
% (len_extra_left, "" if len_extra_left == 1 else "s")
|
||||
)
|
||||
explanation.extend(
|
||||
highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines()
|
||||
pprint.pformat({k: left[k] for k in extra_left}).splitlines()
|
||||
)
|
||||
extra_right = set_right - set_left
|
||||
len_extra_right = len(extra_right)
|
||||
if len_extra_right:
|
||||
explanation.append(
|
||||
f"Right contains {len_extra_right} more item{'' if len_extra_right == 1 else 's'}:"
|
||||
"Right contains %d more item%s:"
|
||||
% (len_extra_right, "" if len_extra_right == 1 else "s")
|
||||
)
|
||||
explanation.extend(
|
||||
highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines()
|
||||
pprint.pformat({k: right[k] for k in extra_right}).splitlines()
|
||||
)
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_cls(
|
||||
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int
|
||||
) -> list[str]:
|
||||
def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
|
||||
if not has_default_eq(left):
|
||||
return []
|
||||
if isdatacls(left):
|
||||
@@ -572,37 +475,35 @@ def _compare_eq_cls(
|
||||
if same or diff:
|
||||
explanation += [""]
|
||||
if same and verbose < 2:
|
||||
explanation.append(f"Omitting {len(same)} identical items, use -vv to show")
|
||||
explanation.append("Omitting %s identical items, use -vv to show" % len(same))
|
||||
elif same:
|
||||
explanation += ["Matching attributes:"]
|
||||
explanation += highlighter(pprint.pformat(same)).splitlines()
|
||||
explanation += pprint.pformat(same).splitlines()
|
||||
if diff:
|
||||
explanation += ["Differing attributes:"]
|
||||
explanation += highlighter(pprint.pformat(diff)).splitlines()
|
||||
explanation += pprint.pformat(diff).splitlines()
|
||||
for field in diff:
|
||||
field_left = getattr(left, field)
|
||||
field_right = getattr(right, field)
|
||||
explanation += [
|
||||
"",
|
||||
f"Drill down into differing attribute {field}:",
|
||||
f"{indent}{field}: {highlighter(repr(field_left))} != {highlighter(repr(field_right))}",
|
||||
"Drill down into differing attribute %s:" % field,
|
||||
("%s%s: %r != %r") % (indent, field, field_left, field_right),
|
||||
]
|
||||
explanation += [
|
||||
indent + line
|
||||
for line in _compare_eq_any(
|
||||
field_left, field_right, highlighter, verbose
|
||||
)
|
||||
for line in _compare_eq_any(field_left, field_right, verbose)
|
||||
]
|
||||
return explanation
|
||||
|
||||
|
||||
def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]:
|
||||
def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]:
|
||||
index = text.find(term)
|
||||
head = text[:index]
|
||||
tail = text[index + len(term) :]
|
||||
correct_text = head + tail
|
||||
diff = _diff_text(text, correct_text, dummy_highlighter, verbose)
|
||||
newdiff = [f"{saferepr(term, maxsize=42)} is contained here:"]
|
||||
diff = _diff_text(text, correct_text, verbose)
|
||||
newdiff = ["%s is contained here:" % saferepr(term, maxsize=42)]
|
||||
for line in diff:
|
||||
if line.startswith("Skipping"):
|
||||
continue
|
||||
@@ -613,3 +514,9 @@ def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]:
|
||||
else:
|
||||
newdiff.append(line)
|
||||
return newdiff
|
||||
|
||||
|
||||
def running_on_ci() -> bool:
|
||||
"""Check if we're currently running on a CI system."""
|
||||
env_vars = ["CI", "BUILD_NUMBER"]
|
||||
return any(var in os.environ for var in env_vars)
|
||||
|
||||
Reference in New Issue
Block a user