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

Lines changed: 15 additions & 19 deletions
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

Lines changed: 19 additions & 12 deletions
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

Lines changed: 67 additions & 34 deletions
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

Lines changed: 7 additions & 6 deletions
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

Lines changed: 3 additions & 2 deletions
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

Lines changed: 6 additions & 4 deletions
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

Lines changed: 24 additions & 19 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy