Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
UNRELEASED
==========

* Fix unclosed stdout/stderr files created by forked worker
(`#106 <https://github.com/pytest-dev/pytest-forked/issues/106>`__).

v1.7.5
======

Expand Down
12 changes: 10 additions & 2 deletions src/pytest_forked/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,11 @@ def _worker(runforked_fn, stdout_path, stderr_path, retval_path):
os.close(stderr_fd)

# redirect Python-level streams so print() etc. work
sys.stdout = open(stdout_path, "w", buffering=1)
sys.stderr = open(stderr_path, "w", buffering=1)
# keep handles to close below: capture swaps sys streams without closing
stdout_file = open(stdout_path, "w", buffering=1)
stderr_file = open(stderr_path, "w", buffering=1)
sys.stdout = stdout_file
sys.stderr = stderr_file

try:
retval = runforked_fn()
Expand All @@ -104,6 +107,11 @@ def _worker(runforked_fn, stdout_path, stderr_path, retval_path):
sys.stderr.flush()
except Exception:
pass
for stream in (stdout_file, stderr_file):
try:
stream.close()
except Exception:
pass

os._exit(0)

Expand Down
21 changes: 21 additions & 0 deletions testing/test_boxed.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,27 @@ def test_other_module():
assert result.ret == 0


@needsfork
@pytest.mark.parametrize("capmode", ["fd", "sys"])
def test_forked_no_unclosed_output_warning(testdir, capmode):
testdir.makeini("[pytest]\nfilterwarnings = error\n")
p1 = testdir.makepyfile(
"""
import pytest

@pytest.mark.forked
def test_forked_pass():
assert True

def test_after_forked():
assert True
"""
)
result = testdir.runpytest(p1, "--capture=%s" % capmode)
result.assert_outcomes(passed=2)
assert result.ret == 0


def test_is_not_boxed_by_default(testdir):
config = testdir.parseconfig(testdir.tmpdir)
assert not config.option.forked