testgres 1.16.2__tar.gz → 1.16.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. {testgres-1.16.2/testgres.egg-info → testgres-1.16.3}/PKG-INFO +1 -2
  2. {testgres-1.16.2 → testgres-1.16.3}/pyproject.toml +0 -1
  3. {testgres-1.16.2 → testgres-1.16.3}/src/__init__.py +1 -1
  4. {testgres-1.16.2 → testgres-1.16.3}/src/backup.py +2 -4
  5. {testgres-1.16.2 → testgres-1.16.3}/src/cache.py +3 -5
  6. {testgres-1.16.2 → testgres-1.16.3}/src/config.py +1 -1
  7. {testgres-1.16.2 → testgres-1.16.3}/src/connection.py +1 -1
  8. {testgres-1.16.2 → testgres-1.16.3}/src/consts.py +3 -0
  9. {testgres-1.16.2 → testgres-1.16.3}/src/decorators.py +2 -3
  10. {testgres-1.16.2 → testgres-1.16.3}/src/enums.py +1 -2
  11. {testgres-1.16.2 → testgres-1.16.3}/src/exceptions.py +2 -5
  12. testgres-1.16.3/src/impl/internal_node_utils.py +221 -0
  13. {testgres-1.16.2 → testgres-1.16.3}/src/impl/internal_utils.py +58 -0
  14. {testgres-1.16.2 → testgres-1.16.3}/src/impl/platforms/darwin/internal_platform_utils.py +1 -1
  15. {testgres-1.16.2 → testgres-1.16.3}/src/impl/platforms/internal_platform_utils.py +5 -3
  16. testgres-1.16.3/src/impl/platforms/internal_platform_utils_provider.py +32 -0
  17. {testgres-1.16.2 → testgres-1.16.3}/src/impl/platforms/linux/internal_platform_utils.py +12 -5
  18. {testgres-1.16.2 → testgres-1.16.3}/src/impl/platforms/win32/internal_platform_utils.py +1 -1
  19. {testgres-1.16.2 → testgres-1.16.3}/src/node.py +13 -21
  20. testgres-1.16.3/src/node_state.py +20 -0
  21. {testgres-1.16.2 → testgres-1.16.3}/src/port_manager.py +5 -2
  22. {testgres-1.16.2 → testgres-1.16.3}/src/pubsub.py +3 -5
  23. {testgres-1.16.2 → testgres-1.16.3}/src/raise_error.py +14 -0
  24. {testgres-1.16.2 → testgres-1.16.3}/src/standby.py +6 -8
  25. {testgres-1.16.2 → testgres-1.16.3}/src/utils.py +17 -271
  26. {testgres-1.16.2 → testgres-1.16.3/testgres.egg-info}/PKG-INFO +1 -2
  27. {testgres-1.16.2 → testgres-1.16.3}/testgres.egg-info/SOURCES.txt +3 -0
  28. {testgres-1.16.2 → testgres-1.16.3}/testgres.egg-info/requires.txt +0 -1
  29. {testgres-1.16.2 → testgres-1.16.3}/tests/test_testgres_common.py +258 -31
  30. {testgres-1.16.2 → testgres-1.16.3}/LICENSE +0 -0
  31. {testgres-1.16.2 → testgres-1.16.3}/README.md +0 -0
  32. {testgres-1.16.2 → testgres-1.16.3}/setup.cfg +0 -0
  33. {testgres-1.16.2 → testgres-1.16.3}/src/api.py +0 -0
  34. {testgres-1.16.2 → testgres-1.16.3}/src/defaults.py +0 -0
  35. {testgres-1.16.2 → testgres-1.16.3}/src/impl/file_line_reader.py +0 -0
  36. {testgres-1.16.2 → testgres-1.16.3}/src/impl/platforms/internal_platform_utils_factory.py +0 -0
  37. {testgres-1.16.2 → testgres-1.16.3}/src/impl/port_manager__generic.py +0 -0
  38. {testgres-1.16.2 → testgres-1.16.3}/src/impl/port_manager__generic2.py +0 -0
  39. {testgres-1.16.2 → testgres-1.16.3}/src/impl/port_manager__this_host.py +0 -0
  40. {testgres-1.16.2 → testgres-1.16.3}/src/logger.py +0 -0
  41. {testgres-1.16.2 → testgres-1.16.3}/src/node_app.py +0 -0
  42. {testgres-1.16.2 → testgres-1.16.3}/testgres.egg-info/dependency_links.txt +0 -0
  43. {testgres-1.16.2 → testgres-1.16.3}/testgres.egg-info/top_level.txt +0 -0
  44. {testgres-1.16.2 → testgres-1.16.3}/tests/test_api.py +0 -0
  45. {testgres-1.16.2 → testgres-1.16.3}/tests/test_config.py +0 -0
  46. {testgres-1.16.2 → testgres-1.16.3}/tests/test_os_ops_common.py +0 -0
  47. {testgres-1.16.2 → testgres-1.16.3}/tests/test_os_ops_local.py +0 -0
  48. {testgres-1.16.2 → testgres-1.16.3}/tests/test_os_ops_remote.py +0 -0
  49. {testgres-1.16.2 → testgres-1.16.3}/tests/test_raise_error.py +0 -0
  50. {testgres-1.16.2 → testgres-1.16.3}/tests/test_testgres_local.py +0 -0
  51. {testgres-1.16.2 → testgres-1.16.3}/tests/test_testgres_remote.py +0 -0
  52. {testgres-1.16.2 → testgres-1.16.3}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testgres
