Type annotate _pytest.capture

This commit is contained in:
Ran Benita 2020-05-01 14:40:16 +03:00
parent b51ea4f1a5
commit 3e351afeb3
1 changed files with 59 additions and 53 deletions

View File

@ -9,9 +9,11 @@ import os
import sys import sys
from io import UnsupportedOperation from io import UnsupportedOperation
from tempfile import TemporaryFile from tempfile import TemporaryFile
from typing import Generator
from typing import Optional from typing import Optional
from typing import TextIO from typing import TextIO
from typing import Tuple from typing import Tuple
from typing import Union
import pytest import pytest
from _pytest.compat import TYPE_CHECKING from _pytest.compat import TYPE_CHECKING
@ -46,7 +48,7 @@ def pytest_addoption(parser: Parser) -> None:
) )
def _colorama_workaround(): def _colorama_workaround() -> None:
""" """
Ensure colorama is imported so that it attaches to the correct stdio Ensure colorama is imported so that it attaches to the correct stdio
handles on Windows. handles on Windows.
@ -62,7 +64,7 @@ def _colorama_workaround():
pass pass
def _readline_workaround(): def _readline_workaround() -> None:
""" """
Ensure readline is imported so that it attaches to the correct stdio Ensure readline is imported so that it attaches to the correct stdio
handles on Windows. handles on Windows.
@ -87,7 +89,7 @@ def _readline_workaround():
pass pass
def _py36_windowsconsoleio_workaround(stream): def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
""" """
Python 3.6 implemented unicode console handling for Windows. This works Python 3.6 implemented unicode console handling for Windows. This works
by reading/writing to the raw console handle using by reading/writing to the raw console handle using
@ -202,7 +204,7 @@ class TeeCaptureIO(CaptureIO):
self._other = other self._other = other
super().__init__() super().__init__()
def write(self, s) -> int: def write(self, s: str) -> int:
super().write(s) super().write(s)
return self._other.write(s) return self._other.write(s)
@ -222,13 +224,13 @@ class DontReadFromInput:
def __iter__(self): def __iter__(self):
return self return self
def fileno(self): def fileno(self) -> int:
raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()") raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()")
def isatty(self): def isatty(self) -> bool:
return False return False
def close(self): def close(self) -> None:
pass pass
@property @property
@ -251,7 +253,7 @@ class SysCaptureBinary:
EMPTY_BUFFER = b"" EMPTY_BUFFER = b""
def __init__(self, fd, tmpfile=None, *, tee=False): def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None:
name = patchsysdict[fd] name = patchsysdict[fd]
self._old = getattr(sys, name) self._old = getattr(sys, name)
self.name = name self.name = name
@ -288,7 +290,7 @@ class SysCaptureBinary:
op, self._state, ", ".join(states) op, self._state, ", ".join(states)
) )
def start(self): def start(self) -> None:
self._assert_state("start", ("initialized",)) self._assert_state("start", ("initialized",))
setattr(sys, self.name, self.tmpfile) setattr(sys, self.name, self.tmpfile)
self._state = "started" self._state = "started"
@ -301,7 +303,7 @@ class SysCaptureBinary:
self.tmpfile.truncate() self.tmpfile.truncate()
return res return res
def done(self): def done(self) -> None:
self._assert_state("done", ("initialized", "started", "suspended", "done")) self._assert_state("done", ("initialized", "started", "suspended", "done"))
if self._state == "done": if self._state == "done":
return return
@ -310,19 +312,19 @@ class SysCaptureBinary:
self.tmpfile.close() self.tmpfile.close()
self._state = "done" self._state = "done"
def suspend(self): def suspend(self) -> None:
self._assert_state("suspend", ("started", "suspended")) self._assert_state("suspend", ("started", "suspended"))
setattr(sys, self.name, self._old) setattr(sys, self.name, self._old)
self._state = "suspended" self._state = "suspended"
def resume(self): def resume(self) -> None:
self._assert_state("resume", ("started", "suspended")) self._assert_state("resume", ("started", "suspended"))
if self._state == "started": if self._state == "started":
return return
setattr(sys, self.name, self.tmpfile) setattr(sys, self.name, self.tmpfile)
self._state = "started" self._state = "started"
def writeorg(self, data): def writeorg(self, data) -> None:
self._assert_state("writeorg", ("started", "suspended")) self._assert_state("writeorg", ("started", "suspended"))
self._old.flush() self._old.flush()
self._old.buffer.write(data) self._old.buffer.write(data)
@ -352,7 +354,7 @@ class FDCaptureBinary:
EMPTY_BUFFER = b"" EMPTY_BUFFER = b""
def __init__(self, targetfd): def __init__(self, targetfd: int) -> None:
self.targetfd = targetfd self.targetfd = targetfd
try: try:
@ -369,7 +371,9 @@ class FDCaptureBinary:
# Further complications are the need to support suspend() and the # Further complications are the need to support suspend() and the
# possibility of FD reuse (e.g. the tmpfile getting the very same # possibility of FD reuse (e.g. the tmpfile getting the very same
# target FD). The following approach is robust, I believe. # target FD). The following approach is robust, I believe.
self.targetfd_invalid = os.open(os.devnull, os.O_RDWR) self.targetfd_invalid = os.open(
os.devnull, os.O_RDWR
) # type: Optional[int]
os.dup2(self.targetfd_invalid, targetfd) os.dup2(self.targetfd_invalid, targetfd)
else: else:
self.targetfd_invalid = None self.targetfd_invalid = None
@ -380,7 +384,8 @@ class FDCaptureBinary:
self.syscapture = SysCapture(targetfd) self.syscapture = SysCapture(targetfd)
else: else:
self.tmpfile = EncodedFile( self.tmpfile = EncodedFile(
TemporaryFile(buffering=0), # TODO: Remove type ignore, fixed in next mypy release.
TemporaryFile(buffering=0), # type: ignore[arg-type]
encoding="utf-8", encoding="utf-8",
errors="replace", errors="replace",
write_through=True, write_through=True,
@ -392,7 +397,7 @@ class FDCaptureBinary:
self._state = "initialized" self._state = "initialized"
def __repr__(self): def __repr__(self) -> str:
return "<{} {} oldfd={} _state={!r} tmpfile={!r}>".format( return "<{} {} oldfd={} _state={!r} tmpfile={!r}>".format(
self.__class__.__name__, self.__class__.__name__,
self.targetfd, self.targetfd,
@ -408,7 +413,7 @@ class FDCaptureBinary:
op, self._state, ", ".join(states) op, self._state, ", ".join(states)
) )
def start(self): def start(self) -> None:
""" Start capturing on targetfd using memorized tmpfile. """ """ Start capturing on targetfd using memorized tmpfile. """
self._assert_state("start", ("initialized",)) self._assert_state("start", ("initialized",))
os.dup2(self.tmpfile.fileno(), self.targetfd) os.dup2(self.tmpfile.fileno(), self.targetfd)
@ -423,7 +428,7 @@ class FDCaptureBinary:
self.tmpfile.truncate() self.tmpfile.truncate()
return res return res
def done(self): def done(self) -> None:
""" stop capturing, restore streams, return original capture file, """ stop capturing, restore streams, return original capture file,
seeked to position zero. """ seeked to position zero. """
self._assert_state("done", ("initialized", "started", "suspended", "done")) self._assert_state("done", ("initialized", "started", "suspended", "done"))
@ -439,7 +444,7 @@ class FDCaptureBinary:
self.tmpfile.close() self.tmpfile.close()
self._state = "done" self._state = "done"
def suspend(self): def suspend(self) -> None:
self._assert_state("suspend", ("started", "suspended")) self._assert_state("suspend", ("started", "suspended"))
if self._state == "suspended": if self._state == "suspended":
return return
@ -447,7 +452,7 @@ class FDCaptureBinary:
os.dup2(self.targetfd_save, self.targetfd) os.dup2(self.targetfd_save, self.targetfd)
self._state = "suspended" self._state = "suspended"
def resume(self): def resume(self) -> None:
self._assert_state("resume", ("started", "suspended")) self._assert_state("resume", ("started", "suspended"))
if self._state == "started": if self._state == "started":
return return
@ -497,12 +502,12 @@ class MultiCapture:
self.out = out self.out = out
self.err = err self.err = err
def __repr__(self): def __repr__(self) -> str:
return "<MultiCapture out={!r} err={!r} in_={!r} _state={!r} _in_suspended={!r}>".format( return "<MultiCapture out={!r} err={!r} in_={!r} _state={!r} _in_suspended={!r}>".format(
self.out, self.err, self.in_, self._state, self._in_suspended, self.out, self.err, self.in_, self._state, self._in_suspended,
) )
def start_capturing(self): def start_capturing(self) -> None:
self._state = "started" self._state = "started"
if self.in_: if self.in_:
self.in_.start() self.in_.start()
@ -520,7 +525,7 @@ class MultiCapture:
self.err.writeorg(err) self.err.writeorg(err)
return out, err return out, err
def suspend_capturing(self, in_=False): def suspend_capturing(self, in_: bool = False) -> None:
self._state = "suspended" self._state = "suspended"
if self.out: if self.out:
self.out.suspend() self.out.suspend()
@ -530,7 +535,7 @@ class MultiCapture:
self.in_.suspend() self.in_.suspend()
self._in_suspended = True self._in_suspended = True
def resume_capturing(self): def resume_capturing(self) -> None:
self._state = "resumed" self._state = "resumed"
if self.out: if self.out:
self.out.resume() self.out.resume()
@ -540,7 +545,7 @@ class MultiCapture:
self.in_.resume() self.in_.resume()
self._in_suspended = False self._in_suspended = False
def stop_capturing(self): def stop_capturing(self) -> None:
""" stop capturing and reset capturing streams """ """ stop capturing and reset capturing streams """
if self._state == "stopped": if self._state == "stopped":
raise ValueError("was already stopped") raise ValueError("was already stopped")
@ -596,15 +601,15 @@ class CaptureManager:
def __init__(self, method: "_CaptureMethod") -> None: def __init__(self, method: "_CaptureMethod") -> None:
self._method = method self._method = method
self._global_capturing = None self._global_capturing = None # type: Optional[MultiCapture]
self._capture_fixture = None # type: Optional[CaptureFixture] self._capture_fixture = None # type: Optional[CaptureFixture]
def __repr__(self): def __repr__(self) -> str:
return "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".format( return "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".format(
self._method, self._global_capturing, self._capture_fixture self._method, self._global_capturing, self._capture_fixture
) )
def is_capturing(self): def is_capturing(self) -> Union[str, bool]:
if self.is_globally_capturing(): if self.is_globally_capturing():
return "global" return "global"
if self._capture_fixture: if self._capture_fixture:
@ -613,40 +618,41 @@ class CaptureManager:
# Global capturing control # Global capturing control
def is_globally_capturing(self): def is_globally_capturing(self) -> bool:
return self._method != "no" return self._method != "no"
def start_global_capturing(self): def start_global_capturing(self) -> None:
assert self._global_capturing is None assert self._global_capturing is None
self._global_capturing = _get_multicapture(self._method) self._global_capturing = _get_multicapture(self._method)
self._global_capturing.start_capturing() self._global_capturing.start_capturing()
def stop_global_capturing(self): def stop_global_capturing(self) -> None:
if self._global_capturing is not None: if self._global_capturing is not None:
self._global_capturing.pop_outerr_to_orig() self._global_capturing.pop_outerr_to_orig()
self._global_capturing.stop_capturing() self._global_capturing.stop_capturing()
self._global_capturing = None self._global_capturing = None
def resume_global_capture(self): def resume_global_capture(self) -> None:
# During teardown of the python process, and on rare occasions, capture # During teardown of the python process, and on rare occasions, capture
# attributes can be `None` while trying to resume global capture. # attributes can be `None` while trying to resume global capture.
if self._global_capturing is not None: if self._global_capturing is not None:
self._global_capturing.resume_capturing() self._global_capturing.resume_capturing()
def suspend_global_capture(self, in_=False): def suspend_global_capture(self, in_: bool = False) -> None:
if self._global_capturing is not None: if self._global_capturing is not None:
self._global_capturing.suspend_capturing(in_=in_) self._global_capturing.suspend_capturing(in_=in_)
def suspend(self, in_=False): def suspend(self, in_: bool = False) -> None:
# Need to undo local capsys-et-al if it exists before disabling global capture. # Need to undo local capsys-et-al if it exists before disabling global capture.
self.suspend_fixture() self.suspend_fixture()
self.suspend_global_capture(in_) self.suspend_global_capture(in_)
def resume(self): def resume(self) -> None:
self.resume_global_capture() self.resume_global_capture()
self.resume_fixture() self.resume_fixture()
def read_global_capture(self): def read_global_capture(self):
assert self._global_capturing is not None
return self._global_capturing.readouterr() return self._global_capturing.readouterr()
# Fixture Control # Fixture Control
@ -665,30 +671,30 @@ class CaptureManager:
def unset_fixture(self) -> None: def unset_fixture(self) -> None:
self._capture_fixture = None self._capture_fixture = None
def activate_fixture(self): def activate_fixture(self) -> None:
"""If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over """If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
the global capture. the global capture.
""" """
if self._capture_fixture: if self._capture_fixture:
self._capture_fixture._start() self._capture_fixture._start()
def deactivate_fixture(self): def deactivate_fixture(self) -> None:
"""Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any.""" """Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any."""
if self._capture_fixture: if self._capture_fixture:
self._capture_fixture.close() self._capture_fixture.close()
def suspend_fixture(self): def suspend_fixture(self) -> None:
if self._capture_fixture: if self._capture_fixture:
self._capture_fixture._suspend() self._capture_fixture._suspend()
def resume_fixture(self): def resume_fixture(self) -> None:
if self._capture_fixture: if self._capture_fixture:
self._capture_fixture._resume() self._capture_fixture._resume()
# Helper context managers # Helper context managers
@contextlib.contextmanager @contextlib.contextmanager
def global_and_fixture_disabled(self): def global_and_fixture_disabled(self) -> Generator[None, None, None]:
"""Context manager to temporarily disable global and current fixture capturing.""" """Context manager to temporarily disable global and current fixture capturing."""
self.suspend() self.suspend()
try: try:
@ -697,7 +703,7 @@ class CaptureManager:
self.resume() self.resume()
@contextlib.contextmanager @contextlib.contextmanager
def item_capture(self, when, item): def item_capture(self, when: str, item: Item) -> Generator[None, None, None]:
self.resume_global_capture() self.resume_global_capture()
self.activate_fixture() self.activate_fixture()
try: try:
@ -757,21 +763,21 @@ class CaptureFixture:
fixtures. fixtures.
""" """
def __init__(self, captureclass, request): def __init__(self, captureclass, request: SubRequest) -> None:
self.captureclass = captureclass self.captureclass = captureclass
self.request = request self.request = request
self._capture = None self._capture = None # type: Optional[MultiCapture]
self._captured_out = self.captureclass.EMPTY_BUFFER self._captured_out = self.captureclass.EMPTY_BUFFER
self._captured_err = self.captureclass.EMPTY_BUFFER self._captured_err = self.captureclass.EMPTY_BUFFER
def _start(self): def _start(self) -> None:
if self._capture is None: if self._capture is None:
self._capture = MultiCapture( self._capture = MultiCapture(
in_=None, out=self.captureclass(1), err=self.captureclass(2), in_=None, out=self.captureclass(1), err=self.captureclass(2),
) )
self._capture.start_capturing() self._capture.start_capturing()
def close(self): def close(self) -> None:
if self._capture is not None: if self._capture is not None:
out, err = self._capture.pop_outerr_to_orig() out, err = self._capture.pop_outerr_to_orig()
self._captured_out += out self._captured_out += out
@ -793,18 +799,18 @@ class CaptureFixture:
self._captured_err = self.captureclass.EMPTY_BUFFER self._captured_err = self.captureclass.EMPTY_BUFFER
return CaptureResult(captured_out, captured_err) return CaptureResult(captured_out, captured_err)
def _suspend(self): def _suspend(self) -> None:
"""Suspends this fixture's own capturing temporarily.""" """Suspends this fixture's own capturing temporarily."""
if self._capture is not None: if self._capture is not None:
self._capture.suspend_capturing() self._capture.suspend_capturing()
def _resume(self): def _resume(self) -> None:
"""Resumes this fixture's own capturing temporarily.""" """Resumes this fixture's own capturing temporarily."""
if self._capture is not None: if self._capture is not None:
self._capture.resume_capturing() self._capture.resume_capturing()
@contextlib.contextmanager @contextlib.contextmanager
def disabled(self): def disabled(self) -> Generator[None, None, None]:
"""Temporarily disables capture while inside the 'with' block.""" """Temporarily disables capture while inside the 'with' block."""
capmanager = self.request.config.pluginmanager.getplugin("capturemanager") capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
with capmanager.global_and_fixture_disabled(): with capmanager.global_and_fixture_disabled():
@ -815,7 +821,7 @@ class CaptureFixture:
@pytest.fixture @pytest.fixture
def capsys(request): def capsys(request: SubRequest):
"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsys.readouterr()`` method The captured output is made available via ``capsys.readouterr()`` method
@ -832,7 +838,7 @@ def capsys(request):
@pytest.fixture @pytest.fixture
def capsysbinary(request): def capsysbinary(request: SubRequest):
"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsysbinary.readouterr()`` The captured output is made available via ``capsysbinary.readouterr()``
@ -849,7 +855,7 @@ def capsysbinary(request):
@pytest.fixture @pytest.fixture
def capfd(request): def capfd(request: SubRequest):
"""Enable text capturing of writes to file descriptors ``1`` and ``2``. """Enable text capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method The captured output is made available via ``capfd.readouterr()`` method
@ -866,7 +872,7 @@ def capfd(request):
@pytest.fixture @pytest.fixture
def capfdbinary(request): def capfdbinary(request: SubRequest):
"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``. """Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method The captured output is made available via ``capfd.readouterr()`` method