Skip to content

Commit 3648587

Browse files
demonolockvshepard
authored and
vshepard
committed
Refactoring local_ops.py
1 parent de1ce5c commit 3648587

File tree

5 files changed

+63
-120
lines changed

5 files changed

+63
-120
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
setup(
3030
version='1.9.3',
3131
name='testgres',
32-
packages=['testgres', 'testgres.operations'],
32+
packages=['testgres', 'testgres.operations', 'testgres.helpers'],
3333
description='Testing utility for PostgreSQL and its extensions',
3434
url='https://github.com/postgrespro/testgres',
3535
long_description=readme,

testgres/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
from .operations.local_ops import LocalOperations
5353
from .operations.remote_ops import RemoteOperations
5454

55+
from .helpers.port_manager import PortManager
56+
5557
__all__ = [
5658
"get_new_node",
5759
"get_remote_node",
@@ -62,6 +64,6 @@
6264
"XLogMethod", "IsolationLevel", "NodeStatus", "ProcessType", "DumpFormat",
6365
"PostgresNode", "NodeApp",
6466
"reserve_port", "release_port", "bound_ports", "get_bin_path", "get_pg_config", "get_pg_version",
65-
"First", "Any",
67+
"First", "Any", "PortManager",
6668
"OsOperations", "LocalOperations", "RemoteOperations", "ConnectionParams"
6769
]

testgres/node.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -623,8 +623,8 @@ def status(self):
623623
"-D", self.data_dir,
624624
"status"
625625
] # yapf: disable
626-
status_code, out, err = execute_utility(_params, self.utils_log_file, verbose=True)
627-
if 'does not exist' in err:
626+
status_code, out, error = execute_utility(_params, self.utils_log_file, verbose=True)
627+
if error and 'does not exist' in error:
628628
return NodeStatus.Uninitialized
629629
elif 'no server running' in out:
630630
return NodeStatus.Stopped
@@ -717,7 +717,7 @@ def start(self, params=[], wait=True):
717717

718718
try:
719719
exit_status, out, error = execute_utility(_params, self.utils_log_file, verbose=True)
720-
if 'does not exist' in error:
720+
if error and 'does not exist' in error:
721721
raise Exception
722722
except Exception as e:
723723
msg = 'Cannot start node'
@@ -791,7 +791,7 @@ def restart(self, params=[]):
791791

792792
try:
793793
error_code, out, error = execute_utility(_params, self.utils_log_file, verbose=True)
794-
if 'could not start server' in error:
794+
if error and 'could not start server' in error:
795795
raise ExecUtilException
796796
except ExecUtilException as e:
797797
msg = 'Cannot restart node'

testgres/operations/local_ops.py

Lines changed: 54 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -38,32 +38,6 @@ def __init__(self, conn_params=None):
3838
self.remote = False
3939
self.username = conn_params.username or self.get_user()
4040

41-
@staticmethod
42-
def _run_command(cmd, shell, input, stdin, stdout, stderr, timeout, encoding, temp_file=None, get_process=None):
43-
"""Execute a command and return the process."""
44-
if temp_file is not None:
45-
stdout = stdout or temp_file
46-
stderr = stderr or subprocess.STDOUT
47-
else:
48-
stdout = stdout or subprocess.PIPE
49-
stderr = stderr or subprocess.PIPE
50-
51-
process = subprocess.Popen(
52-
cmd,
53-
shell=shell,
54-
stdin=stdin or subprocess.PIPE if input is not None else None,
55-
stdout=stdout,
56-
stderr=stderr,
57-
)
58-
59-
if get_process:
60-
return None, process
61-
try:
62-
return process.communicate(input=input.encode(encoding) if input else None, timeout=timeout), process
63-
except subprocess.TimeoutExpired:
64-
process.kill()
65-
raise ExecUtilException("Command timed out after {} seconds.".format(timeout))
66-
6741
@staticmethod
6842
def _raise_exec_exception(message, command, exit_code, output):
6943
"""Raise an ExecUtilException."""
@@ -72,105 +46,72 @@ def _raise_exec_exception(message, command, exit_code, output):
7246
exit_code=exit_code,
7347
out=output)
7448

