Skip to content

Commit

Permalink
fix: save coverage data on SIGTERM (#1600)
Browse files Browse the repository at this point in the history
* Add test that reproduces the issue

* Suggested fix - always save data in sigterm exit flow

* Address test failures on MacOS due to lack of 'Terminated' output on SIGTERM
  • Loading branch information
LewisGaul authored Apr 6, 2023
1 parent 3bc7d2c commit f27c9ca
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 2 deletions.
2 changes: 1 addition & 1 deletion coverage/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 653,7 @@ def _atexit(self, event: str = "atexit") -> None:
self._debug.write(f"{event}: pid: {os.getpid()}, instance: {self!r}")
if self._started:
self.stop()
if self._auto_save:
if self._auto_save or event == "sigterm":
self.save()

def _on_sigterm(self, signum_unused: int, frame_unused: Optional[FrameType]) -> None:
Expand Down
27 changes: 26 additions & 1 deletion tests/test_concurrency.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 705,7 @@ class SigtermTest(CoverageTest):
"""Tests of our handling of SIGTERM."""

@pytest.mark.parametrize("sigterm", [False, True])
def test_sigterm_saves_data(self, sigterm: bool) -> None:
def test_sigterm_multiprocessing_saves_data(self, sigterm: bool) -> None:
# A terminated process should save its coverage data.
self.make_file("clobbered.py", """\
import multiprocessing
Expand Down Expand Up @@ -751,6 751,31 @@ def subproc(x):
expected = "clobbered.py 17 5 71% 5-10"
assert self.squeezed_lines(out)[2] == expected

def test_sigterm_threading_saves_data(self) -> None:
# A terminated process should save its coverage data.
self.make_file("handler.py", """\
import os, signal
print("START", flush=True)
print("SIGTERM", flush=True)
os.kill(os.getpid(), signal.SIGTERM)
print("NOT HERE", flush=True)
""")
self.make_file(".coveragerc", """\
[run]
# The default concurrency option.
concurrency = thread
sigterm = true
""")
out = self.run_command("coverage run handler.py")
if env.LINUX:
assert out == "START\nSIGTERM\nTerminated\n"
else:
assert out == "START\nSIGTERM\n"
out = self.run_command("coverage report -m")
expected = "handler.py 5 1 80% 6"
assert self.squeezed_lines(out)[2] == expected

def test_sigterm_still_runs(self) -> None:
# A terminated process still runs its own SIGTERM handler.
self.make_file("handler.py", """\
Expand Down

0 comments on commit f27c9ca

Please sign in to comment.