From f3446b006d875d1d80b94edfe01b2541f875725e Mon Sep 17 00:00:00 2001 From: pctablet505 Date: Wed, 15 Jul 2026 13:15:00 +0000 Subject: [PATCH] feat: option to hide pluggy frames even with --full-trace Added --tb-hide-internal option that, when used together with --full-trace, hides pytest/pluggy internal frames from the traceback while still showing the full user-written traceback. Fixes #9509 --- changelog/9509.feature.rst | 1 + doc/en/how-to/output.rst | 4 ++++ doc/en/reference/reference.rst | 8 ++++++++ src/_pytest/nodes.py | 9 ++++++++- src/_pytest/terminal.py | 6 ++++++ testing/test_terminal.py | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 changelog/9509.feature.rst diff --git a/changelog/9509.feature.rst b/changelog/9509.feature.rst new file mode 100644 index 00000000000..bc891135597 --- /dev/null +++ b/changelog/9509.feature.rst @@ -0,0 +1 @@ +Added :option:`--tb-hide-internal` option to hide pytest/pluggy internal frames even when :option:`--full-trace` is used. diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index db36a5a7206..dd1b069e429 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -38,6 +38,10 @@ with Ctrl+C to find out where the tests are *hanging*. By default no output will be shown (because KeyboardInterrupt is caught by pytest). By using this option you make sure a trace is shown. +The :option:`--tb-hide-internal` option can be used together with +:option:`--full-trace` to hide pytest/pluggy internal frames from the traceback, +showing the full user-written traceback without pytest's internal call stack. + Verbosity -------------------------------------------------- diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 644b78668a7..57ff848feea 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -3005,6 +3005,12 @@ Debugging See :ref:`how-to-modifying-python-tb-printing` for more information. +.. option:: --tb-hide-internal + + Hide pytest/pluggy internal frames even with :option:`--full-trace`. + + See :ref:`how-to-modifying-python-tb-printing` for more information. + .. option:: --debug, --debug=DEBUG_FILE_NAME Store internal tracing debug information in this log file. @@ -3472,6 +3478,8 @@ All the command-line flags can also be obtained by running ``pytest --help``:: Controls how captured stdout/stderr/log is shown on failed tests. Default: all. --full-trace Don't cut any tracebacks (default is to cut) + --tb-hide-internal Hide pytest/pluggy internal frames even with --full- + trace --color=color Color terminal output (yes/no/auto) --code-highlight={yes,no} Whether code should be highlighted (only if --color diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index f0629c2daf7..ac263955bb0 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -24,6 +24,7 @@ import _pytest._code from _pytest._code import getfslineno from _pytest._code.code import ExceptionInfo +from _pytest._code.code import filter_traceback from _pytest._code.code import TerminalRepr from _pytest._code.code import Traceback from _pytest._code.code import TracebackStyle @@ -415,7 +416,13 @@ def _repr_failure_py( tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] if self.config.getoption("fulltrace", False): style = "long" - tbfilter = False + if self.config.getoption("tb_hide_internal", False): + + def tbfilter(excinfo: ExceptionInfo[BaseException]) -> Traceback: + return excinfo.traceback.filter(filter_traceback) + + else: + tbfilter = False else: tbfilter = self._traceback_filter if style == "auto": diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index b9a65ff191e..3ce45957c25 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -254,6 +254,12 @@ def pytest_addoption(parser: Parser) -> None: default=False, help="Don't cut any tracebacks (default is to cut)", ) + group.addoption( + "--tb-hide-internal", + action="store_true", + default=False, + help="Hide pytest/pluggy internal frames even with --full-trace", + ) group.addoption( "--color", metavar="color", diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 3053f5ef9a1..3b4bbdd3cad 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -315,6 +315,39 @@ def test_interrupt_me(): ) result.stdout.fnmatch_lines(["*KeyboardInterrupt*"]) + @pytest.mark.parametrize( + ("extra_args", "expect_internal"), + [ + (("--fulltrace",), True), + (("--fulltrace", "--tb-hide-internal"), False), + ], + ) + def test_fulltrace_hide_internal( + self, + pytester: Pytester, + extra_args: tuple[str, ...], + expect_internal: bool, + ) -> None: + pytester.makepyfile( + """ + def helper(): + assert False + + def test_fail(): + helper() + """ + ) + result = pytester.runpytest(*extra_args) + assert result.ret == 1 + if expect_internal: + result.stdout.fnmatch_lines(["*/pluggy/*.py*"]) + result.stdout.fnmatch_lines(["*/_pytest/runner.py*"]) + else: + result.stdout.no_fnmatch_line("*/pluggy/*.py*") + result.stdout.no_fnmatch_line("*/_pytest/runner.py*") + # Ensure the user frames are still shown. + result.stdout.fnmatch_lines(["> helper()", "> assert False"]) + def test_keyboard_in_sessionstart(self, pytester: Pytester) -> None: pytester.makeconftest( """