From fcc3d46587fd271ec5bdecebdec3fcca955a7a3f Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Wed, 28 Aug 2024 14:33:57 -0500 Subject: [PATCH 1/4] Modify exit code if cmd terminated by signal Fixes: https://github.com/con/duct/issues/148 --- src/con_duct/__main__.py | 4 ++++ test/test_execution.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/con_duct/__main__.py b/src/con_duct/__main__.py index 8d81cf8a..ab09bd8e 100644 --- a/src/con_duct/__main__.py +++ b/src/con_duct/__main__.py @@ -436,6 +436,10 @@ def write_subreport(self) -> None: @property def execution_summary(self) -> dict[str, Any]: + # killed by a signal + # https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_08_02 + if self.process and self.process.returncode < 0: + self.process.returncode = 128 + abs(self.process.returncode) # prepare the base, but enrich if we did get process running return { "exit_code": self.process.returncode if self.process else None, diff --git a/test/test_execution.py b/test/test_execution.py index a0d1cc4c..e2b6bb78 100644 --- a/test/test_execution.py +++ b/test/test_execution.py @@ -2,7 +2,10 @@ import json import os from pathlib import Path -from time import time +import signal +import subprocess +import threading +from time import sleep, time import pytest from utils import assert_files from con_duct.__main__ import SUFFIXES, Arguments, Outputs, execute @@ -193,3 +196,29 @@ def test_execute_unknown_command( assert execute(args) == 127 assert f"{cmd}: command not found\n" == capsys.readouterr().err assert_expected_files(temp_output_dir, exists=False) + + +def test_signal_exit(temp_output_dir: str) -> None: + + def runner() -> int: + args = Arguments.from_argv( + ["sleep", "60"], + output_prefix=temp_output_dir, + ) + return execute(args) + + thread = threading.Thread(target=runner) + thread.start() + sleep(0.01) # make sure the process is started + ps_command = "ps aux | grep '[s]leep 60'" # brackets to not match grep process + ps_output = subprocess.check_output(ps_command, shell=True).decode() + pid = int(ps_output.split()[1]) + os.kill(pid, signal.SIGTERM) + + thread.join() + # Cannot retrieve the exit code from the thread, it is written to the file + with open(os.path.join(temp_output_dir, SUFFIXES["info"])) as info: + info_data = json.loads(info.read()) + + exit_code = info_data["execution_summary"]["exit_code"] + assert exit_code == 128 + 15 From 9b359b1a30f600872474676671a8182add914d29 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Tue, 10 Sep 2024 09:57:24 -0500 Subject: [PATCH 2/4] Fixup: mock process needs int returncode --- test/test_report.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_report.py b/test/test_report.py index b2177092..31084679 100644 --- a/test/test_report.py +++ b/test/test_report.py @@ -168,6 +168,7 @@ def test_execution_summary_formatted( # Test with process report.process = mock_popen + report.process.returncode = 0 output = report.execution_summary_formatted assert "None" not in output assert "unknown" in output From a6637ed2c2dcab9794c0ac2e852d2dd896aef2ba Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Tue, 10 Sep 2024 09:59:48 -0500 Subject: [PATCH 3/4] use long float to reduce chance of collision --- test/test_execution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_execution.py b/test/test_execution.py index e2b6bb78..352b3878 100644 --- a/test/test_execution.py +++ b/test/test_execution.py @@ -202,7 +202,7 @@ def test_signal_exit(temp_output_dir: str) -> None: def runner() -> int: args = Arguments.from_argv( - ["sleep", "60"], + ["sleep", "60.74016230000801"], output_prefix=temp_output_dir, ) return execute(args) @@ -210,7 +210,7 @@ def runner() -> int: thread = threading.Thread(target=runner) thread.start() sleep(0.01) # make sure the process is started - ps_command = "ps aux | grep '[s]leep 60'" # brackets to not match grep process + ps_command = "ps aux | grep '[s]leep 60.74016230000801'" # brackets to not match grep process ps_output = subprocess.check_output(ps_command, shell=True).decode() pid = int(ps_output.split()[1]) os.kill(pid, signal.SIGTERM) From 4742aea58ef7dff5e6baf26556bdc8f3ef35287b Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Wed, 11 Sep 2024 09:33:19 -0500 Subject: [PATCH 4/4] Give more time to start process On PyPy (3.8, 3.9, 3.10) 0.01s was not long enough --- test/test_execution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_execution.py b/test/test_execution.py index 352b3878..21e1a1aa 100644 --- a/test/test_execution.py +++ b/test/test_execution.py @@ -209,7 +209,7 @@ def runner() -> int: thread = threading.Thread(target=runner) thread.start() - sleep(0.01) # make sure the process is started + sleep(0.03) # make sure the process is started ps_command = "ps aux | grep '[s]leep 60.74016230000801'" # brackets to not match grep process ps_output = subprocess.check_output(ps_command, shell=True).decode() pid = int(ps_output.split()[1])