3
- Version: 1.16.2
3
+ Version: 1.16.3
4
4
  Summary: Testing utility for PostgreSQL and its extensions
5
5
  Author-email: Postgres Professional <testgres@postgrespro.ru>
6
6
  License: PostgreSQL
@@ -24,7 +24,6 @@ Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: pg8000
26
26
  Requires-Dist: port-for>=0.4
27
- Requires-Dist: six>=1.9.0
28
27
  Requires-Dist: psutil
29
28
  Requires-Dist: packaging
30
29
  Requires-Dist: testgres.os_ops<4.0.0,>=3.3.3
@@ -62,7 +62,6 @@ classifiers = [
62
62
  dependencies = [
63
63
  "pg8000",
64
64
  "port-for>=0.4",
65
- "six>=1.9.0",
66
65
  "psutil",
67
66
  "packaging",
68
67
  "testgres.os_ops>=3.3.3,<4.0.0",
@@ -60,7 +60,7 @@ from testgres.operations.os_ops import OsProcessController
60
60
  from testgres.operations.local_ops import LocalOperations
61
61
  from testgres.operations.remote_ops import RemoteOperations
62
62
 
63
- __version__ = "1.16.2"
63
+ __version__ = "1.16.3"
64
64
 
65
65
  __all__ = [
66
66
  "get_new_node",
@@ -1,7 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
- from six import raise_from
4
-
5
3
  from .enums import XLogMethod
6
4
 
7
5
  from .consts import \
@@ -22,7 +20,7 @@ from .utils import \
22
20
  clean_on_error
23
21
 
24
22
 
25
- class NodeBackup(object):
23
+ class NodeBackup:
26
24
  """
27
25
  Smart object responsible for backups
28
26
  """
@@ -131,7 +129,7 @@ class NodeBackup(object):
131
129
  # Copy backup to new data dir
132
130
  self.os_ops.copytree(data1, data2)
133
131
  except Exception as e:
134
- raise_from(BackupException('Failed to copy files'), e)
132
+ raise BackupException('Failed to copy files') from e
135
133
  else:
136
134
  dest_base_dir = self.base_dir
137
135
 
@@ -1,7 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
- from six import raise_from
4
-
5
3
  from .config import testgres_config
6
4
 
7
5
  from .consts import XLOG_CONTROL_FILE
@@ -49,7 +47,7 @@ def cached_initdb(data_dir, logfile=None, params=None, os_ops: OsOperations = No
49
47
  log,
50
48
  )
51
49
  except ExecUtilException as e:
52
- raise_from(InitNodeException("Failed to run initdb"), e)
50
+ raise InitNodeException("Failed to run initdb") from e
53
51
 
54
52
  if params or not testgres_config.cache_initdb or not cached:
55
53
  call_initdb(data_dir, logfile)
@@ -89,7 +87,7 @@ def cached_initdb(data_dir, logfile=None, params=None, os_ops: OsOperations = No
89
87
 
90
88
  except ExecUtilException as e:
91
89
  msg = "Failed to reset WAL for system id"
92
- raise_from(InitNodeException(msg), e)
90
+ raise InitNodeException(msg) from e
93
91
 
94
92
  except Exception as e:
95
- raise_from(InitNodeException("Failed to spawn a node"), e)
93
+ raise InitNodeException("Failed to spawn a node") from e
@@ -17,7 +17,7 @@ log_format = os.getenv('LOGGING_FORMAT', '%(asctime)s - %(levelname)s - %(messag
17
17
  logging.basicConfig(level=log_level, format=log_format)
18
18
 
19
19
 
20
- class GlobalConfig(object):
20
+ class GlobalConfig:
21
21
  """
22
22
  Global configuration object which allows user to override default settings.
23
23
  """
@@ -25,7 +25,7 @@ ProgrammingError = pglib.ProgrammingError
25
25
  OperationalError = pglib.OperationalError
26
26
 
27
27
 
28
- class NodeConnection(object):
28
+ class NodeConnection:
29
29
  """
30
30
  Transaction wrapper returned by Node
31
31
  """
@@ -2,6 +2,7 @@
2
2
 
3
3
  # binary names
4
4
  BINARY_NAME__POSTGRES = "postgres"
5
+ BINARY_NAME__PG_CTL = "pg_ctl"
5
6
 
6
7
  # names for dirs in base_dir
7
8
  DATA_DIR = "data"
@@ -44,5 +45,7 @@ MAX_WAL_SENDERS = 10
44
45
  LOGICAL_REPL_MAX_CATCHUP_ATTEMPTS = 60
45
46
 
46
47
  PG_CTL__STATUS__OK = 0
48
+
49
+ PG_CTL__STATUS__FAILED = 1
47
50
  PG_CTL__STATUS__NODE_IS_STOPPED = 3
48
51
  PG_CTL__STATUS__BAD_DATADIR = 4
@@ -1,4 +1,3 @@
1
- import six
2
1
  import functools
3
2
 
4
3
 
@@ -18,7 +17,7 @@ def positional_args_hack(*special_cases):
18
17
 
19
18
  for case in special_cases:
20
19
  k = len(case)
21
- assert k not in six.iterkeys(cases), 'len must be unique'
20
+ assert k not in cases, 'len must be unique'
22
21
  cases[k] = case
23
22
 
24
23
  def decorator(function):
@@ -26,7 +25,7 @@ def positional_args_hack(*special_cases):
26
25
  def wrapper(*args, **kwargs):
27
26
  k = len(args)
28
27
 
29
- if k in six.iterkeys(cases):
28
+ if k in cases:
30
29
  case = cases[k]
31
30
 
32
31
  for i in range(0, k):
@@ -1,5 +1,4 @@
1
1
  from enum import Enum, IntEnum
2
- from six import iteritems
3
2
  from psutil import NoSuchProcess
4
3
 
5
4
 
@@ -83,7 +82,7 @@ class ProcessType(Enum):
83
82
  if cmdline.startswith(ptype.value.replace(' ', '')):
84
83
  return ptype
85
84
 
86
- for ptype, names in iteritems(alternative_names):
85
+ for ptype, names in alternative_names.items():
87
86
  for name in names:
88
87
  if cmdline.startswith(name.replace(' ', '')):
89
88
  return ptype
@@ -1,6 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
- import six
4
3
  import typing
5
4
 
6
5
  from testgres.operations.exceptions import TestgresException
@@ -44,7 +43,6 @@ class PortForException(TestgresException):
44
43
  return result
45
44
 
46
45
 
47
- @six.python_2_unicode_compatible
48
46
  class QueryException(TestgresException):
49
47
  _description: typing.Optional[str]
50
48
  _query: typing.Optional[str]
@@ -76,7 +74,7 @@ class QueryException(TestgresException):
76
74
  if self._query:
77
75
  msg.append(u'Query: {}'.format(self._query))
78
76
 
79
- r = six.text_type('\n').join(msg)
77
+ r = '\n'.join(msg)
80
78
  assert type(r) is str
81
79
  return r
82
80
 
@@ -162,7 +160,6 @@ class CatchUpException(TestgresException):
162
160
  return result
163
161
 
164
162
 
165
- @six.python_2_unicode_compatible
166
163
  class StartNodeException(TestgresException):
167
164
  _description: typing.Optional[str]
168
165
  _files: typing.Optional[typing.Iterable]
@@ -196,7 +193,7 @@ class StartNodeException(TestgresException):
196
193
  assert type(lines) in [str, bytes]
197
194
  msg.append(u'{}\n----\n{}\n'.format(f, lines))
198
195
 
199
- return six.text_type('\n').join(msg)
196
+ return '\n'.join(msg)
200
197
 
201
198
  @property
202
199
  def description(self) -> typing.Optional[str]:
@@ -0,0 +1,221 @@
1
+ from __future__ import annotations
2
+
3
+ from . import internal_utils
4
+ from . platforms.internal_platform_utils_provider import InternalPlaformUtilsProvider
5
+
6
+ from .. import consts
7
+ from ..node_state import PostgresNodeState
8
+ from ..enums import NodeStatus
9
+ from ..raise_error import RaiseError
10
+ from ..exceptions import ExecUtilException
11
+
12
+ from testgres.operations.os_ops import OsOperations
13
+
14
+ import typing
15
+ import time
16
+
17
+
18
+ class InternalNodeUtils:
19
+ T_PLATFORM_UTILS = InternalPlaformUtilsProvider.T_PLATFORM_UTILS
20
+
21
+ @staticmethod
22
+ def get_pg_node_state(
23
+ os_ops: OsOperations,
24
+ bin_dir: str,
25
+ data_dir: str,
26
+ utils_log_file: typing.Optional[str],
27
+ ) -> PostgresNodeState:
28
+ assert isinstance(os_ops, OsOperations)
29
+ assert type(bin_dir) is str
30
+ assert type(data_dir) is str
31
+ assert utils_log_file is None or type(utils_log_file) is str
32
+
33
+ C_MAX_ATTEMPTS = 3
34
+ C_SLEEP_TIME1 = 1
35
+ C_SLEEP_TIME_MULT = 2
36
+
37
+ pg_ctl_params = [
38
+ os_ops.build_path(bin_dir, consts.BINARY_NAME__PG_CTL),
39
+ "-D",
40
+ data_dir,
41
+ "status",
42
+ ]
43
+
44
+ attempt = 0
45
+ sleep_time = C_SLEEP_TIME1
46
+
47
+ platform_utils_provider = InternalPlaformUtilsProvider(
48
+ os_ops,
49
+ )
50
+
51
+ while True:
52
+ assert type(attempt) is int
53
+ assert attempt >= 0
54
+ assert attempt < C_MAX_ATTEMPTS
55
+
56
+ attempt += 1
57
+
58
+ if attempt > 1:
59
+ internal_utils.send_log_debug("Sleep {} second(s) before an attempt #{}".format(
60
+ sleep_time,
61
+ attempt,
62
+ ))
63
+ time.sleep(sleep_time)
64
+ sleep_time = sleep_time * C_SLEEP_TIME_MULT
65
+
66
+ exec_r = internal_utils.execute_utility3(
67
+ os_ops,
68
+ pg_ctl_params,
69
+ utils_log_file,
70
+ check=False,
71
+ )
72
+
73
+ status_code = exec_r.returncode
74
+ out = exec_r.stdout
75
+ error = exec_r.stderr
76
+
77
+ assert type(status_code) is int
78
+ assert type(out) is str
79
+ assert type(error) is str
80
+
81
+ # -----------------
82
+ if status_code == consts.PG_CTL__STATUS__NODE_IS_STOPPED:
83
+ return PostgresNodeState(NodeStatus.Stopped, None)
84
+
85
+ # -----------------
86
+ if status_code == consts.PG_CTL__STATUS__BAD_DATADIR:
87
+ return PostgresNodeState(NodeStatus.Uninitialized, None)
88
+
89
+ # -----------------
90
+ if status_code == consts.PG_CTL__STATUS__OK:
91
+ pid = __class__._parse_pid(
92
+ out,
93
+ pg_ctl_params,
94
+ )
95
+ assert type(pid) is int
96
+ assert pid != 0
97
+
98
+ # ----------------- detect zombie
99
+ if platform_utils_provider.get().ProcessIsZombie_soft_check(os_ops, pid) is True:
100
+ internal_utils.send_log_debug("Postmaster process {} is a zombie.".format(
101
+ pid,
102
+ ))
103
+ return PostgresNodeState(NodeStatus.Zombie, pid)
104
+
105
+ # -----------------
106
+ return PostgresNodeState(NodeStatus.Running, pid)
107
+
108
+ assert status_code != consts.PG_CTL__STATUS__OK
109
+
110
+ errMsg = "Getting of a node status [data_dir is {0}] failed.".format(
111
+ data_dir,
112
+ )
113
+
114
+ e1 = ExecUtilException(
115
+ message=errMsg,
116
+ command=pg_ctl_params,
117
+ exit_code=status_code,
118
+ out=out,
119
+ error=error,
120
+ )
121
+
122
+ if status_code == consts.PG_CTL__STATUS__FAILED:
123
+ internal_utils.send_log_debug(
124
+ "pg_ctl fails with an error: {}".format(
125
+ exec_r.stderr,
126
+ ))
127
+
128
+ try:
129
+ find_postmaster_r = platform_utils_provider.get().FindPostmaster(
130
+ os_ops,
131
+ bin_dir,
132
+ data_dir,
133
+ )
134
+ except Exception as e2:
135
+ raise e2 from e1
136
+
137
+ assert type(find_postmaster_r) is __class__.T_PLATFORM_UTILS.FindPostmasterResult
138
+
139
+ if find_postmaster_r.code == __class__.T_PLATFORM_UTILS.FindPostmasterResultCode.ok:
140
+ # Postmaster is alive. Let's wait a few seconds and check its status again.
141
+ internal_utils.send_log_debug(
142
+ "Postmaster is found and has PID {}.".format(
143
+ find_postmaster_r.pid,
144
+ ))
145
+
146
+ if attempt < C_MAX_ATTEMPTS:
147
+ continue
148
+
149
+ raise e1
150
+
151
+ @staticmethod
152
+ def _parse_pid(
153
+ out: str,
154
+ pg_ctl_params,
155
+ ) -> int:
156
+ assert type(out) is str
157
+
158
+ if out == "":
159
+ RaiseError.pg_ctl_returns_an_empty_string(
160
+ pg_ctl_params,
161
+ )
162
+
163
+ C_PID_PREFIX = "(PID: "
164
+
165
+ i = out.find(C_PID_PREFIX)
166
+
167
+ if i == -1:
168
+ RaiseError.pg_ctl_returns_an_unexpected_string(
169
+ out,
170
+ pg_ctl_params,
171
+ )
172
+
173
+ assert i > 0
174
+ assert i < len(out)
175
+ assert len(C_PID_PREFIX) <= len(out)
176
+ assert i <= len(out) - len(C_PID_PREFIX)
177
+
178
+ i += len(C_PID_PREFIX)
179
+ start_pid_s = i
180
+
181
+ while True:
182
+ if i == len(out):
183
+ RaiseError.pg_ctl_returns_an_unexpected_string(
184
+ out,
185
+ pg_ctl_params,
186
+ )
187
+
188
+ ch = out[i]
189
+
190
+ if ch == ")":
191
+ break
192
+
193
+ if ch.isdigit():
194
+ i += 1
195
+ continue
196
+
197
+ RaiseError.pg_ctl_returns_an_unexpected_string(
198
+ out,
199
+ pg_ctl_params,
200
+ )
201
+ assert False
202
+
203
+ if i == start_pid_s:
204
+ RaiseError.pg_ctl_returns_an_unexpected_string(
205
+ out,
206
+ pg_ctl_params,
207
+ )
208
+
209
+ # TODO: Let's verify a length of pid string.
210
+
211
+ pid = int(out[start_pid_s:i])
212
+
213
+ if pid == 0:
214
+ RaiseError.pg_ctl_returns_a_zero_pid(
215
+ out,
216
+ pg_ctl_params,
217
+ )
218
+
219
+ assert pid != 0
220
+
221
+ return pid
@@ -1,4 +1,10 @@
1
1
  from testgres.operations.os_ops import OsOperations
2
+ from testgres.operations.os_ops import OsCommandResult
3
+ from testgres.operations.types import T_OS_CMD
4
+ from testgres.operations.types import T_OS_EXEC_ENV
5
+ from testgres.operations.helpers import Helpers as OsHelpers
6
+
7
+ from ..exceptions import ExecUtilException
2
8
 
3
9
  import logging
4
10
  import typing
@@ -90,3 +96,55 @@ def read_line_to_pos__bin(
90
96
  assert type(result) is bytes
91
97
  assert len(result) <= (position - read_position)
92
98
  return result
99
+
100
+
101
+ def execute_utility3(
102
+ os_ops: OsOperations,
103
+ args: T_OS_CMD,
104
+ logfile: typing.Optional[str] = None,
105
+ check: bool = True,
106
+ exec_env: typing.Optional[T_OS_EXEC_ENV] = None,
107
+ ) -> OsCommandResult:
108
+ assert os_ops is not None
109
+ assert isinstance(os_ops, OsOperations)
110
+ assert type(check) is bool
111
+ assert exec_env is None or type(exec_env) is dict
112
+
113
+ exec_r = os_ops.run(
114
+ args,
115
+ check=check,
116
+ encoding=OsHelpers.GetDefaultEncoding(),
117
+ exec_env=exec_env,
118
+ )
119
+
120
+ assert type(exec_r) is OsCommandResult
121
+
122
+ # write new log entry if possible
123
+ if logfile:
124
+ try:
125
+ log_lines = [
126
+ os_ops.join_command_arguments(args),
127
+ ]
128
+
129
+ if exec_r.stdout is None:
130
+ log_lines.append("# #NONE#")
131
+ else:
132
+ # comment-out lines
133
+ assert type(exec_r.stdout) is str
134
+ log_lines += ['# ' + line for line in exec_r.stdout.splitlines()]
135
+
136
+ log_lines.append("")
137
+
138
+ os_ops.write(
139
+ filename=logfile,
140
+ data="\n".join(log_lines),
141
+ truncate=False,
142
+ )
143
+ except IOError:
144
+ raise ExecUtilException(
145
+ "Problem with writing to logfile `{}` during run command `{}`".format(
146
+ logfile,
147
+ args,
148
+ ))
149
+
150
+ return exec_r
@@ -248,7 +248,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
248
248
  )
249
249
 
250
250
  # --------------------------------------------------------------------
251
- def ProcessIsZombi_soft_check(
251
+ def ProcessIsZombie_soft_check(
252
252
  self,
253
253
  os_ops: OsOperations,
254
254
  pid: int,
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from ...raise_error import RaiseError
4
+
3
5
  import enum
4
6
  import typing
5
7
 
@@ -59,13 +61,13 @@ class InternalPlatformUtils:
59
61
  assert isinstance(os_ops, OsOperations)
60
62
  assert type(bin_dir) is str
61
63
  assert type(data_dir) is str
62
- raise NotImplementedError("InternalPlatformUtils::FindPostmaster is not implemented.")
64
+ RaiseError.method_is_not_implemented(__class__, "FindPostmaster")
63
65
 
64
- def ProcessIsZombi_soft_check(
66
+ def ProcessIsZombie_soft_check(
65
67
  self,
66
68
  os_ops: OsOperations,
67
69
  pid: int,
68
70
  ) -> typing.Optional[bool]:
69
71
  assert isinstance(os_ops, OsOperations)
70
72
  assert type(pid) is int
71
- raise NotImplementedError("InternalPlatformUtils::ProcessIsZombi_soft_ver is not implemented.")
73
+ RaiseError.method_is_not_implemented(__class__, "ProcessIsZombie_soft_check")
@@ -0,0 +1,32 @@
1
+ from .internal_platform_utils_factory import create_internal_platform_utils
2
+ from .internal_platform_utils_factory import InternalPlatformUtils
3
+
4
+ from testgres.operations.os_ops import OsOperations
5
+
6
+ import typing
7
+
8
+
9
+ class InternalPlaformUtilsProvider:
10
+ T_PLATFORM_UTILS = InternalPlatformUtils
11
+
12
+ _os_ops: OsOperations
13
+ _platform_utils: typing.Optional[T_PLATFORM_UTILS] = None
14
+
15
+ def __init__(
16
+ self,
17
+ os_ops: OsOperations,
18
+ ):
19
+ assert isinstance(os_ops, OsOperations)
20
+ self._os_ops = os_ops
21
+ self._platform_utils = None
22
+ return
23
+
24
+ def get(self) -> T_PLATFORM_UTILS:
25
+ if self._platform_utils is None:
26
+ self._platform_utils = create_internal_platform_utils(
27
+ self._os_ops,
28
+ )
29
+ assert isinstance(self._platform_utils, __class__.T_PLATFORM_UTILS)
30
+
31
+ assert isinstance(self._platform_utils, __class__.T_PLATFORM_UTILS)
32
+ return self._platform_utils
@@ -247,7 +247,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
247
247
  )
248
248
 
249
249
  # --------------------------------------------------------------------
250
- def ProcessIsZombi_soft_check(
250
+ def ProcessIsZombie_soft_check(
251
251
  self,
252
252
  os_ops: OsOperations,
253
253
  pid: int,
@@ -264,7 +264,10 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
264
264
 
265
265
  try:
266
266
  # Read one line from /proc/PID/stat
267
- stat_content = os_ops.read_binary(proc_stat_file, 0).decode("utf-8", errors="ignore")
267
+ stat_content_b = os_ops.read_binary(proc_stat_file, 0)
268
+ assert type(stat_content_b) is bytes
269
+
270
+ stat_content = stat_content_b.decode("utf-8", errors="ignore")
268
271
 
269
272
  # We look for the closing parenthesis of the process name to ensure that
270
273
  # we start from it and not depend on spaces inside the parentheses!
@@ -281,19 +284,23 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
281
284
  result = proc_status == "Z"
282
285
  except Exception as e:
283
286
  # If the file disappeared right during reading, it means the process is completely erased
284
- if __class__._is_file_not_found_exception(e):
287
+ if __class__._is_zombie_file_exception(e):
285
288
  result = False
286
289
 
287
290
  return result
288
291
 
289
292
  @staticmethod
290
- def _is_file_not_found_exception(e: Exception) -> bool:
293
+ def _is_zombie_file_exception(e: Exception) -> bool:
291
294
  if isinstance(e, FileNotFoundError):
292
295
  return True
293
296
 
297
+ if isinstance(e, ProcessLookupError):
298
+ return True
299
+
294
300
  if isinstance(e, ExecUtilException):
295
- if e.exit_code == 2:
301
+ if e.exit_code == 1:
296
302
  return True
303
+ return False
297
304
 
298
305
  return False
299
306
 
@@ -17,7 +17,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
17
17
  assert type(data_dir) is str
18
18
  return __class__.FindPostmasterResult.create_not_implemented()
19
19
 
20
- def ProcessIsZombi_soft_check(
20
+ def ProcessIsZombie_soft_check(
21
21
  self,
22
22
  os_ops: OsOperations,
23
23
  pid: int,