Add syntactic highlighting to the error explanations

This updates the various error reporting to highlight python code when
displayed, to increase readability and make it easier to understand
This commit is contained in:
Benjamin Schubert 2023-11-27 12:38:43 +00:00
parent c5da533c79
commit 35f36da865
3 changed files with 66 additions and 35 deletions

View File

@ -1,3 +1,5 @@
Improved very verbose diff output to color it as a diff instead of only red. Improved very verbose diff output to color it as a diff instead of only red.
Improved the error reporting to better separate each section. Improved the error reporting to better separate each section.
Improved the error reporting to highlight python code as python code when pygments is available

View File

@ -195,8 +195,9 @@ def assertrepr_compare(
explanation = None explanation = None
try: try:
writer = config.get_terminal_writer()
if op == "==": if op == "==":
writer = config.get_terminal_writer()
explanation = _compare_eq_any(left, right, writer._highlight, verbose) explanation = _compare_eq_any(left, right, writer._highlight, verbose)
elif op == "not in": elif op == "not in":
if istext(left) and istext(right): if istext(left) and istext(right):
@ -206,16 +207,16 @@ def assertrepr_compare(
explanation = ["Both sets are equal"] explanation = ["Both sets are equal"]
elif op == ">=": elif op == ">=":
if isset(left) and isset(right): if isset(left) and isset(right):
explanation = _compare_gte_set(left, right, verbose) explanation = _compare_gte_set(left, right, writer._highlight)
elif op == "<=": elif op == "<=":
if isset(left) and isset(right): if isset(left) and isset(right):
explanation = _compare_lte_set(left, right, verbose) explanation = _compare_lte_set(left, right, writer._highlight)
elif op == ">": elif op == ">":
if isset(left) and isset(right): if isset(left) and isset(right):
explanation = _compare_gt_set(left, right, verbose) explanation = _compare_gt_set(left, right, writer._highlight)
elif op == "<": elif op == "<":
if isset(left) and isset(right): if isset(left) and isset(right):
explanation = _compare_lt_set(left, right, verbose) explanation = _compare_lt_set(left, right, writer._highlight)
except outcomes.Exit: except outcomes.Exit:
raise raise
@ -259,11 +260,11 @@ def _compare_eq_any(
# used in older code bases before dataclasses/attrs were available. # used in older code bases before dataclasses/attrs were available.
explanation = _compare_eq_cls(left, right, highlighter, verbose) explanation = _compare_eq_cls(left, right, highlighter, verbose)
elif issequence(left) and issequence(right): 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): elif isset(left) and isset(right):
explanation = _compare_eq_set(left, right, verbose) explanation = _compare_eq_set(left, right, highlighter)
elif isdict(left) and isdict(right): 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): if isiterable(left) and isiterable(right):
expl = _compare_eq_iterable(left, right, highlighter, verbose) expl = _compare_eq_iterable(left, right, highlighter, verbose)
@ -350,7 +351,10 @@ def _compare_eq_iterable(
def _compare_eq_sequence( 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]: ) -> List[str]:
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
explanation: List[str] = [] explanation: List[str] = []
@ -373,7 +377,10 @@ def _compare_eq_sequence(
left_value = left[i] left_value = left[i]
right_value = right[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)).strip()} != {highlighter(repr(right_value)).strip()}"
)
break break
if comparing_bytes: if comparing_bytes:
@ -393,68 +400,82 @@ def _compare_eq_sequence(
extra = saferepr(right[len_left]) extra = saferepr(right[len_left])
if len_diff == 1: 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).strip()}"
]
else: else:
explanation += [ explanation += [
"%s contains %d more items, first extra item: %s" "%s contains %d more items, first extra item: %s"
% (dir_with_more, len_diff, extra) % (dir_with_more, len_diff, highlighter(extra).strip())
] ]
return explanation return explanation
def _compare_eq_set( def _compare_eq_set(
left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 left: AbstractSet[Any],
right: AbstractSet[Any],
highlighter: _HighlightFunc,
) -> List[str]: ) -> List[str]:
explanation = [] explanation = []
explanation.extend(_set_one_sided_diff("left", left, right)) explanation.extend(_set_one_sided_diff("left", left, right, highlighter))
explanation.extend(_set_one_sided_diff("right", right, left)) explanation.extend(_set_one_sided_diff("right", right, left, highlighter))
return explanation return explanation
def _compare_gt_set( def _compare_gt_set(
left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 left: AbstractSet[Any],
right: AbstractSet[Any],
highlighter: _HighlightFunc,
) -> List[str]: ) -> List[str]:
explanation = _compare_gte_set(left, right, verbose) explanation = _compare_gte_set(left, right, highlighter)
if not explanation: if not explanation:
return ["Both sets are equal"] return ["Both sets are equal"]
return explanation return explanation
def _compare_lt_set( def _compare_lt_set(
left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 left: AbstractSet[Any], right: AbstractSet[Any], highlighter: _HighlightFunc
) -> List[str]: ) -> List[str]:
explanation = _compare_lte_set(left, right, verbose) explanation = _compare_lte_set(left, right, highlighter)
if not explanation: if not explanation:
return ["Both sets are equal"] return ["Both sets are equal"]
return explanation return explanation
def _compare_gte_set( def _compare_gte_set(
left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 left: AbstractSet[Any],
right: AbstractSet[Any],
highlighter: _HighlightFunc,
) -> List[str]: ) -> List[str]:
return _set_one_sided_diff("right", right, left) return _set_one_sided_diff("right", right, left, highlighter)
def _compare_lte_set( def _compare_lte_set(
left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 left: AbstractSet[Any], right: AbstractSet[Any], highlighter: _HighlightFunc
) -> List[str]: ) -> List[str]:
return _set_one_sided_diff("left", left, right) return _set_one_sided_diff("left", left, right, highlighter)
def _set_one_sided_diff( def _set_one_sided_diff(
posn: str, set1: AbstractSet[Any], set2: AbstractSet[Any] posn: str,
set1: AbstractSet[Any],
set2: AbstractSet[Any],
highlighter: _HighlightFunc,
) -> List[str]: ) -> List[str]:
explanation = [] explanation = []
diff = set1 - set2 diff = set1 - set2
if diff: if diff:
explanation.append(f"Extra items in the {posn} set:") explanation.append(f"Extra items in the {posn} set:")
for item in diff: for item in diff:
explanation.append(saferepr(item)) explanation.append(highlighter(saferepr(item)).strip())
return explanation return explanation
def _compare_eq_dict( 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]: ) -> List[str]:
explanation: List[str] = [] explanation: List[str] = []
set_left = set(left) set_left = set(left)
@ -465,12 +486,16 @@ def _compare_eq_dict(
explanation += ["Omitting %s identical items, use -vv to show" % len(same)] explanation += ["Omitting %s identical items, use -vv to show" % len(same)]
elif same: elif same:
explanation += ["Common items:"] explanation += ["Common items:"]
explanation += pprint.pformat(same).splitlines() explanation += highlighter(pprint.pformat(same)).strip().splitlines()
diff = {k for k in common if left[k] != right[k]} diff = {k for k in common if left[k] != right[k]}
if diff: if diff:
explanation += ["Differing items:"] explanation += ["Differing items:"]
for k in diff: for k in diff:
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})] explanation += [
highlighter(saferepr({k: left[k]})).strip()
+ " != "
+ highlighter(saferepr({k: right[k]})).strip()
]
extra_left = set_left - set_right extra_left = set_left - set_right
len_extra_left = len(extra_left) len_extra_left = len(extra_left)
if len_extra_left: if len_extra_left:
@ -479,7 +504,9 @@ def _compare_eq_dict(
% (len_extra_left, "" if len_extra_left == 1 else "s") % (len_extra_left, "" if len_extra_left == 1 else "s")
) )
explanation.extend( explanation.extend(
pprint.pformat({k: left[k] for k in extra_left}).splitlines() highlighter(pprint.pformat({k: left[k] for k in extra_left}))
.strip()
.splitlines()
) )
extra_right = set_right - set_left extra_right = set_right - set_left
len_extra_right = len(extra_right) len_extra_right = len(extra_right)
@ -489,7 +516,9 @@ def _compare_eq_dict(
% (len_extra_right, "" if len_extra_right == 1 else "s") % (len_extra_right, "" if len_extra_right == 1 else "s")
) )
explanation.extend( explanation.extend(
pprint.pformat({k: right[k] for k in extra_right}).splitlines() highlighter(pprint.pformat({k: right[k] for k in extra_right}))
.strip()
.splitlines()
) )
return explanation return explanation
@ -528,17 +557,17 @@ def _compare_eq_cls(
explanation.append("Omitting %s identical items, use -vv to show" % len(same)) explanation.append("Omitting %s identical items, use -vv to show" % len(same))
elif same: elif same:
explanation += ["Matching attributes:"] explanation += ["Matching attributes:"]
explanation += pprint.pformat(same).splitlines() explanation += highlighter(pprint.pformat(same)).strip().splitlines()
if diff: if diff:
explanation += ["Differing attributes:"] explanation += ["Differing attributes:"]
explanation += pprint.pformat(diff).splitlines() explanation += highlighter(pprint.pformat(diff)).strip().splitlines()
for field in diff: for field in diff:
field_left = getattr(left, field) field_left = getattr(left, field)
field_right = getattr(right, field) field_right = getattr(right, field)
explanation += [ explanation += [
"", "",
"Drill down into differing attribute %s:" % field, f"Drill down into differing attribute {field}:",
("%s%s: %r != %r") % (indent, field, field_left, field_right), f"{indent}{field}: {highlighter(repr(field_left)).strip()} != {highlighter(repr(field_right)).strip()}",
] ]
explanation += [ explanation += [
indent + line indent + line

View File

@ -20,7 +20,7 @@ from _pytest.pytester import Pytester
def mock_config(verbose: int = 0, assertion_override: Optional[int] = None): def mock_config(verbose: int = 0, assertion_override: Optional[int] = None):
class TerminalWriter: class TerminalWriter:
def _highlight(self, source, lexer): def _highlight(self, source, lexer="python"):
return source return source
class Config: class Config: