-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add capteesys capture fixture to bubble up output to --capture
handler
#12854
base: main
Are you sure you want to change the base?
Conversation
The config dict is passed alongside the class that the fixture will eventually initialize. It can use the config dict for optional arguments to the implementation's constructor.
2227ade
to
11578a9
Compare
It may be better to use "impl_kwargs" instead of "config" or something, in the future maybe have a "impl_args" that expands with * to allow positional arguments and a "impl_kwargs" that expands with **. |
11578a9
to
e859f40
Compare
When I started this feature, I expected passing the "tee" flag to "syscap" would copy output directly to the calling terminal, to the original ORIGINAL sys.stdout/sys.stderr. That's not how it works- it passes output to whatever capture was set earlier! This actually makes the feature more flexible because it enables tee-like behavior as well as pytests reporting behavior. Awesome!
e859f40
to
82afa8f
Compare
@@ -1004,6 +1004,40 @@ def test_output(capsys): | |||
capman.unset_fixture() | |||
|
|||
|
|||
@fixture | |||
def capteesys(request: SubRequest) -> Generator[CaptureFixture[str]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this fixture is almost 100% a copy of the capsys
one above it, since a tee-sys
is just a sys
w/ an extra argument (tree=True
)
I've created a polyfill to add to @pytest.fixture(scope="function")
def capteesys(request):
from _pytest import capture
import warnings
if hasattr(capture, "capteesys"):
warnings.warn(( "You are using a polyfill for capteesys, but this"
" version of pytest supports it natively- you may"
f" remove the polyfill from your {__file__}"),
DeprecationWarning)
# Remove next two lines if you don't want to ever switch to native version
yield request.getfixturevalue("capteesys")
return
capman = request.config.pluginmanager.getplugin("capturemanager")
capture_fixture = capture.CaptureFixture(capture.SysCapture, request, _ispytest=True)
def _inject_start():
self = capture_fixture # closure seems easier than importing Type or Partial
if self._capture is None:
self._capture = capture.MultiCapture(
in_ = None,
out = self.captureclass(1, tee=True),
err = self.captureclass(2, tee=True)
)
self._capture.start_capturing()
capture_fixture._start = _inject_start
capman.set_fixture(capture_fixture)
capture_fixture._start()
yield capture_fixture
capture_fixture.close()
capman.unset_fixture() |
eaa4036
to
9cd57b1
Compare
👍 this would be very useful (albeit in rather esoteric contexts :-) ) |
potentially closes #12081(and more!)
closes #XYZW
to the PR description and/or commits (whereXYZW
is the issue number). See the github docs for more information.changelog
folder, with a name like<ISSUE NUMBER>.<TYPE>.rst
. See changelog/README.rst for details.AUTHORS
in alphabetical order.Explanation
To set global capture behavior, we set
--capture=tee-sys
so that pytest can a) capture output for printing reports and b) dump output to our terminals. Both of these things are useful.Passing capture-type fixtures in our tests will block global capture behavior, but allow us to analyze captured output inside the test.
Unfortunately, capture fixtures don't support anything like
--capture=tee-sys
- until now. This PR adds a new fixture:capteesys
which captures output for analysis in tests but also pushes the output to whatever is set by--capture=
. It allows the output to "bubble up" to the next capture handler.Docs build, tests pass.
My Approach (Engineering)
How the system works right now:
Right now, when you include a fixture (
syscap
,fdcap
, etc), that fixture initializes theCaptureFixture
wrapper and passes it the class of the implementation it wants to use, one of:which the wrapper will instantiate as an object later (depending on fixture scope) as such:
There is no current way to pass other options to the constructor, which would enable other features, such as
tee
.Solution:
Discarded Solutions
Make that each Capture class accept a
tee
argument- most will discard it (FDCapture
probably could accept it). Most of the time it would be nonsensical and maybe misleading to developers or users.Create a conditional switch for the
CaptureFixture
wrapper which changes the initialization process based on the actual constructor- But it seems bad to create individual branches in base/containing classes for all possible children/attributes.Chosen Solution
Since the
CaptureFixture
class is already initialized by passing the class of the implementation, you just add a new configure dictionary:Which is then expanded when initializing the implementation:
It requires very few code changes. This explanation was longer.
To create the
capteesys
fixture, I just copied thecapsys
fixture but added the config dictionary as part of its setup.