75-
def exec_command(self, cmd, wait_exit=False, verbose=False,
76-
expect_error=False, encoding=None, shell=False, text=False,
77-
input=None, stdin=None, stdout=None, stderr=None,
78-
get_process=None, timeout=None):
79-
"""
80-
Execute a command in a subprocess.
81-
82-
Args:
83-
- cmd: The command to execute.
84-
- wait_exit: Whether to wait for the subprocess to exit before returning.
85-
- verbose: Whether to return verbose output.
86-
- expect_error: Whether to raise an error if the subprocess exits with an error status.
87-
- encoding: The encoding to use for decoding the subprocess output.
88-
- shell: Whether to use shell when executing the subprocess.
89-
- text: Whether to return str instead of bytes for the subprocess output.
90-
- input: The input to pass to the subprocess.
91-
- stdout: The stdout to use for the subprocess.
92-
- stderr: The stderr to use for the subprocess.
93-
- proc: The process to use for subprocess creation.
94-
:return: The output of the subprocess.
95-
"""
96-
if os.name == 'nt':
97-
return self._exec_command_windows(cmd, wait_exit=wait_exit, verbose=verbose,
98-
expect_error=expect_error, encoding=encoding, shell=shell, text=text,
99-
input=input, stdin=stdin, stdout=stdout, stderr=stderr,
100-
get_process=get_process, timeout=timeout)
101-
else:
49+
@staticmethod
50+
def _process_output(encoding, temp_file_path):
51+
"""Process the output of a command from a temporary file."""
52+
with open(temp_file_path, 'rb') as temp_file:
53+
output = temp_file.read()
54+
if encoding:
55+
output = output.decode(encoding)
56+
return output, None # In Windows stderr writing in stdout
57+
58+
def _run_command(self, cmd, shell, input, stdin, stdout, stderr, get_process, timeout, encoding):
59+
"""Execute a command and return the process and its output."""
60+
if os.name == 'nt' and stdout is None: # Windows
61+
with tempfile.NamedTemporaryFile(mode='w+b', delete=False) as temp_file:
62+
stdout = temp_file
63+
stderr = subprocess.STDOUT
64+
process = subprocess.Popen(
65+
cmd,
66+
shell=shell,
67+
stdin=stdin or subprocess.PIPE if input is not None else None,
68+
stdout=stdout,
69+
stderr=stderr,
70+
)
71+
if get_process:
72+
return process, None, None
73+
temp_file_path = temp_file.name
74+
75+
# Wait process finished
76+
process.wait()
77+
78+
output, error = self._process_output(encoding, temp_file_path)
79+
return process, output, error
80+
else: # Other OS
10281
process = subprocess.Popen(
10382
cmd,
10483
shell=shell,
105-
stdin=stdin,
106-
stdout=stdout,
107-
stderr=stderr,
84+
stdin=stdin or subprocess.PIPE if input is not None else None,
85+
stdout=stdout or subprocess.PIPE,
86+
stderr=stderr or subprocess.PIPE,
10887
)
10988
if get_process:
110-
return process
111-
89+
return process, None, None
11290
try:
113-
result, error = process.communicate(input, timeout=timeout)
91+
output, error = process.communicate(input=input.encode(encoding) if input else None, timeout=timeout)
92+
if encoding:
93+
output = output.decode(encoding)
94+
error = error.decode(encoding)
95+
return process, output, error
11496
except subprocess.TimeoutExpired:
11597
process.kill()
11698
raise ExecUtilException("Command timed out after {} seconds.".format(timeout))
117-
exit_status = process.returncode
11899

119-
error_found = exit_status != 0 or has_errors(error)
120-
121-
if encoding:
122-
result = result.decode(encoding)
123-
error = error.decode(encoding)
124-
125-
if expect_error:
126-
raise Exception(result, error)
127-
128-
if exit_status != 0 or error_found:
129-
if exit_status == 0:
130-
exit_status = 1
131-
self._raise_exec_exception('Utility exited with non-zero code. Error `{}`', cmd, exit_status, result)
132-
if verbose:
133-
return exit_status, result, error
134-
else:
135-
return result
100+
def exec_command(self, cmd, wait_exit=False, verbose=False, expect_error=False, encoding=None, shell=False,
101+
text=False, input=None, stdin=None, stdout=None, stderr=None, get_process=False, timeout=None):
102+
"""
103+
Execute a command in a subprocess and handle the output based on the provided parameters.
104+
"""
105+
process, output, error = self._run_command(cmd, shell, input, stdin, stdout, stderr, get_process, timeout, encoding)
106+
if get_process:
107+
return process
108+
if process.returncode != 0 or (has_errors(error) and not expect_error):
109+
self._raise_exec_exception('Utility exited with non-zero code. Error `{}`', cmd, process.returncode, error)
136110

137-
@staticmethod
138-
def _process_output(process, encoding, temp_file=None):
139-
"""Process the output of a command."""
140-
if temp_file is not None:
141-
temp_file.seek(0)
142-
output = temp_file.read()
111+
if verbose:
112+
return process.returncode, output, error
143113
else:
144-
output = process.stdout.read()
145-
146-
if encoding:
147-
output = output.decode(encoding)
148-
149-
return output
150-
151-
def _exec_command_windows(self, cmd, wait_exit=False, verbose=False,
152-
expect_error=False, encoding=None, shell=False, text=False,
153-
input=None, stdin=None, stdout=None, stderr=None,
154-
get_process=None, timeout=None):
155-
with tempfile.NamedTemporaryFile(mode='w+b') as temp_file:
156-
_, process = self._run_command(cmd, shell, input, stdin, stdout, stderr, timeout, encoding, temp_file, get_process)
157-
if get_process:
158-
return process
159-
result = self._process_output(process, encoding, temp_file)
160-
161-
if process.returncode != 0 or has_errors(result):
162-
if process.returncode == 0:
163-
process.returncode = 1
164-
if expect_error:
165-
if verbose:
166-
return process.returncode, result, result
167-
else:
168-
return result
169-
else:
170-
self._raise_exec_exception('Utility exited with non-zero code. Error `{}`', cmd, process.returncode,
171-
result)
172-
173-
return (process.returncode, result, result) if verbose else result
114+
return output
174115

175116
# Environment setup
176117
def environ(self, var_name):

testgres/operations/os_ops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def write(self, filename, data, truncate=False, binary=False, read_and_write=Fal
8181
def touch(self, filename):
8282
raise NotImplementedError()
8383

84-
def read(self, filename):
84+
def read(self, filename, encoding, binary):
8585
raise NotImplementedError()
8686

8787
def readlines(self, filename):

0 commit comments

Comments
 (0)