testgres 1.16.1__tar.gz → 1.16.2__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 (49) hide show
  1. {testgres-1.16.1/testgres.egg-info → testgres-1.16.2}/PKG-INFO +2 -2
  2. {testgres-1.16.1 → testgres-1.16.2}/pyproject.toml +1 -1
  3. {testgres-1.16.1 → testgres-1.16.2}/src/__init__.py +1 -1
  4. testgres-1.16.2/src/impl/platforms/darwin/internal_platform_utils.py +456 -0
  5. {testgres-1.16.1 → testgres-1.16.2}/src/impl/platforms/internal_platform_utils.py +1 -1
  6. {testgres-1.16.1 → testgres-1.16.2}/src/impl/platforms/internal_platform_utils_factory.py +4 -0
  7. {testgres-1.16.1 → testgres-1.16.2}/src/impl/platforms/linux/internal_platform_utils.py +3 -2
  8. {testgres-1.16.1 → testgres-1.16.2}/src/node_app.py +1 -1
  9. {testgres-1.16.1 → testgres-1.16.2/testgres.egg-info}/PKG-INFO +2 -2
  10. {testgres-1.16.1 → testgres-1.16.2}/testgres.egg-info/SOURCES.txt +1 -0
  11. {testgres-1.16.1 → testgres-1.16.2}/testgres.egg-info/requires.txt +1 -1
  12. {testgres-1.16.1 → testgres-1.16.2}/tests/test_os_ops_common.py +181 -10
  13. {testgres-1.16.1 → testgres-1.16.2}/tests/test_testgres_common.py +1 -1
  14. {testgres-1.16.1 → testgres-1.16.2}/LICENSE +0 -0
  15. {testgres-1.16.1 → testgres-1.16.2}/README.md +0 -0
  16. {testgres-1.16.1 → testgres-1.16.2}/setup.cfg +0 -0
  17. {testgres-1.16.1 → testgres-1.16.2}/src/api.py +0 -0
  18. {testgres-1.16.1 → testgres-1.16.2}/src/backup.py +0 -0
  19. {testgres-1.16.1 → testgres-1.16.2}/src/cache.py +0 -0
  20. {testgres-1.16.1 → testgres-1.16.2}/src/config.py +0 -0
  21. {testgres-1.16.1 → testgres-1.16.2}/src/connection.py +0 -0
  22. {testgres-1.16.1 → testgres-1.16.2}/src/consts.py +0 -0
  23. {testgres-1.16.1 → testgres-1.16.2}/src/decorators.py +0 -0
  24. {testgres-1.16.1 → testgres-1.16.2}/src/defaults.py +0 -0
  25. {testgres-1.16.1 → testgres-1.16.2}/src/enums.py +0 -0
  26. {testgres-1.16.1 → testgres-1.16.2}/src/exceptions.py +0 -0
  27. {testgres-1.16.1 → testgres-1.16.2}/src/impl/file_line_reader.py +0 -0
  28. {testgres-1.16.1 → testgres-1.16.2}/src/impl/internal_utils.py +0 -0
  29. {testgres-1.16.1 → testgres-1.16.2}/src/impl/platforms/win32/internal_platform_utils.py +0 -0
  30. {testgres-1.16.1 → testgres-1.16.2}/src/impl/port_manager__generic.py +0 -0
  31. {testgres-1.16.1 → testgres-1.16.2}/src/impl/port_manager__generic2.py +0 -0
  32. {testgres-1.16.1 → testgres-1.16.2}/src/impl/port_manager__this_host.py +0 -0
  33. {testgres-1.16.1 → testgres-1.16.2}/src/logger.py +0 -0
  34. {testgres-1.16.1 → testgres-1.16.2}/src/node.py +0 -0
  35. {testgres-1.16.1 → testgres-1.16.2}/src/port_manager.py +0 -0
  36. {testgres-1.16.1 → testgres-1.16.2}/src/pubsub.py +0 -0
  37. {testgres-1.16.1 → testgres-1.16.2}/src/raise_error.py +0 -0
  38. {testgres-1.16.1 → testgres-1.16.2}/src/standby.py +0 -0
  39. {testgres-1.16.1 → testgres-1.16.2}/src/utils.py +0 -0
  40. {testgres-1.16.1 → testgres-1.16.2}/testgres.egg-info/dependency_links.txt +0 -0
  41. {testgres-1.16.1 → testgres-1.16.2}/testgres.egg-info/top_level.txt +0 -0
  42. {testgres-1.16.1 → testgres-1.16.2}/tests/test_api.py +0 -0
  43. {testgres-1.16.1 → testgres-1.16.2}/tests/test_config.py +0 -0
  44. {testgres-1.16.1 → testgres-1.16.2}/tests/test_os_ops_local.py +0 -0
  45. {testgres-1.16.1 → testgres-1.16.2}/tests/test_os_ops_remote.py +0 -0
  46. {testgres-1.16.1 → testgres-1.16.2}/tests/test_raise_error.py +0 -0
  47. {testgres-1.16.1 → testgres-1.16.2}/tests/test_testgres_local.py +0 -0
  48. {testgres-1.16.1 → testgres-1.16.2}/tests/test_testgres_remote.py +0 -0
  49. {testgres-1.16.1 → testgres-1.16.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testgres
3
- Version: 1.16.1
3
+ Version: 1.16.2
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<4.0.0,>=3.3.0
30
+ Requires-Dist: testgres.os_ops<4.0.0,>=3.3.3
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>=3.3.0,<4.0.0",
68
+ "testgres.os_ops>=3.3.3,<4.0.0",
69
69
  ]
