Skip to content

Capture: copy output when using readouterr() instead of consuming it #12081

@RedHellion

Description

@RedHellion

Issue

Currently pytest's capfd/capsys fixtures (CaptureFixture) that allow stdout capturing via readouterr() consume stdout, meaning that logs are no longer available in the "Captured stdout call" up to the point in the test where readouterr() was called. The documentation indicates that this is expected behaviour ("readouterr() : Read and return the captured output so far, resetting the internal buffer."), however the "How to capture stdout/stderr output" tutorial seems to imply that the output is copied from the stdout/stderr streams instead of consumed and reset ("The readouterr() call snapshots the output so far - and capturing will be continued. After the test function finishes the original streams will be restored.")

Example

Code:

def method_under_test():
    print("Hallo, Welt!")
    return 41

def test_result(capsys):
    result = method_under_test()
    out, err = capsys.readouterr()
    assert out.startswith("Hello")
    assert result == 42

Expected:

================================== FAILURES ===================================
______________________________ test_result _______________________________

...

pytestest.py:9: AssertionError
---------------------------- Captured stdout call -----------------------------
Hallo, Welt!
========================== 1 failed in 0.03 seconds ===========================

Actual (no captured stdout because it was consumed by capsys.readouterr()):

================================== FAILURES ===================================
___________________________ test_result ____________________________

...

pytestest.py:14: AssertionError
========================== 1 failed in 0.03 seconds ===========================

Current workaround

Any tests requiring validation of stdout currently need to manually write-back the captured stdout and stderr immediately after capture in order to have them available for viewing under "Captured stdout call" in pytest results. It is not obvious that this is required from the documentation in order to both capture this output for test validation and have the output available for viewing to troubleshoot in the event of test failure.

Example

Code:

def method_under_test():
    print("Hallo, Welt!")
    return 41

def test_result(capsys):
    result = method_under_test()
    out, err = capsys.readouterr()
    sys.stdout.write(out)
    sys.stderr.write(err)
    assert out.startswith("Hello")
    assert result == 42

Result:

================================== FAILURES ===================================
______________________________ test_result _______________________________

...

pytestest.py:9: AssertionError
---------------------------- Captured stdout call -----------------------------
Hallo, Welt!
========================== 1 failed in 0.03 seconds ===========================

Proposed solution

Either

  1. Minor change: have CaptureFixture.readouterr(...) call sys.stdout.write(out) and sys.stderr.write(err) itself after performing the consuming capture. Can use a new method arg capture_writeback or similar which defaults to False in order to preserve backwards-compatibility. Update "How to capture stdout/stderr output" tutorial documentation to make it more clear that by default readouterr destructively consumes the output such that it won't be available in the "Captured stdout call" test results.
  2. Larger change: create a new CaptureFixture.copyouterr() method that does not destructively consume the stdout/stderror streams and instead copies them for use in test validation, such that the streams also remain intact for the "Captured stdout call" test results. Update "How to capture stdout/stderr output" tutorial documentation to make it more clear that readouterr destructively consumes the output such that it won't be available in the "Captured stdout call" test results, while copyouterr does the same thing non-destructively.

Additional context

Originally mentioned on Stackoverflow in 2014
https://stackoverflow.com/questions/26561822/pytest-capsys-checking-output-and-getting-it-reported

Logged as an Issue in this repo in 2018, but closed as "completed" for unknown reasons
#3448

Metadata

Metadata

Assignees

No one assigned

    Labels

    plugin: capturerelated to the capture builtin plugintype: enhancementnew feature or API change, should be merged into features branch

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions