diff options
author | Lewis Gaul <lewis.gaul@gmail.com> | 2023-04-06 11:58:16 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-06 03:58:16 -0700 |
commit | f27c9ca775173d32bf71aeca9075c70f893b6542 (patch) | |
tree | 7c1499d131bc74dc7fa9a5af4a1b2916a929a669 | |
parent | 3bc7d2ccdde692e6a5b968bb750751acc54b82a5 (diff) | |
download | python-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.py | 2 | ||||
-rw-r--r-- | tests/test_concurrency.py | 27 |
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", """\ |