Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

capture: fix disabled()/global_and_fixture_disabled() enabling capturing when it was disabled #7651

Merged
merged 1 commit into from Aug 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/7148.bugfix.rst
@@ -0,0 +1 @@
Fixed ``--log-cli`` potentially causing unrelated ``print`` output to be swallowed.
24 changes: 21 additions & 3 deletions src/_pytest/capture.py
Expand Up @@ -592,7 +592,7 @@ def suspend_capturing(self, in_: bool = False) -> None:
self._in_suspended = True

def resume_capturing(self) -> None:
self._state = "resumed"
self._state = "started"
if self.out:
self.out.resume()
if self.err:
Expand All @@ -613,6 +613,10 @@ def stop_capturing(self) -> None:
if self.in_:
self.in_.done()

def is_started(self) -> bool:
"""Whether actively capturing -- not suspended or stopped."""
return self._state == "started"

def readouterr(self) -> CaptureResult[AnyStr]:
if self.out:
out = self.out.snap()
Expand Down Expand Up @@ -757,11 +761,19 @@ def resume_fixture(self) -> None:
@contextlib.contextmanager
def global_and_fixture_disabled(self) -> Generator[None, None, None]:
"""Context manager to temporarily disable global and current fixture capturing."""
self.suspend()
do_fixture = self._capture_fixture and self._capture_fixture._is_started()
if do_fixture:
self.suspend_fixture()
do_global = self._global_capturing and self._global_capturing.is_started()
if do_global:
self.suspend_global_capture()
try:
yield
finally:
self.resume()
if do_global:
self.resume_global_capture()
if do_fixture:
self.resume_fixture()

@contextlib.contextmanager
def item_capture(self, when: str, item: Item) -> Generator[None, None, None]:
Expand Down Expand Up @@ -871,6 +883,12 @@ def _resume(self) -> None:
if self._capture is not None:
self._capture.resume_capturing()

def _is_started(self) -> bool:
"""Whether actively capturing -- not disabled or closed."""
if self._capture is not None:
return self._capture.is_started()
return False

@contextlib.contextmanager
def disabled(self) -> Generator[None, None, None]:
"""Temporarily disable capturing while inside the ``with`` block."""
Expand Down
29 changes: 29 additions & 0 deletions testing/test_capture.py
Expand Up @@ -17,6 +17,7 @@
from _pytest.capture import CaptureResult
from _pytest.capture import MultiCapture
from _pytest.config import ExitCode
from _pytest.pytester import Testdir

# note: py.io capture tests where copied from
# pylib 1.4.20.dev2 (rev 13d9af95547e)
Expand Down Expand Up @@ -640,6 +641,34 @@ def test_normal():
else:
result.stdout.no_fnmatch_line("*test_normal executed*")

def test_disabled_capture_fixture_twice(self, testdir: Testdir) -> None:
"""Test that an inner disabled() exit doesn't undo an outer disabled().

Issue #7148.
"""
testdir.makepyfile(
"""
def test_disabled(capfd):
print('captured before')
with capfd.disabled():
print('while capture is disabled 1')
with capfd.disabled():
print('while capture is disabled 2')
print('while capture is disabled 1 after')
print('captured after')
assert capfd.readouterr() == ('captured before\\ncaptured after\\n', '')
"""
)
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(
[
"*while capture is disabled 1",
"*while capture is disabled 2",
"*while capture is disabled 1 after",
],
consecutive=True,
)

@pytest.mark.parametrize("fixture", ["capsys", "capfd"])
def test_fixture_use_by_other_fixtures(self, testdir, fixture):
"""Ensure that capsys and capfd can be used by other fixtures during
Expand Down