testgres 1.13.7__tar.gz → 1.14.1__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.
- {testgres-1.13.7/testgres.egg-info → testgres-1.14.1}/PKG-INFO +3 -4
- {testgres-1.13.7 → testgres-1.14.1}/README.md +1 -2
- {testgres-1.13.7 → testgres-1.14.1}/pyproject.toml +1 -1
- {testgres-1.13.7 → testgres-1.14.1}/src/__init__.py +5 -5
- {testgres-1.13.7 → testgres-1.14.1}/src/api.py +11 -2
- {testgres-1.13.7 → testgres-1.14.1}/src/backup.py +1 -6
- {testgres-1.13.7 → testgres-1.14.1}/src/node.py +137 -95
- {testgres-1.13.7 → testgres-1.14.1}/src/node_app.py +40 -40
- {testgres-1.13.7 → testgres-1.14.1}/src/utils.py +28 -1
- {testgres-1.13.7 → testgres-1.14.1/testgres.egg-info}/PKG-INFO +3 -4
- {testgres-1.13.7 → testgres-1.14.1}/testgres.egg-info/SOURCES.txt +1 -0
- {testgres-1.13.7 → testgres-1.14.1}/testgres.egg-info/requires.txt +1 -1
- testgres-1.14.1/tests/test_api.py +30 -0
- {testgres-1.13.7 → testgres-1.14.1}/tests/test_testgres_common.py +52 -4
- {testgres-1.13.7 → testgres-1.14.1}/LICENSE +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/setup.cfg +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/cache.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/config.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/connection.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/consts.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/decorators.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/defaults.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/enums.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/exceptions.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/impl/internal_utils.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/impl/platforms/internal_platform_utils.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/impl/platforms/internal_platform_utils_factory.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/impl/platforms/linux/internal_platform_utils.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/impl/platforms/win32/internal_platform_utils.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/impl/port_manager__generic.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/impl/port_manager__this_host.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/logger.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/port_manager.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/pubsub.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/raise_error.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/src/standby.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/testgres.egg-info/dependency_links.txt +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/testgres.egg-info/top_level.txt +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/tests/test_config.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/tests/test_os_ops_common.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/tests/test_os_ops_local.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/tests/test_os_ops_remote.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/tests/test_raise_error.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/tests/test_testgres_local.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/tests/test_testgres_remote.py +0 -0
- {testgres-1.13.7 → testgres-1.14.1}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testgres
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.14.1
|
|
4
4
|
Summary: Testing utility for PostgreSQL and its extensions
|
|
5
5
|
Author-email: Postgres Professional <testgres@postgrespro.ru>
|
|
6
6
|
License: PostgreSQL
|
|
@@ -27,11 +27,10 @@ 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.
|
|
30
|
+
Requires-Dist: testgres.os_ops<3.0.0,>=2.3.0
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
33
|
-
[](https://codecov.io/gh/postgrespro/testgres)
|
|
33
|
+
[](https://github.com/postgrespro/testgres/actions/workflows/ci.yml)
|
|
35
34
|
[](https://badge.fury.io/py/testgres)
|
|
36
35
|
[](https://pypi.org/project/testgres)
|
|
37
36
|
[](https://pypi.org/project/testgres)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
[](https://codecov.io/gh/postgrespro/testgres)
|
|
1
|
+
[](https://github.com/postgrespro/testgres/actions/workflows/ci.yml)
|
|
3
2
|
[](https://badge.fury.io/py/testgres)
|
|
4
3
|
[](https://pypi.org/project/testgres)
|
|
5
4
|
[](https://pypi.org/project/testgres)
|
|
@@ -56,7 +56,7 @@ from testgres.operations.os_ops import OsOperations, ConnectionParams
|
|
|
56
56
|
from testgres.operations.local_ops import LocalOperations
|
|
57
57
|
from testgres.operations.remote_ops import RemoteOperations
|
|
58
58
|
|
|
59
|
-
__version__ = "1.
|
|
59
|
+
__version__ = "1.14.1"
|
|
60
60
|
|
|
61
61
|
__all__ = [
|
|
62
62
|
"get_new_node",
|
|
@@ -65,12 +65,12 @@ __all__ = [
|
|
|
65
65
|
"TestgresConfig", "configure_testgres", "scoped_config", "push_config", "pop_config",
|
|
66
66
|
"NodeConnection", "DatabaseError", "InternalError", "ProgrammingError", "OperationalError",
|
|
67
67
|
"TestgresException", "ExecUtilException", "QueryException",
|
|
68
|
-
QueryTimeoutException
|
|
68
|
+
"QueryTimeoutException",
|
|
69
69
|
"TimeoutException", "CatchUpException", "StartNodeException", "InitNodeException", "BackupException", "InvalidOperationException",
|
|
70
70
|
"XLogMethod", "IsolationLevel", "NodeStatus", "ProcessType", "DumpFormat",
|
|
71
|
-
NodeApp
|
|
72
|
-
PostgresNode
|
|
73
|
-
PortManager
|
|
71
|
+
"NodeApp",
|
|
72
|
+
"PostgresNode",
|
|
73
|
+
"PortManager",
|
|
74
74
|
"reserve_port", "release_port", "bound_ports", "get_bin_path", "get_pg_config", "get_pg_version",
|
|
75
75
|
"First", "Any",
|
|
76
76
|
"OsOperations", "LocalOperations", "RemoteOperations", "ConnectionParams"
|
|
@@ -31,6 +31,10 @@ PostgresNode(name='...', port=..., base_dir='...')
|
|
|
31
31
|
[(3,)]
|
|
32
32
|
"""
|
|
33
33
|
from .node import PostgresNode
|
|
34
|
+
from testgres.operations.remote_ops import ConnectionParams
|
|
35
|
+
from testgres.operations.remote_ops import RemoteOperations
|
|
36
|
+
|
|
37
|
+
import typing
|
|
34
38
|
|
|
35
39
|
|
|
36
40
|
def get_new_node(name=None, base_dir=None, **kwargs):
|
|
@@ -42,11 +46,16 @@ def get_new_node(name=None, base_dir=None, **kwargs):
|
|
|
42
46
|
return PostgresNode(name=name, base_dir=base_dir, **kwargs)
|
|
43
47
|
|
|
44
48
|
|
|
45
|
-
def get_remote_node(name=None, conn_params=None):
|
|
49
|
+
def get_remote_node(name=None, conn_params: typing.Optional[ConnectionParams] = None):
|
|
46
50
|
"""
|
|
47
51
|
Simply a wrapper around :class:`.PostgresNode` constructor for remote node.
|
|
48
52
|
See :meth:`.PostgresNode.__init__` for details.
|
|
49
53
|
For remote connection you can add the next parameter:
|
|
50
54
|
conn_params = ConnectionParams(host='127.0.0.1', ssh_key=None, username=default_username())
|
|
51
55
|
"""
|
|
52
|
-
|
|
56
|
+
|
|
57
|
+
if conn_params is None:
|
|
58
|
+
raise ValueError("Argument 'conn_params' is None.")
|
|
59
|
+
|
|
60
|
+
os_ops = RemoteOperations(conn_params)
|
|
61
|
+
return PostgresNode(name=name, os_ops=os_ops)
|
|
@@ -152,12 +152,7 @@ class NodeBackup(object):
|
|
|
152
152
|
# Build a new PostgresNode
|
|
153
153
|
assert self.original_node is not None
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
node = self.original_node.clone_with_new_name_and_base_dir(name=name, base_dir=base_dir)
|
|
157
|
-
else:
|
|
158
|
-
# For backward compatibility
|
|
159
|
-
NodeClass = self.original_node.__class__
|
|
160
|
-
node = NodeClass(name=name, base_dir=base_dir, conn_params=self.original_node.os_ops.conn_params)
|
|
155
|
+
node = self.original_node.clone_with_new_name_and_base_dir(name=name, base_dir=base_dir)
|
|
161
156
|
|
|
162
157
|
assert node is not None
|
|
163
158
|
assert type(node) is self.original_node.__class__
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
|
-
import os
|
|
6
5
|
import signal
|
|
7
6
|
import subprocess
|
|
8
7
|
|
|
@@ -91,7 +90,6 @@ from . import utils
|
|
|
91
90
|
from .utils import \
|
|
92
91
|
PgVer, \
|
|
93
92
|
eprint, \
|
|
94
|
-
get_bin_path2, \
|
|
95
93
|
get_pg_version2, \
|
|
96
94
|
execute_utility2, \
|
|
97
95
|
options_string, \
|
|
@@ -101,7 +99,6 @@ from .raise_error import RaiseError
|
|
|
101
99
|
|
|
102
100
|
from .backup import NodeBackup
|
|
103
101
|
|
|
104
|
-
from testgres.operations.os_ops import ConnectionParams
|
|
105
102
|
from testgres.operations.os_ops import OsOperations
|
|
106
103
|
from testgres.operations.local_ops import LocalOperations
|
|
107
104
|
|
|
@@ -164,21 +161,25 @@ class PostgresNode(object):
|
|
|
164
161
|
_C_PM_PID__IS_NOT_DETECTED = -1
|
|
165
162
|
|
|
166
163
|
_name: typing.Optional[str]
|
|
164
|
+
_host: str
|
|
167
165
|
_port: typing.Optional[int]
|
|
166
|
+
_bin_dir: str
|
|
168
167
|
_should_free_port: bool
|
|
169
168
|
_os_ops: OsOperations
|
|
170
169
|
_port_manager: typing.Optional[PortManager]
|
|
171
170
|
_manually_started_pm_pid: typing.Optional[int]
|
|
172
171
|
|
|
173
|
-
def __init__(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
name=None,
|
|
175
|
+
base_dir=None,
|
|
176
|
+
port: typing.Optional[int] = None,
|
|
177
|
+
bin_dir: typing.Optional[str] = None,
|
|
178
|
+
prefix=None,
|
|
179
|
+
os_ops: typing.Optional[OsOperations] = None,
|
|
180
|
+
port_manager: typing.Optional[PortManager] = None,
|
|
181
|
+
host: typing.Optional[str] = None,
|
|
182
|
+
):
|
|
182
183
|
"""
|
|
183
184
|
PostgresNode constructor.
|
|
184
185
|
|
|
@@ -189,15 +190,13 @@ class PostgresNode(object):
|
|
|
189
190
|
bin_dir: path to node's binary directory.
|
|
190
191
|
os_ops: None or correct OS operation object.
|
|
191
192
|
port_manager: None or correct port manager object.
|
|
193
|
+
host: None or valid address of node host.
|
|
192
194
|
"""
|
|
193
195
|
assert port is None or type(port) is int
|
|
196
|
+
assert bin_dir is None or type(bin_dir) is str
|
|
194
197
|
assert os_ops is None or isinstance(os_ops, OsOperations)
|
|
195
198
|
assert port_manager is None or isinstance(port_manager, PortManager)
|
|
196
|
-
|
|
197
|
-
if conn_params is not None:
|
|
198
|
-
assert type(conn_params) is ConnectionParams
|
|
199
|
-
|
|
200
|
-
raise InvalidOperationException("conn_params is deprecated, please use os_ops parameter instead.")
|
|
199
|
+
assert host is None or type(host) is str
|
|
201
200
|
|
|
202
201
|
# private
|
|
203
202
|
if os_ops is None:
|
|
@@ -210,9 +209,15 @@ class PostgresNode(object):
|
|
|
210
209
|
assert self._os_ops is not None
|
|
211
210
|
assert isinstance(self._os_ops, OsOperations)
|
|
212
211
|
|
|
213
|
-
|
|
212
|
+
if bin_dir is not None:
|
|
213
|
+
self._bin_dir = bin_dir
|
|
214
|
+
else:
|
|
215
|
+
self._bin_dir = utils.get_bin_dir(self._os_ops)
|
|
216
|
+
|
|
217
|
+
assert type(self._bin_dir) is str
|
|
218
|
+
|
|
219
|
+
self._pg_version = PgVer(get_pg_version2(self._os_ops, self._bin_dir))
|
|
214
220
|
self._base_dir = base_dir
|
|
215
|
-
self._bin_dir = bin_dir
|
|
216
221
|
self._prefix = prefix
|
|
217
222
|
self._logger = None
|
|
218
223
|
self._master = None
|
|
@@ -220,6 +225,16 @@ class PostgresNode(object):
|
|
|
220
225
|
# basic
|
|
221
226
|
self._name = name or generate_app_name()
|
|
222
227
|
|
|
228
|
+
if host is not None:
|
|
229
|
+
assert type(host) is str
|
|
230
|
+
self._host = host
|
|
231
|
+
else:
|
|
232
|
+
self._host = self._os_ops.host
|
|
233
|
+
assert type(self._host) is str
|
|
234
|
+
|
|
235
|
+
if self._host == "":
|
|
236
|
+
raise RuntimeError("PostgresNode host is empty.")
|
|
237
|
+
|
|
223
238
|
if port is not None:
|
|
224
239
|
assert type(port) is int
|
|
225
240
|
assert port_manager is None
|
|
@@ -250,10 +265,6 @@ class PostgresNode(object):
|
|
|
250
265
|
self.cleanup_on_bad_exit = testgres_config.node_cleanup_on_bad_exit
|
|
251
266
|
self.shutdown_max_attempts = 3
|
|
252
267
|
|
|
253
|
-
# NOTE: for compatibility
|
|
254
|
-
self.utils_log_name = self.utils_log_file
|
|
255
|
-
self.pg_log_name = self.pg_log_file
|
|
256
|
-
|
|
257
268
|
# Node state
|
|
258
269
|
self._manually_started_pm_pid = None
|
|
259
270
|
|
|
@@ -318,6 +329,7 @@ class PostgresNode(object):
|
|
|
318
329
|
assert isinstance(self._port_manager, PortManager)
|
|
319
330
|
assert self._os_ops is not None
|
|
320
331
|
assert isinstance(self._os_ops, OsOperations)
|
|
332
|
+
assert type(self._host) is str
|
|
321
333
|
|
|
322
334
|
node = PostgresNode(
|
|
323
335
|
name=name,
|
|
@@ -325,7 +337,9 @@ class PostgresNode(object):
|
|
|
325
337
|
bin_dir=self._bin_dir,
|
|
326
338
|
prefix=self._prefix,
|
|
327
339
|
os_ops=self._os_ops,
|
|
328
|
-
port_manager=self._port_manager
|
|
340
|
+
port_manager=self._port_manager,
|
|
341
|
+
host=self._host,
|
|
342
|
+
)
|
|
329
343
|
|
|
330
344
|
return node
|
|
331
345
|
|
|
@@ -349,9 +363,9 @@ class PostgresNode(object):
|
|
|
349
363
|
|
|
350
364
|
@property
|
|
351
365
|
def host(self) -> str:
|
|
352
|
-
assert self.
|
|
353
|
-
assert
|
|
354
|
-
return self.
|
|
366
|
+
assert self._host is not None
|
|
367
|
+
assert type(self._host) is str
|
|
368
|
+
return self._host
|
|
355
369
|
|
|
356
370
|
@property
|
|
357
371
|
def port(self) -> int:
|
|
@@ -468,7 +482,7 @@ class PostgresNode(object):
|
|
|
468
482
|
assert type(self.master) is PostgresNode
|
|
469
483
|
|
|
470
484
|
# master should be on the same host
|
|
471
|
-
assert self.master.host == self.
|
|
485
|
+
assert self.master.host == self._host
|
|
472
486
|
|
|
473
487
|
with self.master.connect() as con:
|
|
474
488
|
for row in con.execute(sql, self.name):
|
|
@@ -486,18 +500,17 @@ class PostgresNode(object):
|
|
|
486
500
|
@property
|
|
487
501
|
def base_dir(self):
|
|
488
502
|
if not self._base_dir:
|
|
489
|
-
self._base_dir = self.
|
|
503
|
+
self._base_dir = self._os_ops.mkdtemp(prefix=self._prefix or TMP_NODE)
|
|
490
504
|
|
|
491
505
|
# NOTE: it's safe to create a new dir
|
|
492
|
-
if not self.
|
|
493
|
-
self.
|
|
506
|
+
if not self._os_ops.path_exists(self._base_dir):
|
|
507
|
+
self._os_ops.makedirs(self._base_dir)
|
|
494
508
|
|
|
495
509
|
return self._base_dir
|
|
496
510
|
|
|
497
511
|
@property
|
|
498
|
-
def bin_dir(self):
|
|
499
|
-
|
|
500
|
-
self._bin_dir = os.path.dirname(get_bin_path2(self.os_ops, "pg_config"))
|
|
512
|
+
def bin_dir(self) -> str:
|
|
513
|
+
assert type(self._bin_dir) is str
|
|
501
514
|
return self._bin_dir
|
|
502
515
|
|
|
503
516
|
@property
|
|
@@ -509,8 +522,8 @@ class PostgresNode(object):
|
|
|
509
522
|
assert type(path) is str
|
|
510
523
|
|
|
511
524
|
# NOTE: it's safe to create a new dir
|
|
512
|
-
if not self.
|
|
513
|
-
self.
|
|
525
|
+
if not self._os_ops.path_exists(path):
|
|
526
|
+
self._os_ops.makedirs(path)
|
|
514
527
|
|
|
515
528
|
return path
|
|
516
529
|
|
|
@@ -542,6 +555,16 @@ class PostgresNode(object):
|
|
|
542
555
|
assert type(path) is str
|
|
543
556
|
return path
|
|
544
557
|
|
|
558
|
+
# NOTE: for compatibility
|
|
559
|
+
@property
|
|
560
|
+
def utils_log_name(self) -> str:
|
|
561
|
+
return self.utils_log_file
|
|
562
|
+
|
|
563
|
+
# NOTE: for compatibility
|
|
564
|
+
@property
|
|
565
|
+
def pg_log_name(self) -> str:
|
|
566
|
+
return self.pg_log_file
|
|
567
|
+
|
|
545
568
|
@property
|
|
546
569
|
def version(self):
|
|
547
570
|
"""
|
|
@@ -587,7 +610,7 @@ class PostgresNode(object):
|
|
|
587
610
|
|
|
588
611
|
ps_command = ['ps', '-o', 'pid=', '-p', str(node_pid)]
|
|
589
612
|
|
|
590
|
-
ps_output = self.
|
|
613
|
+
ps_output = self._os_ops.exec_command(cmd=ps_command, shell=True, ignore_errors=True).decode('utf-8')
|
|
591
614
|
assert type(ps_output) is str
|
|
592
615
|
|
|
593
616
|
if ps_output == "":
|
|
@@ -600,13 +623,13 @@ class PostgresNode(object):
|
|
|
600
623
|
|
|
601
624
|
try:
|
|
602
625
|
eprint('Force stopping node {0} with PID {1}'.format(self.name, node_pid))
|
|
603
|
-
self.
|
|
626
|
+
self._os_ops.kill(node_pid, signal.SIGKILL)
|
|
604
627
|
except Exception:
|
|
605
628
|
# The node has already stopped
|
|
606
629
|
pass
|
|
607
630
|
|
|
608
631
|
# Check that node stopped - print only column pid without headers
|
|
609
|
-
ps_output = self.
|
|
632
|
+
ps_output = self._os_ops.exec_command(cmd=ps_command, shell=True, ignore_errors=True).decode('utf-8')
|
|
610
633
|
assert type(ps_output) is str
|
|
611
634
|
|
|
612
635
|
if ps_output == "":
|
|
@@ -669,7 +692,7 @@ class PostgresNode(object):
|
|
|
669
692
|
|
|
670
693
|
signal_name = self._os_ops.build_path(self.data_dir, "standby.signal")
|
|
671
694
|
assert type(signal_name) is str
|
|
672
|
-
self.
|
|
695
|
+
self._os_ops.touch(signal_name)
|
|
673
696
|
else:
|
|
674
697
|
line += "standby_mode=on\n"
|
|
675
698
|
|
|
@@ -730,10 +753,10 @@ class PostgresNode(object):
|
|
|
730
753
|
|
|
731
754
|
for f, num_lines in files:
|
|
732
755
|
# skip missing files
|
|
733
|
-
if not self.
|
|
756
|
+
if not self._os_ops.path_exists(f):
|
|
734
757
|
continue
|
|
735
758
|
|
|
736
|
-
file_lines = self.
|
|
759
|
+
file_lines = self._os_ops.readlines(f, num_lines, binary=True, encoding=None)
|
|
737
760
|
lines = b''.join(file_lines)
|
|
738
761
|
|
|
739
762
|
# fill list
|
|
@@ -800,14 +823,14 @@ class PostgresNode(object):
|
|
|
800
823
|
|
|
801
824
|
# filter lines in hba file
|
|
802
825
|
# get rid of comments and blank lines
|
|
803
|
-
hba_conf_file = self.
|
|
826
|
+
hba_conf_file = self._os_ops.readlines(hba_conf)
|
|
804
827
|
lines = [
|
|
805
828
|
s for s in hba_conf_file
|
|
806
829
|
if len(s.strip()) > 0 and not s.startswith('#')
|
|
807
830
|
]
|
|
808
831
|
|
|
809
832
|
# write filtered lines
|
|
810
|
-
self.
|
|
833
|
+
self._os_ops.write(hba_conf, lines, truncate=True)
|
|
811
834
|
|
|
812
835
|
# replication-related settings
|
|
813
836
|
if allow_streaming:
|
|
@@ -819,7 +842,7 @@ class PostgresNode(object):
|
|
|
819
842
|
# get auth methods
|
|
820
843
|
auth_local = get_auth_method('local')
|
|
821
844
|
auth_host = get_auth_method('host')
|
|
822
|
-
subnet_base = ".".join(self.
|
|
845
|
+
subnet_base = ".".join(self._os_ops.host.split('.')[:-1] + ['0'])
|
|
823
846
|
|
|
824
847
|
new_lines = [
|
|
825
848
|
u"local\treplication\tall\t\t\t{}\n".format(auth_local),
|
|
@@ -832,15 +855,15 @@ class PostgresNode(object):
|
|
|
832
855
|
] # yapf: disable
|
|
833
856
|
|
|
834
857
|
# write missing lines
|
|
835
|
-
self.
|
|
858
|
+
self._os_ops.write(hba_conf, new_lines)
|
|
836
859
|
|
|
837
860
|
# overwrite config file
|
|
838
|
-
self.
|
|
861
|
+
self._os_ops.write(postgres_conf, '', truncate=True)
|
|
839
862
|
|
|
840
863
|
self.append_conf(fsync=fsync,
|
|
841
864
|
max_worker_processes=MAX_WORKER_PROCESSES,
|
|
842
865
|
log_statement=log_statement,
|
|
843
|
-
listen_addresses=self.
|
|
866
|
+
listen_addresses=self._host,
|
|
844
867
|
port=self.port) # yapf:disable
|
|
845
868
|
|
|
846
869
|
# common replication settings
|
|
@@ -915,7 +938,7 @@ class PostgresNode(object):
|
|
|
915
938
|
conf_text = ''
|
|
916
939
|
for line in lines:
|
|
917
940
|
conf_text += text_type(line) + '\n'
|
|
918
|
-
self.
|
|
941
|
+
self._os_ops.write(config_name, conf_text)
|
|
919
942
|
|
|
920
943
|
return self
|
|
921
944
|
|
|
@@ -931,6 +954,12 @@ class PostgresNode(object):
|
|
|
931
954
|
return x.node_status
|
|
932
955
|
|
|
933
956
|
def _get_node_state(self) -> utils.PostgresNodeState:
|
|
957
|
+
if self._base_dir is None:
|
|
958
|
+
return utils.PostgresNodeState(
|
|
959
|
+
node_status=NodeStatus.Uninitialized,
|
|
960
|
+
pid=None,
|
|
961
|
+
)
|
|
962
|
+
|
|
934
963
|
return utils.get_pg_node_state(
|
|
935
964
|
self._os_ops,
|
|
936
965
|
self.bin_dir,
|
|
@@ -948,7 +977,7 @@ class PostgresNode(object):
|
|
|
948
977
|
_params += ["-D"] if self._pg_version >= PgVer('9.5') else []
|
|
949
978
|
_params += [self.data_dir]
|
|
950
979
|
|
|
951
|
-
data = execute_utility2(self.
|
|
980
|
+
data = execute_utility2(self._os_ops, _params, self.utils_log_file)
|
|
952
981
|
|
|
953
982
|
out_dict = {}
|
|
954
983
|
|
|
@@ -958,7 +987,14 @@ class PostgresNode(object):
|
|
|
958
987
|
|
|
959
988
|
return out_dict
|
|
960
989
|
|
|
961
|
-
def slow_start(
|
|
990
|
+
def slow_start(
|
|
991
|
+
self,
|
|
992
|
+
replica: bool = False,
|
|
993
|
+
dbname: typing.Optional[str] = 'template1',
|
|
994
|
+
username: typing.Optional[str] = None,
|
|
995
|
+
max_attempts: int = 0,
|
|
996
|
+
exec_env: typing.Optional[typing.Dict[str, str]] = None,
|
|
997
|
+
):
|
|
962
998
|
"""
|
|
963
999
|
Starts the PostgreSQL instance and then polls the instance
|
|
964
1000
|
until it reaches the expected state (primary or replica). The state is checked
|
|
@@ -971,6 +1007,8 @@ class PostgresNode(object):
|
|
|
971
1007
|
If False, waits for the instance to be in primary mode. Default is False.
|
|
972
1008
|
max_attempts:
|
|
973
1009
|
"""
|
|
1010
|
+
assert dbname is None or type(dbname) is str
|
|
1011
|
+
assert username is None or type(username) is str
|
|
974
1012
|
assert exec_env is None or type(exec_env) is dict
|
|
975
1013
|
|
|
976
1014
|
self.start(exec_env=exec_env)
|
|
@@ -992,7 +1030,7 @@ class PostgresNode(object):
|
|
|
992
1030
|
self.poll_query_until(
|
|
993
1031
|
query=query,
|
|
994
1032
|
dbname=dbname,
|
|
995
|
-
username=username or self.
|
|
1033
|
+
username=username or self._os_ops.username,
|
|
996
1034
|
suppress=suppressed_exceptions,
|
|
997
1035
|
max_attempts=max_attempts,
|
|
998
1036
|
)
|
|
@@ -1094,7 +1132,7 @@ class PostgresNode(object):
|
|
|
1094
1132
|
|
|
1095
1133
|
def LOCAL__start_node():
|
|
1096
1134
|
# 'error' will be None on Windows
|
|
1097
|
-
_, _, error = execute_utility2(self.
|
|
1135
|
+
_, _, error = execute_utility2(self._os_ops, _params, self.utils_log_file, verbose=True, exec_env=exec_env)
|
|
1098
1136
|
assert error is None or type(error) is str
|
|
1099
1137
|
if error and 'does not exist' in error:
|
|
1100
1138
|
raise Exception(error)
|
|
@@ -1183,7 +1221,7 @@ class PostgresNode(object):
|
|
|
1183
1221
|
"stop"
|
|
1184
1222
|
] + params # yapf: disable
|
|
1185
1223
|
|
|
1186
|
-
execute_utility2(self.
|
|
1224
|
+
execute_utility2(self._os_ops, _params, self.utils_log_file)
|
|
1187
1225
|
|
|
1188
1226
|
self._manually_started_pm_pid = None
|
|
1189
1227
|
|
|
@@ -1207,7 +1245,10 @@ class PostgresNode(object):
|
|
|
1207
1245
|
|
|
1208
1246
|
assert x.node_status == NodeStatus.Running
|
|
1209
1247
|
assert type(x.pid) is int
|
|
1210
|
-
|
|
1248
|
+
if self._os_ops.get_platform() == "win32":
|
|
1249
|
+
sig = 21 # signal.SIGBREAK
|
|
1250
|
+
else:
|
|
1251
|
+
sig = signal.SIGKILL
|
|
1211
1252
|
if someone is None:
|
|
1212
1253
|
self._os_ops.kill(x.pid, sig)
|
|
1213
1254
|
self._manually_started_pm_pid = None
|
|
@@ -1240,7 +1281,7 @@ class PostgresNode(object):
|
|
|
1240
1281
|
] + params # yapf: disable
|
|
1241
1282
|
|
|
1242
1283
|
try:
|
|
1243
|
-
error_code, out, error = execute_utility2(self.
|
|
1284
|
+
error_code, out, error = execute_utility2(self._os_ops, _params, self.utils_log_file, verbose=True)
|
|
1244
1285
|
if error and 'could not start server' in error:
|
|
1245
1286
|
raise ExecUtilException
|
|
1246
1287
|
except ExecUtilException as e:
|
|
@@ -1269,7 +1310,7 @@ class PostgresNode(object):
|
|
|
1269
1310
|
"reload"
|
|
1270
1311
|
] + params # yapf: disable
|
|
1271
1312
|
|
|
1272
|
-
execute_utility2(self.
|
|
1313
|
+
execute_utility2(self._os_ops, _params, self.utils_log_file)
|
|
1273
1314
|
|
|
1274
1315
|
return self
|
|
1275
1316
|
|
|
@@ -1291,7 +1332,7 @@ class PostgresNode(object):
|
|
|
1291
1332
|
"promote"
|
|
1292
1333
|
] # yapf: disable
|
|
1293
1334
|
|
|
1294
|
-
execute_utility2(self.
|
|
1335
|
+
execute_utility2(self._os_ops, _params, self.utils_log_file)
|
|
1295
1336
|
|
|
1296
1337
|
# for versions below 10 `promote` is asynchronous so we need to wait
|
|
1297
1338
|
# until it actually becomes writable
|
|
@@ -1326,7 +1367,7 @@ class PostgresNode(object):
|
|
|
1326
1367
|
"-w" # wait
|
|
1327
1368
|
] + params # yapf: disable
|
|
1328
1369
|
|
|
1329
|
-
return execute_utility2(self.
|
|
1370
|
+
return execute_utility2(self._os_ops, _params, self.utils_log_file)
|
|
1330
1371
|
|
|
1331
1372
|
def release_resources(self):
|
|
1332
1373
|
"""
|
|
@@ -1362,7 +1403,7 @@ class PostgresNode(object):
|
|
|
1362
1403
|
else:
|
|
1363
1404
|
rm_dir = self.data_dir # just data, save logs
|
|
1364
1405
|
|
|
1365
|
-
self.
|
|
1406
|
+
self._os_ops.rmdirs(rm_dir, ignore_errors=False)
|
|
1366
1407
|
|
|
1367
1408
|
if release_resources:
|
|
1368
1409
|
self._release_resources()
|
|
@@ -1443,7 +1484,7 @@ class PostgresNode(object):
|
|
|
1443
1484
|
raise Exception("Input data must be None or bytes.")
|
|
1444
1485
|
|
|
1445
1486
|
if host is None:
|
|
1446
|
-
host = self.
|
|
1487
|
+
host = self._host
|
|
1447
1488
|
|
|
1448
1489
|
if port is None:
|
|
1449
1490
|
port = self.port
|
|
@@ -1457,7 +1498,7 @@ class PostgresNode(object):
|
|
|
1457
1498
|
self._get_bin_path("psql"),
|
|
1458
1499
|
"-p", str(port),
|
|
1459
1500
|
"-h", host,
|
|
1460
|
-
"-U", username or self.
|
|
1501
|
+
"-U", username or self._os_ops.username,
|
|
1461
1502
|
"-d", dbname or default_dbname(),
|
|
1462
1503
|
"-X", # no .psqlrc
|
|
1463
1504
|
"-A", # unaligned output
|
|
@@ -1477,7 +1518,7 @@ class PostgresNode(object):
|
|
|
1477
1518
|
else:
|
|
1478
1519
|
raise QueryException('Query or filename must be provided')
|
|
1479
1520
|
|
|
1480
|
-
return self.
|
|
1521
|
+
return self._os_ops.exec_command(
|
|
1481
1522
|
psql_params,
|
|
1482
1523
|
verbose=True,
|
|
1483
1524
|
input=input,
|
|
@@ -1560,9 +1601,9 @@ class PostgresNode(object):
|
|
|
1560
1601
|
# Generate tmpfile or tmpdir
|
|
1561
1602
|
def tmpfile():
|
|
1562
1603
|
if format == DumpFormat.Directory:
|
|
1563
|
-
fname = self.
|
|
1604
|
+
fname = self._os_ops.mkdtemp(prefix=TMP_DUMP)
|
|
1564
1605
|
else:
|
|
1565
|
-
fname = self.
|
|
1606
|
+
fname = self._os_ops.mkstemp(prefix=TMP_DUMP)
|
|
1566
1607
|
return fname
|
|
1567
1608
|
|
|
1568
1609
|
filename = filename or tmpfile()
|
|
@@ -1570,9 +1611,9 @@ class PostgresNode(object):
|
|
|
1570
1611
|
_params = [
|
|
1571
1612
|
self._get_bin_path("pg_dump"),
|
|
1572
1613
|
"-p", str(self.port),
|
|
1573
|
-
"-h", self.
|
|
1614
|
+
"-h", self._host,
|
|
1574
1615
|
"-f", filename,
|
|
1575
|
-
"-U", username or self.
|
|
1616
|
+
"-U", username or self._os_ops.username,
|
|
1576
1617
|
"-d", dbname or default_dbname(),
|
|
1577
1618
|
"-F", format.value
|
|
1578
1619
|
] # yapf: disable
|
|
@@ -1581,7 +1622,7 @@ class PostgresNode(object):
|
|
|
1581
1622
|
if options:
|
|
1582
1623
|
_params.extend(options)
|
|
1583
1624
|
|
|
1584
|
-
execute_utility2(self.
|
|
1625
|
+
execute_utility2(self._os_ops, _params, self.utils_log_file)
|
|
1585
1626
|
|
|
1586
1627
|
return filename
|
|
1587
1628
|
|
|
@@ -1597,12 +1638,12 @@ class PostgresNode(object):
|
|
|
1597
1638
|
|
|
1598
1639
|
# Set default arguments
|
|
1599
1640
|
dbname = dbname or default_dbname()
|
|
1600
|
-
username = username or self.
|
|
1641
|
+
username = username or self._os_ops.username
|
|
1601
1642
|
|
|
1602
1643
|
_params = [
|
|
1603
1644
|
self._get_bin_path("pg_restore"),
|
|
1604
1645
|
"-p", str(self.port),
|
|
1605
|
-
"-h", self.
|
|
1646
|
+
"-h", self._host,
|
|
1606
1647
|
"-U", username,
|
|
1607
1648
|
"-d", dbname,
|
|
1608
1649
|
filename
|
|
@@ -1610,20 +1651,22 @@ class PostgresNode(object):
|
|
|
1610
1651
|
|
|
1611
1652
|
# try pg_restore if dump is binary format, and psql if not
|
|
1612
1653
|
try:
|
|
1613
|
-
execute_utility2(self.
|
|
1654
|
+
execute_utility2(self._os_ops, _params, self.utils_log_file)
|
|
1614
1655
|
except ExecUtilException:
|
|
1615
1656
|
self.psql(filename=filename, dbname=dbname, username=username)
|
|
1616
1657
|
|
|
1617
1658
|
@method_decorator(positional_args_hack(['dbname', 'query']))
|
|
1618
|
-
def poll_query_until(
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1659
|
+
def poll_query_until(
|
|
1660
|
+
self,
|
|
1661
|
+
query,
|
|
1662
|
+
dbname: typing.Optional[str] = None,
|
|
1663
|
+
username: typing.Optional[str] = None,
|
|
1664
|
+
max_attempts: int = 0,
|
|
1665
|
+
sleep_time: typing.Union[int, float] = 1,
|
|
1666
|
+
expected: bool = True,
|
|
1667
|
+
commit: bool = True,
|
|
1668
|
+
suppress: typing.Optional[typing.Iterable[BaseException]] = None,
|
|
1669
|
+
) -> None:
|
|
1627
1670
|
"""
|
|
1628
1671
|
Run a query once per second until it returns 'expected'.
|
|
1629
1672
|
Query should return a single value (1 row, 1 column).
|
|
@@ -1650,6 +1693,8 @@ class PostgresNode(object):
|
|
|
1650
1693
|
assert max_attempts >= 0
|
|
1651
1694
|
assert type(sleep_time) in [int, float]
|
|
1652
1695
|
assert sleep_time > 0
|
|
1696
|
+
assert suppress is None or isinstance(suppress, typing.Iterable)
|
|
1697
|
+
|
|
1653
1698
|
attempts = 0
|
|
1654
1699
|
while max_attempts == 0 or attempts < max_attempts:
|
|
1655
1700
|
try:
|
|
@@ -1874,14 +1919,14 @@ class PostgresNode(object):
|
|
|
1874
1919
|
_params = [
|
|
1875
1920
|
self._get_bin_path("pgbench"),
|
|
1876
1921
|
"-p", str(self.port),
|
|
1877
|
-
"-h", self.
|
|
1878
|
-
"-U", username or self.
|
|
1922
|
+
"-h", self._host,
|
|
1923
|
+
"-U", username or self._os_ops.username
|
|
1879
1924
|
] + options # yapf: disable
|
|
1880
1925
|
|
|
1881
1926
|
# should be the last one
|
|
1882
1927
|
_params.append(dbname)
|
|
1883
1928
|
|
|
1884
|
-
proc = self.
|
|
1929
|
+
proc = self._os_ops.exec_command(_params, stdout=stdout, stderr=stderr, wait_exit=True, get_process=True)
|
|
1885
1930
|
|
|
1886
1931
|
return proc
|
|
1887
1932
|
|
|
@@ -1947,8 +1992,8 @@ class PostgresNode(object):
|
|
|
1947
1992
|
_params = [
|
|
1948
1993
|
self._get_bin_path("pgbench"),
|
|
1949
1994
|
"-p", str(self.port),
|
|
1950
|
-
"-h", self.
|
|
1951
|
-
"-U", username or self.
|
|
1995
|
+
"-h", self._host,
|
|
1996
|
+
"-U", username or self._os_ops.username
|
|
1952
1997
|
] + options # yapf: disable
|
|
1953
1998
|
|
|
1954
1999
|
for key, value in iteritems(kwargs):
|
|
@@ -1965,7 +2010,7 @@ class PostgresNode(object):
|
|
|
1965
2010
|
# should be the last one
|
|
1966
2011
|
_params.append(dbname)
|
|
1967
2012
|
|
|
1968
|
-
return execute_utility2(self.
|
|
2013
|
+
return execute_utility2(self._os_ops, _params, self.utils_log_file)
|
|
1969
2014
|
|
|
1970
2015
|
def connect(self,
|
|
1971
2016
|
dbname=None,
|
|
@@ -2054,9 +2099,9 @@ class PostgresNode(object):
|
|
|
2054
2099
|
assert isinstance(self._os_ops, OsOperations)
|
|
2055
2100
|
|
|
2056
2101
|
# parse postgresql.auto.conf
|
|
2057
|
-
path = self.
|
|
2102
|
+
path = self._os_ops.build_path(self.data_dir, config)
|
|
2058
2103
|
|
|
2059
|
-
lines = self.
|
|
2104
|
+
lines = self._os_ops.readlines(path)
|
|
2060
2105
|
current_options = {}
|
|
2061
2106
|
current_directives = []
|
|
2062
2107
|
for line in lines:
|
|
@@ -2103,7 +2148,7 @@ class PostgresNode(object):
|
|
|
2103
2148
|
for directive in current_directives:
|
|
2104
2149
|
auto_conf += directive + "\n"
|
|
2105
2150
|
|
|
2106
|
-
self.
|
|
2151
|
+
self._os_ops.write(path, auto_conf, truncate=True)
|
|
2107
2152
|
|
|
2108
2153
|
def upgrade_from(self, old_node, options=None, expect_error=False):
|
|
2109
2154
|
"""
|
|
@@ -2138,7 +2183,7 @@ class PostgresNode(object):
|
|
|
2138
2183
|
]
|
|
2139
2184
|
upgrade_command += options
|
|
2140
2185
|
|
|
2141
|
-
return self.
|
|
2186
|
+
return self._os_ops.exec_command(upgrade_command, expect_error=expect_error)
|
|
2142
2187
|
|
|
2143
2188
|
def _release_resources(self):
|
|
2144
2189
|
self._free_port()
|
|
@@ -2162,12 +2207,9 @@ class PostgresNode(object):
|
|
|
2162
2207
|
def _get_bin_path(self, filename):
|
|
2163
2208
|
assert self._os_ops is not None
|
|
2164
2209
|
assert isinstance(self._os_ops, OsOperations)
|
|
2210
|
+
assert type(self._bin_dir) is str
|
|
2165
2211
|
|
|
2166
|
-
|
|
2167
|
-
bin_path = self._os_ops.build_path(self.bin_dir, filename)
|
|
2168
|
-
else:
|
|
2169
|
-
bin_path = get_bin_path2(self.os_ops, filename)
|
|
2170
|
-
return bin_path
|
|
2212
|
+
return self._os_ops.build_path(self._bin_dir, filename)
|
|
2171
2213
|
|
|
2172
2214
|
@staticmethod
|
|
2173
2215
|
def _escape_config_value(value):
|
|
@@ -3,9 +3,6 @@ from .node import LocalOperations
|
|
|
3
3
|
from .node import PostgresNode
|
|
4
4
|
from .node import PortManager
|
|
5
5
|
|
|
6
|
-
import os
|
|
7
|
-
import platform
|
|
8
|
-
import tempfile
|
|
9
6
|
import typing
|
|
10
7
|
|
|
11
8
|
|
|
@@ -16,7 +13,7 @@ T_LIST_STR = typing.List[str]
|
|
|
16
13
|
class NodeApp:
|
|
17
14
|
_test_path: str
|
|
18
15
|
_os_ops: OsOperations
|
|
19
|
-
_port_manager: PortManager
|
|
16
|
+
_port_manager: typing.Optional[PortManager]
|
|
20
17
|
_nodes_to_cleanup: typing.List[PostgresNode]
|
|
21
18
|
|
|
22
19
|
def __init__(
|
|
@@ -39,7 +36,7 @@ class NodeApp:
|
|
|
39
36
|
|
|
40
37
|
if test_path is None:
|
|
41
38
|
self._test_path = os_ops.cwd()
|
|
42
|
-
elif
|
|
39
|
+
elif self._os_ops.is_abs_path(test_path):
|
|
43
40
|
self._test_path = test_path
|
|
44
41
|
else:
|
|
45
42
|
self._test_path = os_ops.build_path(os_ops.cwd(), test_path)
|
|
@@ -60,7 +57,7 @@ class NodeApp:
|
|
|
60
57
|
return self._os_ops
|
|
61
58
|
|
|
62
59
|
@property
|
|
63
|
-
def port_manager(self) -> PortManager:
|
|
60
|
+
def port_manager(self) -> typing.Optional[PortManager]:
|
|
64
61
|
assert self._port_manager is None or isinstance(self._port_manager, PortManager)
|
|
65
62
|
return self._port_manager
|
|
66
63
|
|
|
@@ -158,6 +155,8 @@ class NodeApp:
|
|
|
158
155
|
|
|
159
156
|
# set major version
|
|
160
157
|
pg_version_file = self._os_ops.read(self._os_ops.build_path(node.data_dir, 'PG_VERSION'))
|
|
158
|
+
|
|
159
|
+
# What is it ???
|
|
161
160
|
node.major_version_str = str(pg_version_file.rstrip())
|
|
162
161
|
node.major_version = float(node.major_version_str)
|
|
163
162
|
|
|
@@ -200,7 +199,7 @@ class NodeApp:
|
|
|
200
199
|
|
|
201
200
|
# Define delayed propertyes
|
|
202
201
|
if "unix_socket_directories" not in options.keys():
|
|
203
|
-
options["unix_socket_directories"] =
|
|
202
|
+
options["unix_socket_directories"] = self._gettempdir_for_socket()
|
|
204
203
|
|
|
205
204
|
# Set config values
|
|
206
205
|
node.set_auto_conf(options)
|
|
@@ -260,49 +259,50 @@ class NodeApp:
|
|
|
260
259
|
return updated_params
|
|
261
260
|
return __class__._paramlist_append(user_params, updated_params, param)
|
|
262
261
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
platform_system_name = platform.system().lower()
|
|
266
|
-
|
|
267
|
-
if platform_system_name == "windows":
|
|
268
|
-
return __class__._gettempdir()
|
|
269
|
-
|
|
270
|
-
#
|
|
271
|
-
# [2025-02-17] Hot fix.
|
|
272
|
-
#
|
|
273
|
-
# Let's use hard coded path as Postgres likes.
|
|
274
|
-
#
|
|
275
|
-
# pg_config_manual.h:
|
|
276
|
-
#
|
|
277
|
-
# #ifndef WIN32
|
|
278
|
-
# #define DEFAULT_PGSOCKET_DIR "/tmp"
|
|
279
|
-
# #else
|
|
280
|
-
# #define DEFAULT_PGSOCKET_DIR ""
|
|
281
|
-
# #endif
|
|
282
|
-
#
|
|
283
|
-
# On the altlinux-10 tempfile.gettempdir() may return
|
|
284
|
-
# the path to "private" temp directiry - "/temp/.private/<username>/"
|
|
285
|
-
#
|
|
286
|
-
# But Postgres want to find a socket file in "/tmp" (see above).
|
|
287
|
-
#
|
|
262
|
+
def _gettempdir_for_socket(self) -> str:
|
|
263
|
+
assert isinstance(self._os_ops, OsOperations)
|
|
288
264
|
|
|
289
|
-
|
|
265
|
+
platform_name = self._os_ops.get_platform()
|
|
266
|
+
|
|
267
|
+
if platform_name == "linux":
|
|
268
|
+
#
|
|
269
|
+
# [2025-02-17] Hot fix.
|
|
270
|
+
#
|
|
271
|
+
# Let's use hard coded path as Postgres likes.
|
|
272
|
+
#
|
|
273
|
+
# pg_config_manual.h:
|
|
274
|
+
#
|
|
275
|
+
# #ifndef WIN32
|
|
276
|
+
# #define DEFAULT_PGSOCKET_DIR "/tmp"
|
|
277
|
+
# #else
|
|
278
|
+
# #define DEFAULT_PGSOCKET_DIR ""
|
|
279
|
+
# #endif
|
|
280
|
+
#
|
|
281
|
+
# On the altlinux-10 tempfile.gettempdir() may return
|
|
282
|
+
# the path to "private" temp directiry - "/temp/.private/<username>/"
|
|
283
|
+
#
|
|
284
|
+
# But Postgres want to find a socket file in "/tmp" (see above).
|
|
285
|
+
#
|
|
286
|
+
return "/tmp"
|
|
287
|
+
|
|
288
|
+
return self._gettempdir()
|
|
289
|
+
|
|
290
|
+
def _gettempdir(self) -> str:
|
|
291
|
+
assert isinstance(self._os_ops, OsOperations)
|
|
290
292
|
|
|
291
|
-
|
|
292
|
-
def _gettempdir() -> str:
|
|
293
|
-
v = tempfile.gettempdir()
|
|
293
|
+
v = self._os_ops.get_tempdir()
|
|
294
294
|
|
|
295
295
|
#
|
|
296
296
|
# Paranoid checks
|
|
297
297
|
#
|
|
298
298
|
if type(v) is str:
|
|
299
|
-
__class__._raise_bugcheck("
|
|
299
|
+
__class__._raise_bugcheck("os_ops.get_tempdir returned a value with type {0}.".format(type(v).__name__))
|
|
300
300
|
|
|
301
301
|
if v == "":
|
|
302
|
-
__class__._raise_bugcheck("
|
|
302
|
+
__class__._raise_bugcheck("os_ops.get_tempdir returned an empty string.")
|
|
303
303
|
|
|
304
|
-
if not
|
|
305
|
-
__class__._raise_bugcheck("
|
|
304
|
+
if not self._os_ops.path_exists(v):
|
|
305
|
+
__class__._raise_bugcheck("os_ops.get_tempdir returned a not exist path [{0}].".format(v))
|
|
306
306
|
|
|
307
307
|
# OK
|
|
308
308
|
return v
|
|
@@ -140,7 +140,7 @@ def get_bin_path2(os_ops: OsOperations, filename):
|
|
|
140
140
|
assert isinstance(os_ops, OsOperations)
|
|
141
141
|
|
|
142
142
|
# check if it's already absolute
|
|
143
|
-
if
|
|
143
|
+
if os_ops.is_abs_path(filename):
|
|
144
144
|
return filename
|
|
145
145
|
if isinstance(os_ops, RemoteOperations):
|
|
146
146
|
pg_config = os.environ.get("PG_CONFIG_REMOTE") or os.environ.get("PG_CONFIG")
|
|
@@ -165,6 +165,33 @@ def get_bin_path2(os_ops: OsOperations, filename):
|
|
|
165
165
|
return filename
|
|
166
166
|
|
|
167
167
|
|
|
168
|
+
def get_bin_dir(os_ops: OsOperations) -> str:
|
|
169
|
+
assert os_ops is not None
|
|
170
|
+
assert isinstance(os_ops, OsOperations)
|
|
171
|
+
|
|
172
|
+
if isinstance(os_ops, RemoteOperations):
|
|
173
|
+
pg_config = os.environ.get("PG_CONFIG_REMOTE") or os.environ.get("PG_CONFIG")
|
|
174
|
+
else:
|
|
175
|
+
# try PG_CONFIG - get from local machine
|
|
176
|
+
pg_config = os.environ.get("PG_CONFIG")
|
|
177
|
+
|
|
178
|
+
if pg_config:
|
|
179
|
+
bindir = get_pg_config(pg_config, os_ops)["BINDIR"]
|
|
180
|
+
return bindir
|
|
181
|
+
|
|
182
|
+
# try PG_BIN
|
|
183
|
+
pg_bin = os_ops.environ("PG_BIN")
|
|
184
|
+
if pg_bin:
|
|
185
|
+
return pg_bin
|
|
186
|
+
|
|
187
|
+
pg_config_path = os_ops.find_executable('pg_config')
|
|
188
|
+
if pg_config_path:
|
|
189
|
+
bindir = get_pg_config(pg_config_path)["BINDIR"]
|
|
190
|
+
return bindir
|
|
191
|
+
|
|
192
|
+
raise RuntimeError("BinDir is not detected.")
|
|
193
|
+
|
|
194
|
+
|
|
168
195
|
def get_pg_config(pg_config_path=None, os_ops=None):
|
|
169
196
|
"""
|
|
170
197
|
Return output of pg_config (provided that it is installed).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testgres
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.14.1
|
|
4
4
|
Summary: Testing utility for PostgreSQL and its extensions
|
|
5
5
|
Author-email: Postgres Professional <testgres@postgrespro.ru>
|
|
6
6
|
License: PostgreSQL
|
|
@@ -27,11 +27,10 @@ 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.
|
|
30
|
+
Requires-Dist: testgres.os_ops<3.0.0,>=2.3.0
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
33
|
-
[](https://codecov.io/gh/postgrespro/testgres)
|
|
33
|
+
[](https://github.com/postgrespro/testgres/actions/workflows/ci.yml)
|
|
35
34
|
[](https://badge.fury.io/py/testgres)
|
|
36
35
|
[](https://pypi.org/project/testgres)
|
|
37
36
|
[](https://pypi.org/project/testgres)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from src import api as testgres_api
|
|
2
|
+
from src.node import PostgresNode
|
|
3
|
+
|
|
4
|
+
from tests.helpers.global_data import OsOpsDescrs
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestAPI:
|
|
8
|
+
def test_001__get_new_node(self):
|
|
9
|
+
C_NODE_NAME = "abc"
|
|
10
|
+
|
|
11
|
+
with testgres_api.get_new_node(name=C_NODE_NAME) as node:
|
|
12
|
+
assert type(node) is PostgresNode
|
|
13
|
+
assert node.name == C_NODE_NAME
|
|
14
|
+
node.init()
|
|
15
|
+
node.slow_start()
|
|
16
|
+
node.stop()
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
def test_001__get_remote_node(self):
|
|
20
|
+
C_NODE_NAME = "abc"
|
|
21
|
+
|
|
22
|
+
conn_params = OsOpsDescrs.sm_remote_conn_params
|
|
23
|
+
|
|
24
|
+
with testgres_api.get_remote_node(name=C_NODE_NAME, conn_params=conn_params) as node:
|
|
25
|
+
assert type(node) is PostgresNode
|
|
26
|
+
assert node.name == C_NODE_NAME
|
|
27
|
+
node.init()
|
|
28
|
+
node.slow_start()
|
|
29
|
+
node.stop()
|
|
30
|
+
return
|
|
@@ -94,8 +94,8 @@ class TestTestgresCommon:
|
|
|
94
94
|
|
|
95
95
|
# Author: Mark G.
|
|
96
96
|
assert v.major == 1
|
|
97
|
-
assert v.minor ==
|
|
98
|
-
assert v.micro ==
|
|
97
|
+
assert v.minor == 14
|
|
98
|
+
assert v.micro == 1
|
|
99
99
|
|
|
100
100
|
assert str(v) == testgres_version
|
|
101
101
|
return
|
|
@@ -132,7 +132,53 @@ class TestTestgresCommon:
|
|
|
132
132
|
assert (isinstance(node.version, PgVer))
|
|
133
133
|
assert (node.version == PgVer(version))
|
|
134
134
|
|
|
135
|
+
def test_node_constructor__default(self):
|
|
136
|
+
node = PostgresNode()
|
|
137
|
+
assert node._os_ops is not None
|
|
138
|
+
assert isinstance(node._os_ops, OsOperations)
|
|
139
|
+
assert node._port_manager is not None
|
|
140
|
+
assert isinstance(node._port_manager, PortManager)
|
|
141
|
+
assert node._name is not None
|
|
142
|
+
assert type(node._name) is str
|
|
143
|
+
assert node._name != ""
|
|
144
|
+
assert node._base_dir is None
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
def test_node_constructor__host(self):
|
|
148
|
+
C_HOST = "AbCdE"
|
|
149
|
+
|
|
150
|
+
unique_id = uuid.uuid4().hex
|
|
151
|
+
|
|
152
|
+
with PostgresNode(host=C_HOST) as node:
|
|
153
|
+
assert node._host == C_HOST
|
|
154
|
+
assert node.host == C_HOST
|
|
155
|
+
assert isinstance(node.os_ops, OsOperations)
|
|
156
|
+
|
|
157
|
+
tmpdir = node.os_ops.get_tempdir()
|
|
158
|
+
nodedir2 = node.os_ops.build_path(tmpdir, "node2--" + unique_id)
|
|
159
|
+
|
|
160
|
+
C_NODE2_NAME = "node2"
|
|
161
|
+
|
|
162
|
+
with node.clone_with_new_name_and_base_dir(
|
|
163
|
+
name=C_NODE2_NAME,
|
|
164
|
+
base_dir=nodedir2,
|
|
165
|
+
) as node2:
|
|
166
|
+
assert node2 is not None
|
|
167
|
+
assert node2 is not node
|
|
168
|
+
|
|
169
|
+
assert node2._host == C_HOST
|
|
170
|
+
assert node2.host == C_HOST
|
|
171
|
+
|
|
172
|
+
assert node2._name == C_NODE2_NAME
|
|
173
|
+
assert node2._base_dir == nodedir2
|
|
174
|
+
assert node2._port != node._port
|
|
175
|
+
assert node2._os_ops is node._os_ops
|
|
176
|
+
assert node2._port_manager is node._port_manager
|
|
177
|
+
return
|
|
178
|
+
|
|
135
179
|
def test_node_repr(self, node_svc: PostgresNodeService):
|
|
180
|
+
assert isinstance(node_svc, PostgresNodeService)
|
|
181
|
+
|
|
136
182
|
with __class__.helper__get_node(node_svc).init() as node:
|
|
137
183
|
pattern = r"PostgresNode\(name='.+', port=.+, base_dir='.+'\)"
|
|
138
184
|
assert re.match(pattern, str(node)) is not None
|
|
@@ -2422,13 +2468,14 @@ where c.relname=%s;"""
|
|
|
2422
2468
|
class tag_rmdirs_protector:
|
|
2423
2469
|
_os_ops: OsOperations
|
|
2424
2470
|
_cwd: str
|
|
2425
|
-
_old_rmdirs:
|
|
2471
|
+
_old_rmdirs: typing.Optional[typing.Callable]
|
|
2426
2472
|
_cwd: str
|
|
2427
2473
|
|
|
2428
2474
|
def __init__(self, os_ops: OsOperations):
|
|
2429
2475
|
self._os_ops = os_ops
|
|
2430
2476
|
self._cwd = os.path.abspath(os_ops.cwd())
|
|
2431
2477
|
self._old_rmdirs = os_ops.rmdirs
|
|
2478
|
+
return
|
|
2432
2479
|
|
|
2433
2480
|
def __enter__(self):
|
|
2434
2481
|
assert self._os_ops.rmdirs == self._old_rmdirs
|
|
@@ -2437,6 +2484,7 @@ where c.relname=%s;"""
|
|
|
2437
2484
|
|
|
2438
2485
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
2439
2486
|
assert self._os_ops.rmdirs == self.proxy__rmdirs
|
|
2487
|
+
assert isinstance(self._old_rmdirs, typing.Callable)
|
|
2440
2488
|
self._os_ops.rmdirs = self._old_rmdirs
|
|
2441
2489
|
return False
|
|
2442
2490
|
|
|
@@ -2465,7 +2513,7 @@ where c.relname=%s;"""
|
|
|
2465
2513
|
assert node_app.os_ops is os_ops
|
|
2466
2514
|
|
|
2467
2515
|
with pytest.raises(expected_exception=BaseException) as x:
|
|
2468
|
-
node_app.make_empty(base_dir=None)
|
|
2516
|
+
node_app.make_empty(base_dir=None) # type: ignore
|
|
2469
2517
|
|
|
2470
2518
|
if type(x.value) is AssertionError:
|
|
2471
2519
|
pass
|
|
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
|
|
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
|