testgres 1.14.4__tar.gz → 1.15.0__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 (51) hide show
  1. {testgres-1.14.4/testgres.egg-info → testgres-1.15.0}/PKG-INFO +2 -2
  2. {testgres-1.14.4 → testgres-1.15.0}/pyproject.toml +1 -1
  3. {testgres-1.14.4 → testgres-1.15.0}/src/__init__.py +3 -3
  4. {testgres-1.14.4 → testgres-1.15.0}/src/connection.py +2 -2
  5. {testgres-1.14.4 → testgres-1.15.0}/src/consts.py +4 -0
  6. {testgres-1.14.4 → testgres-1.15.0}/src/defaults.py +24 -2
  7. {testgres-1.14.4 → testgres-1.15.0}/src/enums.py +1 -1
  8. testgres-1.15.0/src/impl/file_line_reader.py +80 -0
  9. {testgres-1.14.4 → testgres-1.15.0}/src/impl/platforms/internal_platform_utils.py +9 -0
  10. testgres-1.15.0/src/impl/platforms/linux/internal_platform_utils.py +429 -0
  11. {testgres-1.14.4 → testgres-1.15.0}/src/impl/platforms/win32/internal_platform_utils.py +10 -0
  12. testgres-1.15.0/src/impl/port_manager__generic2.py +166 -0
  13. {testgres-1.14.4 → testgres-1.15.0}/src/impl/port_manager__this_host.py +2 -1
  14. testgres-1.15.0/src/logger.py +116 -0
  15. {testgres-1.14.4 → testgres-1.15.0}/src/node.py +197 -60
  16. {testgres-1.14.4 → testgres-1.15.0}/src/pubsub.py +154 -29
  17. {testgres-1.14.4 → testgres-1.15.0}/src/raise_error.py +37 -5
  18. {testgres-1.14.4 → testgres-1.15.0}/src/utils.py +43 -22
  19. {testgres-1.14.4 → testgres-1.15.0/testgres.egg-info}/PKG-INFO +2 -2
  20. {testgres-1.14.4 → testgres-1.15.0}/testgres.egg-info/SOURCES.txt +2 -0
  21. {testgres-1.14.4 → testgres-1.15.0}/testgres.egg-info/requires.txt +1 -1
  22. testgres-1.15.0/tests/test_os_ops_common.py +3906 -0
  23. {testgres-1.14.4 → testgres-1.15.0}/tests/test_os_ops_local.py +42 -6
  24. {testgres-1.14.4 → testgres-1.15.0}/tests/test_os_ops_remote.py +57 -13
  25. {testgres-1.14.4 → testgres-1.15.0}/tests/test_testgres_common.py +183 -22
  26. {testgres-1.14.4 → testgres-1.15.0}/tests/test_testgres_local.py +0 -35
  27. {testgres-1.14.4 → testgres-1.15.0}/tests/test_testgres_remote.py +31 -9
  28. testgres-1.14.4/src/impl/platforms/linux/internal_platform_utils.py +0 -120
  29. testgres-1.14.4/src/logger.py +0 -49
  30. testgres-1.14.4/tests/test_os_ops_common.py +0 -1400
  31. {testgres-1.14.4 → testgres-1.15.0}/LICENSE +0 -0
  32. {testgres-1.14.4 → testgres-1.15.0}/README.md +0 -0
  33. {testgres-1.14.4 → testgres-1.15.0}/setup.cfg +0 -0
  34. {testgres-1.14.4 → testgres-1.15.0}/src/api.py +0 -0
  35. {testgres-1.14.4 → testgres-1.15.0}/src/backup.py +0 -0
  36. {testgres-1.14.4 → testgres-1.15.0}/src/cache.py +0 -0
  37. {testgres-1.14.4 → testgres-1.15.0}/src/config.py +0 -0
  38. {testgres-1.14.4 → testgres-1.15.0}/src/decorators.py +0 -0
  39. {testgres-1.14.4 → testgres-1.15.0}/src/exceptions.py +0 -0
  40. {testgres-1.14.4 → testgres-1.15.0}/src/impl/internal_utils.py +0 -0
  41. {testgres-1.14.4 → testgres-1.15.0}/src/impl/platforms/internal_platform_utils_factory.py +0 -0
  42. {testgres-1.14.4 → testgres-1.15.0}/src/impl/port_manager__generic.py +0 -0
  43. {testgres-1.14.4 → testgres-1.15.0}/src/node_app.py +0 -0
  44. {testgres-1.14.4 → testgres-1.15.0}/src/port_manager.py +0 -0
  45. {testgres-1.14.4 → testgres-1.15.0}/src/standby.py +0 -0
  46. {testgres-1.14.4 → testgres-1.15.0}/testgres.egg-info/dependency_links.txt +0 -0
  47. {testgres-1.14.4 → testgres-1.15.0}/testgres.egg-info/top_level.txt +0 -0
  48. {testgres-1.14.4 → testgres-1.15.0}/tests/test_api.py +0 -0
  49. {testgres-1.14.4 → testgres-1.15.0}/tests/test_config.py +0 -0
  50. {testgres-1.14.4 → testgres-1.15.0}/tests/test_raise_error.py +0 -0
  51. {testgres-1.14.4 → testgres-1.15.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testgres
3
- Version: 1.14.4
3
+ Version: 1.15.0
4
4
  Summary: Testing utility for PostgreSQL and its extensions
5
5
  Author-email: Postgres Professional <testgres@postgrespro.ru>
6
6
  License: PostgreSQL
@@ -27,7 +27,7 @@ Requires-Dist: port-for>=0.4
27
27
  Requires-Dist: six>=1.9.0
28
28
  Requires-Dist: psutil
29
29
  Requires-Dist: packaging
30
- Requires-Dist: testgres.os_ops<3.0.0,>=2.4.0
30
+ Requires-Dist: testgres.os_ops<4.0.0,>=3.1.0
31
31
  Dynamic: license-file
32
32
 
33
33
  [![CI Status](https://img.shields.io/github/actions/workflow/status/postgrespro/testgres/.github/workflows/ci.yml?label=CI)](https://github.com/postgrespro/testgres/actions/workflows/ci.yml)
@@ -65,7 +65,7 @@ dependencies = [
65
65
  "six>=1.9.0",
66
66
  "psutil",
67
67
  "packaging",
68
- "testgres.os_ops>=2.4.0,<3.0.0",
68
+ "testgres.os_ops>=3.1.0,<4.0.0",
69
69
  ]
70
70
 
71
71
  [project.urls]
@@ -41,8 +41,8 @@ from .node_app import NodeApp
41
41
  from .utils import \
42
42
  reserve_port, \
43
43
  release_port, \
44
- bound_ports, \
45
44
  get_bin_path, \
45
+ get_bin_dir, \
46
46
  get_pg_config, \
47
47
  get_pg_version, \
48
48
  parse_pg_version
@@ -57,7 +57,7 @@ from testgres.operations.os_ops import OsOperations, ConnectionParams
57
57
  from testgres.operations.local_ops import LocalOperations
58
58
  from testgres.operations.remote_ops import RemoteOperations
59
59
 
60
- __version__ = "1.14.4"
60
+ __version__ = "1.15.0"
61
61
 
62
62
  __all__ = [
63
63
  "get_new_node",
@@ -72,7 +72,7 @@ __all__ = [
72
72
  "NodeApp",
73
73
  "PostgresNode",
74
74
  "PortManager",
75
- "reserve_port", "release_port", "bound_ports", "get_bin_path", "get_pg_config", "get_pg_version", "parse_pg_version",
75
+ "reserve_port", "release_port", "get_bin_path", "get_bin_dir", "get_pg_config", "get_pg_version", "parse_pg_version",
76
76
  "First", "Any",
77
77
  "OsOperations", "LocalOperations", "RemoteOperations", "ConnectionParams"
78
78
  ]
@@ -14,7 +14,7 @@ from .enums import IsolationLevel
14
14
 
15
15
  from .defaults import \
16
16
  default_dbname, \
17
- default_username
17
+ default_username2
18
18
 
19
19
  from .exceptions import QueryException
20
20
 
@@ -38,7 +38,7 @@ class NodeConnection(object):
38
38
 
39
39
  # Set default arguments
40
40
  dbname = dbname or default_dbname()
41
- username = username or default_username()
41
+ username = username or default_username2(node.os_ops)
42
42
 
43
43
  self._node = node
44
44
 
@@ -10,6 +10,10 @@ TMP_DUMP = 'tgsd_'
10
10
  TMP_CACHE = 'tgsc_'
11
11
  TMP_BACKUP = 'tgsb_'
12
12
 
13
+ TMP_TESTGRES = "testgres"
14
+
15
+ TMP_TESTGRES_PORTS = TMP_TESTGRES + "/ports"
16
+
13
17
  # path to control file
14
18
  XLOG_CONTROL_FILE = "global/pg_control"
15
19
 
@@ -1,6 +1,9 @@
1
1
  import datetime
2
2
  import struct
3
3
  import uuid
4
+ import typing
5
+
6
+ from testgres.operations.os_ops import OsOperations
4
7
 
5
8
  from .config import testgres_config as tconf
6
9
 
@@ -13,11 +16,30 @@ def default_dbname():
13
16
  return 'postgres'
14
17
 
15
18
 
16
- def default_username():
19
+ def default_username(os_ops: typing.Optional[OsOperations] = None) -> str:
17
20
  """
18
21
  Return default username (current user).
19
22
  """
20
- return tconf.os_ops.get_user()
23
+ assert os_ops is None or isinstance(os_ops, OsOperations)
24
+
25
+ if os_ops is None:
26
+ os_ops = tconf.os_ops
27
+
28
+ assert isinstance(os_ops, OsOperations)
29
+ result = default_username2(os_ops)
30
+ assert type(result) is str
31
+ return result
32
+
33
+
34
+ def default_username2(os_ops: OsOperations) -> str:
35
+ """
36
+ Return default username (current user).
37
+ """
38
+ assert isinstance(os_ops, OsOperations)
39
+
40
+ result = os_ops.get_user()
41
+ assert type(result) is str
42
+ return result
21
43
 
22
44
 
23
45
  def generate_app_name():
@@ -29,7 +29,7 @@ class NodeStatus(IntEnum):
29
29
  Status of a PostgresNode
30
30
  """
31
31
 
32
- Running, Stopped, Uninitialized = range(3)
32
+ Running, Stopped, Uninitialized, Zombie = range(4)
33
33
 
34
34
  # for Python 3.x
35
35
  def __bool__(self):
@@ -0,0 +1,80 @@
1
+ # coding: utf-8
2
+
3
+ import typing
4
+
5
+ from testgres.operations.os_ops import OsOperations
6
+
7
+
8
+ class FileLineReader:
9
+ _os_ops: OsOperations
10
+ _file_name: str
11
+ _file_encoding: str
12
+ _file_pos: int
13
+ _buffer_pos: int
14
+ _buffer: bytes
15
+
16
+ # --------------------------------------------------------------------
17
+ def __init__(
18
+ self,
19
+ os_ops: OsOperations,
20
+ file_name: str,
21
+ file_encoding: str = "utf-8",
22
+ ):
23
+ assert isinstance(os_ops, OsOperations)
24
+ assert type(file_encoding) is str
25
+ self._os_ops = os_ops
26
+ self._file_name = file_name
27
+ self._file_encoding = file_encoding
28
+ self._file_pos = 0
29
+ self._buffer_pos = 0
30
+ self._buffer = b''
31
+ return
32
+
33
+ # interface ----------------------------------------------------------
34
+ def read_line(self) -> typing.Optional[str]:
35
+ assert isinstance(self._os_ops, OsOperations)
36
+ assert type(self._buffer_pos) is int
37
+ assert type(self._buffer) is bytes
38
+ assert self._buffer_pos >= 0
39
+ assert self._buffer_pos <= len(self._buffer)
40
+
41
+ scan_pos = self._buffer_pos
42
+
43
+ while True:
44
+ sz1 = len(self._buffer)
45
+
46
+ if scan_pos == sz1:
47
+ block = self._os_ops.read_binary(
48
+ self._file_name,
49
+ self._file_pos,
50
+ )
51
+ assert type(block) is bytes
52
+ self._buffer += block
53
+ self._file_pos += len(block)
54
+
55
+ x = self._buffer.find(b'\n', scan_pos)
56
+
57
+ sz2 = len(self._buffer)
58
+
59
+ if x == -1:
60
+ if scan_pos == sz2:
61
+ return None
62
+
63
+ if self._buffer_pos == 0:
64
+ scan_pos = sz2
65
+ else:
66
+ assert self._buffer_pos > 0
67
+ self._buffer = self._buffer[self._buffer_pos:]
68
+ scan_pos = sz2 - self._buffer_pos
69
+ self._buffer_pos = 0
70
+ continue
71
+
72
+ assert x >= 0
73
+ assert x < sz2
74
+
75
+ b = self._buffer[self._buffer_pos:(x+1)]
76
+
77
+ s = b.decode(self._file_encoding)
78
+
79
+ self._buffer_pos = x + 1
80
+ return s
@@ -60,3 +60,12 @@ class InternalPlatformUtils:
60
60
  assert type(bin_dir) is str
61
61
  assert type(data_dir) is str
62
62
  raise NotImplementedError("InternalPlatformUtils::FindPostmaster is not implemented.")
63
+
64
+ def ProcessIsZombi_soft_check(
65
+ self,
66
+ os_ops: OsOperations,
67
+ pid: int,
68
+ ) -> typing.Optional[bool]:
69
+ assert isinstance(os_ops, OsOperations)
70
+ assert type(pid) is int
71
+ raise NotImplementedError("InternalPlatformUtils::ProcessIsZombi_soft_ver is not implemented.")
@@ -0,0 +1,429 @@
1
+ from __future__ import annotations
2
+
3
+ from .. import internal_platform_utils as base
4
+ from ... import internal_utils
5
+ from ....raise_error import RaiseError
6
+
7
+ from testgres.operations.os_ops import OsOperations
8
+ from testgres.operations.exceptions import ExecUtilException
9
+
10
+ import re
11
+ import shlex
12
+ import typing
13
+ import time
14
+
15
+
16
+ class InternalPlatformUtils(base.InternalPlatformUtils):
17
+ C_MAX_FIND_POSTMASTER_ATTEMPTS = 5
18
+ C_BASH_EXE = "/bin/bash"
19
+
20
+ sm_exec_env = {
21
+ "LANG": "en_US.UTF-8",
22
+ "LC_ALL": "en_US.UTF-8",
23
+ }
24
+
25
+ # --------------------------------------------------------------------
26
+ def FindPostmaster(
27
+ self,
28
+ os_ops: OsOperations,
29
+ bin_dir: str,
30
+ data_dir: str
31
+ ) -> InternalPlatformUtils.FindPostmasterResult:
32
+ assert isinstance(os_ops, OsOperations)
33
+ assert type(bin_dir) is str
34
+ assert type(data_dir) is str
35
+ assert type(__class__.C_BASH_EXE) is str
36
+ assert type(__class__.sm_exec_env) is dict
37
+ assert len(__class__.C_BASH_EXE) > 0
38
+ assert len(bin_dir) > 0
39
+ assert len(data_dir) > 0
40
+
41
+ failures: typing.List[Exception] = []
42
+
43
+ postmaster_pid: typing.Optional[int] = None
44
+
45
+ nAttempts = 0
46
+
47
+ while True:
48
+ nAttempts += 1
49
+
50
+ try:
51
+ postmaster_pid = __class__._FindPostmaster(
52
+ os_ops,
53
+ bin_dir,
54
+ data_dir,
55
+ )
56
+ except Exception as e:
57
+ failures.append(e)
58
+
59
+ log_msg = "FindPostmaster (bin_dir={!r}, data_dir={!r}) detects a problem. Exception {}:\n{}".format(
60
+ bin_dir,
61
+ data_dir,
62
+ type(e).__name__,
63
+ e,
64
+ )
65
+ internal_utils.send_log_debug(log_msg)
66
+
67
+ if nAttempts < __class__.C_MAX_FIND_POSTMASTER_ATTEMPTS:
68
+ time.sleep(0.05)
69
+ continue
70
+
71
+ __class__._find_postmaster__throw_error__fail(
72
+ bin_dir=bin_dir,
73
+ data_dir=data_dir,
74
+ failures=failures,
75
+ )
76
+
77
+ break
78
+
79
+ if postmaster_pid is None:
80
+ return InternalPlatformUtils.FindPostmasterResult.create_not_found()
81
+
82
+ assert type(postmaster_pid) is int
83
+
84
+ return InternalPlatformUtils.FindPostmasterResult.create_ok(postmaster_pid)
85
+
86
+ # --------------------------------------------------------------------
87
+ @staticmethod
88
+ def _FindPostmaster(
89
+ os_ops: OsOperations,
90
+ bin_dir: str,
91
+ data_dir: str
92
+ ) -> typing.Optional[int]:
93
+ assert isinstance(os_ops, OsOperations)
94
+ assert type(bin_dir) is str
95
+ assert type(data_dir) is str
96
+ assert type(__class__.C_BASH_EXE) is str
97
+ assert type(__class__.sm_exec_env) is dict
98
+ assert len(__class__.C_BASH_EXE) > 0
99
+ assert len(bin_dir) > 0
100
+ assert len(data_dir) > 0
101
+
102
+ pg_path_e = re.escape(os_ops.build_path(bin_dir, "postgres"))
103
+ data_dir_e = re.escape(data_dir)
104
+
105
+ assert type(pg_path_e) is str
106
+ assert type(data_dir_e) is str
107
+
108
+ regexp = r"^\s*[0-9]+\s+[0-9]+\s+" + pg_path_e + r"(\s+.*)?\s+\-[D]\s+" + data_dir_e + r"(\s+.*)?"
109
+
110
+ cmd = [
111
+ __class__.C_BASH_EXE,
112
+ "-c",
113
+ "ps -ewwo \"pid=,ppid=,args=\" | grep -E " + shlex.quote(regexp),
114
+ ]
115
+
116
+ exec_r = os_ops.exec_command(
117
+ cmd=cmd,
118
+ ignore_errors=True,
119
+ verbose=True,
120
+ exec_env=__class__.sm_exec_env,
121
+ )
122
+
123
+ assert type(exec_r) is tuple
124
+ assert len(exec_r) == 3
125
+
126
+ exit_status, output_b, error_b = exec_r
127
+
128
+ assert type(exit_status) is int
129
+ assert type(output_b) is bytes
130
+ assert type(error_b) is bytes
131
+
132
+ if exit_status == 1:
133
+ return None
134
+
135
+ output = output_b.decode("utf-8")
136
+ error = error_b.decode("utf-8")
137
+
138
+ assert type(output) is str
139
+ assert type(error) is str
140
+
141
+ if exit_status != 0:
142
+ errMsg = f"test command returned an unexpected exit code: {exit_status}"
143
+ raise ExecUtilException(
144
+ message=errMsg,
145
+ command=cmd,
146
+ exit_code=exit_status,
147
+ out=output,
148
+ error=error,
149
+ )
150
+
151
+ lines = output.splitlines()
152
+ assert type(lines) is list
153
+
154
+ if len(lines) == 0:
155
+ # ACHTUNG!
156
+ raise RuntimeError("Command returns 0 error code without output.")
157
+
158
+ # parse result lines
159
+ pid_to_ppid: __class__.T_PID_TO_PPID = {}
160
+
161
+ for i_line in range(len(lines)):
162
+ assert type(lines[i_line]) is str
163
+
164
+ parts = lines[i_line].split()
165
+ assert type(parts) is list
166
+
167
+ if len(parts) < 2:
168
+ __class__._find_postmaster__throw_error__bad_line_format(
169
+ lines,
170
+ i_line,
171
+ "no usefull data",
172
+ )
173
+
174
+ if not parts[0].isdigit():
175
+ __class__._find_postmaster__throw_error__bad_line_format(
176
+ lines,
177
+ i_line,
178
+ "bad pid",
179
+ )
180
+
181
+ if not parts[1].isdigit():
182
+ __class__._find_postmaster__throw_error__bad_line_format(
183
+ lines,
184
+ i_line,
185
+ "bad ppid",
186
+ )
187
+
188
+ pid = int(parts[0])
189
+ ppid = int(parts[1])
190
+
191
+ if pid not in pid_to_ppid:
192
+ pid_to_ppid[pid] = ppid
193
+ continue
194
+
195
+ other_ppid = pid_to_ppid[pid]
196
+ assert type(other_ppid) is int
197
+
198
+ if ppid == other_ppid:
199
+ log_msg = "FindPostmaster (data_dir={!r}) get pid ({}) with ppid ({}) more than one time.".format(
200
+ data_dir,
201
+ pid,
202
+ ppid,
203
+ )
204
+ internal_utils.send_log_debug(log_msg)
205
+ continue
206
+
207
+ # ACTUNG ppid is changed --> restart
208
+ __class__._find_postmaster__throw_error__ppid_is_changed(
209
+ pid,
210
+ ppid,
211
+ other_ppid,
212
+ lines,
213
+ )
214
+
215
+ assert len(pid_to_ppid) <= len(lines)
216
+
217
+ true_postmasters = [
218
+ pid for pid, ppid in pid_to_ppid.items()
219
+ if ppid not in pid_to_ppid
220
+ ]
221
+
222
+ if len(true_postmasters) == 0:
223
+ __class__._find_postmaster__throw_error__cycle(
224
+ pid_to_ppid,
225
+ )
226
+
227
+ if len(true_postmasters) == 1:
228
+ true_pid = true_postmasters[0]
229
+
230
+ if len(pid_to_ppid) > 1:
231
+ msg = "Many processes like a postmaster for data dir [{}] are found ({}).".format(
232
+ data_dir,
233
+ len(true_postmasters),
234
+ )
235
+
236
+ msg += " List (ppid->pid): {}.".format(
237
+ __class__._make_text_from_pid_to_ppid(pid_to_ppid),
238
+ )
239
+
240
+ msg += " True postmaster PID is {}.".format(true_pid)
241
+ internal_utils.send_log_debug(msg)
242
+
243
+ return true_pid
244
+
245
+ assert len(true_postmasters) > 1
246
+
247
+ __class__._find_postmaster__throw_error__many_postmasters(
248
+ true_postmasters,
249
+ pid_to_ppid,
250
+ )
251
+
252
+ def ProcessIsZombi_soft_check(
253
+ self,
254
+ os_ops: OsOperations,
255
+ pid: int,
256
+ ) -> typing.Optional[bool]:
257
+ assert isinstance(os_ops, OsOperations)
258
+ assert type(pid) is int
259
+
260
+ proc_stat_file = os_ops.build_path("/proc", str(pid), "stat")
261
+
262
+ if not os_ops.path_exists(proc_stat_file):
263
+ return False
264
+
265
+ result: typing.Optional[bool] = None
266
+
267
+ try:
268
+ # Read one line from /proc/PID/stat
269
+ stat_content = os_ops.read_binary(proc_stat_file, 0).decode("utf-8", errors="ignore")
270
+
271
+ # We look for the closing parenthesis of the process name to ensure that
272
+ # we start from it and not depend on spaces inside the parentheses!
273
+ r_paren_idx = stat_content.rfind(")")
274
+
275
+ if r_paren_idx == -1:
276
+ pass
277
+ elif len(stat_content) <= r_paren_idx + 2:
278
+ pass
279
+ else:
280
+ # The status goes exactly one space after the closing bracket
281
+ assert (r_paren_idx + 2) < len(stat_content)
282
+ proc_status = stat_content[r_paren_idx + 2]
283
+ result = proc_status == "Z"
284
+ except Exception as e:
285
+ # If the file disappeared right during reading, it means the process is completely erased
286
+ if __class__._is_file_not_found_exception(e):
287
+ result = False
288
+
289
+ return result
290
+
291
+ @staticmethod
292
+ def _is_file_not_found_exception(e: Exception) -> bool:
293
+ if isinstance(e, FileNotFoundError):
294
+ return True
295
+
296
+ if isinstance(e, ExecUtilException):
297
+ if e.exit_code == 2:
298
+ return True
299
+
300
+ return False
301
+
302
+ T_PID_TO_PPID = typing.Dict[int, int]
303
+
304
+ @staticmethod
305
+ def _make_text_from_pid_to_ppid(pid_to_ppid: T_PID_TO_PPID) -> str:
306
+ result = ""
307
+ sep = ""
308
+ for pid, ppid in pid_to_ppid.items():
309
+ result += sep + " {}->{}".format(ppid, pid)
310
+ sep = ", "
311
+ continue
312
+ return result
313
+
314
+ @staticmethod
315
+ def _find_postmaster__throw_error__bad_line_format(
316
+ lines: typing.List[str],
317
+ i_line: int,
318
+ hint: str,
319
+ ) -> typing.NoReturn:
320
+ assert type(lines) is list
321
+ assert type(i_line) is int
322
+ assert type(hint) is int
323
+
324
+ error_lines: typing.List[str] = []
325
+ error_lines.append(
326
+ "Line {} has bad format. Hint: {}.".format(
327
+ i_line + 1,
328
+ hint,
329
+ ),
330
+ )
331
+ error_lines.append(
332
+ "Problem line is:"
333
+ )
334
+ error_lines.append(
335
+ " " + repr(lines[i_line]),
336
+ )
337
+ error_lines.append(
338
+ "All the lines is:",
339
+ )
340
+ for i in range(len(lines)):
341
+ error_lines.append(
342
+ " {}. {!r}".format(i + 1, lines[i]),
343
+ )
344
+ continue
345
+
346
+ raise RuntimeError("\n".join(error_lines))
347
+
348
+ @staticmethod
349
+ def _find_postmaster__throw_error__ppid_is_changed(
350
+ pid: int,
351
+ ppid: int,
352
+ other_ppid: int,
353
+ lines: typing.List[str],
354
+ ) -> typing.NoReturn:
355
+ assert type(pid) is int
356
+ assert type(ppid) is int
357
+ assert type(other_ppid) is int
358
+ assert type(lines) is list
359
+
360
+ error_lines: typing.List[str] = []
361
+ error_lines.append(
362
+ "Parent of process ({}) is changed from {} to {}.".format(
363
+ pid,
364
+ other_ppid,
365
+ ppid,
366
+ ),
367
+ )
368
+ error_lines.append(
369
+ "All the lines is:",
370
+ )
371
+ for i in range(len(lines)):
372
+ error_lines.append(
373
+ " {}. {!r}".format(i + 1, lines[i]),
374
+ )
375
+ continue
376
+
377
+ raise RuntimeError("\n".join(error_lines))
378
+
379
+ @staticmethod
380
+ def _find_postmaster__throw_error__cycle(
381
+ pid_to_ppid: T_PID_TO_PPID,
382
+ ) -> typing.NoReturn:
383
+ msg = "Cycle in processes postgres process tree. "
384
+
385
+ msg += " List (ppid->pid): {},".format(
386
+ __class__._make_text_from_pid_to_ppid(pid_to_ppid),
387
+ )
388
+
389
+ msg += " List size is {}.".format(
390
+ len(pid_to_ppid),
391
+ )
392
+ raise RuntimeError(msg)
393
+
394
+ @staticmethod
395
+ def _find_postmaster__throw_error__many_postmasters(
396
+ postmaster_pids: typing.List[int],
397
+ pid_to_ppid: T_PID_TO_PPID,
398
+ ) -> typing.NoReturn:
399
+ msg = "Many processes like a postmaster are found ({}): {}.".format(
400
+ len(postmaster_pids),
401
+ ", ".join(map(str, postmaster_pids)),
402
+ )
403
+
404
+ msg += " Trees (ppid->pid): {}.".format(
405
+ __class__._make_text_from_pid_to_ppid(pid_to_ppid),
406
+ )
407
+
408
+ msg += " Total process count is {}.".format(len(pid_to_ppid))
409
+ raise RuntimeError(msg)
410
+
411
+ @staticmethod
412
+ def _find_postmaster__throw_error__fail(
413
+ bin_dir: str,
414
+ data_dir: str,
415
+ failures: typing.List[Exception],
416
+ ) -> typing.NoReturn:
417
+ assert type(bin_dir) is str
418
+ assert type(data_dir) is str
419
+ assert type(failures) is list
420
+
421
+ method_name = "InternalPlatformUtils::FindPostmaster(bin_dir={!r}, data_dir={!r})".format(
422
+ bin_dir,
423
+ data_dir,
424
+ )
425
+
426
+ RaiseError.function_did_multiple_attempts_without_stable_result(
427
+ method_name,
428
+ failures,
429
+ )
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from .. import internal_platform_utils as base
4
4
  from testgres.operations.os_ops import OsOperations
5
+ import typing
5
6
 
6
7
 
7
8
  class InternalPlatformUtils(base.InternalPlatformUtils):
@@ -15,3 +16,12 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
15
16
  assert type(bin_dir) is str
16
17
  assert type(data_dir) is str
17
18
  return __class__.FindPostmasterResult.create_not_implemented()
19
+
20
+ def ProcessIsZombi_soft_check(
21
+ self,
22
+ os_ops: OsOperations,
23
+ pid: int,
24
+ ) -> typing.Optional[bool]:
25
+ assert isinstance(os_ops, OsOperations)
26
+ assert type(pid) is int
27
+ return None