Skip to content

Commit ba90190

Browse files
author
tboy1337
committed
Refactor PostgreSQLExecutor for cross-platform command compatibility
- Updated _get_base_command method to provide a unified command format for both Windows and Unix systems, eliminating the use of single quotes around configuration values. - Simplified process termination logic by removing platform checks from the _windows_terminate_process method, ensuring consistent behavior across operating systems. - Enhanced tests to verify that the base command format is consistent across platforms.
1 parent 6ebc7eb commit ba90190

File tree

2 files changed

+58
-69
lines changed

2 files changed

+58
-69
lines changed

pytest_postgresql/executor.py

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,17 @@ class PostgreSQLExecutor(TCPExecutor):
5151
"""
5252

5353
def _get_base_command(self) -> str:
54-
"""Get the base PostgreSQL command, Windows-compatible."""
55-
if platform.system() == "Windows":
56-
# Windows doesn't handle single quotes well in subprocess calls
57-
return (
58-
'{executable} start -D "{datadir}" '
59-
'-o "-F -p {port} -c log_destination=stderr '
60-
"-c logging_collector=off "
61-
'-c unix_socket_directories={unixsocketdir} {postgres_options}" '
62-
'-l "{logfile}" {startparams}'
63-
)
64-
else:
65-
# Unix/Linux systems work with single quotes
66-
return (
67-
'{executable} start -D "{datadir}" '
68-
"-o \"-F -p {port} -c log_destination='stderr' "
69-
"-c logging_collector=off "
70-
"-c unix_socket_directories='{unixsocketdir}' {postgres_options}\" "
71-
'-l "{logfile}" {startparams}'
72-
)
54+
"""Get the base PostgreSQL command, cross-platform compatible."""
55+
# Use unified format without single quotes around values
56+
# This format works on both Windows and Unix systems since PostgreSQL
57+
# configuration values without spaces don't require quotes
58+
return (
59+
'{executable} start -D "{datadir}" '
60+
'-o "-F -p {port} -c log_destination=stderr '
61+
"-c logging_collector=off "
62+
'-c unix_socket_directories={unixsocketdir} {postgres_options}" '
63+
'-l "{logfile}" {startparams}'
64+
)
7365

7466
BASE_PROC_START_COMMAND = "" # Will be set dynamically
7567

@@ -242,20 +234,28 @@ def _windows_terminate_process(self, sig: Optional[int] = None) -> None:
242234
return
243235

244236
try:
245-
if platform.system() == "Windows":
246-
# On Windows, try to terminate gracefully first
247-
self.process.terminate()
248-
# Give it a chance to terminate gracefully
249-
try:
250-
self.process.wait(timeout=5)
251-
except subprocess.TimeoutExpired:
252-
# If it doesn't terminate gracefully, force kill
253-
self.process.kill()
254-
self.process.wait()
255-
else:
256-
# On Unix systems, use the signal
257-
actual_sig = sig or signal.SIGTERM
258-
os.killpg(self.process.pid, actual_sig)
237+
# On Windows, try to terminate gracefully first
238+
self.process.terminate()
239+
# Give it a chance to terminate gracefully
240+
try:
241+
self.process.wait(timeout=5)
242+
except subprocess.TimeoutExpired:
243+
# If it doesn't terminate gracefully, force kill
244+
self.process.kill()
245+
self.process.wait()
246+
except (OSError, AttributeError):
247+
# Process might already be dead or other issues
248+
pass
249+
250+
def _unix_terminate_process(self, sig: Optional[int] = None) -> None:
251+
"""Terminate process on Unix systems."""
252+
if self.process is None:
253+
return
254+
255+
try:
256+
# On Unix systems, use the signal
257+
actual_sig = sig or signal.SIGTERM
258+
os.killpg(self.process.pid, actual_sig)
259259
except (OSError, AttributeError):
260260
# Process might already be dead or other issues
261261
pass

tests/test_windows_compatibility.py

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
class TestWindowsCompatibility:
1010
"""Test Windows-specific functionality."""
1111

12-
def test_get_base_command_windows(self):
13-
"""Test that Windows base command doesn't use single quotes."""
12+
def test_get_base_command_unified(self):
13+
"""Test that base command is unified across platforms."""
1414
executor = PostgreSQLExecutor(
1515
executable="/path/to/pg_ctl",
1616
host="localhost",
@@ -22,32 +22,21 @@ def test_get_base_command_windows(self):
2222
dbname="test",
2323
)
2424

