From 5feabb5ad5e3922866f10de932d63fc267d9828a Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Fri, 19 Dec 2025 19:56:14 +0300 Subject: [PATCH 01/12] fix: LocalOsOperation::kill uses os.kill, arg "expect_error" is removed Changes: - pid and signal must be int - argument "expected_error" is not supported anymore (unification with RemoteOsOperation) - it uses os.kill function instead "kill" command from OS. This commit brokes applications where expect_error is passed (testgres 1.12.0, for example). --- src/local_ops.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/local_ops.py b/src/local_ops.py index a55270b..499eb6d 100644 --- a/src/local_ops.py +++ b/src/local_ops.py @@ -568,10 +568,11 @@ def remove_file(self, filename): return os.remove(filename) # Processes control - def kill(self, pid, signal, expect_error=False): + def kill(self, pid: int, signal: int): # Kill the process - cmd = "kill -{} {}".format(signal, pid) - return self.exec_command(cmd, expect_error=expect_error) + assert type(pid) == int # noqa: E721 + assert type(signal) == int # noqa: E721 + os.kill(pid, signal) def get_pid(self): # Get current process id From 5503513558a915cb92edf1f6f722a09d7080e70a Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Fri, 19 Dec 2025 19:57:13 +0300 Subject: [PATCH 02/12] refactoring: OsOperation::kill(self, pid: int, signal: int) pid and signal must be int --- src/os_ops.py | 4 +++- src/remote_ops.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/os_ops.py b/src/os_ops.py index 4642226..f43f8c7 100644 --- a/src/os_ops.py +++ b/src/os_ops.py @@ -126,8 +126,10 @@ def remove_file(self, filename): raise NotImplementedError() # Processes control - def kill(self, pid, signal): + def kill(self, pid: int, signal: int): # Kill the process + assert type(pid) == int # noqa: E721 + assert type(signal) == int # noqa: E721 raise NotImplementedError() def get_pid(self): diff --git a/src/remote_ops.py b/src/remote_ops.py index 7fcc642..42eede9 100644 --- a/src/remote_ops.py +++ b/src/remote_ops.py @@ -658,8 +658,10 @@ def remove_file(self, filename): return self.exec_command(cmd) # Processes control - def kill(self, pid, signal): + def kill(self, pid: int, signal: int): # Kill the process + assert type(pid) == int # noqa: E721 + assert type(signal) == int # noqa: E721 cmd = "kill -{} {}".format(signal, pid) return self.exec_command(cmd) From b23a80bc429be7bfcd31c4a375bab01679137824 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sat, 20 Dec 2025 02:01:25 +0300 Subject: [PATCH 03/12] os_ops.kill supports signal:typing.Union[int, os_signal.Signals] --- src/local_ops.py | 5 +++-- src/os_ops.py | 6 ++++-- src/remote_ops.py | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/local_ops.py b/src/local_ops.py index 499eb6d..ea246bf 100644 --- a/src/local_ops.py +++ b/src/local_ops.py @@ -14,6 +14,7 @@ import typing import threading import copy +import signal as os_signal from .exceptions import ExecUtilException from .exceptions import InvalidOperationException @@ -568,10 +569,10 @@ def remove_file(self, filename): return os.remove(filename) # Processes control - def kill(self, pid: int, signal: int): + def kill(self, pid: int, signal: typing.Union[int, os_signal.Signals]): # Kill the process assert type(pid) == int # noqa: E721 - assert type(signal) == int # noqa: E721 + assert type(signal) in [int, os_signal.Signals] # noqa: E721 os.kill(pid, signal) def get_pid(self): diff --git a/src/os_ops.py b/src/os_ops.py index f43f8c7..e06f908 100644 --- a/src/os_ops.py +++ b/src/os_ops.py @@ -1,6 +1,8 @@ from __future__ import annotations import locale +import typing +import signal as os_signal class ConnectionParams: @@ -126,10 +128,10 @@ def remove_file(self, filename): raise NotImplementedError() # Processes control - def kill(self, pid: int, signal: int): + def kill(self, pid: int, signal: typing.Union[int, os_signal.Signals]): # Kill the process assert type(pid) == int # noqa: E721 - assert type(signal) == int # noqa: E721 + assert type(signal) in [int, os_signal.Signals] # noqa: E721 raise NotImplementedError() def get_pid(self): diff --git a/src/remote_ops.py b/src/remote_ops.py index 42eede9..e5f7279 100644 --- a/src/remote_ops.py +++ b/src/remote_ops.py @@ -11,6 +11,7 @@ import typing import copy import re +import signal as os_signal from .exceptions import ExecUtilException from .exceptions import InvalidOperationException @@ -658,10 +659,10 @@ def remove_file(self, filename): return self.exec_command(cmd) # Processes control - def kill(self, pid: int, signal: int): + def kill(self, pid: int, signal: typing.Union[int, os_signal.Signals]): # Kill the process assert type(pid) == int # noqa: E721 - assert type(signal) == int # noqa: E721 + assert type(signal) in [int, os_signal.Signals] # noqa: E721 cmd = "kill -{} {}".format(signal, pid) return self.exec_command(cmd) From 6354710c87ea378e13ca9372945bb35d7ce10c3f Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sat, 20 Dec 2025 02:02:20 +0300 Subject: [PATCH 04/12] Tests for os_ops.kill are added --- tests/test_os_ops_common.py | 103 ++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index 5a797d4..c84a9af 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -5,6 +5,7 @@ from tests.helpers.run_conditions import RunConditions import os +import sys import pytest import re @@ -14,6 +15,10 @@ import threading import typing import uuid +import subprocess +import psutil +import time +import signal as os_signal from src.exceptions import InvalidOperationException from src.exceptions import ExecUtilException @@ -1137,3 +1142,101 @@ class tadWorkerData: logging.info("Test is finished! Total error count is {}.".format(nErrors)) return + + T_KILL_SIGNAL_DESCR = typing.Tuple[ + str, + typing.Union[int, os_signal.Signals], + str + ] + + sm_kill_signal_ids: typing.List[T_KILL_SIGNAL_DESCR] = [ + ("SIGKILL", os_signal.SIGKILL, "9"), + ("SIGQUIT", os_signal.SIGQUIT, "3"), + ("9", 9, "9"), + ("3", 3, "3"), + ] + + @pytest.fixture( + params=sm_kill_signal_ids, + ids=["signal: {}".format(x[0]) for x in sm_kill_signal_ids], + ) + def kill_signal_id(self, request: pytest.FixtureRequest) -> T_KILL_SIGNAL_DESCR: + assert isinstance(request, pytest.FixtureRequest) + assert type(request.param) == tuple # noqa: E721 + return request.param + + def test_kill_signal( + self, + kill_signal_id: T_KILL_SIGNAL_DESCR, + ): + assert type(kill_signal_id) == tuple # noqa: E721 + assert "{}".format(kill_signal_id[1]) == kill_signal_id[2] + + def test_kill( + self, + os_ops: OsOperations, + kill_signal_id: T_KILL_SIGNAL_DESCR, + ): + """ + Test listdir for listing directory contents. + """ + assert isinstance(os_ops, OsOperations) + assert type(kill_signal_id) == tuple # noqa: E721 + + cmd = [ + sys.executable, + "-c", + "import time; print('ENTER');time.sleep(300);print('EXIT')" + ] + + logging.info("Local test process is creating ...") + proc = subprocess.Popen( + cmd, + text=True, + ) + + assert proc is not None + assert type(proc) == subprocess.Popen # noqa: E721 + proc_pid = proc.pid + assert type(proc_pid) == int # noqa: E721 + logging.info("Test process pid is {}".format(proc_pid)) + + logging.info("Get this test process ...") + p1 = psutil.Process(proc_pid) + assert p1 is not None + del p1 + + logging.info("Kill this test process ...") + os_ops.kill(proc_pid, kill_signal_id[1]) + + logging.info("Wait for finish ...") + proc.wait() + + logging.info("Try to get this test process ...") + + attempt = 0 + while True: + if attempt == 20: + raise RuntimeError("Process did not die,") + + attempt += 1 + + if attempt > 1: + logging.info("Sleep 1 seconds...") + time.sleep(1) + + try: + psutil.Process(proc_pid) + except psutil.ZombieProcess as e: + logging.info("Exception {}: {}".format( + type(e).__name__, + str(e), + )) + except psutil.NoSuchProcess: + logging.info("OK. Process died.") + break + + logging.info("Process is alive!") + continue + + return From 4d8647c6b6fcd66b982663d28ea73bebb6da0797 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sat, 20 Dec 2025 11:58:40 +0300 Subject: [PATCH 05/12] test_kill is corrected (error message) --- tests/test_os_ops_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index c84a9af..140b1a9 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -1217,7 +1217,7 @@ def test_kill( attempt = 0 while True: if attempt == 20: - raise RuntimeError("Process did not die,") + raise RuntimeError("Process did not die.") attempt += 1 From 67dece31ef9987a23d76ee9ef96086f2f0acff9c Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 23 Dec 2025 10:50:39 +0300 Subject: [PATCH 06/12] [test] SIGQUIT creates an crashdump We will test SIGINT, SIGKILL, SIGTERM. --- tests/test_os_ops_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index 140b1a9..efa614d 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -1151,9 +1151,9 @@ class tadWorkerData: sm_kill_signal_ids: typing.List[T_KILL_SIGNAL_DESCR] = [ ("SIGKILL", os_signal.SIGKILL, "9"), - ("SIGQUIT", os_signal.SIGQUIT, "3"), + # ("SIGQUIT", os_signal.SIGQUIT, "3"), ("9", 9, "9"), - ("3", 3, "3"), + # ("3", 3, "3"), ] @pytest.fixture( From ba0e06c936c5a1503bd0e244fe8775c8f9a63d13 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 23 Dec 2025 11:02:56 +0300 Subject: [PATCH 07/12] test_kill: SIGINT and SIGTERM are added --- tests/test_os_ops_common.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index efa614d..71075e3 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -1150,9 +1150,13 @@ class tadWorkerData: ] sm_kill_signal_ids: typing.List[T_KILL_SIGNAL_DESCR] = [ + ("SIGINT", os_signal.SIGINT, "2"), ("SIGKILL", os_signal.SIGKILL, "9"), + ("SIGTERM", os_signal.SIGTERM, "15"), # ("SIGQUIT", os_signal.SIGQUIT, "3"), + ("2", 2, "2"), ("9", 9, "9"), + ("15", 15, "15"), # ("3", 3, "3"), ] From 7b3a70c41082906f400cdc8f7245fdee3a510af4 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 23 Dec 2025 11:06:02 +0300 Subject: [PATCH 08/12] os.kill is updated (asserts) --- src/local_ops.py | 2 +- src/os_ops.py | 2 +- src/remote_ops.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/local_ops.py b/src/local_ops.py index ea246bf..3aba4e8 100644 --- a/src/local_ops.py +++ b/src/local_ops.py @@ -572,7 +572,7 @@ def remove_file(self, filename): def kill(self, pid: int, signal: typing.Union[int, os_signal.Signals]): # Kill the process assert type(pid) == int # noqa: E721 - assert type(signal) in [int, os_signal.Signals] # noqa: E721 + assert type(signal) == int or type(signal) == os_signal.Signals # noqa: E721 E501 os.kill(pid, signal) def get_pid(self): diff --git a/src/os_ops.py b/src/os_ops.py index e06f908..da4ff83 100644 --- a/src/os_ops.py +++ b/src/os_ops.py @@ -131,7 +131,7 @@ def remove_file(self, filename): def kill(self, pid: int, signal: typing.Union[int, os_signal.Signals]): # Kill the process assert type(pid) == int # noqa: E721 - assert type(signal) in [int, os_signal.Signals] # noqa: E721 + assert type(signal) == int or type(signal) == os_signal.Signals # noqa: E721 E501 raise NotImplementedError() def get_pid(self): diff --git a/src/remote_ops.py b/src/remote_ops.py index e5f7279..939c728 100644 --- a/src/remote_ops.py +++ b/src/remote_ops.py @@ -662,7 +662,7 @@ def remove_file(self, filename): def kill(self, pid: int, signal: typing.Union[int, os_signal.Signals]): # Kill the process assert type(pid) == int # noqa: E721 - assert type(signal) in [int, os_signal.Signals] # noqa: E721 + assert type(signal) == int or type(signal) == os_signal.Signals # noqa: E721 E501 cmd = "kill -{} {}".format(signal, pid) return self.exec_command(cmd) From bacd128ceeb40440adc92dc2f04d22addf43cf97 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 23 Dec 2025 12:16:13 +0300 Subject: [PATCH 09/12] kill: int(signal) == signal --- src/remote_ops.py | 3 ++- tests/test_os_ops_common.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/remote_ops.py b/src/remote_ops.py index 939c728..32d2d07 100644 --- a/src/remote_ops.py +++ b/src/remote_ops.py @@ -663,7 +663,8 @@ def kill(self, pid: int, signal: typing.Union[int, os_signal.Signals]): # Kill the process assert type(pid) == int # noqa: E721 assert type(signal) == int or type(signal) == os_signal.Signals # noqa: E721 E501 - cmd = "kill -{} {}".format(signal, pid) + assert int(signal) == signal + cmd = "kill -{} {}".format(int(signal), pid) return self.exec_command(cmd) def get_pid(self): diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index 71075e3..95ae72d 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -1175,6 +1175,7 @@ def test_kill_signal( ): assert type(kill_signal_id) == tuple # noqa: E721 assert "{}".format(kill_signal_id[1]) == kill_signal_id[2] + assert "{}".format(int(kill_signal_id[1])) == kill_signal_id[2] def test_kill( self, From a35a8c65812591dbc89655cab9581c5504280a1a Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 23 Dec 2025 12:19:53 +0300 Subject: [PATCH 10/12] [test] comments on sm_kill_signal_ids are added [SIGQUIT, coredump] --- tests/test_os_ops_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index 95ae72d..45ad117 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -1151,13 +1151,13 @@ class tadWorkerData: sm_kill_signal_ids: typing.List[T_KILL_SIGNAL_DESCR] = [ ("SIGINT", os_signal.SIGINT, "2"), + # ("SIGQUIT", os_signal.SIGQUIT, "3"), # it creates coredump ("SIGKILL", os_signal.SIGKILL, "9"), ("SIGTERM", os_signal.SIGTERM, "15"), - # ("SIGQUIT", os_signal.SIGQUIT, "3"), ("2", 2, "2"), + # ("3", 3, "3"), # it creates coredump ("9", 9, "9"), ("15", 15, "15"), - # ("3", 3, "3"), ] @pytest.fixture( From 49d8b60b56dac7bc791d126cda94d7e82114d518 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 23 Dec 2025 13:46:01 +0300 Subject: [PATCH 11/12] new test: TestOsOpsCommon.test_kill__unk_pid --- tests/test_os_ops_common.py | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index 45ad117..40318d4 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -1245,3 +1245,94 @@ def test_kill( continue return + + def test_kill__unk_pid( + self, + os_ops: OsOperations, + kill_signal_id: T_KILL_SIGNAL_DESCR, + ): + """ + Test listdir for listing directory contents. + """ + assert isinstance(os_ops, OsOperations) + assert type(kill_signal_id) == tuple # noqa: E721 + + cmd = [ + sys.executable, + "-c", + "import sys; print(\"a\", file=sys.stdout); print(\"b\", file=sys.stderr)" + ] + + logging.info("Local test process is creating ...") + proc = subprocess.Popen( + cmd, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + assert proc is not None + assert type(proc) == subprocess.Popen # noqa: E721 + proc_pid = proc.pid + assert type(proc_pid) == int # noqa: E721 + logging.info("Test process pid is {}".format(proc_pid)) + + logging.info("Wait for finish ...") + pout, perr = proc.communicate() + logging.info("STDOUT: {}".format(pout)) + logging.info("STDERR: {}".format(pout)) + assert type(pout) == str # noqa: E721 + assert type(perr) == str # noqa: E721 + assert pout == "a\n" + assert perr == "b\n" + assert type(proc.returncode) == int # noqa: E721 + assert proc.returncode == 0 + + logging.info("Try to get this test process ...") + + attempt = 0 + while True: + if attempt == 20: + raise RuntimeError("Process did not die.") + + attempt += 1 + + if attempt > 1: + logging.info("Sleep 1 seconds...") + time.sleep(1) + + try: + psutil.Process(proc_pid) + except psutil.ZombieProcess as e: + logging.info("Exception {}: {}".format( + type(e).__name__, + str(e), + )) + except psutil.NoSuchProcess: + logging.info("OK. Process died.") + break + + logging.info("Process is alive!") + continue + + # -------------------- + with pytest.raises(expected_exception=Exception) as x: + os_ops.kill(proc_pid, kill_signal_id[1]) + + assert x is not None + assert isinstance(x.value, Exception) + assert not isinstance(x.value, AssertionError) + + logging.info("Our error is [{}]".format(str(x.value))) + logging.info("Our exception has type [{}]".format(type(x.value).__name__)) + + if type(os_ops).__name__ == "LocalOsOperations": + assert type(x.value) == ProcessLookupError # noqa: E721 + assert "No such process" in str(x.value) + elif type(os_ops).__name__ == "RemoteOsOperations": + assert type(x.value) == ExecUtilException # noqa: E721 + assert "No such process" in str(x.value) + else: + RuntimeError("Unknown os_ops type: {}".format(type(os_ops).__name__)) + + return From 320d7ff45893408a6342d2cf5abee332a103dd5b Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Tue, 23 Dec 2025 14:01:57 +0300 Subject: [PATCH 12/12] RemoteOperations::kill uses encoding=get_default_encoding() --- src/remote_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote_ops.py b/src/remote_ops.py index 32d2d07..d7b5d4d 100644 --- a/src/remote_ops.py +++ b/src/remote_ops.py @@ -665,7 +665,7 @@ def kill(self, pid: int, signal: typing.Union[int, os_signal.Signals]): assert type(signal) == int or type(signal) == os_signal.Signals # noqa: E721 E501 assert int(signal) == signal cmd = "kill -{} {}".format(int(signal), pid) - return self.exec_command(cmd) + return self.exec_command(cmd, encoding=get_default_encoding()) def get_pid(self): # Get current process id