Stdout/Stderr Capture
Purpose
This subtopic addresses the need to **capture standard output (stdout) and standard error (stderr) streams** generated during test execution. Within the broader context of output and log capture, it specifically focuses on mechanisms to intercept, store, and optionally display or suppress these streams. This is crucial for isolating test output, enabling detailed failure diagnostics, avoiding pollution of test runner outputs, and supporting test fixtures that allow tests to examine their own output.
Functionality
The core functionality revolves around **intercepting writes to stdout and stderr (and optionally stdin)** through various capturing strategies selectable by the user:
File Descriptor (fd) capturing: Redirects the underlying OS-level file descriptors (1 for stdout, 2 for stderr) to temporary files. This captures output at a low level, including output from C extensions or subprocesses writing directly to these descriptors.
Sys capturing: Replaces Python’s
sys.stdoutand sys.stderr objects with in-memory buffers or wrappers. This captures output generated by high-level Python code but may miss output from lower-level writes.No capturing: Disables capturing entirely, allowing output to pass through directly.
Tee capturing (tee-sys): Captures output while simultaneously allowing it to be displayed live on the console.
Key Classes and Workflows
CaptureBase and its subclasses: Abstract base classes define the interface for starting, stopping, suspending, resuming capturing, and retrieving captured output (
snap()method). Two main categories exist:SysCapture and SysCaptureBinary capture via replacing
sysstreams.FDCapture and FDCaptureBinary capture via OS-level file descriptor redirection.
MultiCapture: Aggregates capture objects for stdin, stdout, and stderr, coordinating their lifecycle and providing unified readout of captured content.
CaptureManager: Registers and manages the global capturing state during test collection and test phases (setup, call, teardown). It integrates tightly with pytest’s hooks to start and stop capturing automatically, and attaches captured output to test reports.
CaptureFixture: Implements user-facing fixtures like
capsysandcapfd. These fixtures allow test functions to explicitly access captured output and control capturing behavior (e.g., temporarily disabling capture within a test).
Capture Lifecycle Example
Pytest initializes
CaptureManagerwith the configured capture method (fd,sys,no, ortee-sys).At test collection and each test phase, global capturing is started.
During test execution, output sent to stdout/stderr is redirected to buffers or temporary files.
After each phase, captured output is retrieved and attached to test reports.
Test fixtures such as
capsysprovide granular control to tests for reading or disabling capture.Capturing can be suspended and resumed to allow output to pass through temporarily (e.g., for debugging).
Example Fixture Usage
def test_output(capsys):
print("hello")
captured = capsys.readouterr()
assert captured.out == "hello\n"
Here, `capsys` captures output written to Python’s `sys.stdout`, allowing the test to assert on it.
Integration
Stdout/Stderr Capture is a **critical subcomponent of the overall output and log capture system**. It complements:
Logging Capture and Formatting: While this subtopic captures raw output streams, the logging capture subtopic intercepts Python’s logging records and formats them for reporting.
CaptureManager and pytest hooks: It integrates with pytest’s lifecycle hooks to automate capture management during collection and test execution phases.
Capture Fixtures: Provides user-facing fixtures (
capsys,capfd, and their binary variants) that rely on this subtopic’s capturing mechanisms.Global vs Fixture capture: It handles coordination between global capturing active during test phases and fixture-level capturing requested by tests, ensuring proper priority and isolation.
This subtopic introduces the **low-level implementation details of capturing output streams**, which are not covered in the parent topic’s overview of output capture in general. It provides the foundational mechanisms enabling pytest to capture, control, and expose stdout and stderr output effectively.
Diagram
The following flowchart illustrates the **stdout/stderr capture lifecycle during test execution** managed by the CaptureManager:
flowchart TD
Start[Test Execution Begins]
Init[Initialize CaptureManager with method]
StartGlobal[Start Global Capture (stdout, stderr)]
TestPhase[Run Test Phase (setup/call/teardown)]
CaptureOutput[Output is redirected to capture buffers/files]
PhaseEnd[End of Test Phase]
Retrieve[Retrieve Captured Output]
Attach[Attach Output to Test Report]
SuspendResume[Suspend/Resume Capture if requested]
FixtureCapture[Activate/Deactivate CaptureFixture (capsys/capfd)]
End[Test Execution Ends]
Start --> Init --> StartGlobal --> TestPhase --> CaptureOutput --> PhaseEnd --> Retrieve --> Attach
Retrieve --> SuspendResume --> TestPhase
SuspendResume --> FixtureCapture --> TestPhase
Attach --> TestPhase
PhaseEnd --> SuspendResume
TestPhase --> End
This flowchart highlights how output is captured transparently during test phases, optionally overridden by fixture-level capture, and then attached to reporting.
Code Snippet Highlight
def pytest_addoption(parser: Parser) -> None:
group = parser.getgroup("general")
group.addoption(
"--capture",
action="store",
default="fd",
choices=["fd", "sys", "no", "tee-sys"],
help="Per-test capturing method: one of fd|sys|no|tee-sys",
)
This snippet shows how the capture method is configured via pytest’s CLI.
class CaptureManager:
def start_global_capturing(self) -> None:
self._global_capturing = _get_multicapture(self._method)
self._global_capturing.start_capturing()
def stop_global_capturing(self) -> None:
self._global_capturing.pop_outerr_to_orig()
self._global_capturing.stop_capturing()
self._global_capturing = None
This excerpt demonstrates how capturing is started and stopped globally during test run phases.
Stdout/Stderr Capture is fundamental for pytest’s ability to **control and report test output cleanly**, underpinning many advanced test features and user conveniences.