Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fc06bae
feat: implement customization of truncation limits
zhukoff-pavel Sep 2, 2024
42e4e9c
create changelog
zhukoff-pavel Sep 2, 2024
85fe194
append authors
zhukoff-pavel Sep 2, 2024
24b075b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 2, 2024
550b725
fix changelog
zhukoff-pavel Sep 2, 2024
ea7d90b
add test
zhukoff-pavel Sep 2, 2024
fff0e7b
fix no_fnmatch_line invocations
zhukoff-pavel Sep 2, 2024
3fd50ad
escape newlines
zhukoff-pavel Sep 2, 2024
868c26e
Change order
zhukoff-pavel Sep 2, 2024
5526cba
Merge branch 'main' into impl-12765
zhukoff-pavel Sep 3, 2024
856bb8f
Merge branch 'main' into impl-12765
zhukoff-pavel Sep 8, 2024
e1edf2c
Rewrite to .ini parameters instead of CLI
zhukoff-pavel Sep 16, 2024
b4b5fb1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 16, 2024
23ea5dd
Merge branch 'main' into impl-12765
zhukoff-pavel Sep 16, 2024
d123ac3
Convert CLI flags to confvals in changelog
zhukoff-pavel Sep 16, 2024
98938ce
Clarify truncation usage order in doc/en/how-to/output.rst
zhukoff-pavel Sep 16, 2024
ae3cd2f
Handle parameters being set to zero
zhukoff-pavel Sep 16, 2024
b2287af
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 16, 2024
eee9e70
Fix typo in test case description
zhukoff-pavel Sep 16, 2024
7027624
Fix failing tests
zhukoff-pavel Sep 16, 2024
6fb0d8b
Add version added to doc/en/how-to/output.rst
zhukoff-pavel Sep 18, 2024
d1e555f
Add reference to truncation-params section in changelog/12765.feature…
zhukoff-pavel Sep 18, 2024
dba7fef
Add section in doc/en/how-to/output.rst
zhukoff-pavel Sep 18, 2024
8dddd73
Refactor _should_truncate_item to _get_truncation_parameters
zhukoff-pavel Sep 18, 2024
c3e8d7b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 18, 2024
8b69cda
Add a couple new test cases
zhukoff-pavel Sep 18, 2024
f9fd936
Merge branch 'main' into impl-12765
zhukoff-pavel Sep 18, 2024
f0ebeb0
Update docstring in src/_pytest/assertion/truncate.py
zhukoff-pavel Sep 19, 2024
dc3f065
Add reference to config parameters
zhukoff-pavel Sep 19, 2024
fced07c
Apply suggestions from code review
nicoddemus Sep 19, 2024
da08959
Apply suggestions from code review
nicoddemus Sep 19, 2024
d3e19af
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 19, 2024
5fa412a
Fix note markup
nicoddemus Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ Paul Müller
Paul Reece
Pauli Virtanen
Pavel Karateev
Pavel Zhukov
Paweł Adamczak
Pedro Algarvio
Petter Strandmark
Expand Down
3 changes: 3 additions & 0 deletions changelog/12765.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Thresholds to trigger snippet truncation can now be set with :confval:`truncation_limit_lines` and :confval:`truncation_limit_chars`.

See :ref:`truncation-params` for more information.
22 changes: 22 additions & 0 deletions doc/en/how-to/output.rst
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,28 @@ captured output:
By default, parametrized variants of skipped tests are grouped together if
they share the same skip reason. You can use ``--no-fold-skipped`` to print each skipped test separately.


.. _truncation-params:

Modifying truncation limits
--------------------------------------------------

.. versionadded: 8.4

Default truncation limits are 8 lines or 640 characters, whichever comes first.
To set custom truncation limits you can use following ``pytest.ini`` file options:

.. code-block:: ini

[pytest]
truncation_limit_lines = 10
truncation_limit_chars = 90

That will cause pytest to truncate the assertions to 10 lines or 90 characters, whichever comes first.

Setting both :confval:`truncation_limit_lines` and :confval:`truncation_limit_chars` to ``0`` will disable the truncation.
However, setting only one of those values will disable one truncation mode, but will leave the other one intact.

Creating resultlog format files
--------------------------------------------------

Expand Down
26 changes: 26 additions & 0 deletions doc/en/reference/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1873,6 +1873,32 @@ passed multiple times. The expected format is ``name=value``. For example::
Default: ``all``


.. confval:: truncation_limit_chars

Controls the characters limit to truncate to.
Setting value to ``0`` disables the character limit for truncation.

.. code-block:: ini

[pytest]
truncation_limit_chars = 640

Default: ``640``


.. confval:: truncation_limit_lines

Controls the lines limit to truncate to.
Setting value to ``0`` disables the lines limit for truncation.

.. code-block:: ini

[pytest]
truncation_limit_lines = 8

Default: ``8``


.. confval:: usefixtures

List of fixtures that will be applied to all test functions; this is semantically the same to apply
Expand Down
12 changes: 12 additions & 0 deletions src/_pytest/assertion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ def pytest_addoption(parser: Parser) -> None:
help="Enables the pytest_assertion_pass hook. "
"Make sure to delete any previously generated pyc cache files.",
)

parser.addini(
"truncation_limit_lines",
default=None,
help="Set threshold of LINES after which truncation will take effect",
)
parser.addini(
"truncation_limit_chars",
default=None,
help=("Set threshold of CHARS after which truncation will take effect"),
)

