testgres 1.16.1__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 (53) hide show
  1. {testgres-1.16.1/testgres.egg-info → testgres-1.16.3}/PKG-INFO +2 -3
  2. {testgres-1.16.1 → testgres-1.16.3}/pyproject.toml +1 -2
  3. {testgres-1.16.1 → testgres-1.16.3}/src/__init__.py +1 -1
  4. {testgres-1.16.1 → testgres-1.16.3}/src/backup.py +2 -4
  5. {testgres-1.16.1 → testgres-1.16.3}/src/cache.py +3 -5
  6. {testgres-1.16.1 → testgres-1.16.3}/src/config.py +1 -1
  7. {testgres-1.16.1 → testgres-1.16.3}/src/connection.py +1 -1
  8. {testgres-1.16.1 → testgres-1.16.3}/src/consts.py +3 -0
  9. {testgres-1.16.1 → testgres-1.16.3}/src/decorators.py +2 -3
  10. {testgres-1.16.1 → testgres-1.16.3}/src/enums.py +1 -2
  11. {testgres-1.16.1 → 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.1 → testgres-1.16.3}/src/impl/internal_utils.py +58 -0
  14. testgres-1.16.3/src/impl/platforms/darwin/internal_platform_utils.py +456 -0
  15. {testgres-1.16.1 → testgres-1.16.3}/src/impl/platforms/internal_platform_utils.py +6 -4
  16. {testgres-1.16.1 → testgres-1.16.3}/src/impl/platforms/internal_platform_utils_factory.py +4 -0
  17. testgres-1.16.3/src/impl/platforms/internal_platform_utils_provider.py +32 -0
  18. {testgres-1.16.1 → testgres-1.16.3}/src/impl/platforms/linux/internal_platform_utils.py +15 -7
  19. {testgres-1.16.1 → testgres-1.16.3}/src/impl/platforms/win32/internal_platform_utils.py +1 -1
  20. {testgres-1.16.1 → testgres-1.16.3}/src/node.py +13 -21
  21. {testgres-1.16.1 → testgres-1.16.3}/src/node_app.py +1 -1
  22. testgres-1.16.3/src/node_state.py +20 -0
  23. {testgres-1.16.1 → testgres-1.16.3}/src/port_manager.py +5 -2
  24. {testgres-1.16.1 → testgres-1.16.3}/src/pubsub.py +3 -5
  25. {testgres-1.16.1 → testgres-1.16.3}/src/raise_error.py +14 -0
  26. {testgres-1.16.1 → testgres-1.16.3}/src/standby.py +6 -8
  27. {testgres-1.16.1 → testgres-1.16.3}/src/utils.py +17 -271
  28. {testgres-1.16.1 → testgres-1.16.3/testgres.egg-info}/PKG-INFO +2 -3
  29. {testgres-1.16.1 → testgres-1.16.3}/testgres.egg-info/SOURCES.txt +4 -0
  30. testgres-1.16.3/testgres.egg-info/requires.txt +5 -0
  31. {testgres-1.16.1 → testgres-1.16.3}/tests/test_os_ops_common.py +181 -10
  32. {testgres-1.16.1 → testgres-1.16.3}/tests/test_testgres_common.py +258 -31
  33. testgres-1.16.1/testgres.egg-info/requires.txt +0 -6
  34. {testgres-1.16.1 → testgres-1.16.3}/LICENSE +0 -0
  35. {testgres-1.16.1 → testgres-1.16.3}/README.md +0 -0
  36. {testgres-1.16.1 → testgres-1.16.3}/setup.cfg +0 -0
  37. {testgres-1.16.1 → testgres-1.16.3}/src/api.py +0 -0
  38. {testgres-1.16.1 → testgres-1.16.3}/src/defaults.py +0 -0
  39. {testgres-1.16.1 → testgres-1.16.3}/src/impl/file_line_reader.py +0 -0
  40. {testgres-1.16.1 → testgres-1.16.3}/src/impl/port_manager__generic.py +0 -0
  41. {testgres-1.16.1 → testgres-1.16.3}/src/impl/port_manager__generic2.py +0 -0
  42. {testgres-1.16.1 → testgres-1.16.3}/src/impl/port_manager__this_host.py +0 -0
  43. {testgres-1.16.1 → testgres-1.16.3}/src/logger.py +0 -0
  44. {testgres-1.16.1 → testgres-1.16.3}/testgres.egg-info/dependency_links.txt +0 -0
  45. {testgres-1.16.1 → testgres-1.16.3}/testgres.egg-info/top_level.txt +0 -0
  46. {testgres-1.16.1 → testgres-1.16.3}/tests/test_api.py +0 -0
  47. {testgres-1.16.1 → testgres-1.16.3}/tests/test_config.py +0 -0
  48. {testgres-1.16.1 → testgres-1.16.3}/tests/test_os_ops_local.py +0 -0
  49. {testgres-1.16.1 → testgres-1.16.3}/tests/test_os_ops_remote.py +0 -0
  50. {testgres-1.16.1 → testgres-1.16.3}/tests/test_raise_error.py +0 -0
  51. {testgres-1.16.1 → testgres-1.16.3}/tests/test_testgres_local.py +0 -0
  52. {testgres-1.16.1 → testgres-1.16.3}/tests/test_testgres_remote.py +0 -0
  53. {testgres-1.16.1 → 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.1
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,10 +24,9 @@ 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
- Requires-Dist: testgres.os_ops<4.0.0,>=3.3.0
29
+ Requires-Dist: testgres.os_ops<4.0.0,>=3.3.3
31
30
  Dynamic: license-file
32
31
 
33
32
  [![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)
@@ -62,10 +62,9 @@ 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
- "testgres.os_ops>=3.3.0,<4.0.0",
67
+ "testgres.os_ops>=3.3.3,<4.0.0",
69
68
  ]
70
69
 
71
70
  [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.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