Skip to content

Commit f5a5eea

Browse files
committed
wip: patch os._exit #310
1 parent 9a97393 commit f5a5eea

File tree

4 files changed

+70
-0
lines changed

4 files changed

+70
-0
lines changed

coverage/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ def __init__(self) -> None:
207207
self.disable_warnings: list[str] = []
208208
self.dynamic_context: str | None = None
209209
self.parallel = False
210+
self.patch: list[str] = []
210211
self.plugins: list[str] = []
211212
self.relative_files = False
212213
self.run_include: list[str] = []
@@ -267,6 +268,7 @@ def __init__(self) -> None:
267268
"debug", "concurrency", "plugins",
268269
"report_omit", "report_include",
269270
"run_omit", "run_include",
271+
"patch",
270272
}
271273

272274
def from_args(self, **kwargs: TConfigValueIn) -> None:
@@ -390,6 +392,7 @@ def copy(self) -> CoverageConfig:
390392
("disable_warnings", "run:disable_warnings", "list"),
391393
("dynamic_context", "run:dynamic_context"),
392394
("parallel", "run:parallel", "boolean"),
395+
("patch", "run:patch", "list"),
393396
("plugins", "run:plugins", "list"),
394397
("relative_files", "run:relative_files", "boolean"),
395398
("run_include", "run:include", "list"),

coverage/control.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from coverage.misc import bool_or_none, join_regex
4343
from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
4444
from coverage.multiproc import patch_multiprocessing
45+
from coverage.patch import apply_patches
4546
from coverage.plugin import FileReporter
4647
from coverage.plugin_support import Plugins, TCoverageInit
4748
from coverage.python import PythonFileReporter
@@ -677,6 +678,8 @@ def start(self) -> None:
677678
if self._auto_load:
678679
self.load()
679680

681+
apply_patches(self, self.config)
682+
680683
self._collector.start()
681684
self._started = True
682685
self._instances.append(self)

coverage/patch.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2+
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3+
4+
"""Invasive patches for coverage.py"""
5+
6+
from __future__ import annotations
7+
8+
import os
9+
10+
from typing import NoReturn, TYPE_CHECKING
11+
12+
if TYPE_CHECKING:
13+
from coverage import Coverage
14+
from coverage.config import CoverageConfig
15+
16+
def apply_patches(cov: Coverage, config: CoverageConfig) -> None:
17+
"""Apply invasive patches requested by `[run] patch=`."""
18+
19+
if "os._exit" in config.patch:
20+
_old_os_exit = os._exit
21+
def _new_os_exit(status: int) -> NoReturn:
22+
try:
23+
cov.save()
24+
except: # pylint: disable=bare-except
25+
pass
26+
_old_os_exit(status)
27+
os._exit = _new_os_exit

tests/test_process.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,43 @@ def test_fork(self) -> None:
421421
reported_pids = {line.split(".")[0] for line in debug_text.splitlines()}
422422
assert len(reported_pids) == 2
423423

424+
@pytest.mark.skipif(not hasattr(os, "fork"), reason="Can't test os.fork, it doesn't exist.")
425+
@pytest.mark.parametrize("patch", [False, True])
426+
def test_os_exit(self, patch: bool) -> None:
427+
self.make_file("forky.py", """\
428+
import os
429+
import tempfile
430+
import time
431+
432+
complete_file = tempfile.mkstemp()[1]
433+
pid = os.fork()
434+
if pid:
435+
while pid: # 3.9 wouldn't count "while True": change this. PYVERSION
436+
with open(complete_file, encoding="ascii") as f:
437+
data = f.read()
438+
if "Complete" in data:
439+
break
440+
time.sleep(.02)
441+
os.remove(complete_file)
442+
else:
443+
time.sleep(.1)
444+
with open(complete_file, mode="w", encoding="ascii") as f:
445+
f.write("Complete")
446+
os._exit(0)
447+
""")
448+
total_lines = 17
449+
if patch:
450+
self.make_file(".coveragerc", "[run]\npatch = os._exit\n")
451+
self.run_command("coverage run -p forky.py")
452+
self.run_command("coverage combine")
453+
data = coverage.CoverageData()
454+
data.read()
455+
seen = line_counts(data)["forky.py"]
456+
if patch:
457+
assert seen == total_lines
458+
else:
459+
assert seen < total_lines
460+
424461
def test_warnings_during_reporting(self) -> None:
425462
# While fixing issue #224, the warnings were being printed far too
426463
# often. Make sure they're not any more.

0 commit comments

Comments
 (0)