70
70
 
71
71
  [project.urls]
@@ -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.1"
63
+ __version__ = "1.16.2"
64
64
 
65
65
  __all__ = [
66
66
  "get_new_node",
@@ -0,0 +1,456 @@
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.os_ops import OsCommandResult
9
+ from testgres.operations.exceptions import ExecUtilException
10
+ from testgres.operations.types import T_OS_EXEC_ENV
11
+
12
+ import re
13
+ import shlex
14
+ import typing
15
+ import time
16
+ import subprocess
17
+
18
+
19
+ class InternalPlatformUtils(base.InternalPlatformUtils):
20
+ C_MAX_FIND_POSTMASTER_ATTEMPTS = 5
21
+ C_BASH_EXE = "/bin/bash"
22
+
23
+ sm_exec_env: T_OS_EXEC_ENV = {
24
+ "LANG": "en_US.UTF-8",
25
+ "LC_ALL": "en_US.UTF-8",
26
+ }
27
+
28
+ # --------------------------------------------------------------------
29
+ def FindPostmaster(
30
+ self,
31
+ os_ops: OsOperations,
32
+ bin_dir: str,
33
+ data_dir: str
34
+ ) -> InternalPlatformUtils.FindPostmasterResult:
35
+ assert isinstance(os_ops, OsOperations)
36
+ assert type(bin_dir) is str
37
+ assert type(data_dir) is str
38
+ assert type(__class__.C_BASH_EXE) is str
39
+ assert type(__class__.sm_exec_env) is dict
40
+ assert len(__class__.C_BASH_EXE) > 0
41
+ assert len(bin_dir) > 0
42
+ assert len(data_dir) > 0
43
+
44
+ failures: typing.List[Exception] = []
45
+
46
+ postmaster_pid: typing.Optional[int] = None
47
+
48
+ nAttempts = 0
49
+
50
+ while True:
51
+ nAttempts += 1
52
+
53
+ try:
54
+ postmaster_pid = __class__._FindPostmaster(
55
+ os_ops,
56
+ bin_dir,
57
+ data_dir,
58
+ )
59
+ except Exception as e:
60
+ failures.append(e)
61
+
62
+ log_msg = "FindPostmaster (bin_dir={!r}, data_dir={!r}) detects a problem. Exception {}:\n{}".format(
63
+ bin_dir,
64
+ data_dir,
65
+ type(e).__name__,
66
+ e,
67
+ )
68
+ internal_utils.send_log_debug(log_msg)
69
+
70
+ if nAttempts < __class__.C_MAX_FIND_POSTMASTER_ATTEMPTS:
71
+ time.sleep(0.05)
72
+ continue
73
+
74
+ __class__._find_postmaster__throw_error__fail(
75
+ bin_dir=bin_dir,
76
+ data_dir=data_dir,
77
+ failures=failures,
78
+ )
79
+
80
+ break
81
+
82
+ if postmaster_pid is None:
83
+ return InternalPlatformUtils.FindPostmasterResult.create_not_found()
84
+
85
+ assert type(postmaster_pid) is int
86
+
87
+ return InternalPlatformUtils.FindPostmasterResult.create_ok(postmaster_pid)
88
+
89
+ # --------------------------------------------------------------------
90
+ @staticmethod
91
+ def _FindPostmaster(
92
+ os_ops: OsOperations,
93
+ bin_dir: str,
94
+ data_dir: str
95
+ ) -> typing.Optional[int]:
96
+ assert isinstance(os_ops, OsOperations)
97
+ assert type(bin_dir) is str
98
+ assert type(data_dir) is str
99
+ assert type(__class__.C_BASH_EXE) is str
100
+ assert type(__class__.sm_exec_env) is dict
101
+ assert len(__class__.C_BASH_EXE) > 0
102
+ assert len(bin_dir) > 0
103
+ assert len(data_dir) > 0
104
+
105
+ pg_path_e = re.escape(os_ops.build_path(bin_dir, "postgres"))
106
+ data_dir_e = re.escape(data_dir)
107
+
108
+ assert type(pg_path_e) is str
109
+ assert type(data_dir_e) is str
110
+
111
+ regexp = r"^\s*[0-9]+\s+[0-9]+\s+" + pg_path_e + r"(\s+.*)?\s+\-[D]\s+" + data_dir_e + r"(\s+.*)?"
112
+
113
+ cmd = [
114
+ __class__.C_BASH_EXE,
115
+ "-c",
116
+ "ps -ewwo \"pid=,ppid=,args=\" | grep -E " + shlex.quote(regexp),
117
+ ]
118
+
119
+ exec_r = os_ops.run(
120
+ cmd=cmd,
121
+ check=False,
122
+ exec_env=__class__.sm_exec_env,
123
+ )
124
+
125
+ assert type(exec_r) is OsCommandResult
126
+ assert type(exec_r.returncode) is int
127
+ assert type(exec_r.stdout) is bytes
128
+ assert type(exec_r.stderr) is bytes
129
+
130
+ if exec_r.returncode == 1:
131
+ return None
132
+
133
+ output = exec_r.stdout.decode("utf-8")
134
+ error = exec_r.stderr.decode("utf-8")
135
+
136
+ assert type(output) is str
137
+ assert type(error) is str
138
+
139
+ if exec_r.returncode != 0:
140
+ errMsg = f"test command returned an unexpected exit code: {exec_r.returncode}"
141
+ raise ExecUtilException(
142
+ message=errMsg,
143
+ command=cmd,
144
+ exit_code=exec_r.returncode,
145
+ out=output,
146
+ error=error,
147
+ )
148
+
149
+ lines = output.splitlines()
150
+ assert type(lines) is list
151
+
152
+ if len(lines) == 0:
153
+ # ACHTUNG!
154
+ raise RuntimeError("Command returns 0 error code without output.")
155
+
156
+ # parse result lines
157
+ pid_to_ppid: __class__.T_PID_TO_PPID = {}
158
+
159
+ for i_line in range(len(lines)):
160
+ assert type(lines[i_line]) is str
161
+
162
+ parts = lines[i_line].split()
163
+ assert type(parts) is list
164
+
165
+ if len(parts) < 2:
166
+ __class__._find_postmaster__throw_error__bad_line_format(
167
+ lines,
168
+ i_line,
169
+ "no usefull data",
170
+ )
171
+
172
+ if not parts[0].isdigit():
173
+ __class__._find_postmaster__throw_error__bad_line_format(
174
+ lines,
175
+ i_line,
176
+ "bad pid",
177
+ )
178
+
179
+ if not parts[1].isdigit():
180
+ __class__._find_postmaster__throw_error__bad_line_format(
181
+ lines,
182
+ i_line,
183
+ "bad ppid",
184
+ )
185
+
186
+ pid = int(parts[0])
187
+ ppid = int(parts[1])
188
+
189
+ if pid not in pid_to_ppid:
190
+ pid_to_ppid[pid] = ppid
191
+ continue
192
+
193
+ other_ppid = pid_to_ppid[pid]
194
+ assert type(other_ppid) is int
195
+
196
+ if ppid == other_ppid:
197
+ log_msg = "FindPostmaster (data_dir={!r}) get pid ({}) with ppid ({}) more than one time.".format(
198
+ data_dir,
199
+ pid,
200
+ ppid,
201
+ )
202
+ internal_utils.send_log_debug(log_msg)
203
+ continue
204
+
205
+ # ACTUNG ppid is changed --> restart
206
+ __class__._find_postmaster__throw_error__ppid_is_changed(
207
+ pid,
208
+ ppid,
209
+ other_ppid,
210
+ lines,
211
+ )
212
+
213
+ assert len(pid_to_ppid) <= len(lines)
214
+
215
+ true_postmasters = [
216
+ pid for pid, ppid in pid_to_ppid.items()
217
+ if ppid not in pid_to_ppid
218
+ ]
219
+
220
+ if len(true_postmasters) == 0:
221
+ __class__._find_postmaster__throw_error__cycle(
222
+ pid_to_ppid,
223
+ )
224
+
225
+ if len(true_postmasters) == 1:
226
+ true_pid = true_postmasters[0]
227
+
228
+ if len(pid_to_ppid) > 1:
229
+ msg = "Many processes like a postmaster for data dir [{}] are found ({}).".format(
230
+ data_dir,
231
+ len(true_postmasters),
232
+ )
233
+
234
+ msg += " List (ppid->pid): {}.".format(
235
+ __class__._make_text_from_pid_to_ppid(pid_to_ppid),
236
+ )
237
+
238
+ msg += " True postmaster PID is {}.".format(true_pid)
239
+ internal_utils.send_log_debug(msg)
240
+
241
+ return true_pid
242
+
243
+ assert len(true_postmasters) > 1
244
+
245
+ __class__._find_postmaster__throw_error__many_postmasters(
246
+ true_postmasters,
247
+ pid_to_ppid,
248
+ )
249
+
250
+ # --------------------------------------------------------------------
251
+ def ProcessIsZombi_soft_check(
252
+ self,
253
+ os_ops: OsOperations,
254
+ pid: int,
255
+ ) -> typing.Optional[bool]:
256
+ assert isinstance(os_ops, OsOperations)
257
+ assert type(pid) is int
258
+
259
+ # Change for macOS: Instead of reading non-existent /proc/PID/stat,
260
+ # we make a native call to the system ps and request the status (state) of the process.
261
+ cmd = ["ps", "-p", str(pid), "-wwo", "state="]
262
+
263
+ exec_r = os_ops.run(
264
+ cmd=cmd,
265
+ check=False,
266
+ exec_env=__class__.sm_exec_env,
267
+ stdout=subprocess.PIPE,
268
+ )
269
+
270
+ assert type(exec_r) is OsCommandResult
271
+ assert type(exec_r.stdout) is bytes
272
+
273
+ # If the process is not found (already terminated and deleted), ps will return code 1
274
+ if exec_r.returncode == 1:
275
+ return False
276
+
277
+ if exec_r.returncode != 0:
278
+ return None
279
+
280
+ proc_status = exec_r.stdout.decode("utf-8").rstrip()
281
+
282
+ if not proc_status:
283
+ return None
284
+
285
+ assert len(proc_status) > 0
286
+
287
+ # state The state is given by a sequence of letters, for example,
288
+ # "RWNA". The first letter indicates the run state of the process:
289
+ #
290
+ # D Marks a process in disk (or other short term, uninterruptible) wait.[legacy option]
291
+ # I Marks a process that is idle (sleeping for longer than about 20 seconds).
292
+ # R Marks a runnable process.
293
+ # S Marks a process that is sleeping for less than about 20 seconds.
294
+ # T Marks a stopped process.
295
+ # U Marks a process in uninterruptible wait.
296
+ # Z Marks a dead process (a 'zombie').
297
+ #
298
+ # Additional characters after these, if any, indicate additional
299
+ # state information:
300
+ #
301
+ # + The process is in the foreground process group of its control terminal.
302
+ # < The process has raised CPU scheduling priority.
303
+ # > The process has specified a soft limit on memory requirements and is currently
304
+ # exceeding that limit; such a process is (necessarily) not swapped.
305
+ # A the process has asked for random page replacement
306
+ # (VA_ANOM, from vadvise(2), for example, lisp(1) in a garbage collect).
307
+ # E The process is trying to exit.
308
+ # L The process has pages locked in core (for example, for raw I/O).
309
+ # N The process has reduced CPU scheduling priority (see setpriority(2)).
310
+ # S The process has asked for FIFO page replacement (VA_SEQL,
311
+ # from vadvise(2), for example, a large image processing
312
+ # program using virtual memory to sequentially address
313
+ # voluminous data).
314
+ # s The process is a session leader.
315
+ # V The process is suspended during a vfork.
316
+ # W The process is swapped out.
317
+ # X The process is being traced or debugged.
318
+
319
+ ch1 = proc_status[0]
320
+
321
+ if ch1 == "Z":
322
+ return True
323
+
324
+ if ch1 in "DIRSTU":
325
+ return False
326
+
327
+ return None
328
+
329
+ T_PID_TO_PPID = typing.Dict[int, int]
330
+
331
+ @staticmethod
332
+ def _make_text_from_pid_to_ppid(pid_to_ppid: T_PID_TO_PPID) -> str:
333
+ result = ""
334
+ sep = ""
335
+ for pid, ppid in pid_to_ppid.items():
336
+ result += sep + " {}->{}".format(ppid, pid)
337
+ sep = ", "
338
+ continue
339
+ return result
340
+
341
+ @staticmethod
342
+ def _find_postmaster__throw_error__bad_line_format(
343
+ lines: typing.List[str],
344
+ i_line: int,
345
+ hint: str,
346
+ ) -> typing.NoReturn:
347
+ assert type(lines) is list
348
+ assert type(i_line) is int
349
+ assert type(hint) is str
350
+
351
+ error_lines: typing.List[str] = []
352
+ error_lines.append(
353
+ "Line {} has bad format. Hint: {}.".format(
354
+ i_line + 1,
355
+ hint,
356
+ ),
357
+ )
358
+ error_lines.append(
359
+ "Problem line is:"
360
+ )
361
+ error_lines.append(
362
+ " " + repr(lines[i_line]),
363
+ )
364
+ error_lines.append(
365
+ "All the lines is:",
366
+ )
367
+ for i in range(len(lines)):
368
+ error_lines.append(
369
+ " {}. {!r}".format(i + 1, lines[i]),
370
+ )
371
+ continue
372
+
373
+ raise RuntimeError("\n".join(error_lines))
374
+
375
+ @staticmethod
376
+ def _find_postmaster__throw_error__ppid_is_changed(
377
+ pid: int,
378
+ ppid: int,
379
+ other_ppid: int,
380
+ lines: typing.List[str],
381
+ ) -> typing.NoReturn:
382
+ assert type(pid) is int
383
+ assert type(ppid) is int
384
+ assert type(other_ppid) is int
385
+ assert type(lines) is list
386
+
387
+ error_lines: typing.List[str] = []
388
+ error_lines.append(
389
+ "Parent of process ({}) is changed from {} to {}.".format(
390
+ pid,
391
+ other_ppid,
392
+ ppid,
393
+ ),
394
+ )
395
+ error_lines.append(
396
+ "All the lines is:",
397
+ )
398
+ for i in range(len(lines)):
399
+ error_lines.append(
400
+ " {}. {!r}".format(i + 1, lines[i]),
401
+ )
402
+ continue
403
+
404
+ raise RuntimeError("\n".join(error_lines))
405
+
406
+ @staticmethod
407
+ def _find_postmaster__throw_error__cycle(
408
+ pid_to_ppid: T_PID_TO_PPID,
409
+ ) -> typing.NoReturn:
410
+ msg = "Cycle in processes postgres process tree. "
411
+
412
+ msg += " List (ppid->pid): {},".format(
413
+ __class__._make_text_from_pid_to_ppid(pid_to_ppid),
414
+ )
415
+
416
+ msg += " List size is {}.".format(
417
+ len(pid_to_ppid),
418
+ )
419
+ raise RuntimeError(msg)
420
+
421
+ @staticmethod
422
+ def _find_postmaster__throw_error__many_postmasters(
423
+ postmaster_pids: typing.List[int],
424
+ pid_to_ppid: T_PID_TO_PPID,
425
+ ) -> typing.NoReturn:
426
+ msg = "Many processes like a postmaster are found ({}): {}.".format(
427
+ len(postmaster_pids),
428
+ ", ".join(map(str, postmaster_pids)),
429
+ )
430
+
431
+ msg += " Trees (ppid->pid): {}.".format(
432
+ __class__._make_text_from_pid_to_ppid(pid_to_ppid),
433
+ )
434
+
435
+ msg += " Total process count is {}.".format(len(pid_to_ppid))
436
+ raise RuntimeError(msg)
437
+
438
+ @staticmethod
439
+ def _find_postmaster__throw_error__fail(
440
+ bin_dir: str,
441
+ data_dir: str,
442
+ failures: typing.List[Exception],
443
+ ) -> typing.NoReturn:
444
+ assert type(bin_dir) is str
445
+ assert type(data_dir) is str
446
+ assert type(failures) is list
447
+
448
+ method_name = "InternalPlatformUtils::FindPostmaster(bin_dir={!r}, data_dir={!r})".format(
449
+ bin_dir,
450
+ data_dir,
451
+ )
452
+
453
+ RaiseError.function_did_multiple_attempts_without_stable_result(
454
+ method_name,
455
+ failures,
456
+ )
@@ -9,7 +9,7 @@ from testgres.operations.os_ops import OsOperations
9
9
  class InternalPlatformUtils:
10
10
  class FindPostmasterResultCode(enum.Enum):
11
11
  ok = 0
12
- not_found = 1,
12
+ not_found = 1
13
13
  not_implemented = 2
14
14
  many_processes = 3
15
15
  has_problems = 4
@@ -19,5 +19,9 @@ def create_internal_platform_utils(
19
19
  from .win32 import internal_platform_utils as x
20
20
  return x.InternalPlatformUtils()
21
21
 
22
+ if platform_name == "darwin":
23
+ from .darwin import internal_platform_utils as x
24
+ return x.InternalPlatformUtils()
25
+
22
26
  # not implemented
23
27
  return InternalPlatformUtils()
@@ -113,7 +113,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
113
113
  __class__.C_BASH_EXE,
114
114
  "-c",
115
115
  "ps -ewwo \"pid=,ppid=,args=\" | grep -E " + shlex.quote(regexp),
116
- ]
116
+ ]
117
117
 
