-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
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
- Minor change: have
CaptureFixture.readouterr(...)
callsys.stdout.write(out)
andsys.stderr.write(err)
itself after performing the consuming capture. Can use a new method argcapture_writeback
or similar which defaults toFalse
in order to preserve backwards-compatibility. Update "How to capture stdout/stderr output" tutorial documentation to make it more clear that by defaultreadouterr
destructively consumes the output such that it won't be available in the "Captured stdout call" test results. - 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 thatreadouterr
destructively consumes the output such that it won't be available in the "Captured stdout call" test results, whilecopyouterr
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