Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b67ae42

Browse files
Merge branch 'master' into skip-ssl-check
The following files were modified manualy: - testgres/node.py - testgres/operations/local_ops.py - tests/test_remote.py
2 parents 67f23a7 + b949eb8 commit b67ae42

File tree

11 files changed

+267
-58
lines changed

11 files changed

+267
-58
lines changed

testgres/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
CatchUpException, \
2424
StartNodeException, \
2525
InitNodeException, \
26-
BackupException
26+
BackupException, \
27+
InvalidOperationException
2728

2829
from .enums import \
2930
XLogMethod, \
@@ -60,7 +61,7 @@
6061
"NodeBackup", "testgres_config",
6162
"TestgresConfig", "configure_testgres", "scoped_config", "push_config", "pop_config",
6263
"NodeConnection", "DatabaseError", "InternalError", "ProgrammingError", "OperationalError",
63-
"TestgresException", "ExecUtilException", "QueryException", "TimeoutException", "CatchUpException", "StartNodeException", "InitNodeException", "BackupException",
64+
"TestgresException", "ExecUtilException", "QueryException", "TimeoutException", "CatchUpException", "StartNodeException", "InitNodeException", "BackupException", "InvalidOperationException",
6465
"XLogMethod", "IsolationLevel", "NodeStatus", "ProcessType", "DumpFormat",
6566
"PostgresNode", "NodeApp",
6667
"reserve_port", "release_port", "bound_ports", "get_bin_path", "get_pg_config", "get_pg_version",

testgres/exceptions.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ class TestgresException(Exception):
99

1010
@six.python_2_unicode_compatible
1111
class ExecUtilException(TestgresException):
12-
def __init__(self, message=None, command=None, exit_code=0, out=None):
12+
def __init__(self, message=None, command=None, exit_code=0, out=None, error=None):
1313
super(ExecUtilException, self).__init__(message)
1414

1515
self.message = message
1616
self.command = command
1717
self.exit_code = exit_code
1818
self.out = out
19+
self.error = error
1920

2021
def __str__(self):
2122
msg = []
@@ -24,13 +25,17 @@ def __str__(self):
2425
msg.append(self.message)
2526

2627
if self.command:
27-
msg.append(u'Command: {}'.format(self.command))
28+
command_s = ' '.join(self.command) if isinstance(self.command, list) else self.command,
29+
msg.append(u'Command: {}'.format(command_s))
2830

2931
if self.exit_code:
3032
msg.append(u'Exit code: {}'.format(self.exit_code))
3133

34+
if self.error:
35+
msg.append(u'---- Error:\n{}'.format(self.error))
36+
3237
if self.out:
33-
msg.append(u'----\n{}'.format(self.out))
38+
msg.append(u'---- Out:\n{}'.format(self.out))
3439

3540
return self.convert_and_join(msg)
3641

@@ -98,3 +103,7 @@ class InitNodeException(TestgresException):
98103

99104
class BackupException(TestgresException):
100105
pass
106+
107+
108+
class InvalidOperationException(TestgresException):
109+
pass

testgres/node.py

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@
7474
TimeoutException, \
7575
InitNodeException, \
7676
TestgresException, \
77-
BackupException
77+
BackupException, \
78+
InvalidOperationException
7879

7980
from .logger import TestgresLogger
8081

