summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLewis Gaul <lewis.gaul@gmail.com>2023-04-06 11:58:16 +0100
committerGitHub <noreply@github.com>2023-04-06 03:58:16 -0700
commitf27c9ca775173d32bf71aeca9075c70f893b6542 (patch)
tree7c1499d131bc74dc7fa9a5af4a1b2916a929a669
parent3bc7d2ccdde692e6a5b968bb750751acc54b82a5 (diff)
downloadpython-coveragepy-git-f27c9ca775173d32bf71aeca9075c70f893b6542.tar.gz
fix: save coverage data on SIGTERM (#1600)
* 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
-rw-r--r--coverage/control.py2
-rw-r--r--tests/test_concurrency.py27
2 files changed, 27 insertions, 2 deletions
diff --git a/coverage/control.py b/coverage/control.py
index acce622d..e405a5bf 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -653,7 +653,7 @@ class Coverage(TConfigurable):
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:
diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py
index 9f12e77e..a9b64d15 100644
--- a/tests/test_concurrency.py
+++ b/tests/test_concurrency.py
@@ -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
@@ -751,6 +751,31 @@ class SigtermTest(CoverageTest):
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", """\