From fc06bae97a452ccba0c561df75762121886b1879 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 2 Sep 2024 19:29:39 +0300 Subject: [PATCH 01/29] feat: implement customization of truncation limits --- src/_pytest/assertion/__init__.py | 20 ++++++++++++++++++++ src/_pytest/assertion/truncate.py | 14 +++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index f2f1d029b4c..80b52b0c382 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -45,6 +45,26 @@ def pytest_addoption(parser: Parser) -> None: help="Enables the pytest_assertion_pass hook. " "Make sure to delete any previously generated pyc cache files.", ) + + group.addoption( + "--truncation-limit-lines", + action="store", + type=int, + dest="truncation_limit_lines", + metavar="LINES", + help="Set threshold of LINES after which truncation will take effect", + ) + group.addoption( + "--truncation-limit-chars", + action="store", + type=int, + dest="truncation_limit_chars", + metavar="CHARS", + help=( + "Set threshold of CHARS after which truncation will take effect" + ) + ) + Config._add_verbosity_ini( parser, Config.VERBOSITY_ASSERTIONS, diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index b67f02ccaf8..d9afde782fd 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -12,16 +12,20 @@ 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) + max_lines = item.config.getoption("truncation_limit_lines", default=None) + max_chars = item.config.getoption("truncation_limit_chars", default=None) + return _truncate_explanation( + explanation, + max_lines=max_lines, + max_chars=max_chars, + ) return explanation From 42e4e9c55916dcfcaed4e3f0ba154bf45b856850 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 2 Sep 2024 19:49:51 +0300 Subject: [PATCH 02/29] create changelog --- changelog/12765.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/12765.feature.rst diff --git a/changelog/12765.feature.rst b/changelog/12765.feature.rst new file mode 100644 index 00000000000..0ef38c28a7f --- /dev/null +++ b/changelog/12765.feature.rst @@ -0,0 +1 @@ +Thresholds to trigger snippet truncation can now be set via command line options. \ No newline at end of file From 85fe194554b14e121667875b82b4c6d91474e997 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 2 Sep 2024 19:51:08 +0300 Subject: [PATCH 03/29] append authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index db993353689..1ae0a74a656 100644 --- a/AUTHORS +++ b/AUTHORS @@ -325,6 +325,7 @@ Paul Müller Paul Reece Pauli Virtanen Pavel Karateev +Pavel Zhukov Paweł Adamczak Pedro Algarvio Petter Strandmark From 24b075b856b1d6c47c6aa942284f1a7677c9b40b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:55:55 +0000 Subject: [PATCH 04/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- changelog/12765.feature.rst | 2 +- src/_pytest/assertion/__init__.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/changelog/12765.feature.rst b/changelog/12765.feature.rst index 0ef38c28a7f..7203b6bf775 100644 --- a/changelog/12765.feature.rst +++ b/changelog/12765.feature.rst @@ -1 +1 @@ -Thresholds to trigger snippet truncation can now be set via command line options. \ No newline at end of file +Thresholds to trigger snippet truncation can now be set via command line options. diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 80b52b0c382..c5a5e47718f 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -60,9 +60,7 @@ def pytest_addoption(parser: Parser) -> None: type=int, dest="truncation_limit_chars", metavar="CHARS", - help=( - "Set threshold of CHARS after which truncation will take effect" - ) + help=("Set threshold of CHARS after which truncation will take effect"), ) Config._add_verbosity_ini( From 550b72579c1ec62f1382ab8dbf40fa803f469f6c Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 2 Sep 2024 19:54:12 +0300 Subject: [PATCH 05/29] fix changelog --- changelog/12765.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/12765.feature.rst b/changelog/12765.feature.rst index 7203b6bf775..3cd3a919861 100644 --- a/changelog/12765.feature.rst +++ b/changelog/12765.feature.rst @@ -1 +1 @@ -Thresholds to trigger snippet truncation can now be set via command line options. +Thresholds to trigger snippet truncation can now be set with `--truncation-limit-lines` and `--truncation-limit-chars` command line options. From ea7d90b8ef0ff4b82d99c10318c95fe6eaf8572b Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 2 Sep 2024 21:22:28 +0300 Subject: [PATCH 06/29] add test --- testing/test_assertion.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 31192df0f6f..6a569f489b8 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1435,6 +1435,47 @@ def test_many_lines(): result = pytester.runpytest() result.stdout.fnmatch_lines(["* 6*"]) + def test_truncation_with_cli(self, monkeypatch, pytester: Pytester) -> 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) + + result = pytester.runpytest("--truncation-limit-lines=3") + result.stdout.fnmatch_lines(["*truncated (3 lines hidden)*"]) + + result = pytester.runpytest("--truncation-limit-lines=4") + result.stdout.no_fnmatch_line(["*truncated*"]) + result.stdout.fnmatch_lines( + [ + "*+ 3*", + "*- 4*", + ] + ) + + result = pytester.runpytest("--truncation-limit-chars=8") + result.stdout.fnmatch_lines(["*truncated (6 lines hidden)*"]) + + result = pytester.runpytest("--truncation-limit-chars=9") + result.stdout.no_fnmatch_line(["*truncated*"]) + result.stdout.fnmatch_lines( + [ + "*+ 3*", + "*- 4*", + ] + ) + def test_python25_compile_issue257(pytester: Pytester) -> None: pytester.makepyfile( From fff0e7b260abb6eb20edf71898fc7ce62b241722 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 2 Sep 2024 21:25:35 +0300 Subject: [PATCH 07/29] fix no_fnmatch_line invocations --- testing/test_assertion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 6a569f489b8..4ff61d24ee9 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1456,7 +1456,7 @@ def test(): result.stdout.fnmatch_lines(["*truncated (3 lines hidden)*"]) result = pytester.runpytest("--truncation-limit-lines=4") - result.stdout.no_fnmatch_line(["*truncated*"]) + result.stdout.no_fnmatch_line("*truncated*") result.stdout.fnmatch_lines( [ "*+ 3*", @@ -1468,7 +1468,7 @@ def test(): result.stdout.fnmatch_lines(["*truncated (6 lines hidden)*"]) result = pytester.runpytest("--truncation-limit-chars=9") - result.stdout.no_fnmatch_line(["*truncated*"]) + result.stdout.no_fnmatch_line("*truncated*") result.stdout.fnmatch_lines( [ "*+ 3*", From 3fd50ad18a9e171a2a4d4bae68ffe0386c665325 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 2 Sep 2024 21:42:22 +0300 Subject: [PATCH 08/29] escape newlines --- testing/test_assertion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 4ff61d24ee9..9969266b495 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1438,8 +1438,8 @@ def test_many_lines(): def test_truncation_with_cli(self, monkeypatch, pytester: Pytester) -> None: pytester.makepyfile( """\ - string_a = "123456789\n23456789\n3" - string_b = "123456789\n23456789\n4" + string_a = "123456789\\n23456789\\n3" + string_b = "123456789\\n23456789\\n4" def test(): assert string_a == string_b From 868c26e065bafbb1a9af42de4d778d673a8dbc1c Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 2 Sep 2024 21:45:01 +0300 Subject: [PATCH 09/29] Change order --- testing/test_assertion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 9969266b495..a67026e5d60 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1459,8 +1459,8 @@ def test(): result.stdout.no_fnmatch_line("*truncated*") result.stdout.fnmatch_lines( [ - "*+ 3*", "*- 4*", + "*+ 3*", ] ) @@ -1471,8 +1471,8 @@ def test(): result.stdout.no_fnmatch_line("*truncated*") result.stdout.fnmatch_lines( [ - "*+ 3*", "*- 4*", + "*+ 3*", ] ) From e1edf2cb73f85da66f860e276bde59af22aed43b Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 16 Sep 2024 19:39:27 +0300 Subject: [PATCH 10/29] Rewrite to .ini parameters instead of CLI --- doc/en/how-to/output.rst | 16 +++++++++ src/_pytest/assertion/__init__.py | 18 ++++------- src/_pytest/assertion/truncate.py | 6 ++-- testing/test_assertion.py | 54 +++++++++++++++++++------------ 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 8b15f95f0fd..7e75084bc83 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -549,6 +549,22 @@ 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. + +Modifying truncation limits +-------------------------------------------------- + +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. + + Creating resultlog format files -------------------------------------------------- diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index c5a5e47718f..eb73f66dc3a 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -46,20 +46,14 @@ def pytest_addoption(parser: Parser) -> None: "Make sure to delete any previously generated pyc cache files.", ) - group.addoption( - "--truncation-limit-lines", - action="store", - type=int, - dest="truncation_limit_lines", - metavar="LINES", + parser.addini( + "truncation_limit_lines", + default=0, help="Set threshold of LINES after which truncation will take effect", ) - group.addoption( - "--truncation-limit-chars", - action="store", - type=int, - dest="truncation_limit_chars", - metavar="CHARS", + parser.addini( + "truncation_limit_chars", + default=0, help=("Set threshold of CHARS after which truncation will take effect"), ) diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index d9afde782fd..b8be2494ca7 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -19,8 +19,8 @@ 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): - max_lines = item.config.getoption("truncation_limit_lines", default=None) - max_chars = item.config.getoption("truncation_limit_chars", default=None) + max_lines = int(item.config.getini("truncation_limit_lines") or DEFAULT_MAX_LINES) + max_chars = int(item.config.getini("truncation_limit_chars") or DEFAULT_MAX_CHARS) return _truncate_explanation( explanation, max_lines=max_lines, @@ -42,7 +42,7 @@ def _truncate_explanation( ) -> 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. """ diff --git a/testing/test_assertion.py b/testing/test_assertion.py index a67026e5d60..fa77461f85d 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1435,7 +1435,23 @@ def test_many_lines(): result = pytester.runpytest() result.stdout.fnmatch_lines(["* 6*"]) - def test_truncation_with_cli(self, monkeypatch, pytester: Pytester) -> None: + @pytest.mark.parametrize( + ["truncation_mode", "truncation_limit", "expected_lines_hidden"], + ( + ("lines", 3, 3), + ("lines", 4, 0), + ("chars", 8, 6), + ("chars", 9, 0), + ) + ) + def test_truncation_with_ini( + self, + monkeypatch, + pytester: Pytester, + truncation_mode: str, + truncation_limit: int, + expected_lines_hidden: int, + ) -> None: pytester.makepyfile( """\ string_a = "123456789\\n23456789\\n3" @@ -1452,29 +1468,25 @@ def test(): monkeypatch.delenv("CI", raising=False) - result = pytester.runpytest("--truncation-limit-lines=3") - result.stdout.fnmatch_lines(["*truncated (3 lines hidden)*"]) - - result = pytester.runpytest("--truncation-limit-lines=4") - result.stdout.no_fnmatch_line("*truncated*") - result.stdout.fnmatch_lines( - [ - "*- 4*", - "*+ 3*", - ] + pytester.makeini( + f""" + [pytest] + truncation_limit_{truncation_mode} = {truncation_limit} + """ ) - result = pytester.runpytest("--truncation-limit-chars=8") - result.stdout.fnmatch_lines(["*truncated (6 lines hidden)*"]) + result = pytester.runpytest() - result = pytester.runpytest("--truncation-limit-chars=9") - result.stdout.no_fnmatch_line("*truncated*") - result.stdout.fnmatch_lines( - [ - "*- 4*", - "*+ 3*", - ] - ) + 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: From b4b5fb1cf9a0a36d493a154f5d7fbc220ed76b11 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:39:51 +0000 Subject: [PATCH 11/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/en/how-to/output.rst | 2 +- src/_pytest/assertion/truncate.py | 8 ++++++-- testing/test_assertion.py | 6 ++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 7e75084bc83..4effc1cf012 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -553,7 +553,7 @@ captured output: Modifying truncation limits -------------------------------------------------- -Default truncation limits are 8 lines or 640 characters, whichever comes first. +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 diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index b8be2494ca7..de448b75158 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -19,8 +19,12 @@ 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): - max_lines = int(item.config.getini("truncation_limit_lines") or DEFAULT_MAX_LINES) - max_chars = int(item.config.getini("truncation_limit_chars") or DEFAULT_MAX_CHARS) + max_lines = int( + item.config.getini("truncation_limit_lines") or DEFAULT_MAX_LINES + ) + max_chars = int( + item.config.getini("truncation_limit_chars") or DEFAULT_MAX_CHARS + ) return _truncate_explanation( explanation, max_lines=max_lines, diff --git a/testing/test_assertion.py b/testing/test_assertion.py index fa77461f85d..6af1b5c096a 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1442,7 +1442,7 @@ def test_many_lines(): ("lines", 4, 0), ("chars", 8, 6), ("chars", 9, 0), - ) + ), ) def test_truncation_with_ini( self, @@ -1478,7 +1478,9 @@ def test(): result = pytester.runpytest() if expected_lines_hidden != 0: - result.stdout.fnmatch_lines([f"*truncated ({expected_lines_hidden} lines hidden)*"]) + result.stdout.fnmatch_lines( + [f"*truncated ({expected_lines_hidden} lines hidden)*"] + ) else: result.stdout.no_fnmatch_line("*truncated*") result.stdout.fnmatch_lines( From d123ac32cc952489ab942975cf90b107ab2384d0 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov <48322035+zhukoff-pavel@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:11:38 +0300 Subject: [PATCH 12/29] Convert CLI flags to confvals in changelog Co-authored-by: Bruno Oliveira --- changelog/12765.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/12765.feature.rst b/changelog/12765.feature.rst index 3cd3a919861..d7b4ab0f3e4 100644 --- a/changelog/12765.feature.rst +++ b/changelog/12765.feature.rst @@ -1 +1 @@ -Thresholds to trigger snippet truncation can now be set with `--truncation-limit-lines` and `--truncation-limit-chars` command line options. +Thresholds to trigger snippet truncation can now be set with :confval:`truncation_limit_lines` and :confval:`truncation_limit_chars`. From 98938cec519cd7dafb0342e7278b3dc83c8f55e7 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov <48322035+zhukoff-pavel@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:14:33 +0300 Subject: [PATCH 13/29] Clarify truncation usage order in doc/en/how-to/output.rst Co-authored-by: Bruno Oliveira --- doc/en/how-to/output.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 4effc1cf012..2f4cd4a9ce5 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -562,7 +562,7 @@ To set custom truncation limits you can use following ``pytest.ini`` file option truncation_limit_lines = 10 truncation_limit_chars = 90 -That will cause pytest to truncate the assertions to 10 lines or 90 characters. +That will cause pytest to truncate the assertions to 10 lines or 90 characters, whichever comes first. Creating resultlog format files From ae3cd2f2f3f13d04b1a11ff63ad905c3ce5fae1e Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 16 Sep 2024 23:03:27 +0300 Subject: [PATCH 14/29] Handle parameters being set to zero --- doc/en/how-to/output.rst | 2 ++ src/_pytest/assertion/truncate.py | 22 +++++++++++++++++++--- testing/test_assertion.py | 29 ++++++++++++++++------------- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 2f4cd4a9ce5..ae258780bbc 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -564,6 +564,8 @@ To set custom truncation limits you can use following ``pytest.ini`` file option 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 -------------------------------------------------- diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index de448b75158..30e54cbbd80 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -36,7 +36,16 @@ def truncate_if_required(explanation: list[str], item: Item) -> list[str]: def _should_truncate_item(item: Item) -> bool: """Whether or not this test item is eligible for truncation.""" verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS) - return verbose < 2 and not util.running_on_ci() + + max_lines = item.config.getini("truncation_limit_lines") + if max_lines is not None: + max_lines = int(max_lines) + + max_chars = item.config.getini("truncation_limit_chars") + if max_chars is not None: + max_chars = int(max_chars) + + return verbose < 2 and not util.running_on_ci() and (max_lines != 0 or max_chars != 0) def _truncate_explanation( @@ -79,16 +88,23 @@ def _truncate_explanation( ): 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 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 + truncated_line_count = len(input_lines) - len(truncated_explanation) if truncated_explanation[-1]: # Add ellipsis and take into account part-truncated final line diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 6af1b5c096a..ed543350b00 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1436,20 +1436,23 @@ def test_many_lines(): result.stdout.fnmatch_lines(["* 6*"]) @pytest.mark.parametrize( - ["truncation_mode", "truncation_limit", "expected_lines_hidden"], + ["truncation_lines", "truncation_chars", "expected_lines_hidden"], ( - ("lines", 3, 3), - ("lines", 4, 0), - ("chars", 8, 6), - ("chars", 9, 0), + (3, None, 3), + (4, None, 0), + (0, None, 0) + (None, 8, 6), + (None, 9, 0), + (None, 0, 0), + (0, 0, 0), ), ) def test_truncation_with_ini( self, monkeypatch, pytester: Pytester, - truncation_mode: str, - truncation_limit: int, + truncation_lines: int | None, + truncation_chars: int | None, expected_lines_hidden: int, ) -> None: pytester.makepyfile( @@ -1468,12 +1471,12 @@ def test(): monkeypatch.delenv("CI", raising=False) - pytester.makeini( - f""" - [pytest] - truncation_limit_{truncation_mode} = {truncation_limit} - """ - ) + 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() From b2287afdbb6bdcd4d5a3d06c69c3313db3ef2d0a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 20:03:57 +0000 Subject: [PATCH 15/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/assertion/truncate.py | 4 +++- testing/test_assertion.py | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index 30e54cbbd80..3c438078286 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -45,7 +45,9 @@ def _should_truncate_item(item: Item) -> bool: if max_chars is not None: max_chars = int(max_chars) - return verbose < 2 and not util.running_on_ci() and (max_lines != 0 or max_chars != 0) + return ( + verbose < 2 and not util.running_on_ci() and (max_lines != 0 or max_chars != 0) + ) def _truncate_explanation( diff --git a/testing/test_assertion.py b/testing/test_assertion.py index ed543350b00..f2beee507ab 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1440,8 +1440,7 @@ def test_many_lines(): ( (3, None, 3), (4, None, 0), - (0, None, 0) - (None, 8, 6), + (0, None, 0)(None, 8, 6), (None, 9, 0), (None, 0, 0), (0, 0, 0), From eee9e70a204cc96100e9e53022c93168903b717c Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 16 Sep 2024 23:05:01 +0300 Subject: [PATCH 16/29] Fix typo in test case description --- testing/test_assertion.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index f2beee507ab..2dd5f18dc47 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1440,7 +1440,8 @@ def test_many_lines(): ( (3, None, 3), (4, None, 0), - (0, None, 0)(None, 8, 6), + (0, None, 0), + (None, 8, 6), (None, 9, 0), (None, 0, 0), (0, 0, 0), From 702762492fc23fcdb0b03d0bd227b67ba2fe1101 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Mon, 16 Sep 2024 23:59:21 +0300 Subject: [PATCH 17/29] Fix failing tests --- src/_pytest/assertion/__init__.py | 4 ++-- src/_pytest/assertion/truncate.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index eb73f66dc3a..cbdf9fa0298 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -48,12 +48,12 @@ def pytest_addoption(parser: Parser) -> None: parser.addini( "truncation_limit_lines", - default=0, + default=None, help="Set threshold of LINES after which truncation will take effect", ) parser.addini( "truncation_limit_chars", - default=0, + default=None, help=("Set threshold of CHARS after which truncation will take effect"), ) diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index 3c438078286..ecb76196763 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -38,12 +38,10 @@ def _should_truncate_item(item: Item) -> bool: verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS) max_lines = item.config.getini("truncation_limit_lines") - if max_lines is not None: - max_lines = int(max_lines) + max_lines = int(max_lines if max_lines is not None else DEFAULT_MAX_LINES) max_chars = item.config.getini("truncation_limit_chars") - if max_chars is not None: - max_chars = int(max_chars) + max_chars = int(max_chars if max_chars is not None else DEFAULT_MAX_CHARS) return ( verbose < 2 and not util.running_on_ci() and (max_lines != 0 or max_chars != 0) From 6fb0d8b8606ba15d72cfc7e2572673609203c2a7 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov <48322035+zhukoff-pavel@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:27:26 +0300 Subject: [PATCH 18/29] Add version added to doc/en/how-to/output.rst Co-authored-by: Bruno Oliveira --- doc/en/how-to/output.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index ae258780bbc..8041640e6f5 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -553,6 +553,8 @@ captured output: 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: From d1e555f83615334f8fb5a41d836b3eb7d2968587 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov <48322035+zhukoff-pavel@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:27:51 +0300 Subject: [PATCH 19/29] Add reference to truncation-params section in changelog/12765.feature.rst Co-authored-by: Bruno Oliveira --- changelog/12765.feature.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog/12765.feature.rst b/changelog/12765.feature.rst index d7b4ab0f3e4..193c75621f7 100644 --- a/changelog/12765.feature.rst +++ b/changelog/12765.feature.rst @@ -1 +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. From dba7fefc534174d2d210f08bbc3a0d6ad1b9a66e Mon Sep 17 00:00:00 2001 From: Pavel Zhukov <48322035+zhukoff-pavel@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:28:20 +0300 Subject: [PATCH 20/29] Add section in doc/en/how-to/output.rst Co-authored-by: Bruno Oliveira --- doc/en/how-to/output.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 8041640e6f5..2f4adb51714 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -550,6 +550,7 @@ captured output: they share the same skip reason. You can use ``--no-fold-skipped`` to print each skipped test separately. +.. _truncation-params: Modifying truncation limits -------------------------------------------------- From 8dddd73b866ca64080396216d49b73e83a4bae8b Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Wed, 18 Sep 2024 23:29:42 +0300 Subject: [PATCH 21/29] Refactor _should_truncate_item to _get_truncation_parameters --- src/_pytest/assertion/truncate.py | 35 ++++++++++++++----------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index ecb76196763..51401e9784b 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -18,13 +18,8 @@ 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): - max_lines = int( - item.config.getini("truncation_limit_lines") or DEFAULT_MAX_LINES - ) - max_chars = int( - item.config.getini("truncation_limit_chars") or DEFAULT_MAX_CHARS - ) + should_truncate, max_lines, max_chars = _get_truncation_parameters(item) + if should_truncate: return _truncate_explanation( explanation, max_lines=max_lines, @@ -33,25 +28,31 @@ def truncate_if_required(explanation: list[str], item: Item) -> list[str]: return explanation -def _should_truncate_item(item: Item) -> bool: +def _get_truncation_parameters(item: Item) -> tuple[bool, int, int]: """Whether or not this test item is eligible for truncation.""" - verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS) - + # 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) - return ( - verbose < 2 and not util.running_on_ci() and (max_lines != 0 or max_chars != 0) - ) + verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + + 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. @@ -59,10 +60,6 @@ def _truncate_explanation( 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)) From c3e8d7b102bc3f2b18bc46ecfcc5e00bf7196420 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:30:40 +0000 Subject: [PATCH 22/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/assertion/truncate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index 51401e9784b..fc92aeb1b43 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -60,7 +60,6 @@ def _truncate_explanation( first, taking the truncation explanation into account. The remaining lines will be replaced by a usage message. """ - # Check if truncation required input_char_count = len("".join(input_lines)) # The length of the truncation explanation depends on the number of lines From 8b69cda8c58033a8458816ca428d2fb2ce6ed08d Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Wed, 18 Sep 2024 23:36:56 +0300 Subject: [PATCH 23/29] Add a couple new test cases --- testing/test_assertion.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 2dd5f18dc47..b10ca1c91f4 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1445,6 +1445,8 @@ def test_many_lines(): (None, 9, 0), (None, 0, 0), (0, 0, 0), + (0, 1000, 0), + (1000, 0, 0), ), ) def test_truncation_with_ini( From f0ebeb042713822d80be1af7e885c0374baf04e5 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov <48322035+zhukoff-pavel@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:33:09 +0300 Subject: [PATCH 24/29] Update docstring in src/_pytest/assertion/truncate.py Co-authored-by: Bruno Oliveira --- src/_pytest/assertion/truncate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index fc92aeb1b43..4854a62ba6b 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -29,7 +29,7 @@ def truncate_if_required(explanation: list[str], item: Item) -> list[str]: def _get_truncation_parameters(item: Item) -> tuple[bool, int, int]: - """Whether or not this test item is eligible for truncation.""" + """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; From dc3f065e3cb0ac540d2fbcc089d34e0c3b2fbf1c Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Thu, 19 Sep 2024 15:39:43 +0300 Subject: [PATCH 25/29] Add reference to config parameters --- doc/en/reference/reference.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index f7dfb3ffa71..eba3c7cb79d 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -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 From fced07c0ddff3c0a34c8258162cdc566f0e7bb65 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 19 Sep 2024 09:51:22 -0300 Subject: [PATCH 26/29] Apply suggestions from code review --- doc/en/how-to/output.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 2f4adb51714..d53dd4b8ec7 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -551,6 +551,7 @@ captured output: .. _truncation-params: + Modifying truncation limits -------------------------------------------------- From da089594332606236dd94fd0a3873c30ca38c0bf Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 19 Sep 2024 10:06:46 -0300 Subject: [PATCH 27/29] Apply suggestions from code review --- doc/en/reference/reference.rst | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index eba3c7cb79d..c1db4698a0d 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1875,29 +1875,43 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: truncation_limit_chars - Controls the characters limit to truncate to. - Setting value to ``0`` disables the character limit for truncation. + Controls maximum number of characters to truncate assertion message contents. + + Setting value to ``0`` disables the character limit for truncation. - .. code-block:: ini + .. code-block:: ini - [pytest] - truncation_limit_chars = 640 + [pytest] + truncation_limit_chars = 640 + + pytest truncates the assert messages to a certain limit by default to prevent comparison with large data to overload the console output. Default: ``640`` + + ..note:: + + If pytest detects it is :ref:`running on CI `, truncation is disabled automatically. .. confval:: truncation_limit_lines - Controls the lines limit to truncate to. - Setting value to ``0`` disables the lines limit for truncation. + Controls maximum number of linesto truncate assertion message contents. + + Setting value to ``0`` disables the lines limit for truncation. - .. code-block:: ini + .. code-block:: ini - [pytest] - truncation_limit_lines = 8 + [pytest] + truncation_limit_lines = 8 + pytest truncates the assert messages to a certain limit by default to prevent comparison with large data to overload the console output. + Default: ``8`` + ..note:: + + If pytest detects it is :ref:`running on CI `, truncation is disabled automatically. + .. confval:: usefixtures From d3e19af37d2d4043d0cdada0100331057a6dfeac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:07:08 +0000 Subject: [PATCH 28/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/en/reference/reference.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index c1db4698a0d..9e7a48cc457 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1876,7 +1876,7 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: truncation_limit_chars Controls maximum number of characters to truncate assertion message contents. - + Setting value to ``0`` disables the character limit for truncation. .. code-block:: ini @@ -1887,16 +1887,16 @@ passed multiple times. The expected format is ``name=value``. For example:: pytest truncates the assert messages to a certain limit by default to prevent comparison with large data to overload the console output. Default: ``640`` - + ..note:: - + If pytest detects it is :ref:`running on CI `, truncation is disabled automatically. .. confval:: truncation_limit_lines Controls maximum number of linesto truncate assertion message contents. - + Setting value to ``0`` disables the lines limit for truncation. .. code-block:: ini @@ -1905,11 +1905,11 @@ passed multiple times. The expected format is ``name=value``. For example:: truncation_limit_lines = 8 pytest truncates the assert messages to a certain limit by default to prevent comparison with large data to overload the console output. - + Default: ``8`` ..note:: - + If pytest detects it is :ref:`running on CI `, truncation is disabled automatically. From 5fa412aa1b0355ba846edd69cad707621eae14bb Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 19 Sep 2024 10:10:42 -0300 Subject: [PATCH 29/29] Fix note markup --- doc/en/reference/reference.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 9e7a48cc457..73398ac811e 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1888,7 +1888,7 @@ passed multiple times. The expected format is ``name=value``. For example:: Default: ``640`` - ..note:: + .. note:: If pytest detects it is :ref:`running on CI `, truncation is disabled automatically. @@ -1908,7 +1908,7 @@ passed multiple times. The expected format is ``name=value``. For example:: Default: ``8`` - ..note:: + .. note:: If pytest detects it is :ref:`running on CI `, truncation is disabled automatically.