25+
# Test that command format is consistent across platforms
2526
with patch("platform.system", return_value="Windows"):
26-
command = executor._get_base_command()
27-
# Windows command should not have single quotes around stderr
28-
assert "log_destination=stderr" in command
29-
assert "log_destination='stderr'" not in command
30-
assert "unix_socket_directories={unixsocketdir}" in command
31-
assert "unix_socket_directories='{unixsocketdir}'" not in command
32-
33-
def test_get_base_command_unix(self):
34-
"""Test that Unix base command uses single quotes."""
35-
executor = PostgreSQLExecutor(
36-
executable="/path/to/pg_ctl",
37-
host="localhost",
38-
port=5432,
39-
datadir="/tmp/data",
40-
unixsocketdir="/tmp/socket",
41-
logfile="/tmp/log",
42-
startparams="-w",
43-
dbname="test",
44-
)
27+
windows_command = executor._get_base_command()
4528

4629
with patch("platform.system", return_value="Linux"):
47-
command = executor._get_base_command()
48-
# Unix command should have single quotes around stderr
49-
assert "log_destination='stderr'" in command
50-
assert "unix_socket_directories='{unixsocketdir}'" in command
30+
unix_command = executor._get_base_command()
31+
32+
# Both should be the same now
33+
assert windows_command == unix_command
34+
35+
# Both should use the simplified format without single quotes
36+
assert "log_destination=stderr" in windows_command
37+
assert "log_destination='stderr'" not in windows_command
38+
assert "unix_socket_directories={unixsocketdir}" in windows_command
39+
assert "unix_socket_directories='{unixsocketdir}'" not in windows_command
5140

5241
def test_windows_terminate_process(self):
5342
"""Test Windows process termination."""
@@ -66,12 +55,12 @@ def test_windows_terminate_process(self):
6655
mock_process = MagicMock()
6756
executor.process = mock_process
6857

69-
with patch("platform.system", return_value="Windows"):
70-
executor._windows_terminate_process()
58+
# No need to mock platform.system() since the method doesn't check it anymore
59+
executor._windows_terminate_process()
7160

72-
# Should call terminate first
73-
mock_process.terminate.assert_called_once()
74-
mock_process.wait.assert_called()
61+
# Should call terminate first
62+
mock_process.terminate.assert_called_once()
63+
mock_process.wait.assert_called()
7564

7665
def test_windows_terminate_process_force_kill(self):
7766
"""Test Windows process termination with force kill on timeout."""
@@ -91,13 +80,13 @@ def test_windows_terminate_process_force_kill(self):
9180
mock_process.wait.side_effect = [subprocess.TimeoutExpired(cmd="test", timeout=5), None]
9281
executor.process = mock_process
9382

94-
with patch("platform.system", return_value="Windows"):
95-
executor._windows_terminate_process()
83+
# No need to mock platform.system() since the method doesn't check it anymore
84+
executor._windows_terminate_process()
9685

97-
# Should call terminate, wait (timeout), then kill, then wait again
98-
mock_process.terminate.assert_called_once()
99-
mock_process.kill.assert_called_once()
100-
assert mock_process.wait.call_count == 2
86+
# Should call terminate, wait (timeout), then kill, then wait again
87+
mock_process.terminate.assert_called_once()
88+
mock_process.kill.assert_called_once()
89+
assert mock_process.wait.call_count == 2
10190

10291
def test_stop_method_windows(self):
10392
"""Test stop method on Windows."""

0 commit comments

Comments
 (0)