From 3db2db2a0c6734ca86adbd9164c23d287f278f96 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Fri, 18 Sep 2026 17:49:35 +0300 Subject: [PATCH 1/7] node::psql/safe_psql use os_ops::run --- src/node.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/node.py b/src/node.py index c18cbae6..23366fe1 100644 --- a/src/node.py +++ b/src/node.py @@ -100,6 +100,7 @@ from .backup import NodeBackup from testgres.operations.os_ops import OsOperations +from testgres.operations.os_ops import OsCommandResult from testgres.operations.local_ops import LocalOperations InternalError = pglib.InternalError @@ -1598,7 +1599,7 @@ def psql(self, assert port is None or type(port) is int assert type(variables) is dict - return self._psql( + r = self._psql( ignore_errors=True, query=query, filename=filename, @@ -1609,6 +1610,8 @@ def psql(self, port=port, **variables ) + assert type(r) is OsCommandResult + return r.returncode, r.stdout, r.stderr def _psql( self, @@ -1620,7 +1623,8 @@ def _psql( input=None, host: typing.Optional[str] = None, port: typing.Optional[int] = None, - **variables): + **variables + ) -> OsCommandResult: assert host is None or type(host) is str assert port is None or type(port) is int assert type(variables) is dict @@ -1670,13 +1674,15 @@ def _psql( else: raise QueryException('Query or filename must be provided') - return self._os_ops.exec_command( + r = self._os_ops.run( psql_params, - verbose=True, input=input, stderr=subprocess.PIPE, stdout=subprocess.PIPE, - ignore_errors=ignore_errors) + check=not ignore_errors, + ) + assert type(r) is OsCommandResult + return r @method_decorator(positional_args_hack(['dbname', 'query'])) def safe_psql(self, query=None, expect_error=False, **kwargs): @@ -1704,7 +1710,7 @@ def safe_psql(self, query=None, expect_error=False, **kwargs): # force this setting kwargs['ON_ERROR_STOP'] = 1 try: - ret, out, err = self._psql(ignore_errors=False, query=query, **kwargs) + exec_r = self._psql(ignore_errors=False, query=query, **kwargs) except ExecUtilException as e: if not expect_error: raise QueryException(e.message, query) @@ -1719,7 +1725,7 @@ def safe_psql(self, query=None, expect_error=False, **kwargs): if expect_error: raise InvalidOperationException("Exception was expected, but query finished successfully: `{}`.".format(query)) - return out + return exec_r.stdout def dump(self, filename=None, From a6045f10b794d8d507ab998ee51a3ff2cb1d69a6 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Fri, 18 Sep 2026 17:53:59 +0300 Subject: [PATCH 2/7] node::_try_shutdown_internal uses os_ops::run --- src/node.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/node.py b/src/node.py index 23366fe1..6a475118 100644 --- a/src/node.py +++ b/src/node.py @@ -674,7 +674,11 @@ def _try_shutdown_internal(self, max_attempts, with_force): ps_command = ['ps', '-o', 'pid=', '-p', str(node_pid)] - ps_output = self._os_ops.exec_command(cmd=ps_command, shell=True, ignore_errors=True).decode('utf-8') + ps_output = self._os_ops.run( + cmd=ps_command, + shell=True, + check=False, + ).stdout.decode('utf-8') assert type(ps_output) is str if ps_output == "": @@ -693,7 +697,11 @@ def _try_shutdown_internal(self, max_attempts, with_force): pass # Check that node stopped - print only column pid without headers - ps_output = self._os_ops.exec_command(cmd=ps_command, shell=True, ignore_errors=True).decode('utf-8') + ps_output = self._os_ops.run( + cmd=ps_command, + shell=True, + check=False, + ).stdout.decode('utf-8') assert type(ps_output) is str if ps_output == "": From d3d314608315ff92335df43ac290cf3f4c640e15 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Fri, 18 Sep 2026 17:57:37 +0300 Subject: [PATCH 3/7] node::pgbench returns OsProcessController --- src/node.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/node.py b/src/node.py index 6a475118..a8117221 100644 --- a/src/node.py +++ b/src/node.py @@ -101,6 +101,7 @@ from testgres.operations.os_ops import OsOperations from testgres.operations.os_ops import OsCommandResult +from testgres.operations.os_ops import OsProcessController from testgres.operations.local_ops import LocalOperations InternalError = pglib.InternalError @@ -2066,12 +2067,14 @@ def subscribe(self, dbname=dbname, username=username, **params) # yapf: enable - def pgbench(self, - dbname=None, - username=None, - stdout=None, - stderr=None, - options=None): + def pgbench( + self, + dbname=None, + username=None, + stdout=None, + stderr=None, + options=None, + ) -> OsProcessController: """ Spawn a pgbench process. @@ -2083,7 +2086,7 @@ def pgbench(self, options: additional options for pgbench (list). Returns: - Process created by subprocess.Popen. + OsProcessController. """ if options is None: options = [] @@ -2100,10 +2103,14 @@ def pgbench(self, # should be the last one _params.append(dbname) - proc = self._os_ops.exec_command(_params, stdout=stdout, stderr=stderr, get_process=True) + proc = self._os_ops.popen( + _params, + stdout=stdout, + stderr=stderr, + ) # [2026-06-21] It is so - assert isinstance(proc, subprocess.Popen) + assert isinstance(proc, OsProcessController) return proc def pgbench_with_wait(self, From 77be2021504bc3451cf3d82c509995ce8face063 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Fri, 18 Sep 2026 18:00:43 +0300 Subject: [PATCH 4/7] node::upgrade_from uses os_ops::run --- src/node.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/node.py b/src/node.py index a8117221..c8a849c2 100644 --- a/src/node.py +++ b/src/node.py @@ -2370,7 +2370,22 @@ def upgrade_from(self, old_node, options=None, expect_error=False): ] upgrade_command += options - return self._os_ops.exec_command(upgrade_command, expect_error=expect_error) + r: typing.Optional[typing.Any] = None + try: + r = self._os_ops.run(upgrade_command).stdout + except BaseException as e: + if not expect_error: + raise + + logging.info("Exception ({}): {}".format( + type(e).__name__, + e, + )) + + if expect_error: + raise RuntimeError("Operation executed without any errors.") + + return r def _release_resources(self): self._free_port() From 3934be602f7b7ba2917964bac331ab4c7d2615b7 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Fri, 18 Sep 2026 18:11:46 +0300 Subject: [PATCH 5/7] FindPostmaster uses os_ops::run --- .../linux/internal_platform_utils.py | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/impl/platforms/linux/internal_platform_utils.py b/src/impl/platforms/linux/internal_platform_utils.py index 95d9d15a..1afcc0fd 100644 --- a/src/impl/platforms/linux/internal_platform_utils.py +++ b/src/impl/platforms/linux/internal_platform_utils.py @@ -5,7 +5,9 @@ from ....raise_error import RaiseError from testgres.operations.os_ops import OsOperations +from testgres.operations.os_ops import OsCommandResult from testgres.operations.exceptions import ExecUtilException +from testgres.operations.types import T_OS_EXEC_ENV import re import shlex @@ -17,7 +19,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils): C_MAX_FIND_POSTMASTER_ATTEMPTS = 5 C_BASH_EXE = "/bin/bash" - sm_exec_env = { + sm_exec_env: T_OS_EXEC_ENV = { "LANG": "en_US.UTF-8", "LC_ALL": "en_US.UTF-8", } @@ -113,37 +115,32 @@ def _FindPostmaster( "ps -ewwo \"pid=,ppid=,args=\" | grep -E " + shlex.quote(regexp), ] - exec_r = os_ops.exec_command( + exec_r = os_ops.run( cmd=cmd, - ignore_errors=True, - verbose=True, + check=False, exec_env=__class__.sm_exec_env, ) - assert type(exec_r) is tuple - assert len(exec_r) == 3 + assert type(exec_r) is OsCommandResult + assert type(exec_r.returncode) is int + assert type(exec_r.stdout) is bytes + assert type(exec_r.stderr) is bytes - exit_status, output_b, error_b = exec_r - - assert type(exit_status) is int - assert type(output_b) is bytes - assert type(error_b) is bytes - - if exit_status == 1: + if exec_r.returncode == 1: return None - output = output_b.decode("utf-8") - error = error_b.decode("utf-8") + output = exec_r.stdout.decode("utf-8") + error = exec_r.stderr.decode("utf-8") assert type(output) is str assert type(error) is str - if exit_status != 0: - errMsg = f"test command returned an unexpected exit code: {exit_status}" + if exec_r.returncode != 0: + errMsg = f"test command returned an unexpected exit code: {exec_r.returncode}" raise ExecUtilException( message=errMsg, command=cmd, - exit_code=exit_status, + exit_code=exec_r.returncode, out=output, error=error, ) From 97f5c02aa957c55ada34d4da4ccb14f944018194 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Fri, 18 Sep 2026 18:16:09 +0300 Subject: [PATCH 6/7] utils.get_pg_config2 uses os_ops::run --- src/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.py b/src/utils.py index 622ec9e4..b3b643a9 100644 --- a/src/utils.py +++ b/src/utils.py @@ -264,7 +264,7 @@ def get_pg_config2(os_ops: OsOperations, pg_config_path): def cache_pg_config_data(cmd): # execute pg_config and get the output - out = os_ops.exec_command(cmd, encoding='utf-8') + out = os_ops.run(cmd, encoding='utf-8').stdout assert type(out) is str data = {} From f204ca55f17cf66d94fe281e4579cfdfd43e0c5d Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Fri, 18 Sep 2026 18:16:47 +0300 Subject: [PATCH 7/7] utils.get_pg_version2 uses os_ops::run --- src/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils.py b/src/utils.py index b3b643a9..5fa75e1f 100644 --- a/src/utils.py +++ b/src/utils.py @@ -335,7 +335,8 @@ def get_pg_version2(os_ops: OsOperations, bin_dir=None): postgres_path = os_ops.build_path(bin_dir, 'postgres') cmd = [postgres_path, '--version'] - raw_ver = os_ops.exec_command(cmd, encoding='utf-8') + raw_ver = os_ops.run(cmd, encoding='utf-8').stdout + assert type(raw_ver) is str return parse_pg_version(raw_ver)