testgres 1.16.0__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.0/testgres.egg-info → testgres-1.16.2}/PKG-INFO +2 -2
  2. {testgres-1.16.0 → testgres-1.16.2}/pyproject.toml +1 -1
  3. {testgres-1.16.0 → testgres-1.16.2}/src/__init__.py +1 -1
  4. {testgres-1.16.0 → testgres-1.16.2}/src/consts.py +3 -0
  5. {testgres-1.16.0 → testgres-1.16.2}/src/exceptions.py +2 -2
  6. testgres-1.16.2/src/impl/platforms/darwin/internal_platform_utils.py +456 -0
  7. {testgres-1.16.0 → testgres-1.16.2}/src/impl/platforms/internal_platform_utils.py +1 -1
  8. {testgres-1.16.0 → testgres-1.16.2}/src/impl/platforms/internal_platform_utils_factory.py +4 -0
  9. {testgres-1.16.0 → testgres-1.16.2}/src/impl/platforms/linux/internal_platform_utils.py +3 -2
  10. {testgres-1.16.0 → testgres-1.16.2}/src/node.py +297 -175
  11. {testgres-1.16.0 → testgres-1.16.2}/src/node_app.py +28 -20
  12. {testgres-1.16.0 → testgres-1.16.2}/src/pubsub.py +10 -8
  13. {testgres-1.16.0 → testgres-1.16.2}/src/raise_error.py +2 -2
  14. {testgres-1.16.0 → testgres-1.16.2}/src/utils.py +24 -14
  15. {testgres-1.16.0 → testgres-1.16.2/testgres.egg-info}/PKG-INFO +2 -2
  16. {testgres-1.16.0 → testgres-1.16.2}/testgres.egg-info/SOURCES.txt +1 -0
  17. {testgres-1.16.0 → testgres-1.16.2}/testgres.egg-info/requires.txt +1 -1
  18. {testgres-1.16.0 → testgres-1.16.2}/tests/test_os_ops_common.py +239 -33
  19. {testgres-1.16.0 → testgres-1.16.2}/tests/test_os_ops_local.py +11 -4
  20. {testgres-1.16.0 → testgres-1.16.2}/tests/test_testgres_common.py +2 -2
  21. {testgres-1.16.0 → testgres-1.16.2}/LICENSE +0 -0
  22. {testgres-1.16.0 → testgres-1.16.2}/README.md +0 -0
  23. {testgres-1.16.0 → testgres-1.16.2}/setup.cfg +0 -0
  24. {testgres-1.16.0 → testgres-1.16.2}/src/api.py +0 -0
  25. {testgres-1.16.0 → testgres-1.16.2}/src/backup.py +0 -0
  26. {testgres-1.16.0 → testgres-1.16.2}/src/cache.py +0 -0
  27. {testgres-1.16.0 → testgres-1.16.2}/src/config.py +0 -0
  28. {testgres-1.16.0 → testgres-1.16.2}/src/connection.py +0 -0
  29. {testgres-1.16.0 → testgres-1.16.2}/src/decorators.py +0 -0
  30. {testgres-1.16.0 → testgres-1.16.2}/src/defaults.py +0 -0
  31. {testgres-1.16.0 → testgres-1.16.2}/src/enums.py +0 -0
  32. {testgres-1.16.0 → testgres-1.16.2}/src/impl/file_line_reader.py +0 -0
  33. {testgres-1.16.0 → testgres-1.16.2}/src/impl/internal_utils.py +0 -0
  34. {testgres-1.16.0 → testgres-1.16.2}/src/impl/platforms/win32/internal_platform_utils.py +0 -0
  35. {testgres-1.16.0 → testgres-1.16.2}/src/impl/port_manager__generic.py +0 -0
  36. {testgres-1.16.0 → testgres-1.16.2}/src/impl/port_manager__generic2.py +0 -0
  37. {testgres-1.16.0 → testgres-1.16.2}/src/impl/port_manager__this_host.py +0 -0
  38. {testgres-1.16.0 → testgres-1.16.2}/src/logger.py +0 -0
  39. {testgres-1.16.0 → testgres-1.16.2}/src/port_manager.py +0 -0
  40. {testgres-1.16.0 → testgres-1.16.2}/src/standby.py +0 -0
  41. {testgres-1.16.0 → testgres-1.16.2}/testgres.egg-info/dependency_links.txt +0 -0
  42. {testgres-1.16.0 → testgres-1.16.2}/testgres.egg-info/top_level.txt +0 -0
  43. {testgres-1.16.0 → testgres-1.16.2}/tests/test_api.py +0 -0
  44. {testgres-1.16.0 → testgres-1.16.2}/tests/test_config.py +0 -0
  45. {testgres-1.16.0 → testgres-1.16.2}/tests/test_os_ops_remote.py +0 -0
  46. {testgres-1.16.0 → testgres-1.16.2}/tests/test_raise_error.py +0 -0
  47. {testgres-1.16.0 → testgres-1.16.2}/tests/test_testgres_local.py +0 -0
  48. {testgres-1.16.0 → testgres-1.16.2}/tests/test_testgres_remote.py +0 -0
  49. {testgres-1.16.0 → 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.0
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.0"
63
+ __version__ = "1.16.2"
64
64
 
65
65
  __all__ = [
66
66
  "get_new_node",
@@ -1,5 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
+ # binary names
4
+ BINARY_NAME__POSTGRES = "postgres"
5
+
3
6
  # names for dirs in base_dir
4
7
  DATA_DIR = "data"
5
8
  LOGS_DIR = "logs"
@@ -113,7 +113,7 @@ class QueryTimeoutException(QueryException):
113
113
  def __init__(
114
114
  self,
115
115
  message: typing.Optional[str] = None,
116
- query: typing.Optional[str] = None
116
+ query: typing.Optional[str] = None,
117
117
  ):
118
118
  assert message is None or type(message) is str
119
119
  assert query is None or type(query) is str
@@ -170,7 +170,7 @@ class StartNodeException(TestgresException):
170
170
  def __init__(
171
171
  self,
172
172
  message: typing.Optional[str] = None,
173
- files: typing.Optional[typing.Iterable] = None
173
+ files: typing.Optional[typing.Iterable] = None,
174
174
  ):
175
175
  assert message is None or type(message) is str
176
176
  assert files is None or isinstance(files, typing.Iterable)
@@ -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(