fix(capture): let the capture fix decide for itself whether pytest needs it - #14
Merged
Merged
Conversation
…eds it The patch is a stopgap for a pytest defect, and it carried no way to stop. `install_capture_swap_fix` checked that the two methods still came from `_pytest.capture` -- which catches another patcher and nothing else. A pytest that fixed this upstream would keep the methods exactly where they are, so the check passes and the patch lands on the corrected implementation, putting the `sys.stdout` reassignment back into a capture that had stopped doing it. That failure is silent, which is the worst shape available for it. Installing now measures instead: drive a real capture through swap, suspend and resume, and patch only a pytest that loses the swap. A version floor could not answer the same question -- a backport, a fork or a vendored copy each answer for themselves -- and a probe that cannot be driven reports no defect, because patching a class whose behaviour was never established is how a fix becomes an outage. `from _pytest.capture import SysCaptureBase` also sat at module scope, and `pytest_plugin` imports this module while loading. A pytest that renames or drops the class would therefore raise ImportError inside plugin load and take every suite in the family with it, on nothing more than a version bump. The import moved inside the call, where its absence costs the fix alone. The marker is read from the class's own `__dict__` rather than through inheritance, so a subclass cannot report a fix that is not on the class in hand.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The capture patch is a stopgap for a pytest defect, and it carried no way to stop.
Why
install_capture_swap_fixguarded only on the two methods still coming from_pytest.capture. That catches another patcher and nothing else. A pytest that fixes this upstream keeps the methods exactly where they are — so the guard passes, the patch lands on the corrected implementation, and thesys.stdoutreassignment goes back into a capture that had stopped doing it. Silently.Upstream has now said which way it is going: pytest-dev/pytest#14996 drew
We should use a distinct stream instead of this ai confusion. Work on distinct streams is in progress.So the patch is unlikely to be adopted as written, and likely to be obsoleted by a reshaped capture. Both outcomes need this.What changed
_pytest.captureimport moved inside the call. It sat at module scope, andpytest_pluginimports this module while loading, so a renamed or removed class would raise ImportError inside plugin load and take every suite in the family down on a routine pytest bump. Now its absence costs the fix and nothing else.__dict__, so a subclass cannot report a fix that is not on the class in hand.Tests
Four added, all failing before the change:
The existing seven are unchanged, including the two
pytestersubprocess runs that drive a realCliRunnerunderlog_cli=true— one asserting the fix works, one asserting the failure returns with-p no:provide_testkit.391 passed, 1 skippedsingle-process; ruff, ruff format and mypy strict clean.