Skip to content

Commit

Permalink
- improving handling the case where a child process closes the
Browse files Browse the repository at this point in the history
  coverage file before writing anything to it (typical of a fork-exec);
  • Loading branch information
jaltmayerpizzorno committed Jun 7, 2024
1 parent da19450 commit 594c319
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 3 deletions.
7 changes: 5 additions & 2 deletions src/slipcover/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ def get_coverage(sci):
for f in input_tmpfiles:
try:
fname = f.name
f.seek(0)
sc.merge_coverage(cov, json.load(f))
f.seek(0, os.SEEK_END)
# If the file is empty, it was likely closed, possibly upon exec
if f.tell() != 0:
f.seek(0)
sc.merge_coverage(cov, json.load(f))
except json.JSONDecodeError as e:
warnings.warn(f"Error reading {fname}: {e}")
finally:
Expand Down
44 changes: 43 additions & 1 deletion tests/test_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,12 +937,12 @@ def test_pytest_forked(tmp_path):
def test_forked_twice(tmp_path, monkeypatch):
source = (Path('tests') / 'pyt.py').resolve()
out = tmp_path / "out.json"
script = tmp_path / "foo.py"

monkeypatch.chdir(tmp_path)
test_file = 't.py'
Path(test_file).write_text(source.read_text())

script = tmp_path / "foo.py"
script.write_text(f"""\
import os
import sys
Expand Down Expand Up @@ -977,6 +977,48 @@ def test_forked_twice(tmp_path, monkeypatch):
assert [] == cov['missing_lines']


@pytest.mark.skipif(sys.platform == 'win32', reason='fork() and and other functions are Unix-specific')
def test_fork_close(tmp_path, monkeypatch, capfd):
source = (Path('tests') / 'pyt.py').resolve()
out = tmp_path / "out.json"

script = tmp_path / "foo.py"
script.write_text("""\
import os
import sys
if (pid := os.fork()):
pid, status = os.waitpid(pid, 0)
if status:
if os.WIFSIGNALED(status):
exitstatus = os.WTERMSIG(status) + 128
else:
exitstatus = os.WEXITSTATUS(status)
else:
exitstatus = 0
sys.exit(exitstatus)
else:
os.closerange(3, os.sysconf("SC_OPEN_MAX")) #16
""")

# don't use capture_output here to let pytest manage/display the output.
subprocess.run([sys.executable, '-m', 'slipcover', '--debug', '--json', '--out', str(out), script])

with out.open() as f:
cov = json.load(f)

# no warnings about not being able to read from subprocess JSON coverage file
assert capfd.readouterr().err == ""

check_summaries(cov)

script = str(script)
assert script in cov['files']
cov = cov['files'][script]
assert 16 not in cov['executed_lines']


def test_merge_flag(cov_merge_fixture):
subprocess.run([sys.executable, '-m', 'slipcover', '--branch',
'--json', '--out', "a.json", "t.py"], check=True)
Expand Down

0 comments on commit 594c319

Please sign in to comment.