Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 0ffd5f0

Browse files
Total refactoring of os_ops::execute_command (#203)
* Total refactoring of os_ops::execute_command Main - We check only an exit code to detect an error. - If someone utility returns a result through an exit code, a caller side should set ignore_errors=true and process this case itself. - If expect_error is true and no errors occurred, we raise an InvalidOperationException. * The old behaviour of RaiseError.UtilityExitedWithNonZeroCode is restored Let's rollback the new code to avoid problems with probackup2' tests.
1 parent 7177212 commit 0ffd5f0

File tree

7 files changed

+141
-96
lines changed

7 files changed

+141
-96
lines changed

testgres/operations/local_ops.py

+15-19
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,6 @@
2323
from distutils import rmtree
2424

2525
CMD_TIMEOUT_SEC = 60
26-
error_markers = [b'error', b'Permission denied', b'fatal']
27-
err_out_markers = [b'Failure']
28-
29-
30-
def has_errors(output=None, error=None):
31-
if output:
32-
if isinstance(output, str):
33-
output = output.encode(get_default_encoding())
34-
return any(marker in output for marker in err_out_markers)
35-
if error:
36-
if isinstance(error, str):
37-
error = error.encode(get_default_encoding())
38-
return any(marker in error for marker in error_markers)
39-
return False
4026

4127

4228
class LocalOperations(OsOperations):
@@ -134,19 +120,29 @@ def exec_command(self, cmd, wait_exit=False, verbose=False, expect_error=False,
134120
process, output, error = self._run_command(cmd, shell, input, stdin, stdout, stderr, get_process, timeout, encoding)
135121
if get_process:
136122
return process
137-
if not ignore_errors and ((process.returncode != 0 or has_errors(output=output, error=error)) and not expect_error):
123+
124+
if expect_error:
125+
if process.returncode == 0:
126+
raise InvalidOperationException("We expected an execution error.")
127+
elif ignore_errors:
128+
pass
129+
elif process.returncode == 0:
130+
pass
131+
else:
132+
assert not expect_error
133+
assert not ignore_errors
134+
assert process.returncode != 0
138135
RaiseError.UtilityExitedWithNonZeroCode(
139136
cmd=cmd,
140137
exit_code=process.returncode,
141138
msg_arg=error or output,
142139
error=error,
143-
out=output
144-
)
140+
out=output)
145141

146142
if verbose:
147143
return process.returncode, output, error
148-
else:
149-
return output
144+
145+
return output
150146

151147
# Environment setup
152148
def environ(self, var_name):

testgres/operations/raise_error.py

+19-12
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,27 @@ class RaiseError:
77
def UtilityExitedWithNonZeroCode(cmd, exit_code, msg_arg, error, out):
88
assert type(exit_code) == int # noqa: E721
99

10-
msg_arg_s = __class__._TranslateDataIntoString(msg_arg).strip()
10+
msg_arg_s = __class__._TranslateDataIntoString(msg_arg)
1111
assert type(msg_arg_s) == str # noqa: E721
1212

13+
msg_arg_s = msg_arg_s.strip()
1314
if msg_arg_s == "":
1415
msg_arg_s = "#no_error_message"
1516

16-
message = "Utility exited with non-zero code. Error: `" + msg_arg_s + "`"
17+
message = "Utility exited with non-zero code (" + str(exit_code) + "). Error: `" + msg_arg_s + "`"
18+
raise ExecUtilException(
19+
message=message,
20+
command=cmd,
21+
exit_code=exit_code,
22+
out=out,
23+
error=error)
24+
25+
@staticmethod
26+
def CommandExecutionError(cmd, exit_code, message, error, out):
27+
assert type(exit_code) == int # noqa: E721
28+
assert type(message) == str # noqa: E721
29+
assert message != ""
30+
1731
raise ExecUtilException(
1832
message=message,
1933
command=cmd,
@@ -23,6 +37,9 @@ def UtilityExitedWithNonZeroCode(cmd, exit_code, msg_arg, error, out):
2337

2438
@staticmethod
2539
def _TranslateDataIntoString(data):
40+
if data is None:
41+
return ""
42+
2643
if type(data) == bytes: # noqa: E721
2744
return __class__._TranslateDataIntoString__FromBinary(data)
2845

@@ -38,13 +55,3 @@ def _TranslateDataIntoString__FromBinary(data):
3855
pass
3956

4057
return "#cannot_decode_text"
41-
42-
@staticmethod
43-
def _BinaryIsASCII(data):
44-
assert type(data) == bytes # noqa: E721
45-
46-
for b in data:
47-
if not (b >= 0 and b <= 127):
48-
return False
49-
50-
return True

testgres/operations/remote_ops.py

+67-34
Original file line numberDiff line numberDiff line change
@@ -100,41 +100,40 @@ def exec_command(self, cmd, wait_exit=False, verbose=False, expect_error=False,
100100
return process
101101

102102
try:
103-
result, error = process.communicate(input=input_prepared, timeout=timeout)
103+
output, error = process.communicate(input=input_prepared, timeout=timeout)
104104
except subprocess.TimeoutExpired:
105105
process.kill()
106106
raise ExecUtilException("Command timed out after {} seconds.".format(timeout))
107107

108-
exit_status = process.returncode
109-
110-
assert type(result) == bytes # noqa: E721
108+
assert type(output) == bytes # noqa: E721
111109
assert type(error) == bytes # noqa: E721
112110

113-
if not error:
114-
error_found = False
115-
else:
116-
error_found = exit_status != 0 or any(
117-
marker in error for marker in [b'error', b'Permission denied', b'fatal', b'No such file or directory']
118-
)
119-
120-
assert type(error_found) == bool # noqa: E721
121-
122111
if encoding:
123-
result = result.decode(encoding)
112+
output = output.decode(encoding)
124113
error = error.decode(encoding)
125114

126-
if not ignore_errors and error_found and not expect_error:
115+
if expect_error:
116+
if process.returncode == 0:
117+
raise InvalidOperationException("We expected an execution error.")
118+
elif ignore_errors:
119+
pass
120+
elif process.returncode == 0:
121+
pass
122+
else:
123+
assert not expect_error
124+
assert not ignore_errors
125+
assert process.returncode != 0
127126
RaiseError.UtilityExitedWithNonZeroCode(
128127
cmd=cmd,
129-
exit_code=exit_status,
128+
exit_code=process.returncode,
130129
msg_arg=error,
131130
error=error,
132-
out=result)
131+
out=output)
133132

134133
if verbose:
135-
return exit_status, result, error
136-
else:
137-
return result
134+
return process.returncode, output, error
135+
136+
return output
138137

139138
# Environment setup
140139
def environ(self, var_name: str) -> str:
@@ -165,8 +164,30 @@ def find_executable(self, executable):
165164

166165
def is_executable(self, file):
167166
# Check if the file is executable
168-
is_exec = self.exec_command("test -x {} && echo OK".format(file))
169-
return is_exec == b"OK\n"
167+
command = ["test", "-x", file]
168+
169+
exit_status, output, error = self.exec_command(cmd=command, encoding=get_default_encoding(), ignore_errors=True, verbose=True)
170+
171+
assert type(output) == str # noqa: E721
172+
assert type(error) == str # noqa: E721
173+
174+
if exit_status == 0:
175+
return True
176+
177+
if exit_status == 1:
178+
return False
179+
180+
errMsg = "Test operation returns an unknown result code: {0}. File name is [{1}].".format(
181+
exit_status,
182+
file)
183+
184+
RaiseError.CommandExecutionError(
185+
cmd=command,
186+
exit_code=exit_status,
187+
msg_arg=errMsg,
188+
error=error,
189+
out=output
190+
)
170191

171192
def set_env(self, var_name: str, var_val: str):
172193
"""
@@ -251,15 +272,21 @@ def mkdtemp(self, prefix=None):
251272
else:
252273
command = ["mktemp", "-d"]
253274

254-
exit_status, result, error = self.exec_command(command, verbose=True, encoding=get_default_encoding(), ignore_errors=True)
275+
exec_exitcode, exec_output, exec_error = self.exec_command(command, verbose=True, encoding=get_default_encoding(), ignore_errors=True)
255276

256-
assert type(result) == str # noqa: E721
257-
assert type(error) == str # noqa: E721
277+
assert type(exec_exitcode) == int # noqa: E721
278+
assert type(exec_output) == str # noqa: E721
279+
assert type(exec_error) == str # noqa: E721
258280

259-
if exit_status != 0:
260-
raise ExecUtilException("Could not create temporary directory. Error code: {0}. Error message: {1}".format(exit_status, error))
281+
if exec_exitcode != 0:
282+
RaiseError.CommandExecutionError(
283+
cmd=command,
284+
exit_code=exec_exitcode,
285+
message="Could not create temporary directory.",
286+
error=exec_error,
287+
out=exec_output)
261288

262-
temp_dir = result.strip()
289+
temp_dir = exec_output.strip()
263290
return temp_dir
264291

265292
def mkstemp(self, prefix=None):
@@ -273,15 +300,21 @@ def mkstemp(self, prefix=None):
273300
else:
274301
command = ["mktemp"]
275302

276-
exit_status, result, error = self.exec_command(command, verbose=True, encoding=get_default_encoding(), ignore_errors=True)
303+
exec_exitcode, exec_output, exec_error = self.exec_command(command, verbose=True, encoding=get_default_encoding(), ignore_errors=True)
277304

278-
assert type(result) == str # noqa: E721
279-
assert type(error) == str # noqa: E721
305+
assert type(exec_exitcode) == int # noqa: E721
306+
assert type(exec_output) == str # noqa: E721
307+
assert type(exec_error) == str # noqa: E721
280308

281-
if exit_status != 0:
282-
raise ExecUtilException("Could not create temporary file. Error code: {0}. Error message: {1}".format(exit_status, error))
309+
if exec_exitcode != 0:
310+
RaiseError.CommandExecutionError(
311+
cmd=command,
312+
exit_code=exec_exitcode,
313+
message="Could not create temporary file.",
314+
error=exec_error,
315+
out=exec_output)
283316

284-
temp_file = result.strip()
317+
temp_file = exec_output.strip()
285318
return temp_file
286319

287320
def copytree(self, src, dst):

testgres/utils.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .config import testgres_config as tconf
1919
from .operations.os_ops import OsOperations
2020
from .operations.remote_ops import RemoteOperations
21+
from .operations.helpers import Helpers as OsHelpers
2122

2223
# rows returned by PG_CONFIG
2324
_pg_config_data = {}
@@ -79,13 +80,13 @@ def execute_utility2(os_ops: OsOperations, args, logfile=None, verbose=False, ig
7980
assert type(verbose) == bool # noqa: E721
8081
assert type(ignore_errors) == bool # noqa: E721
8182

82-
exit_status, out, error = os_ops.exec_command(args, verbose=True, ignore_errors=ignore_errors)
83-
# decode result
83+
exit_status, out, error = os_ops.exec_command(
84+
args,
85+
verbose=True,
86+
ignore_errors=ignore_errors,
87+
encoding=OsHelpers.GetDefaultEncoding())
88+
8489
out = '' if not out else out
85-
if isinstance(out, bytes):
86-
out = out.decode('utf-8')
87-
if isinstance(error, bytes):
88-
error = error.decode('utf-8')
8990

9091
# write new log entry if possible
9192
if logfile:

tests/test_local.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ def test_exec_command_failure(self):
4040
try:
4141
self.operations.exec_command(cmd, wait_exit=True, shell=True)
4242
except ExecUtilException as e:
43-
error = e.message
43+
assert e.message == "Utility exited with non-zero code (127). Error: `/bin/sh: 1: nonexistent_command: not found`"
44+
assert type(e.error) == bytes # noqa: E721
45+
assert e.error.strip() == b"/bin/sh: 1: nonexistent_command: not found"
4446
break
4547
raise Exception("We wait an exception!")
46-
assert error == "Utility exited with non-zero code. Error: `/bin/sh: 1: nonexistent_command: not found`"
4748

4849
def test_exec_command_failure__expect_error(self):
4950
"""

tests/test_remote.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ def test_exec_command_failure(self):
4040
try:
4141
self.operations.exec_command(cmd, verbose=True, wait_exit=True)
4242
except ExecUtilException as e:
43-
error = e.message
43+
assert e.message == "Utility exited with non-zero code (127). Error: `bash: line 1: nonexistent_command: command not found`"
44+
assert type(e.error) == bytes # noqa: E721
45+
assert e.error.strip() == b"bash: line 1: nonexistent_command: command not found"
4446
break
4547
raise Exception("We wait an exception!")
46-
assert error == 'Utility exited with non-zero code. Error: `bash: line 1: nonexistent_command: command not found`'
4748

4849
def test_exec_command_failure__expect_error(self):
4950
"""
@@ -114,10 +115,11 @@ def test_makedirs_and_rmdirs_failure(self):
114115
try:
115116
self.operations.rmdirs(path, verbose=True)
116117
except ExecUtilException as e:
117-
error = e.message
118+
assert e.message == "Utility exited with non-zero code (1). Error: `rm: cannot remove '/root/test_dir': Permission denied`"
119+
assert type(e.error) == bytes # noqa: E721
120+
assert e.error.strip() == b"rm: cannot remove '/root/test_dir': Permission denied"
118121
break
119122
raise Exception("We wait an exception!")
120-
assert error == "Utility exited with non-zero code. Error: `rm: cannot remove '/root/test_dir': Permission denied`"
121123

122124
def test_listdir(self):
123125
"""

tests/test_simple_remote.py

+24-19
Original file line numberDiff line numberDiff line change
@@ -178,27 +178,32 @@ def test_init__unk_LANG_and_LC_CTYPE(self):
178178
assert os.environ.get("LC_CTYPE") == unkData[1]
179179
assert not ("LC_COLLATE" in os.environ.keys())
180180

181-
while True:
181+
assert os.getenv('LANG') == unkData[0]
182+
assert os.getenv('LANGUAGE') is None
183+
assert os.getenv('LC_CTYPE') == unkData[1]
184+
assert os.getenv('LC_COLLATE') is None
185+
186+
exc: ExecUtilException = None
187+
with __class__.helper__get_node() as node:
182188
try:
183-
with __class__.helper__get_node():
184-
pass
185-
except ExecUtilException as e:
186-
#
187-
# Example of an error message:
188-
#
189-
# warning: setlocale: LC_CTYPE: cannot change locale (UNKNOWN_CTYPE): No such file or directory
190-
# postgres (PostgreSQL) 14.12
191-
#
192-
errMsg = str(e)
193-
194-
logging.info("Error message is: {0}".format(errMsg))
195-
196-
assert "LC_CTYPE" in errMsg
197-
assert unkData[1] in errMsg
198-
assert "warning: setlocale: LC_CTYPE: cannot change locale (" + unkData[1] + "): No such file or directory" in errMsg
199-
assert ("postgres" in errMsg) or ("PostgreSQL" in errMsg)
200-
break
189+
node.init() # IT RAISES!
190+
except InitNodeException as e:
191+
exc = e.__cause__
192+
assert exc is not None
193+
assert isinstance(exc, ExecUtilException)
194+
195+
if exc is None:
201196
raise Exception("We expected an error!")
197+
198+
assert isinstance(exc, ExecUtilException)
199+
200+
errMsg = str(exc)
201+
logging.info("Error message is {0}: {1}".format(type(exc).__name__, errMsg))
202+
203+
assert "warning: setlocale: LC_CTYPE: cannot change locale (" + unkData[1] + ")" in errMsg
204+
assert "initdb: error: invalid locale settings; check LANG and LC_* environment variables" in errMsg
205+
continue
206+
202207
finally:
203208
__class__.helper__restore_envvar("LANG", prev_LANG)
204209
__class__.helper__restore_envvar("LANGUAGE", prev_LANGUAGE)

0 commit comments

Comments
 (0)