diff --git a/src/slipcover/__main__.py b/src/slipcover/__main__.py index 5c269f3..a94f379 100644 --- a/src/slipcover/__main__.py +++ b/src/slipcover/__main__.py @@ -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: diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 1efdd99..5059e45 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -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 @@ -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)