Skip to content

Commit

Permalink
More work on pytest-dev#4597
Browse files Browse the repository at this point in the history
  • Loading branch information
cmachalo committed Dec 4, 2019
1 parent db153a2 commit 84bfb63
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 14 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Carl Friedrich Bolz
Carlos Jenkins
Ceridwen
Charles Cloud
Charles Machalow
Charnjit SiNGH (CCSJ)
Chris Lamb
Christian Boelsen
Expand Down
1 change: 1 addition & 0 deletions changelog/4597.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add tee-stdio capture mechanism to allow both live printing and capturing of test output.
15 changes: 11 additions & 4 deletions doc/en/capture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ a test.
Setting capturing methods or disabling capturing
-------------------------------------------------

There are two ways in which ``pytest`` can perform capturing:
There are three ways in which ``pytest`` can perform capturing:

* file descriptor (FD) level capturing (default): All writes going to the
operating system file descriptors 1 and 2 will be captured.
Expand All @@ -33,15 +33,22 @@ There are two ways in which ``pytest`` can perform capturing:
and ``sys.stderr`` will be captured. No capturing of writes to
filedescriptors is performed.

* tee-stdio capturing: Python writes to ``sys.stdout`` and ``sys.stderr``
will be captured, however the writes will also be passed-through to
the actual ``sys.stdout`` and ``sys.stderr``. This allows output to be
'live printed' and captured for plugin use, such as junitxml.

.. _`disable capturing`:

You can influence output capturing mechanisms from the command line:

.. code-block:: bash
pytest -s # disable all capturing
pytest --capture=sys # replace sys.stdout/stderr with in-mem files
pytest --capture=fd # also point filedescriptors 1 and 2 to temp file
pytest -s # disable all capturing
pytest --capture=sys # replace sys.stdout/stderr with in-mem files
pytest --capture=fd # also point filedescriptors 1 and 2 to temp file
pytest --capture=tee-stdio # 'combines sys and -s' by both capturing sys.stdout/stderr
# and passing it along to the actual sys.stdout/stderr
.. _printdebugging:

Expand Down
6 changes: 3 additions & 3 deletions src/_pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def pytest_addoption(parser):
action="store",
default="fd" if hasattr(os, "dup") else "sys",
metavar="method",
choices=["fd", "sys", "no", "tee-stdui"],
help="per-test capturing method: one of fd|sys|no|tee-stdui.",
choices=["fd", "sys", "no", "tee-stdio"],
help="per-test capturing method: one of fd|sys|no|tee-stdio.",
)
group._addoption(
"-s",
Expand Down Expand Up @@ -90,7 +90,7 @@ def _getcapture(self, method):
return MultiCapture(out=True, err=True, Capture=SysCapture)
elif method == "no":
return MultiCapture(out=False, err=False, in_=False)
elif method == "tee-stdui":
elif method == "tee-stdio":
return MultiCapture(out=True, err=True, in_=False, Capture=TeeSysCapture)
raise ValueError("unknown capturing method: %r" % method) # pragma: no cover

Expand Down
8 changes: 2 additions & 6 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,14 +371,10 @@ def getvalue(self) -> str:
assert isinstance(self.buffer, io.BytesIO)
return self.buffer.getvalue().decode("UTF-8")

class CaptureAndPassthroughIO(io.TextIOWrapper):
class CaptureAndPassthroughIO(CaptureIO):
def __init__(self, other) -> None:
self._other = other
super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True)

def getvalue(self) -> str:
assert isinstance(self.buffer, io.BytesIO)
return self.buffer.getvalue().decode("UTF-8")
super().__init__()

def write(self, s) -> int:
super().write(s)
Expand Down
42 changes: 41 additions & 1 deletion testing/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ def StdCapture(out=True, err=True, in_=True):
return capture.MultiCapture(out, err, in_, Capture=capture.SysCapture)


def TeeStdCapture(out=True, err=True, in_=True):
return capture.MultiCapture(out, err, in_, Capture=capture.TeeSysCapture)


class TestCaptureManager:
def test_getmethod_default_no_fd(self, monkeypatch):
from _pytest.capture import pytest_addoption
Expand Down Expand Up @@ -816,6 +820,26 @@ def test_write_bytes_to_buffer(self):
assert f.getvalue() == "foo\r\n"


class TestCaptureAndPassthroughIO(TestCaptureIO):
def test_text(self):
sio = io.StringIO()
f = capture.CaptureAndPassthroughIO(sio)
f.write("hello")
s1 = f.getvalue()
assert s1 == "hello"
s2 = sio.getvalue()
assert s2 == s1
f.close()
sio.close()

def test_unicode_and_str_mixture(self):
sio = io.StringIO()
f = capture.CaptureAndPassthroughIO(sio)
f.write("\u00f6")
pytest.raises(TypeError, f.write, b"hello")



def test_dontreadfrominput():
from _pytest.capture import DontReadFromInput

Expand Down Expand Up @@ -1112,6 +1136,22 @@ def test_stdin_nulled_by_default(self):
pytest.raises(IOError, sys.stdin.read)


class TestTeeStdCapture(TestStdCapture):
captureclass = staticmethod(TeeStdCapture)

def test_capturing_error_recursive(self):
''' for TeeStdCapture since we passthrough stderr/stdout, cap1
should get all output, while cap2 should only get "cap2\n" '''

with self.getcapture() as cap1:
print("cap1")
with self.getcapture() as cap2:
print("cap2")
out2, err2 = cap2.readouterr()
out1, err1 = cap1.readouterr()
assert out1 == "cap1\ncap2\n"
assert out2 == "cap2\n"

class TestStdCaptureFD(TestStdCapture):
pytestmark = needsosdup
captureclass = staticmethod(StdCaptureFD)
Expand Down Expand Up @@ -1252,7 +1292,7 @@ def test_capture_again():
)


@pytest.mark.parametrize("method", ["SysCapture", "FDCapture"])
@pytest.mark.parametrize("method", ["SysCapture", "FDCapture", "TeeSysCapture"])
def test_capturing_and_logging_fundamentals(testdir, method):
if method == "StdCaptureFD" and not hasattr(os, "dup"):
pytest.skip("need os.dup")
Expand Down

0 comments on commit 84bfb63

Please sign in to comment.