testgres 1.13.4__tar.gz → 1.13.5__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.4/testgres.egg-info → testgres-1.13.5}/PKG-INFO +2 -2
- {testgres-1.13.4 → testgres-1.13.5}/pyproject.toml +10 -2
- testgres-1.13.5/src/impl/internal_utils.py +20 -0
- testgres-1.13.5/src/impl/platforms/internal_platform_utils.py +62 -0
- testgres-1.13.5/src/impl/platforms/internal_platform_utils_factory.py +23 -0
- testgres-1.13.5/src/impl/platforms/linux/internal_platform_utils.py +120 -0
- testgres-1.13.5/src/impl/platforms/win32/internal_platform_utils.py +17 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/node.py +4 -3
- {testgres-1.13.4 → testgres-1.13.5}/src/node_app.py +1 -1
- {testgres-1.13.4 → testgres-1.13.5}/src/utils.py +158 -76
- {testgres-1.13.4 → testgres-1.13.5/testgres.egg-info}/PKG-INFO +2 -2
- {testgres-1.13.4 → testgres-1.13.5}/testgres.egg-info/SOURCES.txt +5 -0
- {testgres-1.13.4 → testgres-1.13.5}/testgres.egg-info/requires.txt +1 -1
- {testgres-1.13.4 → testgres-1.13.5}/tests/test_os_ops_common.py +14 -0
- {testgres-1.13.4 → testgres-1.13.5}/tests/test_testgres_common.py +84 -4
- {testgres-1.13.4 → testgres-1.13.5}/LICENSE +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/README.md +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/setup.cfg +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/__init__.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/api.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/backup.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/cache.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/config.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/connection.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/consts.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/decorators.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/defaults.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/enums.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/exceptions.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/impl/port_manager__generic.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/impl/port_manager__this_host.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/logger.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/port_manager.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/pubsub.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/raise_error.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/src/standby.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/testgres.egg-info/dependency_links.txt +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/testgres.egg-info/top_level.txt +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/tests/test_config.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/tests/test_os_ops_local.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/tests/test_os_ops_remote.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/tests/test_raise_error.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/tests/test_testgres_local.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/tests/test_testgres_remote.py +0 -0
- {testgres-1.13.4 → testgres-1.13.5}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testgres
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.5
|
|
4
4
|
Summary: Testing utility for PostgreSQL and its extensions
|
|
5
5
|
Author-email: Postgres Professional <testgres@postgrespro.ru>
|
|
6
6
|
License: PostgreSQL
|
|
@@ -27,7 +27,7 @@ Requires-Dist: port-for>=0.4
|
|
|
27
27
|
Requires-Dist: six>=1.9.0
|
|
28
28
|
Requires-Dist: psutil
|
|
29
29
|
Requires-Dist: packaging
|
|
30
|
-
Requires-Dist: testgres.os_ops<3.0.0,>=2.0
|
|
30
|
+
Requires-Dist: testgres.os_ops<3.0.0,>=2.1.0
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
33
33
|
[](https://github.com/postgrespro/testgres/actions/workflows/package-verification.yml)
|
|
@@ -9,9 +9,17 @@ build-backend = "setuptools.build_meta"
|
|
|
9
9
|
extend-ignore = ["E501"]
|
|
10
10
|
exclude = [".git", "__pycache__", "env", "venv"]
|
|
11
11
|
|
|
12
|
+
# Pytest settings
|
|
13
|
+
[tool.pytest.ini_options]
|
|
14
|
+
|
|
15
|
+
testpaths = ["tests"]
|
|
16
|
+
log_file_level = "NOTSET"
|
|
17
|
+
log_file_format = "%(levelname)8s [%(asctime)s] %(message)s"
|
|
18
|
+
log_file_date_format = "%Y-%m-%d %H:%M:%S"
|
|
19
|
+
|
|
12
20
|
[project]
|
|
13
21
|
name = "testgres"
|
|
14
|
-
version = "1.13.
|
|
22
|
+
version = "1.13.5"
|
|
15
23
|
|
|
16
24
|
description = "Testing utility for PostgreSQL and its extensions"
|
|
17
25
|
readme = "README.md"
|
|
@@ -54,7 +62,7 @@ dependencies = [
|
|
|
54
62
|
"six>=1.9.0",
|
|
55
63
|
"psutil",
|
|
56
64
|
"packaging",
|
|
57
|
-
"testgres.os_ops>=2.0
|
|
65
|
+
"testgres.os_ops>=2.1.0,<3.0.0",
|
|
58
66
|
]
|
|
59
67
|
|
|
60
68
|
[project.urls]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def send_log(level: int, msg: str) -> None:
|
|
5
|
+
assert type(level) == int # noqa: E721
|
|
6
|
+
assert type(msg) == str # noqa: E721
|
|
7
|
+
|
|
8
|
+
return logging.log(level, "[testgres] " + msg)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def send_log_info(msg: str) -> None:
|
|
12
|
+
assert type(msg) == str # noqa: E721
|
|
13
|
+
|
|
14
|
+
return send_log(logging.INFO, msg)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def send_log_debug(msg: str) -> None:
|
|
18
|
+
assert type(msg) == str # noqa: E721
|
|
19
|
+
|
|
20
|
+
return send_log(logging.DEBUG, msg)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from testgres.operations.os_ops import OsOperations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InternalPlatformUtils:
|
|
10
|
+
class FindPostmasterResultCode(enum.Enum):
|
|
11
|
+
ok = 0
|
|
12
|
+
not_found = 1,
|
|
13
|
+
not_implemented = 2
|
|
14
|
+
many_processes = 3
|
|
15
|
+
has_problems = 4
|
|
16
|
+
|
|
17
|
+
class FindPostmasterResult:
|
|
18
|
+
code: InternalPlatformUtils.FindPostmasterResultCode
|
|
19
|
+
pid: typing.Optional[int]
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
code: InternalPlatformUtils.FindPostmasterResultCode,
|
|
24
|
+
pid: typing.Optional[int]
|
|
25
|
+
):
|
|
26
|
+
assert type(code) == InternalPlatformUtils.FindPostmasterResultCode # noqa: E721
|
|
27
|
+
assert pid is None or type(pid) == int # noqa: E721
|
|
28
|
+
self.code = code
|
|
29
|
+
self.pid = pid
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def create_ok(pid: int) -> InternalPlatformUtils.FindPostmasterResult:
|
|
34
|
+
assert type(pid) == int # noqa: E721
|
|
35
|
+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.ok, pid)
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def create_not_found() -> InternalPlatformUtils.FindPostmasterResult:
|
|
39
|
+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.not_found, None)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def create_not_implemented() -> InternalPlatformUtils.FindPostmasterResult:
|
|
43
|
+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.not_implemented, None)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def create_many_processes() -> InternalPlatformUtils.FindPostmasterResult:
|
|
47
|
+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.many_processes, None)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def create_has_problems() -> InternalPlatformUtils.FindPostmasterResult:
|
|
51
|
+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.has_problems, None)
|
|
52
|
+
|
|
53
|
+
def FindPostmaster(
|
|
54
|
+
self,
|
|
55
|
+
os_ops: OsOperations,
|
|
56
|
+
bin_dir: str,
|
|
57
|
+
data_dir: str
|
|
58
|
+
) -> FindPostmasterResult:
|
|
59
|
+
assert isinstance(os_ops, OsOperations)
|
|
60
|
+
assert type(bin_dir) == str # noqa: E721
|
|
61
|
+
assert type(data_dir) == str # noqa: E721
|
|
62
|
+
raise NotImplementedError("InternalPlatformUtils::FindPostmaster is not implemented.")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .internal_platform_utils import InternalPlatformUtils
|
|
2
|
+
|
|
3
|
+
from testgres.operations.os_ops import OsOperations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_internal_platform_utils(
|
|
7
|
+
os_ops: OsOperations
|
|
8
|
+
) -> InternalPlatformUtils:
|
|
9
|
+
assert isinstance(os_ops, OsOperations)
|
|
10
|
+
|
|
11
|
+
platform_name = os_ops.get_platform()
|
|
12
|
+
assert type(platform_name) == str # noqa: E721
|
|
13
|
+
|
|
14
|
+
if platform_name == "linux":
|
|
15
|
+
from .linux import internal_platform_utils as x
|
|
16
|
+
return x.InternalPlatformUtils()
|
|
17
|
+
|
|
18
|
+
if platform_name == "win32":
|
|
19
|
+
from .win32 import internal_platform_utils as x
|
|
20
|
+
return x.InternalPlatformUtils()
|
|
21
|
+
|
|
22
|
+
# not implemented
|
|
23
|
+
return InternalPlatformUtils()
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .. import internal_platform_utils as base
|
|
4
|
+
from ... import internal_utils
|
|
5
|
+
|
|
6
|
+
from testgres.operations.os_ops import OsOperations
|
|
7
|
+
from testgres.operations.exceptions import ExecUtilException
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import shlex
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
14
|
+
C_BASH_EXE = "/bin/bash"
|
|
15
|
+
|
|
16
|
+
sm_exec_env = {
|
|
17
|
+
"LANG": "en_US.UTF-8",
|
|
18
|
+
"LC_ALL": "en_US.UTF-8",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# --------------------------------------------------------------------
|
|
22
|
+
def FindPostmaster(
|
|
23
|
+
self,
|
|
24
|
+
os_ops: OsOperations,
|
|
25
|
+
bin_dir: str,
|
|
26
|
+
data_dir: str
|
|
27
|
+
) -> InternalPlatformUtils.FindPostmasterResult:
|
|
28
|
+
assert isinstance(os_ops, OsOperations)
|
|
29
|
+
assert type(bin_dir) == str # noqa: E721
|
|
30
|
+
assert type(data_dir) == str # noqa: E721
|
|
31
|
+
assert type(__class__.C_BASH_EXE) == str # noqa: E721
|
|
32
|
+
assert type(__class__.sm_exec_env) == dict # noqa: E721
|
|
33
|
+
assert len(__class__.C_BASH_EXE) > 0
|
|
34
|
+
assert len(bin_dir) > 0
|
|
35
|
+
assert len(data_dir) > 0
|
|
36
|
+
|
|
37
|
+
pg_path_e = re.escape(os_ops.build_path(bin_dir, "postgres"))
|
|
38
|
+
data_dir_e = re.escape(data_dir)
|
|
39
|
+
|
|
40
|
+
assert type(pg_path_e) == str # noqa: E721
|
|
41
|
+
assert type(data_dir_e) == str # noqa: E721
|
|
42
|
+
|
|
43
|
+
regexp = r"^\s*[0-9]+\s+" + pg_path_e + r"(\s+.*)?\s+\-[D]\s+" + data_dir_e + r"(\s+.*)?"
|
|
44
|
+
|
|
45
|
+
cmd = [
|
|
46
|
+
__class__.C_BASH_EXE,
|
|
47
|
+
"-c",
|
|
48
|
+
"ps -ewwo \"pid=,args=\" | grep -E " + shlex.quote(regexp),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
exit_status, output_b, error_b = os_ops.exec_command(
|
|
52
|
+
cmd=cmd,
|
|
53
|
+
ignore_errors=True,
|
|
54
|
+
verbose=True,
|
|
55
|
+
exec_env=__class__.sm_exec_env,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
assert type(output_b) == bytes # noqa: E721
|
|
59
|
+
assert type(error_b) == bytes # noqa: E721
|
|
60
|
+
|
|
61
|
+
output = output_b.decode("utf-8")
|
|
62
|
+
error = error_b.decode("utf-8")
|
|
63
|
+
|
|
64
|
+
assert type(output) == str # noqa: E721
|
|
65
|
+
assert type(error) == str # noqa: E721
|
|
66
|
+
|
|
67
|
+
if exit_status == 1:
|
|
68
|
+
return __class__.FindPostmasterResult.create_not_found()
|
|
69
|
+
|
|
70
|
+
if exit_status != 0:
|
|
71
|
+
errMsg = f"test command returned an unexpected exit code: {exit_status}"
|
|
72
|
+
raise ExecUtilException(
|
|
73
|
+
message=errMsg,
|
|
74
|
+
command=cmd,
|
|
75
|
+
exit_code=exit_status,
|
|
76
|
+
out=output,
|
|
77
|
+
error=error,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
lines = output.splitlines()
|
|
81
|
+
assert type(lines) == list # noqa: E721
|
|
82
|
+
|
|
83
|
+
if len(lines) == 0:
|
|
84
|
+
return __class__.FindPostmasterResult.create_not_found()
|
|
85
|
+
|
|
86
|
+
if len(lines) > 1:
|
|
87
|
+
msgs = []
|
|
88
|
+
msgs.append("Many processes like a postmaster are found: {}.".format(len(lines)))
|
|
89
|
+
|
|
90
|
+
for i in range(len(lines)):
|
|
91
|
+
assert type(lines[i]) == str # noqa: E721
|
|
92
|
+
lines.append("[{}] '{}'".format(i, lines[i]))
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
internal_utils.send_log_debug("\n".join(lines))
|
|
96
|
+
return __class__.FindPostmasterResult.create_many_processes()
|
|
97
|
+
|
|
98
|
+
def is_space_or_tab(ch) -> bool:
|
|
99
|
+
assert type(ch) == str # noqa: E721
|
|
100
|
+
return ch == " " or ch == "\t"
|
|
101
|
+
|
|
102
|
+
line = lines[0]
|
|
103
|
+
start = 0
|
|
104
|
+
while start < len(line) and is_space_or_tab(line[start]):
|
|
105
|
+
start += 1
|
|
106
|
+
|
|
107
|
+
pos = start
|
|
108
|
+
while pos < len(line) and line[pos].isnumeric():
|
|
109
|
+
pos += 1
|
|
110
|
+
|
|
111
|
+
if pos == start:
|
|
112
|
+
return __class__.FindPostmasterResult.create_has_problems()
|
|
113
|
+
|
|
114
|
+
if pos != len(line) and not line[pos].isspace():
|
|
115
|
+
return __class__.FindPostmasterResult.create_has_problems()
|
|
116
|
+
|
|
117
|
+
pid = int(line[start:pos])
|
|
118
|
+
assert type(pid) == int # noqa: E721
|
|
119
|
+
|
|
120
|
+
return __class__.FindPostmasterResult.create_ok(pid)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .. import internal_platform_utils as base
|
|
4
|
+
from testgres.operations.os_ops import OsOperations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
8
|
+
def FindPostmaster(
|
|
9
|
+
self,
|
|
10
|
+
os_ops: OsOperations,
|
|
11
|
+
bin_dir: str,
|
|
12
|
+
data_dir: str
|
|
13
|
+
) -> InternalPlatformUtils.FindPostmasterResult:
|
|
14
|
+
assert isinstance(os_ops, OsOperations)
|
|
15
|
+
assert type(bin_dir) == str # noqa: E721
|
|
16
|
+
assert type(data_dir) == str # noqa: E721
|
|
17
|
+
return __class__.FindPostmasterResult.create_not_implemented()
|
|
@@ -122,7 +122,7 @@ class ProcessProxy(object):
|
|
|
122
122
|
ptype: instance of ProcessType
|
|
123
123
|
"""
|
|
124
124
|
|
|
125
|
-
_process:
|
|
125
|
+
_process: typing.Any
|
|
126
126
|
_ptype: ProcessType
|
|
127
127
|
|
|
128
128
|
def __init__(self, process, ptype: typing.Optional[ProcessType] = None):
|
|
@@ -147,7 +147,7 @@ class ProcessProxy(object):
|
|
|
147
147
|
repr(self.process))
|
|
148
148
|
|
|
149
149
|
@property
|
|
150
|
-
def process(self) ->
|
|
150
|
+
def process(self) -> typing.Any:
|
|
151
151
|
assert self._process is not None
|
|
152
152
|
return self._process
|
|
153
153
|
|
|
@@ -174,7 +174,7 @@ class PostgresNode(object):
|
|
|
174
174
|
name=None,
|
|
175
175
|
base_dir=None,
|
|
176
176
|
port: typing.Optional[int] = None,
|
|
177
|
-
conn_params: ConnectionParams = None,
|
|
177
|
+
conn_params: typing.Optional[ConnectionParams] = None,
|
|
178
178
|
bin_dir=None,
|
|
179
179
|
prefix=None,
|
|
180
180
|
os_ops: typing.Optional[OsOperations] = None,
|
|
@@ -2168,6 +2168,7 @@ class PostgresNode(object):
|
|
|
2168
2168
|
bin_path = get_bin_path2(self.os_ops, filename)
|
|
2169
2169
|
return bin_path
|
|
2170
2170
|
|
|
2171
|
+
@staticmethod
|
|
2171
2172
|
def _escape_config_value(value):
|
|
2172
2173
|
assert type(value) == str # noqa: E721
|
|
2173
2174
|
|
|
@@ -92,7 +92,7 @@ class NodeApp:
|
|
|
92
92
|
self._os_ops.rmdirs(real_base_dir, ignore_errors=True)
|
|
93
93
|
self._os_ops.makedirs(real_base_dir)
|
|
94
94
|
|
|
95
|
-
port_manager: PortManager = None
|
|
95
|
+
port_manager: typing.Optional[PortManager] = None
|
|
96
96
|
|
|
97
97
|
if port is None:
|
|
98
98
|
port_manager = self._port_manager
|
|
@@ -4,8 +4,8 @@ from __future__ import division
|
|
|
4
4
|
from __future__ import print_function
|
|
5
5
|
|
|
6
6
|
import os
|
|
7
|
-
|
|
8
7
|
import sys
|
|
8
|
+
import time
|
|
9
9
|
|
|
10
10
|
from contextlib import contextmanager
|
|
11
11
|
from packaging.version import Version, InvalidVersion
|
|
@@ -28,6 +28,9 @@ from testgres.operations.helpers import Helpers as OsHelpers
|
|
|
28
28
|
|
|
29
29
|
from .impl.port_manager__generic import PortManager__Generic
|
|
30
30
|
|
|
31
|
+
from .impl.platforms import internal_platform_utils_factory
|
|
32
|
+
from .impl import internal_utils
|
|
33
|
+
|
|
31
34
|
# rows returned by PG_CONFIG
|
|
32
35
|
_pg_config_data = {}
|
|
33
36
|
|
|
@@ -351,6 +354,10 @@ def get_pg_node_state(
|
|
|
351
354
|
assert type(data_dir) == str # noqa: E721
|
|
352
355
|
assert utils_log_file is None or type(utils_log_file) == str # noqa: E721
|
|
353
356
|
|
|
357
|
+
C_MAX_ATTEMPTS = 3
|
|
358
|
+
C_SLEEP_TIME1 = 1
|
|
359
|
+
C_SLEEP_TIME_MULT = 2
|
|
360
|
+
|
|
354
361
|
_params = [
|
|
355
362
|
os_ops.build_path(bin_dir, "pg_ctl"),
|
|
356
363
|
"-D",
|
|
@@ -358,102 +365,177 @@ def get_pg_node_state(
|
|
|
358
365
|
"status",
|
|
359
366
|
]
|
|
360
367
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
_params,
|
|
364
|
-
utils_log_file,
|
|
365
|
-
verbose=True,
|
|
366
|
-
ignore_errors=True,
|
|
367
|
-
)
|
|
368
|
+
attempt = 0
|
|
369
|
+
sleep_time = C_SLEEP_TIME1
|
|
368
370
|
|
|
369
|
-
|
|
370
|
-
assert type(out) == str # noqa: E721
|
|
371
|
-
assert type(error) == str # noqa: E721
|
|
371
|
+
platform_utils: typing.Optional[internal_platform_utils_factory.InternalPlatformUtils] = None
|
|
372
372
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
373
|
+
while True:
|
|
374
|
+
assert type(attempt) == int # noqa: E721
|
|
375
|
+
assert attempt >= 0
|
|
376
|
+
assert attempt < C_MAX_ATTEMPTS
|
|
377
|
+
|
|
378
|
+
attempt += 1
|
|
379
|
+
|
|
380
|
+
if attempt > 1:
|
|
381
|
+
internal_utils.send_log_debug("Sleep {} second(s) before an attempt #{}".format(
|
|
382
|
+
sleep_time,
|
|
383
|
+
attempt
|
|
384
|
+
))
|
|
385
|
+
time.sleep(sleep_time)
|
|
386
|
+
sleep_time = sleep_time * C_SLEEP_TIME_MULT
|
|
387
|
+
|
|
388
|
+
status_code, out, error = execute_utility2(
|
|
389
|
+
os_ops,
|
|
390
|
+
_params,
|
|
391
|
+
utils_log_file,
|
|
392
|
+
verbose=True,
|
|
393
|
+
ignore_errors=True,
|
|
394
|
+
)
|
|
376
395
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
396
|
+
assert type(status_code) == int # noqa: E721
|
|
397
|
+
assert type(out) == str # noqa: E721
|
|
398
|
+
assert type(error) == str # noqa: E721
|
|
380
399
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
data_dir
|
|
385
|
-
)
|
|
400
|
+
# -----------------
|
|
401
|
+
if status_code == PG_CTL__STATUS__NODE_IS_STOPPED:
|
|
402
|
+
return PostgresNodeState(NodeStatus.Stopped, None)
|
|
386
403
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
exit_code=status_code,
|
|
391
|
-
out=out,
|
|
392
|
-
error=error,
|
|
393
|
-
)
|
|
404
|
+
# -----------------
|
|
405
|
+
if status_code == PG_CTL__STATUS__BAD_DATADIR:
|
|
406
|
+
return PostgresNodeState(NodeStatus.Uninitialized, None)
|
|
394
407
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
408
|
+
# -----------------
|
|
409
|
+
if status_code == PG_CTL__STATUS__OK:
|
|
410
|
+
if out == "":
|
|
411
|
+
RaiseError.pg_ctl_returns_an_empty_string(
|
|
412
|
+
_params
|
|
413
|
+
)
|
|
399
414
|
|
|
400
|
-
|
|
415
|
+
C_PID_PREFIX = "(PID: "
|
|
401
416
|
|
|
402
|
-
|
|
417
|
+
i = out.find(C_PID_PREFIX)
|
|
403
418
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
419
|
+
if i == -1:
|
|
420
|
+
RaiseError.pg_ctl_returns_an_unexpected_string(
|
|
421
|
+
out,
|
|
422
|
+
_params
|
|
423
|
+
)
|
|
409
424
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
425
|
+
assert i > 0
|
|
426
|
+
assert i < len(out)
|
|
427
|
+
assert len(C_PID_PREFIX) <= len(out)
|
|
428
|
+
assert i <= len(out) - len(C_PID_PREFIX)
|
|
414
429
|
|
|
415
|
-
|
|
416
|
-
|
|
430
|
+
i += len(C_PID_PREFIX)
|
|
431
|
+
start_pid_s = i
|
|
417
432
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
433
|
+
while True:
|
|
434
|
+
if i == len(out):
|
|
435
|
+
RaiseError.pg_ctl_returns_an_unexpected_string(
|
|
436
|
+
out,
|
|
437
|
+
_params
|
|
438
|
+
)
|
|
424
439
|
|
|
425
|
-
|
|
440
|
+
ch = out[i]
|
|
426
441
|
|
|
427
|
-
|
|
428
|
-
|
|
442
|
+
if ch == ")":
|
|
443
|
+
break
|
|
429
444
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
445
|
+
if ch.isdigit():
|
|
446
|
+
i += 1
|
|
447
|
+
continue
|
|
433
448
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
449
|
+
RaiseError.pg_ctl_returns_an_unexpected_string(
|
|
450
|
+
out,
|
|
451
|
+
_params
|
|
452
|
+
)
|
|
453
|
+
assert False
|
|
454
|
+
|
|
455
|
+
if i == start_pid_s:
|
|
456
|
+
RaiseError.pg_ctl_returns_an_unexpected_string(
|
|
457
|
+
out,
|
|
458
|
+
_params
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
# TODO: Let's verify a length of pid string.
|
|
462
|
+
|
|
463
|
+
pid = int(out[start_pid_s:i])
|
|
439
464
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
465
|
+
if pid == 0:
|
|
466
|
+
RaiseError.pg_ctl_returns_a_zero_pid(
|
|
467
|
+
out,
|
|
468
|
+
_params
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
assert pid != 0
|
|
472
|
+
|
|
473
|
+
# -----------------
|
|
474
|
+
return PostgresNodeState(NodeStatus.Running, pid)
|
|
475
|
+
|
|
476
|
+
assert status_code != PG_CTL__STATUS__OK
|
|
477
|
+
|
|
478
|
+
errMsg = "Getting of a node status [data_dir is {0}] failed.".format(
|
|
479
|
+
data_dir
|
|
444
480
|
)
|
|
445
481
|
|
|
446
|
-
|
|
482
|
+
e1 = ExecUtilException(
|
|
483
|
+
message=errMsg,
|
|
484
|
+
command=_params,
|
|
485
|
+
exit_code=status_code,
|
|
486
|
+
out=out,
|
|
487
|
+
error=error,
|
|
488
|
+
)
|
|
447
489
|
|
|
448
|
-
|
|
490
|
+
pid_file = os_ops.build_path(data_dir, "postmaster.pid")
|
|
449
491
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
out,
|
|
453
|
-
_params
|
|
492
|
+
postmaster_pid_is_empty = "pg_ctl: the PID file \"{}\" is empty\n".format(
|
|
493
|
+
pid_file,
|
|
454
494
|
)
|
|
455
495
|
|
|
456
|
-
|
|
496
|
+
if error == postmaster_pid_is_empty:
|
|
497
|
+
internal_utils.send_log_debug(
|
|
498
|
+
"PID file [{}] is empty. A check is being carried out to ensure that the postmaster is alive [bindir: {}] ...".format(
|
|
499
|
+
pid_file,
|
|
500
|
+
bin_dir,
|
|
501
|
+
))
|
|
502
|
+
|
|
503
|
+
if platform_utils is None:
|
|
504
|
+
platform_utils = internal_platform_utils_factory.create_internal_platform_utils(os_ops)
|
|
505
|
+
assert isinstance(platform_utils, internal_platform_utils_factory.InternalPlatformUtils)
|
|
506
|
+
|
|
507
|
+
assert isinstance(platform_utils, internal_platform_utils_factory.InternalPlatformUtils)
|
|
508
|
+
|
|
509
|
+
try:
|
|
510
|
+
find_postmaster_r = platform_utils.FindPostmaster(
|
|
511
|
+
os_ops,
|
|
512
|
+
bin_dir,
|
|
513
|
+
data_dir,
|
|
514
|
+
)
|
|
515
|
+
except Exception as e2:
|
|
516
|
+
e2.__cause__ = e1
|
|
517
|
+
raise e2
|
|
518
|
+
|
|
519
|
+
assert type(find_postmaster_r) == internal_platform_utils_factory.InternalPlatformUtils.FindPostmasterResult # noqa: E721
|
|
520
|
+
|
|
521
|
+
if find_postmaster_r.code == internal_platform_utils_factory.InternalPlatformUtils.FindPostmasterResultCode.ok:
|
|
522
|
+
# Postmaster is alive. Let's wait a few seconds and check its status again.
|
|
523
|
+
internal_utils.send_log_debug(
|
|
524
|
+
"Postmaster is found and has PID {}.".format(
|
|
525
|
+
find_postmaster_r.pid
|
|
526
|
+
))
|
|
527
|
+
|
|
528
|
+
if attempt < C_MAX_ATTEMPTS:
|
|
529
|
+
continue
|
|
457
530
|
|
|
458
|
-
|
|
459
|
-
|
|
531
|
+
errMsg = "Getting of a node status [data_dir is {0}] failed.".format(
|
|
532
|
+
data_dir
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
raise ExecUtilException(
|
|
536
|
+
message=errMsg,
|
|
537
|
+
command=_params,
|
|
538
|
+
exit_code=status_code,
|
|
539
|
+
out=out,
|
|
540
|
+
error=error,
|
|
541
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testgres
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.5
|
|
4
4
|
Summary: Testing utility for PostgreSQL and its extensions
|
|
5
5
|
Author-email: Postgres Professional <testgres@postgrespro.ru>
|
|
6
6
|
License: PostgreSQL
|
|
@@ -27,7 +27,7 @@ Requires-Dist: port-for>=0.4
|
|
|
27
27
|
Requires-Dist: six>=1.9.0
|
|
28
28
|
Requires-Dist: psutil
|
|
29
29
|
Requires-Dist: packaging
|
|
30
|
-
Requires-Dist: testgres.os_ops<3.0.0,>=2.0
|
|
30
|
+
Requires-Dist: testgres.os_ops<3.0.0,>=2.1.0
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
33
33
|
[](https://github.com/postgrespro/testgres/actions/workflows/package-verification.yml)
|
|
@@ -20,8 +20,13 @@ src/pubsub.py
|
|
|
20
20
|
src/raise_error.py
|
|
21
21
|
src/standby.py
|
|
22
22
|
src/utils.py
|
|
23
|
+
src/impl/internal_utils.py
|
|
23
24
|
src/impl/port_manager__generic.py
|
|
24
25
|
src/impl/port_manager__this_host.py
|
|
26
|
+
src/impl/platforms/internal_platform_utils.py
|
|
27
|
+
src/impl/platforms/internal_platform_utils_factory.py
|
|
28
|
+
src/impl/platforms/linux/internal_platform_utils.py
|
|
29
|
+
src/impl/platforms/win32/internal_platform_utils.py
|
|
25
30
|
testgres.egg-info/PKG-INFO
|
|
26
31
|
testgres.egg-info/SOURCES.txt
|
|
27
32
|
testgres.egg-info/dependency_links.txt
|
|
@@ -42,6 +42,20 @@ class TestOsOpsCommon:
|
|
|
42
42
|
assert isinstance(request.param, OsOperations)
|
|
43
43
|
return request.param
|
|
44
44
|
|
|
45
|
+
def test_get_platform(self, os_ops: OsOperations):
|
|
46
|
+
assert isinstance(os_ops, OsOperations)
|
|
47
|
+
p = os_ops.get_platform()
|
|
48
|
+
assert p is not None
|
|
49
|
+
assert type(p) == str # noqa: E721
|
|
50
|
+
assert p == sys.platform
|
|
51
|
+
|
|
52
|
+
def test_get_platform__is_known(self, os_ops: OsOperations):
|
|
53
|
+
assert isinstance(os_ops, OsOperations)
|
|
54
|
+
p = os_ops.get_platform()
|
|
55
|
+
assert p is not None
|
|
56
|
+
assert type(p) == str # noqa: E721
|
|
57
|
+
assert p in {"win32", "linux"}
|
|
58
|
+
|
|
45
59
|
def test_create_clone(self, os_ops: OsOperations):
|
|
46
60
|
assert isinstance(os_ops, OsOperations)
|
|
47
61
|
clone = os_ops.create_clone()
|
|
@@ -416,6 +416,84 @@ class TestTestgresCommon:
|
|
|
416
416
|
assert (node.pid == 0)
|
|
417
417
|
assert (node.status() == NodeStatus.Uninitialized)
|
|
418
418
|
|
|
419
|
+
def test_status__empty_postmaster_pid(self, node_svc: PostgresNodeService):
|
|
420
|
+
assert isinstance(node_svc, PostgresNodeService)
|
|
421
|
+
|
|
422
|
+
assert (NodeStatus.Running)
|
|
423
|
+
assert not (NodeStatus.Stopped)
|
|
424
|
+
assert not (NodeStatus.Uninitialized)
|
|
425
|
+
|
|
426
|
+
# check statuses after each operation
|
|
427
|
+
with __class__.helper__get_node(node_svc) as node:
|
|
428
|
+
assert (node.pid == 0)
|
|
429
|
+
assert (node.status() == NodeStatus.Uninitialized)
|
|
430
|
+
|
|
431
|
+
node.init()
|
|
432
|
+
|
|
433
|
+
postmaster_pid_file = node.os_ops.build_path(node.data_dir, "postmaster.pid")
|
|
434
|
+
|
|
435
|
+
node.os_ops.write(
|
|
436
|
+
postmaster_pid_file,
|
|
437
|
+
""
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
with pytest.raises(expected_exception=ExecUtilException) as x:
|
|
441
|
+
node.status()
|
|
442
|
+
|
|
443
|
+
expected_msg = "pg_ctl: the PID file \"{}\" is empty\n".format(
|
|
444
|
+
postmaster_pid_file
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
assert expected_msg == x.value.error
|
|
448
|
+
return
|
|
449
|
+
|
|
450
|
+
def test_status__force_clean_postmaster_pid(self, node_svc: PostgresNodeService):
|
|
451
|
+
assert isinstance(node_svc, PostgresNodeService)
|
|
452
|
+
|
|
453
|
+
assert (NodeStatus.Running)
|
|
454
|
+
assert not (NodeStatus.Stopped)
|
|
455
|
+
assert not (NodeStatus.Uninitialized)
|
|
456
|
+
|
|
457
|
+
# check statuses after each operation
|
|
458
|
+
with __class__.helper__get_node(node_svc) as node:
|
|
459
|
+
assert (node.pid == 0)
|
|
460
|
+
assert (node.status() == NodeStatus.Uninitialized)
|
|
461
|
+
|
|
462
|
+
node.init()
|
|
463
|
+
node.start()
|
|
464
|
+
|
|
465
|
+
assert node.status() == NodeStatus.Running
|
|
466
|
+
logging.info("Postmaster PID is {}.".format(node.pid))
|
|
467
|
+
|
|
468
|
+
postmaster_pid_file = node.os_ops.build_path(node.data_dir, "postmaster.pid")
|
|
469
|
+
|
|
470
|
+
logging.info("Clean postmaster pid file [{}].".format(
|
|
471
|
+
postmaster_pid_file
|
|
472
|
+
))
|
|
473
|
+
|
|
474
|
+
node.os_ops.write(
|
|
475
|
+
postmaster_pid_file,
|
|
476
|
+
"",
|
|
477
|
+
truncate=True,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
x = node.os_ops.read(
|
|
481
|
+
postmaster_pid_file,
|
|
482
|
+
encoding="utf-8",
|
|
483
|
+
binary=False
|
|
484
|
+
)
|
|
485
|
+
assert x == ""
|
|
486
|
+
|
|
487
|
+
with pytest.raises(expected_exception=ExecUtilException) as x:
|
|
488
|
+
node.status()
|
|
489
|
+
|
|
490
|
+
expected_msg = "pg_ctl: the PID file \"{}\" is empty\n".format(
|
|
491
|
+
postmaster_pid_file
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
assert expected_msg == x.value.error
|
|
495
|
+
return
|
|
496
|
+
|
|
419
497
|
def test_kill__is_not_initialized(
|
|
420
498
|
self,
|
|
421
499
|
node_svc: PostgresNodeService
|
|
@@ -981,14 +1059,16 @@ class TestTestgresCommon:
|
|
|
981
1059
|
assert lines is not None
|
|
982
1060
|
assert type(lines) == list # noqa: E721
|
|
983
1061
|
|
|
984
|
-
def LOCAL__test_lines():
|
|
1062
|
+
def LOCAL__test_lines(lines: typing.Iterable[str]) -> bool:
|
|
1063
|
+
assert isinstance(lines, typing.Iterable)
|
|
985
1064
|
for s in lines:
|
|
986
|
-
|
|
1065
|
+
assert type(s) == str # noqa: E721
|
|
1066
|
+
if C_NODE_NAME in s:
|
|
987
1067
|
logging.info("OK. We found the node_name in a line \"{0}\"".format(s))
|
|
988
1068
|
return True
|
|
989
|
-
|
|
1069
|
+
return False
|
|
990
1070
|
|
|
991
|
-
if LOCAL__test_lines():
|
|
1071
|
+
if LOCAL__test_lines(lines):
|
|
992
1072
|
break
|
|
993
1073
|
|
|
994
1074
|
logging.info("Master node log file does not have an expected information.")
|
|
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
|