devservices 1.1.0__tar.gz → 1.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devservices-1.1.0 → devservices-1.1.1}/PKG-INFO +1 -1
- {devservices-1.1.0 → devservices-1.1.1}/README.md +2 -1
- {devservices-1.1.0 → devservices-1.1.1}/devservices/constants.py +8 -1
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/supervisor.py +62 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices.egg-info/PKG-INFO +1 -1
- {devservices-1.1.0 → devservices-1.1.1}/pyproject.toml +1 -1
- {devservices-1.1.0 → devservices-1.1.1}/tests/utils/test_docker_compose.py +3 -3
- devservices-1.1.1/tests/utils/test_supervisor.py +388 -0
- devservices-1.1.0/tests/utils/test_supervisor.py +0 -189
- {devservices-1.1.0 → devservices-1.1.1}/LICENSE.md +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/__init__.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/__init__.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/down.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/list_dependencies.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/list_services.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/logs.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/purge.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/status.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/toggle.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/up.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/commands/update.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/configs/service_config.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/exceptions.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/main.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/__init__.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/check_for_update.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/console.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/dependencies.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/devenv.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/docker.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/docker_compose.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/file_lock.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/git.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/install_binary.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/services.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices/utils/state.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices.egg-info/SOURCES.txt +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices.egg-info/dependency_links.txt +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices.egg-info/entry_points.txt +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices.egg-info/requires.txt +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/devservices.egg-info/top_level.txt +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/setup.cfg +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/testing/__init__.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/testing/utils.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/__init__.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/commands/test_down.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/commands/test_list_dependencies.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/commands/test_list_services.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/commands/test_logs.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/commands/test_purge.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/commands/test_status.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/commands/test_toggle.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/commands/test_up.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/commands/test_update.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/configs/test_service_config.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/conftest.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/utils/test_check_for_update.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/utils/test_dependencies.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/utils/test_docker.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/utils/test_git.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/utils/test_install_binary.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/utils/test_services.py +0 -0
- {devservices-1.1.0 → devservices-1.1.1}/tests/utils/test_state.py +0 -0
|
@@ -22,6 +22,7 @@ NOTE: service-name is an optional parameter. If not provided, devservices will a
|
|
|
22
22
|
- `devservices list-dependencies <service-name>`: List all dependencies for a service and whether they are enabled/disabled.
|
|
23
23
|
- `devservices update` Update devservices to the latest version.
|
|
24
24
|
- `devservices purge`: Purge the local devservices cache.
|
|
25
|
+
- `devservices toggle <service-name>`: Toggle the runtime for a service between containerized and local.
|
|
25
26
|
|
|
26
27
|
## Installation
|
|
27
28
|
|
|
@@ -30,7 +31,7 @@ NOTE: service-name is an optional parameter. If not provided, devservices will a
|
|
|
30
31
|
The recommended way to install devservices is through a virtualenv in the requirements.txt. Once that is installed and a devservices config file is added, you should be able to run `devservices up` to begin local development.
|
|
31
32
|
|
|
32
33
|
```
|
|
33
|
-
devservices==1.1.
|
|
34
|
+
devservices==1.1.1
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
### 2. Add devservices config files
|
|
@@ -39,7 +39,14 @@ DEPENDENCY_GIT_PARTIAL_CLONE_CONFIG_OPTIONS = {
|
|
|
39
39
|
DEVSERVICES_RELEASES_URL = (
|
|
40
40
|
"https://api.github.com/repos/getsentry/devservices/releases/latest"
|
|
41
41
|
)
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
# We mirror this in our GCP bucket since GitHub downloads can be flaky at times.
|
|
44
|
+
# gsutil cp docker-compose-darwin-aarch64 gs://sentry-dev-infra-assets/docker-compose/v2.29.7/docker-compose-darwin-aarch64
|
|
45
|
+
# gsutil cp docker-compose-linux-x86_64 gs://sentry-dev-infra-assets/docker-compose/v2.29.7/docker-compose-linux-x86_64
|
|
46
|
+
DOCKER_COMPOSE_DOWNLOAD_URL = (
|
|
47
|
+
"https://storage.googleapis.com/sentry-dev-infra-assets/docker-compose"
|
|
48
|
+
)
|
|
49
|
+
|
|
43
50
|
DEVSERVICES_DOWNLOAD_URL = "https://github.com/getsentry/devservices/releases/download"
|
|
44
51
|
BINARY_PERMISSIONS = 0o755
|
|
45
52
|
MAX_LOG_LINES = "100"
|
|
@@ -6,12 +6,31 @@ import os
|
|
|
6
6
|
import socket
|
|
7
7
|
import subprocess
|
|
8
8
|
import xmlrpc.client
|
|
9
|
+
from enum import IntEnum
|
|
9
10
|
|
|
10
11
|
from devservices.constants import DEVSERVICES_SUPERVISOR_CONFIG_DIR
|
|
11
12
|
from devservices.exceptions import SupervisorConfigError
|
|
12
13
|
from devservices.exceptions import SupervisorConnectionError
|
|
13
14
|
from devservices.exceptions import SupervisorError
|
|
14
15
|
from devservices.exceptions import SupervisorProcessError
|
|
16
|
+
from devservices.utils.console import Console
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SupervisorProcessState(IntEnum):
|
|
20
|
+
"""
|
|
21
|
+
Supervisor process states.
|
|
22
|
+
|
|
23
|
+
https://supervisord.org/subprocess.html#process-states
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
STOPPED = 0
|
|
27
|
+
STARTING = 10
|
|
28
|
+
RUNNING = 20
|
|
29
|
+
BACKOFF = 30
|
|
30
|
+
STOPPING = 40
|
|
31
|
+
EXITED = 100
|
|
32
|
+
FATAL = 200
|
|
33
|
+
UNKNOWN = 1000
|
|
15
34
|
|
|
16
35
|
|
|
17
36
|
class UnixSocketHTTPConnection(http.client.HTTPConnection):
|
|
@@ -101,6 +120,21 @@ class SupervisorManager:
|
|
|
101
120
|
f"Failed to connect to supervisor XML-RPC server: {e.errmsg}"
|
|
102
121
|
)
|
|
103
122
|
|
|
123
|
+
def _is_program_running(self, program_name: str) -> bool:
|
|
124
|
+
try:
|
|
125
|
+
client = self._get_rpc_client()
|
|
126
|
+
process_info = client.supervisor.getProcessInfo(program_name)
|
|
127
|
+
if not isinstance(process_info, dict):
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
state = process_info.get("state")
|
|
131
|
+
if not isinstance(state, int):
|
|
132
|
+
return False
|
|
133
|
+
return state == SupervisorProcessState.RUNNING
|
|
134
|
+
except xmlrpc.client.Fault:
|
|
135
|
+
# If we can't get the process info, assume it's not running
|
|
136
|
+
return False
|
|
137
|
+
|
|
104
138
|
def start_supervisor_daemon(self) -> None:
|
|
105
139
|
try:
|
|
106
140
|
subprocess.run(["supervisord", "-c", self.config_file_path], check=True)
|
|
@@ -118,6 +152,8 @@ class SupervisorManager:
|
|
|
118
152
|
raise SupervisorError(f"Failed to stop supervisor: {e.faultString}")
|
|
119
153
|
|
|
120
154
|
def start_program(self, program_name: str) -> None:
|
|
155
|
+
if self._is_program_running(program_name):
|
|
156
|
+
return
|
|
121
157
|
try:
|
|
122
158
|
self._get_rpc_client().supervisor.startProcess(program_name)
|
|
123
159
|
except xmlrpc.client.Fault as e:
|
|
@@ -126,9 +162,35 @@ class SupervisorManager:
|
|
|
126
162
|
)
|
|
127
163
|
|
|
128
164
|
def stop_program(self, program_name: str) -> None:
|
|
165
|
+
if not self._is_program_running(program_name):
|
|
166
|
+
return
|
|
129
167
|
try:
|
|
130
168
|
self._get_rpc_client().supervisor.stopProcess(program_name)
|
|
131
169
|
except xmlrpc.client.Fault as e:
|
|
132
170
|
raise SupervisorProcessError(
|
|
133
171
|
f"Failed to stop program {program_name}: {e.faultString}"
|
|
134
172
|
)
|
|
173
|
+
|
|
174
|
+
def tail_program_logs(self, program_name: str) -> None:
|
|
175
|
+
if not self._is_program_running(program_name):
|
|
176
|
+
console = Console()
|
|
177
|
+
console.failure(f"Program {program_name} is not running")
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
# Use supervisorctl tail command
|
|
182
|
+
subprocess.run(
|
|
183
|
+
[
|
|
184
|
+
"supervisorctl",
|
|
185
|
+
"-c",
|
|
186
|
+
self.config_file_path,
|
|
187
|
+
"tail",
|
|
188
|
+
"-f",
|
|
189
|
+
program_name,
|
|
190
|
+
],
|
|
191
|
+
check=True,
|
|
192
|
+
)
|
|
193
|
+
except subprocess.CalledProcessError as e:
|
|
194
|
+
raise SupervisorError(f"Failed to tail logs for {program_name}: {str(e)}")
|
|
195
|
+
except KeyboardInterrupt:
|
|
196
|
+
pass
|
|
@@ -216,7 +216,7 @@ def test_install_docker_compose_macos_arm64(
|
|
|
216
216
|
mock_tempdir.return_value.__enter__.return_value = "tempdir"
|
|
217
217
|
install_docker_compose()
|
|
218
218
|
mock_urlretrieve.assert_called_once_with(
|
|
219
|
-
"https://
|
|
219
|
+
"https://storage.googleapis.com/sentry-dev-infra-assets/docker-compose/v2.29.7/docker-compose-darwin-aarch64",
|
|
220
220
|
"tempdir/docker-compose",
|
|
221
221
|
)
|
|
222
222
|
mock_chmod.assert_called_once_with("tempdir/docker-compose", 0o755)
|
|
@@ -249,7 +249,7 @@ def test_install_docker_compose_linux_x86(
|
|
|
249
249
|
mock_tempdir.return_value.__enter__.return_value = "tempdir"
|
|
250
250
|
install_docker_compose()
|
|
251
251
|
mock_urlretrieve.assert_called_once_with(
|
|
252
|
-
"https://
|
|
252
|
+
"https://storage.googleapis.com/sentry-dev-infra-assets/docker-compose/v2.29.7/docker-compose-linux-x86_64",
|
|
253
253
|
"tempdir/docker-compose",
|
|
254
254
|
)
|
|
255
255
|
mock_chmod.assert_called_once_with("tempdir/docker-compose", 0o755)
|
|
@@ -286,7 +286,7 @@ def test_install_docker_compose_custom_docker_config_dir(
|
|
|
286
286
|
):
|
|
287
287
|
install_docker_compose()
|
|
288
288
|
mock_urlretrieve.assert_called_once_with(
|
|
289
|
-
"https://
|
|
289
|
+
"https://storage.googleapis.com/sentry-dev-infra-assets/docker-compose/v2.29.7/docker-compose-darwin-aarch64",
|
|
290
290
|
"tempdir/docker-compose",
|
|
291
291
|
)
|
|
292
292
|
mock_chmod.assert_called_once_with("tempdir/docker-compose", 0o755)
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import socket
|
|
4
|
+
import subprocess
|
|
5
|
+
import xmlrpc.client
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from unittest import mock
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
12
|
+
from devservices.exceptions import SupervisorConfigError
|
|
13
|
+
from devservices.exceptions import SupervisorConnectionError
|
|
14
|
+
from devservices.exceptions import SupervisorError
|
|
15
|
+
from devservices.exceptions import SupervisorProcessError
|
|
16
|
+
from devservices.utils.supervisor import SupervisorManager
|
|
17
|
+
from devservices.utils.supervisor import SupervisorProcessState
|
|
18
|
+
from devservices.utils.supervisor import UnixSocketHTTPConnection
|
|
19
|
+
from devservices.utils.supervisor import UnixSocketTransport
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@mock.patch("socket.socket")
|
|
23
|
+
def test_unix_socket_http_connection_connect(
|
|
24
|
+
mock_socket: mock.MagicMock, tmp_path: Path
|
|
25
|
+
) -> None:
|
|
26
|
+
socket_path = str(tmp_path / "test.sock")
|
|
27
|
+
mock_sock = mock_socket.return_value
|
|
28
|
+
|
|
29
|
+
conn = UnixSocketHTTPConnection(socket_path)
|
|
30
|
+
conn.connect()
|
|
31
|
+
|
|
32
|
+
mock_socket.assert_called_once_with(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
33
|
+
mock_sock.connect.assert_called_once_with(socket_path)
|
|
34
|
+
assert conn.sock == mock_sock
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@mock.patch("socket.socket")
|
|
38
|
+
def test_unix_socket_transport_make_connection(
|
|
39
|
+
mock_socket: mock.MagicMock, tmp_path: Path
|
|
40
|
+
) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Test that the Unix socket transport correctly attempts to connect to the socket.
|
|
43
|
+
"""
|
|
44
|
+
socket_path = str(tmp_path / "test.sock")
|
|
45
|
+
mock_sock = mock_socket.return_value
|
|
46
|
+
|
|
47
|
+
transport = UnixSocketTransport(socket_path)
|
|
48
|
+
|
|
49
|
+
connection = transport.make_connection("localhost")
|
|
50
|
+
|
|
51
|
+
# Connect the socket - this happens when we make an RPC call
|
|
52
|
+
connection.connect()
|
|
53
|
+
|
|
54
|
+
# Verify socket creation with correct family and type
|
|
55
|
+
mock_socket.assert_called_with(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
56
|
+
# Verify connection to the right path
|
|
57
|
+
mock_sock.connect.assert_called_with(socket_path)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def supervisor_manager(tmp_path: Path) -> SupervisorManager:
|
|
62
|
+
with mock.patch(
|
|
63
|
+
"devservices.utils.supervisor.DEVSERVICES_SUPERVISOR_CONFIG_DIR", tmp_path
|
|
64
|
+
):
|
|
65
|
+
config_file_path = tmp_path / DEVSERVICES_DIR_NAME / "processes.conf"
|
|
66
|
+
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
config_file_path.write_text(
|
|
68
|
+
"""
|
|
69
|
+
[program:test_program]
|
|
70
|
+
command = python test_program.py
|
|
71
|
+
"""
|
|
72
|
+
)
|
|
73
|
+
return SupervisorManager(
|
|
74
|
+
config_file_path=str(config_file_path), service_name="test-service"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_init_with_config_file(supervisor_manager: SupervisorManager) -> None:
|
|
79
|
+
assert supervisor_manager.service_name == "test-service"
|
|
80
|
+
assert "test-service.processes.conf" in supervisor_manager.config_file_path
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_init_with_nonexistent_config() -> None:
|
|
84
|
+
with pytest.raises(SupervisorConfigError):
|
|
85
|
+
SupervisorManager(
|
|
86
|
+
config_file_path="/nonexistent/path.conf", service_name="test-service"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
91
|
+
def test_get_rpc_client_success(
|
|
92
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
93
|
+
) -> None:
|
|
94
|
+
mock_rpc_client.return_value = mock.MagicMock()
|
|
95
|
+
client = supervisor_manager._get_rpc_client()
|
|
96
|
+
assert client is not None
|
|
97
|
+
mock_rpc_client.assert_called_once()
|
|
98
|
+
transport_arg = mock_rpc_client.call_args[1]["transport"]
|
|
99
|
+
assert isinstance(transport_arg, UnixSocketTransport)
|
|
100
|
+
assert transport_arg.socket_path == supervisor_manager.socket_path
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
104
|
+
def test_get_rpc_client_failure(
|
|
105
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
106
|
+
) -> None:
|
|
107
|
+
mock_rpc_client.side_effect = xmlrpc.client.Fault(1, "Error")
|
|
108
|
+
with pytest.raises(SupervisorConnectionError):
|
|
109
|
+
supervisor_manager._get_rpc_client()
|
|
110
|
+
mock_rpc_client.assert_called_once()
|
|
111
|
+
transport_arg = mock_rpc_client.call_args[1]["transport"]
|
|
112
|
+
assert isinstance(transport_arg, UnixSocketTransport)
|
|
113
|
+
assert transport_arg.socket_path == supervisor_manager.socket_path
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
117
|
+
def test_is_program_running_success(
|
|
118
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
119
|
+
) -> None:
|
|
120
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
121
|
+
"state": SupervisorProcessState.RUNNING
|
|
122
|
+
}
|
|
123
|
+
assert supervisor_manager._is_program_running("test_program")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
127
|
+
def test_is_program_running_program_not_running(
|
|
128
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
129
|
+
) -> None:
|
|
130
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
131
|
+
"state": SupervisorProcessState.STOPPED
|
|
132
|
+
}
|
|
133
|
+
assert not supervisor_manager._is_program_running("test_program")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
137
|
+
def test_is_program_running_typing_error(
|
|
138
|
+
mock_rpc_client: mock.MagicMock,
|
|
139
|
+
supervisor_manager: SupervisorManager,
|
|
140
|
+
capsys: pytest.CaptureFixture[str],
|
|
141
|
+
) -> None:
|
|
142
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = 1
|
|
143
|
+
assert not supervisor_manager._is_program_running("test_program")
|
|
144
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.side_effect = {
|
|
145
|
+
"state": [SupervisorProcessState.STOPPED]
|
|
146
|
+
}
|
|
147
|
+
assert not supervisor_manager._is_program_running("test_program")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
151
|
+
def test_is_program_running_failure(
|
|
152
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
153
|
+
) -> None:
|
|
154
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.side_effect = (
|
|
155
|
+
xmlrpc.client.Fault(1, "Error")
|
|
156
|
+
)
|
|
157
|
+
assert not supervisor_manager._is_program_running("test_program")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
161
|
+
def test_start_supervisor_daemon_success(
|
|
162
|
+
mock_subprocess_run: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
163
|
+
) -> None:
|
|
164
|
+
supervisor_manager.start_supervisor_daemon()
|
|
165
|
+
mock_subprocess_run.assert_called_once_with(
|
|
166
|
+
["supervisord", "-c", supervisor_manager.config_file_path], check=True
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
171
|
+
def test_start_supervisor_daemon_subprocess_failure(
|
|
172
|
+
mock_subprocess_run: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
173
|
+
) -> None:
|
|
174
|
+
mock_subprocess_run.side_effect = subprocess.CalledProcessError(1, "supervisord")
|
|
175
|
+
with pytest.raises(SupervisorError):
|
|
176
|
+
supervisor_manager.start_supervisor_daemon()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
180
|
+
def test_start_supervisor_daemon_file_not_found_failure(
|
|
181
|
+
mock_subprocess_run: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
182
|
+
) -> None:
|
|
183
|
+
mock_subprocess_run.side_effect = FileNotFoundError("supervisord")
|
|
184
|
+
with pytest.raises(SupervisorError):
|
|
185
|
+
supervisor_manager.start_supervisor_daemon()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
189
|
+
def test_stop_supervisor_daemon_success(
|
|
190
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
191
|
+
) -> None:
|
|
192
|
+
supervisor_manager.stop_supervisor_daemon()
|
|
193
|
+
supervisor_manager._get_rpc_client().supervisor.shutdown.assert_called_once()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
197
|
+
def test_stop_supervisor_daemon_failure(
|
|
198
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
199
|
+
) -> None:
|
|
200
|
+
mock_rpc_client.return_value.supervisor.shutdown.side_effect = xmlrpc.client.Fault(
|
|
201
|
+
1, "Error"
|
|
202
|
+
)
|
|
203
|
+
with pytest.raises(SupervisorError):
|
|
204
|
+
supervisor_manager.stop_supervisor_daemon()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
208
|
+
def test_start_program_success(
|
|
209
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
210
|
+
) -> None:
|
|
211
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
212
|
+
"state": SupervisorProcessState.STOPPED
|
|
213
|
+
}
|
|
214
|
+
supervisor_manager.start_program("test_program")
|
|
215
|
+
supervisor_manager._get_rpc_client().supervisor.startProcess.assert_called_once_with(
|
|
216
|
+
"test_program"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
221
|
+
def test_start_program_failure(
|
|
222
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
223
|
+
) -> None:
|
|
224
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
225
|
+
"state": SupervisorProcessState.STOPPED
|
|
226
|
+
}
|
|
227
|
+
mock_rpc_client.return_value.supervisor.startProcess.side_effect = (
|
|
228
|
+
xmlrpc.client.Fault(1, "Error")
|
|
229
|
+
)
|
|
230
|
+
with pytest.raises(SupervisorProcessError):
|
|
231
|
+
supervisor_manager.start_program("test_program")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
235
|
+
def test_start_program_already_running(
|
|
236
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
237
|
+
) -> None:
|
|
238
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
239
|
+
"state": SupervisorProcessState.RUNNING
|
|
240
|
+
}
|
|
241
|
+
supervisor_manager.start_program("test_program")
|
|
242
|
+
mock_rpc_client.supervisor.startProcess.assert_not_called()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
246
|
+
def test_stop_program_success(
|
|
247
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
248
|
+
) -> None:
|
|
249
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
250
|
+
"state": SupervisorProcessState.RUNNING
|
|
251
|
+
}
|
|
252
|
+
supervisor_manager.stop_program("test_program")
|
|
253
|
+
supervisor_manager._get_rpc_client().supervisor.stopProcess.assert_called_once_with(
|
|
254
|
+
"test_program"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
259
|
+
def test_stop_program_failure(
|
|
260
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
261
|
+
) -> None:
|
|
262
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
263
|
+
"state": SupervisorProcessState.RUNNING
|
|
264
|
+
}
|
|
265
|
+
mock_rpc_client.return_value.supervisor.stopProcess.side_effect = (
|
|
266
|
+
xmlrpc.client.Fault(1, "Error")
|
|
267
|
+
)
|
|
268
|
+
with pytest.raises(SupervisorProcessError):
|
|
269
|
+
supervisor_manager.stop_program("test_program")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
273
|
+
def test_stop_program_not_running(
|
|
274
|
+
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
275
|
+
) -> None:
|
|
276
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
277
|
+
"state": SupervisorProcessState.STOPPED
|
|
278
|
+
}
|
|
279
|
+
supervisor_manager.stop_program("test_program")
|
|
280
|
+
mock_rpc_client.supervisor.stopProcess.assert_not_called()
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def test_extend_config_file(
|
|
284
|
+
supervisor_manager: SupervisorManager, tmp_path: Path
|
|
285
|
+
) -> None:
|
|
286
|
+
assert supervisor_manager.config_file_path == str(
|
|
287
|
+
tmp_path / "test-service.processes.conf"
|
|
288
|
+
)
|
|
289
|
+
with open(supervisor_manager.config_file_path, "r") as f:
|
|
290
|
+
assert (
|
|
291
|
+
f.read()
|
|
292
|
+
== f"""[program:test_program]
|
|
293
|
+
command = python test_program.py
|
|
294
|
+
|
|
295
|
+
[unix_http_server]
|
|
296
|
+
file = {tmp_path}/test-service.sock
|
|
297
|
+
|
|
298
|
+
[supervisord]
|
|
299
|
+
pidfile = {tmp_path}/test-service.pid
|
|
300
|
+
|
|
301
|
+
[supervisorctl]
|
|
302
|
+
serverurl = unix://{tmp_path}/test-service.sock
|
|
303
|
+
|
|
304
|
+
[rpcinterface:supervisor]
|
|
305
|
+
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
|
306
|
+
|
|
307
|
+
"""
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
312
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
313
|
+
def tail_program_logs_success(
|
|
314
|
+
mock_rpc_client: mock.MagicMock,
|
|
315
|
+
mock_subprocess_run: mock.MagicMock,
|
|
316
|
+
supervisor_manager: SupervisorManager,
|
|
317
|
+
) -> None:
|
|
318
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
319
|
+
"state": SupervisorProcessState.RUNNING
|
|
320
|
+
}
|
|
321
|
+
supervisor_manager.tail_program_logs("test_program")
|
|
322
|
+
mock_subprocess_run.assert_called_once_with(
|
|
323
|
+
[
|
|
324
|
+
"supervisorctl",
|
|
325
|
+
"-c",
|
|
326
|
+
supervisor_manager.config_file_path,
|
|
327
|
+
"fg",
|
|
328
|
+
"test_program",
|
|
329
|
+
],
|
|
330
|
+
check=True,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
335
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
336
|
+
def test_tail_program_logs_not_running(
|
|
337
|
+
mock_rpc_client: mock.MagicMock,
|
|
338
|
+
mock_subprocess_run: mock.MagicMock,
|
|
339
|
+
supervisor_manager: SupervisorManager,
|
|
340
|
+
capsys: pytest.CaptureFixture[str],
|
|
341
|
+
) -> None:
|
|
342
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
343
|
+
"state": SupervisorProcessState.STOPPED
|
|
344
|
+
}
|
|
345
|
+
supervisor_manager.tail_program_logs("test_program")
|
|
346
|
+
captured = capsys.readouterr()
|
|
347
|
+
assert "Program test_program is not running" in captured.out
|
|
348
|
+
mock_subprocess_run.assert_not_called()
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
352
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
353
|
+
def test_tail_program_logs_failure(
|
|
354
|
+
mock_rpc_client: mock.MagicMock,
|
|
355
|
+
mock_subprocess_run: mock.MagicMock,
|
|
356
|
+
supervisor_manager: SupervisorManager,
|
|
357
|
+
) -> None:
|
|
358
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
359
|
+
"state": SupervisorProcessState.RUNNING
|
|
360
|
+
}
|
|
361
|
+
mock_subprocess_run.side_effect = subprocess.CalledProcessError(1, "supervisorctl")
|
|
362
|
+
with pytest.raises(SupervisorError, match="Failed to tail logs for test_program"):
|
|
363
|
+
supervisor_manager.tail_program_logs("test_program")
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
367
|
+
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
368
|
+
def test_tail_program_logs_keyboard_interrupt(
|
|
369
|
+
mock_rpc_client: mock.MagicMock,
|
|
370
|
+
mock_subprocess_run: mock.MagicMock,
|
|
371
|
+
supervisor_manager: SupervisorManager,
|
|
372
|
+
) -> None:
|
|
373
|
+
mock_rpc_client.return_value.supervisor.getProcessInfo.return_value = {
|
|
374
|
+
"state": SupervisorProcessState.RUNNING
|
|
375
|
+
}
|
|
376
|
+
mock_subprocess_run.side_effect = KeyboardInterrupt()
|
|
377
|
+
supervisor_manager.tail_program_logs("test_program")
|
|
378
|
+
mock_subprocess_run.assert_called_once_with(
|
|
379
|
+
[
|
|
380
|
+
"supervisorctl",
|
|
381
|
+
"-c",
|
|
382
|
+
supervisor_manager.config_file_path,
|
|
383
|
+
"tail",
|
|
384
|
+
"-f",
|
|
385
|
+
"test_program",
|
|
386
|
+
],
|
|
387
|
+
check=True,
|
|
388
|
+
)
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import subprocess
|
|
4
|
-
import xmlrpc.client
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from unittest import mock
|
|
7
|
-
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
from devservices.constants import DEVSERVICES_DIR_NAME
|
|
11
|
-
from devservices.exceptions import SupervisorConfigError
|
|
12
|
-
from devservices.exceptions import SupervisorConnectionError
|
|
13
|
-
from devservices.exceptions import SupervisorError
|
|
14
|
-
from devservices.exceptions import SupervisorProcessError
|
|
15
|
-
from devservices.utils.supervisor import SupervisorManager
|
|
16
|
-
from devservices.utils.supervisor import UnixSocketTransport
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@pytest.fixture
|
|
20
|
-
def supervisor_manager(tmp_path: Path) -> SupervisorManager:
|
|
21
|
-
with mock.patch(
|
|
22
|
-
"devservices.utils.supervisor.DEVSERVICES_SUPERVISOR_CONFIG_DIR", tmp_path
|
|
23
|
-
):
|
|
24
|
-
config_file_path = tmp_path / DEVSERVICES_DIR_NAME / "processes.conf"
|
|
25
|
-
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
-
config_file_path.write_text(
|
|
27
|
-
"""
|
|
28
|
-
[program:test_program]
|
|
29
|
-
command = python test_program.py
|
|
30
|
-
"""
|
|
31
|
-
)
|
|
32
|
-
return SupervisorManager(
|
|
33
|
-
config_file_path=str(config_file_path), service_name="test-service"
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def test_init_with_config_file(supervisor_manager: SupervisorManager) -> None:
|
|
38
|
-
assert supervisor_manager.service_name == "test-service"
|
|
39
|
-
assert "test-service.processes.conf" in supervisor_manager.config_file_path
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def test_init_with_nonexistent_config() -> None:
|
|
43
|
-
with pytest.raises(SupervisorConfigError):
|
|
44
|
-
SupervisorManager(
|
|
45
|
-
config_file_path="/nonexistent/path.conf", service_name="test-service"
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
50
|
-
def test_get_rpc_client_success(
|
|
51
|
-
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
52
|
-
) -> None:
|
|
53
|
-
mock_rpc_client.return_value = mock.MagicMock()
|
|
54
|
-
client = supervisor_manager._get_rpc_client()
|
|
55
|
-
assert client is not None
|
|
56
|
-
mock_rpc_client.assert_called_once()
|
|
57
|
-
transport_arg = mock_rpc_client.call_args[1]["transport"]
|
|
58
|
-
assert isinstance(transport_arg, UnixSocketTransport)
|
|
59
|
-
assert transport_arg.socket_path == supervisor_manager.socket_path
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
63
|
-
def test_get_rpc_client_failure(
|
|
64
|
-
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
65
|
-
) -> None:
|
|
66
|
-
mock_rpc_client.side_effect = xmlrpc.client.Fault(1, "Error")
|
|
67
|
-
with pytest.raises(SupervisorConnectionError):
|
|
68
|
-
supervisor_manager._get_rpc_client()
|
|
69
|
-
mock_rpc_client.assert_called_once()
|
|
70
|
-
transport_arg = mock_rpc_client.call_args[1]["transport"]
|
|
71
|
-
assert isinstance(transport_arg, UnixSocketTransport)
|
|
72
|
-
assert transport_arg.socket_path == supervisor_manager.socket_path
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
76
|
-
def test_start_supervisor_daemon_success(
|
|
77
|
-
mock_subprocess_run: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
78
|
-
) -> None:
|
|
79
|
-
supervisor_manager.start_supervisor_daemon()
|
|
80
|
-
mock_subprocess_run.assert_called_once_with(
|
|
81
|
-
["supervisord", "-c", supervisor_manager.config_file_path], check=True
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
86
|
-
def test_start_supervisor_daemon_subprocess_failure(
|
|
87
|
-
mock_subprocess_run: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
88
|
-
) -> None:
|
|
89
|
-
mock_subprocess_run.side_effect = subprocess.CalledProcessError(1, "supervisord")
|
|
90
|
-
with pytest.raises(SupervisorError):
|
|
91
|
-
supervisor_manager.start_supervisor_daemon()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
@mock.patch("devservices.utils.supervisor.subprocess.run")
|
|
95
|
-
def test_start_supervisor_daemon_file_not_found_failure(
|
|
96
|
-
mock_subprocess_run: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
97
|
-
) -> None:
|
|
98
|
-
mock_subprocess_run.side_effect = FileNotFoundError("supervisord")
|
|
99
|
-
with pytest.raises(SupervisorError):
|
|
100
|
-
supervisor_manager.start_supervisor_daemon()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
104
|
-
def test_stop_supervisor_daemon_success(
|
|
105
|
-
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
106
|
-
) -> None:
|
|
107
|
-
supervisor_manager.stop_supervisor_daemon()
|
|
108
|
-
supervisor_manager._get_rpc_client().supervisor.shutdown.assert_called_once()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
112
|
-
def test_stop_supervisor_daemon_failure(
|
|
113
|
-
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
114
|
-
) -> None:
|
|
115
|
-
mock_rpc_client.return_value.supervisor.shutdown.side_effect = xmlrpc.client.Fault(
|
|
116
|
-
1, "Error"
|
|
117
|
-
)
|
|
118
|
-
with pytest.raises(SupervisorError):
|
|
119
|
-
supervisor_manager.stop_supervisor_daemon()
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
123
|
-
def test_start_program_success(
|
|
124
|
-
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
125
|
-
) -> None:
|
|
126
|
-
supervisor_manager.start_program("test_program")
|
|
127
|
-
supervisor_manager._get_rpc_client().supervisor.startProcess.assert_called_once_with(
|
|
128
|
-
"test_program"
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
133
|
-
def test_start_program_failure(
|
|
134
|
-
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
135
|
-
) -> None:
|
|
136
|
-
mock_rpc_client.return_value.supervisor.startProcess.side_effect = (
|
|
137
|
-
xmlrpc.client.Fault(1, "Error")
|
|
138
|
-
)
|
|
139
|
-
with pytest.raises(SupervisorProcessError):
|
|
140
|
-
supervisor_manager.start_program("test_program")
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
144
|
-
def test_stop_program_success(
|
|
145
|
-
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
146
|
-
) -> None:
|
|
147
|
-
supervisor_manager.stop_program("test_program")
|
|
148
|
-
supervisor_manager._get_rpc_client().supervisor.stopProcess.assert_called_once_with(
|
|
149
|
-
"test_program"
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@mock.patch("devservices.utils.supervisor.xmlrpc.client.ServerProxy")
|
|
154
|
-
def test_stop_program_failure(
|
|
155
|
-
mock_rpc_client: mock.MagicMock, supervisor_manager: SupervisorManager
|
|
156
|
-
) -> None:
|
|
157
|
-
mock_rpc_client.return_value.supervisor.stopProcess.side_effect = (
|
|
158
|
-
xmlrpc.client.Fault(1, "Error")
|
|
159
|
-
)
|
|
160
|
-
with pytest.raises(SupervisorProcessError):
|
|
161
|
-
supervisor_manager.stop_program("test_program")
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def test_extend_config_file(
|
|
165
|
-
supervisor_manager: SupervisorManager, tmp_path: Path
|
|
166
|
-
) -> None:
|
|
167
|
-
assert supervisor_manager.config_file_path == str(
|
|
168
|
-
tmp_path / "test-service.processes.conf"
|
|
169
|
-
)
|
|
170
|
-
with open(supervisor_manager.config_file_path, "r") as f:
|
|
171
|
-
assert (
|
|
172
|
-
f.read()
|
|
173
|
-
== f"""[program:test_program]
|
|
174
|
-
command = python test_program.py
|
|
175
|
-
|
|
176
|
-
[unix_http_server]
|
|
177
|
-
file = {tmp_path}/test-service.sock
|
|
178
|
-
|
|
179
|
-
[supervisord]
|
|
180
|
-
pidfile = {tmp_path}/test-service.pid
|
|
181
|
-
|
|
182
|
-
[supervisorctl]
|
|
183
|
-
serverurl = unix://{tmp_path}/test-service.sock
|
|
184
|
-
|
|
185
|
-
[rpcinterface:supervisor]
|
|
186
|
-
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
|
187
|
-
|
|
188
|
-
"""
|
|
189
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|