Skip to content

fix: stop reusing shared PytestWarning instances whose tracebacks grow under -W error - #14931

Open
Devansh-awat wants to merge 1 commit into
pytest-dev:mainfrom
Devansh-awat:fix/shared-warning-instances-traceback
Open

fix: stop reusing shared PytestWarning instances whose tracebacks grow under -W error#14931
Devansh-awat wants to merge 1 commit into
pytest-dev:mainfrom
Devansh-awat:fix/shared-warning-instances-traceback

Conversation

@Devansh-awat

@Devansh-awat Devansh-awat commented Aug 24, 2026

Copy link
Copy Markdown

Description

src/_pytest/deprecated.py stored most of its deprecation constants as module-level PytestWarning instances that call sites passed straight to warnings.warn(). When a warning is filtered as an error (e.g. -W error or filterwarnings = error), CPython raises that exact object and appends to its existing __traceback__ instead of resetting it. Because the instances live for the whole process, every raise permanently grew their traceback — measured 1 → 2 → 3 → 4 frames across four raises of PRIVATE. With two tests tripping the same deprecation under -W error::pytest.PytestWarning, the second test's failure report carried stale frames (and pinned frame locals) from the first.

The fix stores every constant in the module as an UnformattedWarning and calls .format() at each warn site. UnformattedWarning.format() constructs a fresh instance of the underlying category on every call, so no exception object is ever raised twice. This is the pattern already used by the four constants that needed runtime formatting (HOOK_LEGACY_MARKING, CLASS_FIXTURE_INSTANCE_METHOD, etc.), so it introduces no new mechanism.

  • Warning text, category, stacklevel, and all public constant names are unchanged.
  • The four existing UnformattedWarning constants are untouched.
  • The module docstring previously mandated the plain-instance form; it now documents why shared instances must not be warned with.
  • Added a regression test (testing/deprecated_test.py) that raises the same deprecation twice under an error filter and asserts the second traceback does not grow; it fails on main (3 frames vs 1) and passes with this change.

Checklist

  • Include documentation when adding new features
  • Include a changelog entry (a changelog/14912.bugfix.rst file)
  • Include new tests for the bug fix

Fixes #14912

…w under -W error

The deprecation constants in src/_pytest/deprecated.py were module-level
PytestWarning instances passed directly to warnings.warn(). When a warning
is raised as an error, CPython appends to the exception's existing
__traceback__ instead of replacing it, so each raise grew the traceback of
these process-lifetime singletons (measured 1 -> 2 -> 3 -> 4 frames across
four raises). With multiple tests tripping the same deprecation, later
failure reports carried stale frames -- and stale frame locals -- from
earlier raises.

Fix by storing every constant as an UnformattedWarning and calling
.format() at the warn sites; format() returns a fresh instance of the
underlying category on each call, so no exception object is ever reused.
The four constants that were already UnformattedWarning are unchanged.
Warning text, category, and all public names are unchanged.

Fixes pytest-dev#14912.
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shared PytestWarning instances in deprecated.py accumulate tracebacks under -W error

1 participant