@@ -1005,6 +1006,37 @@ def psql(self,
10051006
>>> psql(query='select 3', ON_ERROR_STOP=1)
10061007
"""
10071008

1009+
return self._psql(
1010+
ignore_errors=True,
1011+
query=query,
1012+
filename=filename,
1013+
dbname=dbname,
1014+
username=username,
1015+
input=input,
1016+
**variables
1017+
)
1018+
1019+
def _psql(
1020+
self,
1021+
ignore_errors,
1022+
query=None,
1023+
filename=None,
1024+
dbname=None,
1025+
username=None,
1026+
input=None,
1027+
**variables):
1028+
assert type(variables) == dict # noqa: E721
1029+
1030+
#
1031+
# We do not support encoding. It may be added later. Ok?
1032+
#
1033+
if input is None:
1034+
pass
1035+
elif type(input) == bytes: # noqa: E721
1036+
pass
1037+
else:
1038+
raise Exception("Input data must be None or bytes.")
1039+
10081040
dbname = dbname or default_dbname()
10091041

10101042
psql_params = [
@@ -1035,20 +1067,14 @@ def psql(self,
10351067

10361068
# should be the last one
10371069
psql_params.append(dbname)
1038-
if not self.os_ops.conn_params.remote:
1039-
# start psql process
1040-
process = subprocess.Popen(psql_params,
1041-
stdin=subprocess.PIPE,
1042-
stdout=subprocess.PIPE,
1043-
stderr=subprocess.PIPE)
1044-
1045-
# wait until it finishes and get stdout and stderr
1046-
out, err = process.communicate(input=input)
1047-
return process.returncode, out, err
1048-
else:
1049-
status_code, out, err = self.os_ops.exec_command(psql_params, verbose=True, input=input)
10501070

1051-
return status_code, out, err
1071+
return self.os_ops.exec_command(
1072+
psql_params,
1073+
verbose=True,
1074+
input=input,
1075+
stderr=subprocess.PIPE,
1076+
stdout=subprocess.PIPE,
1077+
ignore_errors=ignore_errors)
10521078

10531079
@method_decorator(positional_args_hack(['dbname', 'query']))
10541080
def safe_psql(self, query=None, expect_error=False, **kwargs):
@@ -1069,22 +1095,27 @@ def safe_psql(self, query=None, expect_error=False, **kwargs):
10691095
Returns:
10701096
psql's output as str.
10711097
"""
1098+
assert type(kwargs) == dict # noqa: E721
1099+
assert not ("ignore_errors" in kwargs.keys())
1100+
assert not ("expect_error" in kwargs.keys())
10721101

10731102
# force this setting
10741103
kwargs['ON_ERROR_STOP'] = 1
10751104
try:
1076-
ret, out, err = self.psql(query=query, **kwargs)
1105+
ret, out, err = self._psql(ignore_errors=False, query=query, **kwargs)
10771106
except ExecUtilException as e:
1078-
ret = e.exit_code
1079-
out = e.out
1080-
err = e.message
1081-
if ret:
1082-
if expect_error:
1083-
out = (err or b'').decode('utf-8')
1084-
else:
1085-
raise QueryException((err or b'').decode('utf-8'), query)
1086-
elif expect_error:
1087-
assert False, "Exception was expected, but query finished successfully: `{}` ".format(query)
1107+
if not expect_error:
1108+
raise QueryException(e.message, query)
1109+
1110+
if type(e.error) == bytes: # noqa: E721
1111+
return e.error.decode("utf-8") # throw
1112+
1113+
# [2024-12-09] This situation is not expected
1114+
assert False
1115+
return e.error
1116+
1117+
if expect_error:
1118+
raise InvalidOperationException("Exception was expected, but query finished successfully: `{}`.".format(query))
10881119

10891120
return out
10901121

testgres/operations/helpers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import locale
2+
3+
4+
class Helpers:
5+
def _make_get_default_encoding_func():
6+
# locale.getencoding is added in Python 3.11
7+
if hasattr(locale, 'getencoding'):
8+
return locale.getencoding
9+
10+
# It must exist
11+
return locale.getpreferredencoding
12+
13+
# Prepared pointer on function to get a name of system codepage
14+
_get_default_encoding_func = _make_get_default_encoding_func()
15+
16+
def GetDefaultEncoding():
17+
#
18+
# Original idea/source was:
19+
#
20+
# def os_ops.get_default_encoding():
21+
# if not hasattr(locale, 'getencoding'):
22+
# locale.getencoding = locale.getpreferredencoding
23+
# return locale.getencoding() or 'UTF-8'
24+
#
25+
26+
assert __class__._get_default_encoding_func is not None
27+
28+
r = __class__._get_default_encoding_func()
29+
30+
if r:
31+
assert r is not None
32+
assert type(r) == str # noqa: E721
33+
assert r != ""
34+
return r
35+
36+
# Is it an unexpected situation?
37+
return 'UTF-8'
38+
39+
def PrepareProcessInput(input, encoding):
40+
if not input:
41+
return None
42+
43+
if type(input) == str: # noqa: E721
44+
if encoding is None:
45+
return input.encode(__class__.GetDefaultEncoding())
46+
47+
assert type(encoding) == str # noqa: E721
48+
return input.encode(encoding)
49+
50+
# It is expected!
51+
assert type(input) == bytes # noqa: E721
52+
return input

testgres/operations/local_ops.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
from ..exceptions import ExecUtilException
1212
from .os_ops import ConnectionParams, OsOperations, get_default_encoding
13+
from .raise_error import RaiseError
14+
from .helpers import Helpers
1315

1416
try:
1517
from shutil import which as find_executable
@@ -41,14 +43,6 @@ def __init__(self, conn_params=None):
4143
conn_params = ConnectionParams()
4244
super(LocalOperations, self).__init__(conn_params)
4345

44-
@staticmethod
45-
def _raise_exec_exception(message, command, exit_code, output):
46-
"""Raise an ExecUtilException."""
47-
raise ExecUtilException(message=message.format(output),
48-
command=' '.join(command) if isinstance(command, list) else command,
49-
exit_code=exit_code,
50-
out=output)
51-
5246
@staticmethod
5347
def _process_output(encoding, temp_file_path):
5448
"""Process the output of a command from a temporary file."""
@@ -59,6 +53,8 @@ def _process_output(encoding, temp_file_path):
5953
return output, None # In Windows stderr writing in stdout
6054

6155
def _run_command__nt(self, cmd, shell, input, stdin, stdout, stderr, get_process, timeout, encoding):
56+
# TODO: why don't we use the data from input?
57+
6258
with tempfile.NamedTemporaryFile(mode='w+b', delete=False) as temp_file:
6359
stdout = temp_file
6460
stderr = subprocess.STDOUT
@@ -80,25 +76,36 @@ def _run_command__nt(self, cmd, shell, input, stdin, stdout, stderr, get_process
8076
return process, output, error
8177

8278
def _run_command__generic(self, cmd, shell, input, stdin, stdout, stderr, get_process, timeout, encoding):
79+
input_prepared = None
80+
if not get_process:
81+
input_prepared = Helpers.PrepareProcessInput(input, encoding) # throw
82+
83+
assert input_prepared is None or (type(input_prepared) == bytes) # noqa: E721
84+
8385
process = subprocess.Popen(
8486
cmd,
8587
shell=shell,
8688
stdin=stdin or subprocess.PIPE if input is not None else None,
8789
stdout=stdout or subprocess.PIPE,
8890
stderr=stderr or subprocess.PIPE,
8991
)
92+
assert not (process is None)
9093
if get_process:
9194
return process, None, None
9295
try:
93-
output, error = process.communicate(input=input.encode(encoding) if input else None, timeout=timeout)
94-
if encoding:
95-
output = output.decode(encoding)
96-
error = error.decode(encoding)
97-
return process, output, error
96+
output, error = process.communicate(input=input_prepared, timeout=timeout)
9897
except subprocess.TimeoutExpired:
9998
process.kill()
10099
raise ExecUtilException("Command timed out after {} seconds.".format(timeout))
101100

101+
assert type(output) == bytes # noqa: E721
102+
assert type(error) == bytes # noqa: E721
103+
104+
if encoding:
105+
output = output.decode(encoding)
106+
error = error.decode(encoding)
107+
return process, output, error
108+
102109
def _run_command(self, cmd, shell, input, stdin, stdout, stderr, get_process, timeout, encoding):
103110
"""Execute a command and return the process and its output."""
104111
if os.name == 'nt' and stdout is None: # Windows
@@ -114,11 +121,20 @@ def exec_command(self, cmd, wait_exit=False, verbose=False, expect_error=False,
114121
"""
115122
Execute a command in a subprocess and handle the output based on the provided parameters.
116123
"""
124+
assert type(expect_error) == bool # noqa: E721
125+
assert type(ignore_errors) == bool # noqa: E721
126+
117127
process, output, error = self._run_command(cmd, shell, input, stdin, stdout, stderr, get_process, timeout, encoding)
118128
if get_process:
119129
return process
120130
if not ignore_errors and ((process.returncode != 0 or has_errors(output=output, error=error)) and not expect_error):
121-
self._raise_exec_exception('Utility exited with non-zero code. Error `{}`', cmd, process.returncode, error or output)
131+
RaiseError.UtilityExitedWithNonZeroCode(
132+
cmd=cmd,
133+
exit_code=process.returncode,
134+
msg_arg=error or output,
135+
error=error,
136+
out=output
137+
)
122138

123139
if verbose:
124140
return process.returncode, output, error

testgres/operations/raise_error.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from ..exceptions import ExecUtilException
2+
from .helpers import Helpers
3+
4+
5+
class RaiseError:
6+
def UtilityExitedWithNonZeroCode(cmd, exit_code, msg_arg, error, out):
7+
assert type(exit_code) == int # noqa: E721
8+
9+
msg_arg_s = __class__._TranslateDataIntoString(msg_arg).strip()
10+
assert type(msg_arg_s) == str # noqa: E721
11+
12+
if msg_arg_s == "":
13+
msg_arg_s = "#no_error_message"
14+
15+
message = "Utility exited with non-zero code. Error: `" + msg_arg_s + "`"
16+
raise ExecUtilException(
17+
message=message,
18+
command=cmd,
19+
exit_code=exit_code,
20+
out=out,
21+
error=error)
22+
23+
def _TranslateDataIntoString(data):
24+
if type(data) == bytes: # noqa: E721
25+
return __class__._TranslateDataIntoString__FromBinary(data)
26+
27+
return str(data)
28+
29+
def _TranslateDataIntoString__FromBinary(data):
30+
assert type(data) == bytes # noqa: E721
31+
32+
try:
33+
return data.decode(Helpers.GetDefaultEncoding())
34+
except UnicodeDecodeError:
35+
pass
36+
37+
return "#cannot_decode_text"
38+
39+
def _BinaryIsASCII(data):
40+
assert type(data) == bytes # noqa: E721
41+
42+
for b in data:
43+
if not (b >= 0 and b <= 127):
44+
return False
45+
46+
return True

0 commit comments

Comments
 (0)