118
118
  exec_r = os_ops.run(
119
119
  cmd=cmd,
@@ -246,6 +246,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
246
246
  pid_to_ppid,
247
247
  )
248
248
 
249
+ # --------------------------------------------------------------------
249
250
  def ProcessIsZombi_soft_check(
250
251
  self,
251
252
  os_ops: OsOperations,
@@ -316,7 +317,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
316
317
  ) -> typing.NoReturn:
317
318
  assert type(lines) is list
318
319
  assert type(i_line) is int
319
- assert type(hint) is int
320
+ assert type(hint) is str
320
321
 
321
322
  error_lines: typing.List[str] = []
322
323
  error_lines.append(
@@ -300,7 +300,7 @@ class NodeApp:
300
300
  #
301
301
  # Paranoid checks
302
302
  #
303
- if type(v) is str:
303
+ if type(v) is not str:
304
304
  __class__._raise_bugcheck("os_ops.get_tempdir returned a value with type {}.".format(
305
305
  type(v).__name__,
306
306
  ))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testgres
3
- Version: 1.16.1
3
+ Version: 1.16.2
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<4.0.0,>=3.3.0
30
+ Requires-Dist: testgres.os_ops<4.0.0,>=3.3.3
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)
@@ -27,6 +27,7 @@ src/impl/port_manager__generic2.py
27
27
  src/impl/port_manager__this_host.py
