Skip to content

Commit 901a639

Browse files
committed
Refactoring local_ops.py
1 parent de1ce5c commit 901a639

File tree

5 files changed

+68
-123
lines changed

5 files changed

+68
-123
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: 59 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222

2323

2424
def has_errors(output):
25-
if isinstance(output, str):
26-
output = output.encode(get_default_encoding())
27-
return any(marker in output for marker in error_markers)
25+
if output:
26+
if isinstance(output, str):
27+
output = output.encode(get_default_encoding())
28+
return any(marker in output for marker in error_markers)
29+
return False
2830

2931

3032
class LocalOperations(OsOperations):
@@ -38,32 +40,6 @@ def __init__(self, conn_params=None):
3840
self.remote = False
3941
self.username = conn_params.username or self.get_user()
4042

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-
6743
@staticmethod
6844
def _raise_exec_exception(message, command, exit_code, output):
6945
"""Raise an ExecUtilException."""
@@ -72,105 +48,72 @@ def _raise_exec_exception(message, command, exit_code, output):
7248
exit_code=exit_code,
7349
out=output)
7450

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:
51+
@staticmethod
52+
def _process_output(encoding, temp_file_path):
53+
"""Process the output of a command from a temporary file."""
54+
with open(temp_file_path, 'rb') as temp_file:
55+
output = temp_file.read()
56+
if encoding:
57+
output = output.decode(encoding)
58+
return output, None # In Windows stderr writing in stdout
59+
60+
def _run_command(self, cmd, shell, input, stdin, stdout, stderr, get_process, timeout, encoding):
61+
"""Execute a command and return the process and its output."""
62+
if os.name == 'nt' and stdout is None: # Windows
63+
with tempfile.NamedTemporaryFile(mode='w+b', delete=False) as temp_file:
64+
stdout = temp_file
65+
stderr = subprocess.STDOUT
66+
process = subprocess.Popen(
67+
cmd,
68+
shell=shell,
69+
stdin=stdin or subprocess.PIPE if input is not None else None,
70+
stdout=stdout,
71+
stderr=stderr,
72+
)
73+
if get_process:
74+
return process, None, None
75+
temp_file_path = temp_file.name
76+
77+
# Wait process finished
78+
process.wait()
79+
80+
output, error = self._process_output(encoding, temp_file_path)
81+
return process, output, error
82+
else: # Other OS
10283
process = subprocess.Popen(
10384
cmd,
10485
shell=shell,
105-
stdin=stdin,
106-
stdout=stdout,
107-
stderr=stderr,
86+
stdin=stdin or subprocess.PIPE if input is not None else None,
87+
stdout=stdout or subprocess.PIPE,
88+
stderr=stderr or subprocess.PIPE,
10889
)
10990
if get_process:
110-
return process
111-
91+
return process, None, None
11292
try:
113-
result, error = process.communicate(input, timeout=timeout)
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
11498
except subprocess.TimeoutExpired:
11599
process.kill()
116100
raise ExecUtilException("Command timed out after {} seconds.".format(timeout))
117-
exit_status = process.returncode
118101

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
102+
def exec_command(self, cmd, wait_exit=False, verbose=False, expect_error=False, encoding=None, shell=False,
103+
text=False, input=None, stdin=None, stdout=None, stderr=None, get_process=False, timeout=None):
104+
"""
105+
Execute a command in a subprocess and handle the output based on the provided parameters.
106+
"""
107+
process, output, error = self._run_command(cmd, shell, input, stdin, stdout, stderr, get_process, timeout, encoding)
108+
if get_process:
109+
return process
110+
if process.returncode != 0 or (has_errors(error) and not expect_error):
111+
self._raise_exec_exception('Utility exited with non-zero code. Error `{}`', cmd, process.returncode, error)
136112

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()
113+
if verbose:
114+
return process.returncode, output, error
143115
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
116+
return output
174117

175118
# Environment setup
176119
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)