Config._add_verbosity_ini(
parser,
Config.VERBOSITY_ASSERTIONS,
Expand Down
58 changes: 39 additions & 19 deletions src/_pytest/assertion/truncate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,54 @@


DEFAULT_MAX_LINES = 8
DEFAULT_MAX_CHARS = 8 * 80
DEFAULT_MAX_CHARS = DEFAULT_MAX_LINES * 80
USAGE_MSG = "use '-vv' to show"


def truncate_if_required(
explanation: list[str], item: Item, max_length: int | None = None
) -> list[str]:
def truncate_if_required(explanation: list[str], item: Item) -> list[str]:
"""Truncate this assertion explanation if the given test item is eligible."""
if _should_truncate_item(item):
return _truncate_explanation(explanation)
should_truncate, max_lines, max_chars = _get_truncation_parameters(item)
if should_truncate:
return _truncate_explanation(
explanation,
max_lines=max_lines,
max_chars=max_chars,
)
return explanation


def _should_truncate_item(item: Item) -> bool:
"""Whether or not this test item is eligible for truncation."""
def _get_truncation_parameters(item: Item) -> tuple[bool, int, int]:
"""Return the truncation parameters related to the given item, as (should truncate, max lines, max chars)."""
# We do not need to truncate if one of conditions is met:
# 1. Verbosity level is 2 or more;
# 2. Test is being run in CI environment;
# 3. Both truncation_limit_lines and truncation_limit_chars
# .ini parameters are set to 0 explicitly.
max_lines = item.config.getini("truncation_limit_lines")
max_lines = int(max_lines if max_lines is not None else DEFAULT_MAX_LINES)

max_chars = item.config.getini("truncation_limit_chars")
max_chars = int(max_chars if max_chars is not None else DEFAULT_MAX_CHARS)

verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
return verbose < 2 and not util.running_on_ci()

should_truncate = verbose < 2 and not util.running_on_ci()
should_truncate = should_truncate and (max_lines > 0 or max_chars > 0)

return should_truncate, max_lines, max_chars


def _truncate_explanation(
input_lines: list[str],
max_lines: int | None = None,
max_chars: int | None = None,
max_lines: int,
max_chars: int,
) -> list[str]:
"""Truncate given list of strings that makes up the assertion explanation.

Truncates to either 8 lines, or 640 characters - whichever the input reaches
Truncates to either max_lines, or max_chars - whichever the input reaches
first, taking the truncation explanation into account. The remaining lines
will be replaced by a usage message.
"""
if max_lines is None:
max_lines = DEFAULT_MAX_LINES
if max_chars is None:
max_chars = DEFAULT_MAX_CHARS

# Check if truncation required
input_char_count = len("".join(input_lines))
# The length of the truncation explanation depends on the number of lines
Expand All @@ -71,16 +84,23 @@
):
return input_lines
# Truncate first to max_lines, and then truncate to max_chars if necessary
truncated_explanation = input_lines[:max_lines]
if max_lines > 0:
truncated_explanation = input_lines[:max_lines]
else:
truncated_explanation = input_lines

Check warning on line 90 in src/_pytest/assertion/truncate.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/assertion/truncate.py#L90

Added line #L90 was not covered by tests
truncated_char = True
# We reevaluate the need to truncate chars following removal of some lines
if len("".join(truncated_explanation)) > tolerable_max_chars:
if len("".join(truncated_explanation)) > tolerable_max_chars and max_chars > 0:
truncated_explanation = _truncate_by_char_count(
truncated_explanation, max_chars
)
else:
truncated_char = False

if truncated_explanation == input_lines:
# No truncation happened, so we do not need to add any explanations
return truncated_explanation

Check warning on line 102 in src/_pytest/assertion/truncate.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/assertion/truncate.py#L102

Added line #L102 was not covered by tests

truncated_line_count = len(input_lines) - len(truncated_explanation)
if truncated_explanation[-1]:
# Add ellipsis and take into account part-truncated final line
Expand Down
60 changes: 60 additions & 0 deletions testing/test_assertion.py
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,66 @@ def test_many_lines():
result = pytester.runpytest()
result.stdout.fnmatch_lines(["* 6*"])

@pytest.mark.parametrize(
["truncation_lines", "truncation_chars", "expected_lines_hidden"],
(
(3, None, 3),
(4, None, 0),
(0, None, 0),
(None, 8, 6),
(None, 9, 0),
(None, 0, 0),
(0, 0, 0),
(0, 1000, 0),
(1000, 0, 0),
),
)
def test_truncation_with_ini(
self,
monkeypatch,
pytester: Pytester,
truncation_lines: int | None,
truncation_chars: int | None,
expected_lines_hidden: int,
) -> None:
pytester.makepyfile(
"""\
string_a = "123456789\\n23456789\\n3"
string_b = "123456789\\n23456789\\n4"

def test():
assert string_a == string_b
"""
)

# This test produces 6 lines of diff output or 79 characters
# So the effect should be when threshold is < 4 lines (considering 2 additional lines for explanation)
# Or < 9 characters (considering 70 additional characters for explanation)

monkeypatch.delenv("CI", raising=False)

ini = "[pytest]\n"
if truncation_lines is not None:
ini += f"truncation_limit_lines = {truncation_lines}\n"
if truncation_chars is not None:
ini += f"truncation_limit_chars = {truncation_chars}\n"
pytester.makeini(ini)

result = pytester.runpytest()

if expected_lines_hidden != 0:
result.stdout.fnmatch_lines(
[f"*truncated ({expected_lines_hidden} lines hidden)*"]
)
else:
result.stdout.no_fnmatch_line("*truncated*")
result.stdout.fnmatch_lines(
[
"*- 4*",
"*+ 3*",
]
)


def test_python25_compile_issue257(pytester: Pytester) -> None:
pytester.makepyfile(
Expand Down
Loading