28
28
  src/impl/platforms/internal_platform_utils.py
29
29
  src/impl/platforms/internal_platform_utils_factory.py
30
+ src/impl/platforms/darwin/internal_platform_utils.py
30
31
  src/impl/platforms/linux/internal_platform_utils.py
31
32
  src/impl/platforms/win32/internal_platform_utils.py
32
33
  testgres.egg-info/PKG-INFO
@@ -3,4 +3,4 @@ port-for>=0.4
3
3
  six>=1.9.0
4
4
  psutil
5
5
  packaging
6
- testgres.os_ops<4.0.0,>=3.3.0
6
+ testgres.os_ops<4.0.0,>=3.3.3
@@ -7,6 +7,9 @@ from tests.helpers.global_data import OsOperations
7
7
  from tests.helpers.run_conditions import RunConditions
8
8
  from tests.helpers.local_check import LocalCheck
9
9
  from tests.helpers.local_check import OsOpsHelpers
10
+ from tests.helpers.multi_try_call import MultiTryCall
11
+
12
+ from tests.conftest_helpers import TestServices
10
13
 
11
14
  from testgres.operations.os_ops import OsProcessController
12
15
  from testgres.operations.os_ops import OsCommandResult
@@ -225,7 +228,7 @@ class TestOsOpsCommon:
225
228
  p = os_ops.get_platform()
226
229
  assert p is not None
227
230
  assert type(p) is str
228
- assert p in {"win32", "linux"}
231
+ assert p in {"win32", "linux", "darwin"}
229
232
  return
230
233
 
231
234
  def test_create_clone(
@@ -422,11 +425,12 @@ class TestOsOpsCommon:
422
425
  assert not os_ops.path_exists(tmp_file)
423
426
  return
424
427
 
425
- def test_exec_command_with_cwd(self, os_ops_descr: OsOpsDescr):
428
+ def test_exec_command_with_cwd__linux(self, os_ops_descr: OsOpsDescr):
426
429
  assert type(os_ops_descr) is OsOpsDescr
427
430
  assert isinstance(os_ops_descr.os_ops, OsOperations)
428
431
 
429
432
  RunConditions.skip_if_windows()
433
+ RunConditions.skip_if_darwin()
430
434
 
431
435
  os_ops = os_ops_descr.os_ops
432
436
  assert isinstance(os_ops, OsOperations)
@@ -616,7 +620,7 @@ class TestOsOpsCommon:
616
620
  assert not os_ops.path_exists(path)
617
621
  return
618
622
 
619
- def test_makedirs_failure(
623
+ def test_makedirs_failure__linux(
620
624
  self,
621
625
  os_ops_descr: OsOpsDescr,
622
626
  name_with_surprize: tagNameWithSurprize,
@@ -633,6 +637,7 @@ class TestOsOpsCommon:
633
637
  assert isinstance(os_ops, OsOperations)
634
638
 
635
639
  RunConditions.skip_if_windows()
640
+ RunConditions.skip_if_darwin()
636
641
 
637
642
  path = "/root/test_dir-{}-{}".format(
638
643
  name_with_surprize.value,
@@ -651,6 +656,42 @@ class TestOsOpsCommon:
651
656
  __class__.helper__bug_check__unknown_os_ops_type(os_ops)
652
657
  return
653
658
 
659
+ def test_makedirs_failure__darwin(
660
+ self,
661
+ os_ops_descr: OsOpsDescr,
662
+ name_with_surprize: tagNameWithSurprize,
663
+ ):
664
+ """
665
+ Test makedirs for failure.
666
+ """
667
+ # Try to create a directory in a read-only location
668
+ assert type(os_ops_descr) is OsOpsDescr
669
+ assert type(name_with_surprize) is __class__.tagNameWithSurprize
670
+ assert isinstance(os_ops_descr.os_ops, OsOperations)
671
+
672
+ os_ops = os_ops_descr.os_ops
673
+ assert isinstance(os_ops, OsOperations)
674
+
675
+ RunConditions.skip_if_windows()
676
+ RunConditions.skip_if_linux()
677
+
678
+ path = "/root/test_dir-{}-{}".format(
679
+ name_with_surprize.value,
680
+ uuid.uuid4().bytes.hex(),
681
+ )
682
+
683
+ # Test makedirs
684
+ with pytest.raises(Exception) as x:
685
+ os_ops.makedirs(path)
686
+
687
+ if type(os_ops).__name__ == "LocalOperations":
688
+ assert type(x.value) is OSError
689
+ elif type(os_ops).__name__ == "RemoteOperations":
690
+ assert type(x.value) is ExecUtilException
691
+ else:
692
+ __class__.helper__bug_check__unknown_os_ops_type(os_ops)
693
+ return
694
+
654
695
  def test_listdir(
655
696
  self,
656
697
  os_ops_descr: OsOpsDescr,
@@ -2559,13 +2600,16 @@ print('b', file=sys.stderr)
2559
2600
  return
2560
2601
 
2561
2602
  # --------------------------------------------------------------------
2562
- def test_get_abs_path(
2603
+ def test_get_abs_path__linux(
2563
2604
  self,
2564
2605
  os_ops_descr: OsOpsDescr,
2565
2606
  ):
2566
2607
  assert type(os_ops_descr) is OsOpsDescr
2567
2608
  assert isinstance(os_ops_descr.os_ops, OsOperations)
2568
2609
 
2610
+ RunConditions.skip_if_windows()
2611
+ RunConditions.skip_if_darwin()
2612
+
2569
2613
  os_ops = os_ops_descr.os_ops
2570
2614
  assert isinstance(os_ops, OsOperations)
2571
2615
 
@@ -2887,7 +2931,10 @@ print('b', file=sys.stderr)
2887
2931
 
2888
2932
  assert actual_child_pid == expected_child_pid
2889
2933
 
2890
- child_cmdline = childs[0].cmdline()
2934
+ child_cmdline = __class__.helper__wait_for_not_empty_proc_cmdline(
2935
+ childs[0],
2936
+ )
2937
+ assert type(child_cmdline) is list
2891
2938
 
2892
2939
  logging.info("child cmdline: {}".format(
2893
2940
  child_cmdline,
@@ -2970,7 +3017,10 @@ print('b', file=sys.stderr)
2970
3017
  assert actual_child_pids == expected_child_pids
2971
3018
 
2972
3019
  for i in range(len(childs)):
2973
- child_cmdline = childs[i].cmdline()
3020
+ child_cmdline = __class__.helper__wait_for_not_empty_proc_cmdline(
3021
+ childs[i],
3022
+ )
3023
+ assert type(child_cmdline) is list
2974
3024
 
2975
3025
  logging.info("child cmdline: {}".format(
2976
3026
  child_cmdline,
@@ -3154,7 +3204,7 @@ print('b', file=sys.stderr)
3154
3204
  assert lines == result_bin
3155
3205
  return
3156
3206
 
3157
- def test_prove_environment_isolation(
3207
+ def test_prove_environment_isolation__linux(
3158
3208
  self,
3159
3209
  os_ops_descr: OsOpsDescr,
3160
3210
  ):
@@ -3166,6 +3216,9 @@ print('b', file=sys.stderr)
3166
3216
  os_ops = os_ops_descr.os_ops
3167
3217
  assert isinstance(os_ops, OsOperations)
3168
3218
 
3219
+ RunConditions.skip_if_windows()
3220
+ RunConditions.skip_if_darwin()
3221
+
3169
3222
  logging.info("=================== COKANUM PROOF START ===================")
3170
3223
  logging.info(f"Target environment type: [{os_ops_descr.sign}]")
3171
3224
 
@@ -4666,7 +4719,98 @@ print('b', file=sys.stderr)
4666
4719
  type(os_ops).__name__,
4667
4720
  ))
4668
4721
  pass
4669
- pass
4722
+ return
4723
+
4724
+ def test_popen_terminate_mt(
4725
+ self,
4726
+ os_ops_descr: OsOpsDescr,
4727
+ ):
4728
+ assert type(os_ops_descr) is OsOpsDescr
4729
+ assert isinstance(os_ops_descr.os_ops, OsOperations)
4730
+
4731
+ RunConditions.skip_if_windows()
4732
+ os_ops = os_ops_descr.os_ops
4733
+
4734
+ controller: typing.Optional[OsProcessController] = None
4735
+
4736
+ try:
4737
+ N_WORKERS = 100
4738
+
4739
+ logging.info("Process is creating ...")
4740
+ cmd1 = ["sleep", "100"]
4741
+ controller = os_ops.popen(cmd1)
4742
+ assert isinstance(controller, OsProcessController)
4743
+
4744
+ logging.info("Worker are creating ...")
4745
+ threadPool = ThreadPoolExecutor(
4746
+ max_workers=N_WORKERS,
4747
+ thread_name_prefix="ex_creator",
4748
+ )
4749
+
4750
+ class tadWorkerData:
4751
+ future: ThreadFuture
4752
+
4753
+ workerDatas: typing.List[tadWorkerData] = list()
4754
+
4755
+ nErrors = 0
4756
+
4757
+ try:
4758
+ for n in range(N_WORKERS):
4759
+ logging.info("worker #{} is creating ...".format(n))
4760
+
4761
+ workerDatas.append(tadWorkerData())
4762
+
4763
+ workerDatas[n].future = threadPool.submit(
4764
+ controller.terminate,
4765
+ )
4766
+
4767
+ assert workerDatas[n].future is not None
4768
+
4769
+ logging.info("OK. All the workers were created!")
4770
+ except BaseException as e:
4771
+ nErrors += 1
4772
+ logging.error("A problem is detected ({}): {}".format(
4773
+ type(e).__name__,
4774
+ TestServices.ExceptionToHumanText(e),
4775
+ ))
4776
+
4777
+ logging.info("Will wait for stop of all the workers...")
4778
+
4779
+ nWorkers = 0
4780
+
4781
+ assert type(workerDatas) is list
4782
+
4783
+ for i in range(len(workerDatas)):
4784
+ worker = workerDatas[i].future
4785
+
4786
+ if worker is None:
4787
+ break
4788
+
4789
+ nWorkers += 1
4790
+
4791
+ assert isinstance(worker, ThreadFuture)
4792
+
4793
+ try:
4794
+ logging.info("Wait for worker #{}".format(i))
4795
+ worker.result()
4796
+ except BaseException as e:
4797
+ nErrors += 1
4798
+ logging.error("Worker #{} finished with error ({}): {}".format(
4799
+ i,
4800
+ type(e).__name__,
4801
+ TestServices.ExceptionToHumanText(e),
4802
+ ))
4803
+ continue
4804
+
4805
+ assert nWorkers == N_WORKERS
4806
+
4807
+ if nErrors != 0:
4808
+ raise RuntimeError("Some problems were detected. Please examine the log messages.")
4809
+
4810
+ finally:
4811
+ if controller is not None:
4812
+ controller.close()
4813
+
4670
4814
  return
4671
4815
 
4672
4816
  def test_popen_kill(self, os_ops_descr: OsOpsDescr):
@@ -5130,7 +5274,7 @@ print('b', file=sys.stderr)
5130
5274
 
5131
5275
  return
5132
5276
 
5133
- def test_popen_cwd(
5277
+ def test_popen_cwd__linux(
5134
5278
  self,
5135
5279
  os_ops_descr: OsOpsDescr,
5136
5280
  ):
@@ -5138,6 +5282,7 @@ print('b', file=sys.stderr)
5138
5282
  assert isinstance(os_ops_descr.os_ops, OsOperations)
5139
5283
 
5140
5284
  RunConditions.skip_if_windows()
5285
+ RunConditions.skip_if_darwin()
5141
5286
 
5142
5287
  os_ops = os_ops_descr.os_ops
5143
5288
 
@@ -5908,7 +6053,7 @@ print('b', file=sys.stderr)
5908
6053
 
5909
6054
  return
5910
6055
 
5911
- def test_run_cwd(
6056
+ def test_run_cwd__linux(
5912
6057
  self,
5913
6058
  os_ops_descr: OsOpsDescr,
5914
6059
  ):
@@ -5916,6 +6061,7 @@ print('b', file=sys.stderr)
5916
6061
  assert isinstance(os_ops_descr.os_ops, OsOperations)
5917
6062
 
5918
6063
  RunConditions.skip_if_windows()
6064
+ RunConditions.skip_if_darwin()
5919
6065
 
5920
6066
  os_ops = os_ops_descr.os_ops
5921
6067
 
@@ -6621,3 +6767,28 @@ print('b', file=sys.stderr)
6621
6767
  type(os_ops).__name__,
6622
6768
  )
6623
6769
  raise RuntimeError(err_msg)
6770
+
6771
+ @staticmethod
6772
+ def helper__wait_for_not_empty_proc_cmdline(
6773
+ proc_info,
6774
+ ) -> typing.List[str]:
6775
+ assert proc_info is not None
6776
+
6777
+ def LOCAL__not_empty(v: typing.List[str]) -> bool:
6778
+ assert v is not None
6779
+ assert type(v) is list
6780
+ return len(v) != 0
6781
+
6782
+ r = MultiTryCall.exec__until(
6783
+ proc_info.cmdline,
6784
+ LOCAL__not_empty,
6785
+ "wait not empty read proc cmdline",
6786
+ MultiTryCall.tagSETTINGS(
6787
+ 10,
6788
+ 1,
6789
+ ),
6790
+ )
6791
+
6792
+ assert type(r) is list
6793
+ assert len(r) > 0
6794
+ return r
@@ -121,7 +121,7 @@ class TestTestgresCommon:
121
121
  # Author: Mark G.
122
122
  assert v.major == 1
123
123
  assert v.minor == 16
124
- assert v.micro == 1
124
+ assert v.micro == 2
125
125
 
126
126
  assert str(v) == testgres_version
127
127
  return
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes