11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
1 |
# Copyright 2010 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
3 |
|
11869.11.2
by John Arbash Meinel
stop_service() now waits for the service to actually exit, and gets increasingly demanding about it. |
4 |
import errno |
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
5 |
import os |
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
6 |
import shutil |
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
7 |
import signal |
8 |
import socket |
|
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
9 |
import subprocess |
11149.12.25
by John Arbash Meinel
Trying to spawn multiple sessions seems to show some failures. |
10 |
import tempfile |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
11 |
import threading |
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
12 |
import time |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
13 |
|
11149.12.25
by John Arbash Meinel
Trying to spawn multiple sessions seems to show some failures. |
14 |
from testtools import content |
15 |
||
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
16 |
from bzrlib import ( |
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
17 |
errors, |
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
18 |
osutils, |
19 |
tests, |
|
20 |
trace, |
|
21 |
)
|
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
22 |
from bzrlib.plugins import lpserve |
23 |
||
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
24 |
from canonical.config import config |
25 |
from lp.codehosting import get_bzr_path, get_BZR_PLUGIN_PATH_for_subprocess |
|
26 |
||
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
27 |
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
28 |
class TestingLPForkingServiceInAThread(lpserve.LPForkingService): |
11149.12.83
by John Arbash Meinel
Review feedback from Michael Hudson. |
29 |
"""A test-double to run a "forking service" in a thread.
|
30 |
||
31 |
Note that we don't allow actually forking, but it does allow us to interact
|
|
32 |
with the service for other operations.
|
|
33 |
"""
|
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
34 |
|
35 |
# For testing, we set the timeouts much lower, because we want the tests to
|
|
36 |
# run quickly
|
|
37 |
WAIT_FOR_CHILDREN_TIMEOUT = 0.5 |
|
38 |
SOCKET_TIMEOUT = 0.01 |
|
39 |
SLEEP_FOR_CHILDREN_TIMEOUT = 0.01 |
|
11149.12.62
by John Arbash Meinel
test that incomplete messages timeout quickly, rather than hanging the server. |
40 |
WAIT_FOR_REQUEST_TIMEOUT = 0.1 |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
41 |
|
11149.12.83
by John Arbash Meinel
Review feedback from Michael Hudson. |
42 |
# We're running in a thread as part of the test suite, blow up if we try to
|
43 |
# fork
|
|
11149.12.18
by John Arbash Meinel
A command string => argv parser |
44 |
_fork_function = None |
45 |
||
11149.12.78
by John Arbash Meinel
Change LPForkingService to use a unix domain socket. |
46 |
def __init__(self, path, perms=None): |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
47 |
self.service_started = threading.Event() |
48 |
self.service_stopped = threading.Event() |
|
49 |
self.this_thread = None |
|
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
50 |
self.fork_log = [] |
11149.12.78
by John Arbash Meinel
Change LPForkingService to use a unix domain socket. |
51 |
super(TestingLPForkingServiceInAThread, self).__init__( |
52 |
path=path, perms=None) |
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
53 |
|
11149.12.66
by John Arbash Meinel
SIGTERM is now handled gracefully as a normal shutdown, rather than crashing. |
54 |
def _register_signals(self): |
11149.12.48
by John Arbash Meinel
Create a SIGCHLD handler. |
55 |
pass # Don't register it for the test suite |
56 |
||
11149.12.66
by John Arbash Meinel
SIGTERM is now handled gracefully as a normal shutdown, rather than crashing. |
57 |
def _unregister_signals(self): |
58 |
pass # We don't fork, and didn't register, so don't unregister |
|
11149.12.48
by John Arbash Meinel
Create a SIGCHLD handler. |
59 |
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
60 |
def _create_master_socket(self): |
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
61 |
super(TestingLPForkingServiceInAThread, self)._create_master_socket() |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
62 |
self.service_started.set() |
63 |
||
64 |
def main_loop(self): |
|
65 |
self.service_stopped.clear() |
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
66 |
super(TestingLPForkingServiceInAThread, self).main_loop() |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
67 |
self.service_stopped.set() |
68 |
||
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
69 |
def fork_one_request(self, conn, client_addr, command, env): |
11149.12.14
by John Arbash Meinel
try to move the test subprocess code into a common class. |
70 |
# We intentionally don't allow the test suite to request a fork, as
|
71 |
# threads + forks and everything else don't exactly play well together
|
|
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
72 |
self.fork_log.append((command, env)) |
73 |
conn.sendall('ok\nfake forking\n') |
|
74 |
conn.close() |
|
11149.12.14
by John Arbash Meinel
try to move the test subprocess code into a common class. |
75 |
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
76 |
@staticmethod
|
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
77 |
def start_service(test): |
11149.12.78
by John Arbash Meinel
Change LPForkingService to use a unix domain socket. |
78 |
"""Start a new LPForkingService in a thread at a random path.
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
79 |
|
80 |
This will block until the service has created its socket, and is ready
|
|
81 |
to communicate.
|
|
82 |
||
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
83 |
:return: A new TestingLPForkingServiceInAThread instance
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
84 |
"""
|
11149.12.78
by John Arbash Meinel
Change LPForkingService to use a unix domain socket. |
85 |
fd, path = tempfile.mkstemp(prefix='tmp-lp-forking-service-', |
86 |
suffix='.sock') |
|
87 |
# We don't want a temp file, we want a temp socket
|
|
88 |
os.close(fd) |
|
89 |
os.remove(path) |
|
90 |
new_service = TestingLPForkingServiceInAThread(path=path) |
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
91 |
thread = threading.Thread(target=new_service.main_loop, |
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
92 |
name='TestingLPForkingServiceInAThread') |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
93 |
new_service.this_thread = thread |
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
94 |
# should we be doing thread.setDaemon(True) ?
|
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
95 |
thread.start() |
96 |
new_service.service_started.wait(10.0) |
|
97 |
if not new_service.service_started.isSet(): |
|
98 |
raise RuntimeError( |
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
99 |
'Failed to start the TestingLPForkingServiceInAThread') |
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
100 |
test.addCleanup(new_service.stop_service) |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
101 |
# what about returning new_service._sockname ?
|
102 |
return new_service |
|
103 |
||
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
104 |
def stop_service(self): |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
105 |
"""Stop the test-server thread. This can be called multiple times."""
|
106 |
if self.this_thread is None: |
|
107 |
# We already stopped the process
|
|
108 |
return
|
|
109 |
self._should_terminate.set() |
|
110 |
self.service_stopped.wait(10.0) |
|
111 |
if not self.service_stopped.isSet(): |
|
112 |
raise RuntimeError( |
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
113 |
'Failed to stop the TestingLPForkingServiceInAThread') |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
114 |
self.this_thread.join() |
115 |
# Break any refcycles
|
|
116 |
self.this_thread = None |
|
117 |
||
118 |
||
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
119 |
class TestTestingLPForkingServiceInAThread(tests.TestCaseWithTransport): |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
120 |
|
121 |
def test_start_and_stop_service(self): |
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
122 |
service = TestingLPForkingServiceInAThread.start_service(self) |
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
123 |
service.stop_service() |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
124 |
|
125 |
def test_multiple_stops(self): |
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
126 |
service = TestingLPForkingServiceInAThread.start_service(self) |
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
127 |
service.stop_service() |
11149.12.83
by John Arbash Meinel
Review feedback from Michael Hudson. |
128 |
# calling stop_service repeatedly is a no-op (and not an error)
|
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
129 |
service.stop_service() |
11149.12.10
by John Arbash Meinel
Start building the testing infrastructure. |
130 |
|
131 |
def test_autostop(self): |
|
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
132 |
# We shouldn't leak a thread here, as it should be part of the test
|
133 |
# case teardown.
|
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
134 |
service = TestingLPForkingServiceInAThread.start_service(self) |
135 |
||
136 |
||
137 |
class TestCaseWithLPForkingService(tests.TestCaseWithTransport): |
|
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
138 |
|
139 |
def setUp(self): |
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
140 |
super(TestCaseWithLPForkingService, self).setUp() |
141 |
self.service = TestingLPForkingServiceInAThread.start_service(self) |
|
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
142 |
|
11149.12.61
by John Arbash Meinel
Handle messages that take a while to get sent. |
143 |
def send_message_to_service(self, message, one_byte_at_a_time=False): |
11149.12.78
by John Arbash Meinel
Change LPForkingService to use a unix domain socket. |
144 |
client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
145 |
client_sock.connect(self.service.master_socket_path) |
|
11149.12.66
by John Arbash Meinel
SIGTERM is now handled gracefully as a normal shutdown, rather than crashing. |
146 |
if one_byte_at_a_time: |
147 |
for byte in message: |
|
148 |
client_sock.send(byte) |
|
149 |
else: |
|
150 |
client_sock.sendall(message) |
|
151 |
response = client_sock.recv(1024) |
|
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
152 |
return response |
153 |
||
154 |
||
11149.12.18
by John Arbash Meinel
A command string => argv parser |
155 |
class TestLPForkingServiceCommandToArgv(tests.TestCase): |
156 |
||
157 |
def assertAsArgv(self, argv, command_str): |
|
158 |
self.assertEqual(argv, |
|
159 |
lpserve.LPForkingService.command_to_argv(command_str)) |
|
160 |
||
161 |
def test_simple(self): |
|
162 |
self.assertAsArgv([u'foo'], 'foo') |
|
163 |
self.assertAsArgv([u'foo', u'bar'], 'foo bar') |
|
164 |
||
165 |
def test_quoted(self): |
|
166 |
self.assertAsArgv([u'foo'], 'foo') |
|
167 |
self.assertAsArgv([u'foo bar'], '"foo bar"') |
|
168 |
||
169 |
def test_unicode(self): |
|
170 |
self.assertAsArgv([u'command', u'\xe5'], 'command \xc3\xa5') |
|
171 |
||
172 |
||
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
173 |
class TestLPForkingServiceParseEnv(tests.TestCase): |
174 |
||
175 |
def assertEnv(self, env, env_str): |
|
176 |
self.assertEqual(env, lpserve.LPForkingService.parse_env(env_str)) |
|
177 |
||
11149.12.60
by John Arbash Meinel
Change it so that we use a different main command. |
178 |
def assertInvalid(self, env_str): |
179 |
self.assertRaises(ValueError, lpserve.LPForkingService.parse_env, |
|
180 |
env_str) |
|
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
181 |
|
182 |
def test_no_entries(self): |
|
11149.12.60
by John Arbash Meinel
Change it so that we use a different main command. |
183 |
self.assertEnv({}, 'end\n') |
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
184 |
|
185 |
def test_one_entries(self): |
|
186 |
self.assertEnv({'BZR_EMAIL': 'joe@foo.com'}, |
|
187 |
'BZR_EMAIL: joe@foo.com\n' |
|
188 |
'end\n') |
|
189 |
||
190 |
def test_two_entries(self): |
|
191 |
self.assertEnv({'BZR_EMAIL': 'joe@foo.com', 'BAR': 'foo'}, |
|
192 |
'BZR_EMAIL: joe@foo.com\n' |
|
193 |
'BAR: foo\n' |
|
194 |
'end\n') |
|
195 |
||
11149.12.60
by John Arbash Meinel
Change it so that we use a different main command. |
196 |
def test_invalid_empty(self): |
197 |
self.assertInvalid('') |
|
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
198 |
|
199 |
def test_invalid_end(self): |
|
11149.12.60
by John Arbash Meinel
Change it so that we use a different main command. |
200 |
self.assertInvalid("BZR_EMAIL: joe@foo.com\n") |
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
201 |
|
202 |
def test_invalid_entry(self): |
|
11149.12.60
by John Arbash Meinel
Change it so that we use a different main command. |
203 |
self.assertInvalid("BZR_EMAIL joe@foo.com\nend\n") |
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
204 |
|
205 |
||
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
206 |
class TestLPForkingService(TestCaseWithLPForkingService): |
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
207 |
|
208 |
def test_send_quit_message(self): |
|
209 |
response = self.send_message_to_service('quit\n') |
|
11149.12.31
by John Arbash Meinel
Change the returned information to always start with 'ok' or 'FAILURE'. |
210 |
self.assertEqual('ok\nquit command requested... exiting\n', response) |
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
211 |
self.service.service_stopped.wait(10.0) |
212 |
self.assertTrue(self.service.service_stopped.isSet()) |
|
213 |
||
214 |
def test_send_invalid_message_fails(self): |
|
215 |
response = self.send_message_to_service('unknown\n') |
|
216 |
self.assertStartsWith(response, 'FAILURE') |
|
217 |
||
11149.12.13
by John Arbash Meinel
Add a test for the 'hello' heartbeat request. |
218 |
def test_send_hello_heartbeat(self): |
219 |
response = self.send_message_to_service('hello\n') |
|
11149.12.31
by John Arbash Meinel
Change the returned information to always start with 'ok' or 'FAILURE'. |
220 |
self.assertEqual('ok\nyep, still alive\n', response) |
11149.12.13
by John Arbash Meinel
Add a test for the 'hello' heartbeat request. |
221 |
|
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
222 |
def test_send_simple_fork(self): |
223 |
response = self.send_message_to_service('fork rocks\n') |
|
224 |
self.assertEqual('ok\nfake forking\n', response) |
|
225 |
self.assertEqual([(['rocks'], {})], self.service.fork_log) |
|
226 |
||
11149.12.61
by John Arbash Meinel
Handle messages that take a while to get sent. |
227 |
def test_send_fork_env_with_empty_env(self): |
11149.12.60
by John Arbash Meinel
Change it so that we use a different main command. |
228 |
response = self.send_message_to_service( |
229 |
'fork-env rocks\n' |
|
230 |
'end\n') |
|
231 |
self.assertEqual('ok\nfake forking\n', response) |
|
232 |
self.assertEqual([(['rocks'], {})], self.service.fork_log) |
|
233 |
||
11149.12.61
by John Arbash Meinel
Handle messages that take a while to get sent. |
234 |
def test_send_fork_env_with_env(self): |
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
235 |
response = self.send_message_to_service( |
11149.12.60
by John Arbash Meinel
Change it so that we use a different main command. |
236 |
'fork-env rocks\n' |
11149.12.59
by John Arbash Meinel
Enable passing env vars to the 'fork' request. |
237 |
'BZR_EMAIL: joe@example.com\n' |
238 |
'end\n') |
|
239 |
self.assertEqual('ok\nfake forking\n', response) |
|
240 |
self.assertEqual([(['rocks'], {'BZR_EMAIL': 'joe@example.com'})], |
|
241 |
self.service.fork_log) |
|
242 |
||
11149.12.61
by John Arbash Meinel
Handle messages that take a while to get sent. |
243 |
def test_send_fork_env_slowly(self): |
244 |
response = self.send_message_to_service( |
|
245 |
'fork-env rocks\n' |
|
246 |
'BZR_EMAIL: joe@example.com\n' |
|
247 |
'end\n', one_byte_at_a_time=True) |
|
248 |
self.assertEqual('ok\nfake forking\n', response) |
|
249 |
self.assertEqual([(['rocks'], {'BZR_EMAIL': 'joe@example.com'})], |
|
250 |
self.service.fork_log) |
|
251 |
||
11149.12.62
by John Arbash Meinel
test that incomplete messages timeout quickly, rather than hanging the server. |
252 |
def test_send_incomplete_fork_env_timeout(self): |
253 |
# We should get a failure message if we can't quickly read the whole
|
|
254 |
# content
|
|
255 |
response = self.send_message_to_service( |
|
256 |
'fork-env rocks\n' |
|
257 |
'BZR_EMAIL: joe@example.com\n', |
|
258 |
one_byte_at_a_time=True) |
|
259 |
# Note that we *don't* send a final 'end\n'
|
|
260 |
self.assertStartsWith(response, 'FAILURE\n') |
|
261 |
||
262 |
def test_send_incomplete_request_timeout(self): |
|
263 |
# Requests end with '\n', send one without it
|
|
264 |
response = self.send_message_to_service('hello', |
|
265 |
one_byte_at_a_time=True) |
|
266 |
self.assertStartsWith(response, 'FAILURE\n') |
|
267 |
||
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
268 |
def test_child_connection_timeout(self): |
269 |
self.assertEqual(self.service.CHILD_CONNECT_TIMEOUT, |
|
270 |
self.service._child_connect_timeout) |
|
12344.5.4
by John Arbash Meinel
Change the code to use signal.alarm() instead of another python thread. |
271 |
response = self.send_message_to_service('child_connect_timeout 1\n') |
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
272 |
self.assertEqual('ok\n', response) |
12344.5.4
by John Arbash Meinel
Change the code to use signal.alarm() instead of another python thread. |
273 |
self.assertEqual(1, self.service._child_connect_timeout) |
274 |
||
275 |
def test_child_connection_timeout_bad_float(self): |
|
276 |
self.assertEqual(self.service.CHILD_CONNECT_TIMEOUT, |
|
277 |
self.service._child_connect_timeout) |
|
278 |
response = self.send_message_to_service('child_connect_timeout 1.2\n') |
|
279 |
self.assertStartsWith(response, 'FAILURE:') |
|
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
280 |
|
281 |
def test_child_connection_timeout_no_val(self): |
|
282 |
response = self.send_message_to_service('child_connect_timeout \n') |
|
283 |
self.assertStartsWith(response, 'FAILURE:') |
|
284 |
||
285 |
def test_child_connection_timeout_bad_val(self): |
|
286 |
response = self.send_message_to_service('child_connect_timeout b\n') |
|
287 |
self.assertStartsWith(response, 'FAILURE:') |
|
288 |
||
289 |
def test__open_handles_will_timeout(self): |
|
12344.5.4
by John Arbash Meinel
Change the code to use signal.alarm() instead of another python thread. |
290 |
# signal.alarm() has only 1-second granularity. :(
|
291 |
self.service._child_connect_timeout = 1 |
|
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
292 |
tempdir = tempfile.mkdtemp(prefix='testlpserve-') |
293 |
self.addCleanup(shutil.rmtree, tempdir, ignore_errors=True) |
|
294 |
os.mkfifo(os.path.join(tempdir, 'stdin')) |
|
295 |
os.mkfifo(os.path.join(tempdir, 'stdout')) |
|
296 |
os.mkfifo(os.path.join(tempdir, 'stderr')) |
|
12344.5.5
by John Arbash Meinel
Review changes from Gavin Panella. |
297 |
# catch SIGALRM so we don't stop the test suite. It will still
|
298 |
# interupt the blocking open() calls.
|
|
12344.5.4
by John Arbash Meinel
Change the code to use signal.alarm() instead of another python thread. |
299 |
def noop_on_alarm(signal, frame): |
300 |
return
|
|
301 |
signal.signal(signal.SIGALRM, noop_on_alarm) |
|
302 |
self.addCleanup(signal.signal, signal.SIGALRM, signal.SIG_DFL) |
|
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
303 |
e = self.assertRaises(errors.BzrError, |
304 |
self.service._open_handles, tempdir) |
|
305 |
self.assertContainsRe(str(e), r'After \d+.\d+s we failed to open.*') |
|
306 |
||
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
307 |
|
12344.5.3
by John Arbash Meinel
Switch to using the new Timer based interrupt. |
308 |
|
11149.12.14
by John Arbash Meinel
try to move the test subprocess code into a common class. |
309 |
class TestCaseWithSubprocess(tests.TestCaseWithTransport): |
310 |
"""Override the bzr start_bzr_subprocess command.
|
|
311 |
||
312 |
The launchpad infrastructure requires a fair amount of configuration to get
|
|
11149.12.83
by John Arbash Meinel
Review feedback from Michael Hudson. |
313 |
paths, etc correct. This provides a "start_bzr_subprocess" command that
|
314 |
has all of those paths appropriately set, but otherwise functions the same
|
|
315 |
as the bzrlib.tests.TestCase version.
|
|
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
316 |
"""
|
317 |
||
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
318 |
def get_python_path(self): |
319 |
"""Return the path to the Python interpreter."""
|
|
320 |
return '%s/bin/py' % config.root |
|
321 |
||
322 |
def start_bzr_subprocess(self, process_args, env_changes=None, |
|
323 |
working_dir=None): |
|
324 |
"""Start bzr in a subprocess for testing.
|
|
325 |
||
326 |
Copied and modified from `bzrlib.tests.TestCase.start_bzr_subprocess`.
|
|
327 |
This version removes some of the skipping stuff, some of the
|
|
328 |
irrelevant comments (e.g. about win32) and uses Launchpad's own
|
|
329 |
mechanisms for getting the path to 'bzr'.
|
|
330 |
||
331 |
Comments starting with 'LAUNCHPAD' are comments about our
|
|
332 |
modifications.
|
|
333 |
"""
|
|
334 |
if env_changes is None: |
|
335 |
env_changes = {} |
|
336 |
env_changes['BZR_PLUGIN_PATH'] = get_BZR_PLUGIN_PATH_for_subprocess() |
|
337 |
old_env = {} |
|
338 |
||
339 |
def cleanup_environment(): |
|
340 |
for env_var, value in env_changes.iteritems(): |
|
341 |
old_env[env_var] = osutils.set_or_unset_env(env_var, value) |
|
342 |
||
343 |
def restore_environment(): |
|
344 |
for env_var, value in old_env.iteritems(): |
|
345 |
osutils.set_or_unset_env(env_var, value) |
|
346 |
||
347 |
cwd = None |
|
348 |
if working_dir is not None: |
|
349 |
cwd = osutils.getcwd() |
|
350 |
os.chdir(working_dir) |
|
351 |
||
352 |
# LAUNCHPAD: Because of buildout, we need to get a custom Python
|
|
353 |
# binary, not sys.executable.
|
|
354 |
python_path = self.get_python_path() |
|
355 |
# LAUNCHPAD: We can't use self.get_bzr_path(), since it'll find
|
|
356 |
# lib/bzrlib, rather than the path to sourcecode/bzr/bzr.
|
|
357 |
bzr_path = get_bzr_path() |
|
358 |
try: |
|
359 |
cleanup_environment() |
|
360 |
command = [python_path, bzr_path] |
|
361 |
command.extend(process_args) |
|
362 |
process = self._popen( |
|
363 |
command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, |
|
364 |
stderr=subprocess.PIPE) |
|
365 |
finally: |
|
366 |
restore_environment() |
|
367 |
if cwd is not None: |
|
368 |
os.chdir(cwd) |
|
369 |
||
370 |
return process |
|
371 |
||
11149.12.14
by John Arbash Meinel
try to move the test subprocess code into a common class. |
372 |
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
373 |
class TestCaseWithLPForkingServiceSubprocess(TestCaseWithSubprocess): |
11149.12.14
by John Arbash Meinel
try to move the test subprocess code into a common class. |
374 |
"""Tests will get a separate process to communicate to.
|
375 |
||
376 |
The number of these tests should be small, because it is expensive to start
|
|
377 |
and stop the daemon.
|
|
378 |
||
379 |
TODO: This should probably use testresources, or layers somehow...
|
|
380 |
"""
|
|
381 |
||
382 |
def setUp(self): |
|
11149.12.17
by John Arbash Meinel
change the name a bit, prepare for changing the 'fork' request. |
383 |
super(TestCaseWithLPForkingServiceSubprocess, self).setUp() |
11149.12.84
by John Arbash Meinel
Cleanup some 'make lint' warnings. |
384 |
(self.service_process, |
385 |
self.service_path) = self.start_service_subprocess() |
|
11149.12.14
by John Arbash Meinel
try to move the test subprocess code into a common class. |
386 |
self.addCleanup(self.stop_service) |
387 |
||
11149.12.61
by John Arbash Meinel
Handle messages that take a while to get sent. |
388 |
def start_conversation(self, message, one_byte_at_a_time=False): |
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
389 |
"""Start talking to the service, and get the initial response."""
|
11149.12.78
by John Arbash Meinel
Change LPForkingService to use a unix domain socket. |
390 |
client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
391 |
trace.mutter('sending %r to socket %s' % (message, self.service_path)) |
|
392 |
client_sock.connect(self.service_path) |
|
11149.12.66
by John Arbash Meinel
SIGTERM is now handled gracefully as a normal shutdown, rather than crashing. |
393 |
if one_byte_at_a_time: |
394 |
for byte in message: |
|
395 |
client_sock.send(byte) |
|
396 |
else: |
|
397 |
client_sock.sendall(message) |
|
398 |
response = client_sock.recv(1024) |
|
11149.12.19
by John Arbash Meinel
I think I have it hooked up, but the test is failing. |
399 |
trace.mutter('response: %r' % (response,)) |
400 |
if response.startswith("FAILURE"): |
|
401 |
raise RuntimeError('Failed to send message: %r' % (response,)) |
|
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
402 |
return response, client_sock |
403 |
||
11149.12.61
by John Arbash Meinel
Handle messages that take a while to get sent. |
404 |
def send_message_to_service(self, message, one_byte_at_a_time=False): |
405 |
response, client_sock = self.start_conversation(message, |
|
406 |
one_byte_at_a_time=one_byte_at_a_time) |
|
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
407 |
client_sock.close() |
11149.12.14
by John Arbash Meinel
try to move the test subprocess code into a common class. |
408 |
return response |
409 |
||
11149.12.61
by John Arbash Meinel
Handle messages that take a while to get sent. |
410 |
def send_fork_request(self, command, env=None): |
411 |
if env is not None: |
|
412 |
request_lines = ['fork-env %s\n' % (command,)] |
|
413 |
for key, value in env.iteritems(): |
|
414 |
request_lines.append('%s: %s\n' % (key, value)) |
|
415 |
request_lines.append('end\n') |
|
416 |
request = ''.join(request_lines) |
|
417 |
else: |
|
418 |
request = 'fork %s\n' % (command,) |
|
419 |
response, sock = self.start_conversation(request) |
|
11149.12.31
by John Arbash Meinel
Change the returned information to always start with 'ok' or 'FAILURE'. |
420 |
ok, pid, path, tail = response.split('\n') |
421 |
self.assertEqual('ok', ok) |
|
422 |
self.assertEqual('', tail) |
|
423 |
# Don't really care what it is, but should be an integer
|
|
424 |
pid = int(pid) |
|
425 |
path = path.strip() |
|
426 |
self.assertContainsRe(path, '/lp-forking-service-child-') |
|
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
427 |
return path, pid, sock |
11149.12.31
by John Arbash Meinel
Change the returned information to always start with 'ok' or 'FAILURE'. |
428 |
|
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
429 |
def _start_subprocess(self, path, env_changes): |
430 |
proc = self.start_bzr_subprocess( |
|
431 |
['lp-service', '--path', path, '--no-preload', |
|
432 |
'--children-timeout=1'], |
|
433 |
env_changes=env_changes) |
|
434 |
trace.mutter('started lp-service subprocess') |
|
435 |
expected = 'Listening on socket: %s\n' % (path,) |
|
436 |
path_line = proc.stderr.readline() |
|
437 |
trace.mutter(path_line) |
|
438 |
self.assertEqual(expected, path_line) |
|
439 |
return proc |
|
440 |
||
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
441 |
def start_service_subprocess(self): |
442 |
# Make sure this plugin is exposed to the subprocess
|
|
11149.12.78
by John Arbash Meinel
Change LPForkingService to use a unix domain socket. |
443 |
# SLOOWWW (~2 seconds, which is why we are doing the work anyway)
|
11149.12.25
by John Arbash Meinel
Trying to spawn multiple sessions seems to show some failures. |
444 |
fd, tempname = tempfile.mkstemp(prefix='tmp-log-bzr-lp-forking-') |
11149.12.47
by John Arbash Meinel
Remove the 'status' tracking and command. |
445 |
# I'm not 100% sure about when cleanup runs versus addDetail, but I
|
446 |
# think this will work.
|
|
447 |
self.addCleanup(os.remove, tempname) |
|
11149.12.84
by John Arbash Meinel
Cleanup some 'make lint' warnings. |
448 |
|
11149.12.25
by John Arbash Meinel
Trying to spawn multiple sessions seems to show some failures. |
449 |
def read_log(): |
450 |
f = os.fdopen(fd) |
|
451 |
f.seek(0) |
|
452 |
content = f.read() |
|
453 |
f.close() |
|
454 |
return [content] |
|
455 |
self.addDetail('server-log', content.Content( |
|
456 |
content.ContentType('text', 'plain', {"charset": "utf8"}), |
|
457 |
read_log)) |
|
11149.12.78
by John Arbash Meinel
Change LPForkingService to use a unix domain socket. |
458 |
service_fd, path = tempfile.mkstemp(prefix='tmp-lp-service-', |
459 |
suffix='.sock') |
|
460 |
os.close(service_fd) |
|
461 |
os.remove(path) # service wants create it as a socket |
|
11149.12.21
by John Arbash Meinel
Unfortunately 'bzr rocks' test still takes 2s, though lp-serve takes 3.6s. |
462 |
env_changes = {'BZR_PLUGIN_PATH': lpserve.__path__[0], |
11149.12.25
by John Arbash Meinel
Trying to spawn multiple sessions seems to show some failures. |
463 |
'BZR_LOG': tempname} |
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
464 |
proc = self._start_subprocess(path, env_changes) |
11149.12.78
by John Arbash Meinel
Change LPForkingService to use a unix domain socket. |
465 |
return proc, path |
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
466 |
|
467 |
def stop_service(self): |
|
468 |
if self.service_process is None: |
|
469 |
# Already stopped
|
|
470 |
return
|
|
471 |
# First, try to stop the service gracefully, by sending a 'quit'
|
|
472 |
# message
|
|
11149.12.66
by John Arbash Meinel
SIGTERM is now handled gracefully as a normal shutdown, rather than crashing. |
473 |
try: |
474 |
response = self.send_message_to_service('quit\n') |
|
475 |
except socket.error, e: |
|
476 |
# Ignore a failure to connect, the service must be stopping/stopped
|
|
477 |
# already
|
|
478 |
response = None |
|
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
479 |
tend = time.time() + 10.0 |
480 |
while self.service_process.poll() is None: |
|
481 |
if time.time() > tend: |
|
482 |
self.finish_bzr_subprocess(process=self.service_process, |
|
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
483 |
send_signal=signal.SIGINT, retcode=3) |
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
484 |
self.fail('Failed to quit gracefully after 10.0 seconds') |
485 |
time.sleep(0.1) |
|
11149.12.66
by John Arbash Meinel
SIGTERM is now handled gracefully as a normal shutdown, rather than crashing. |
486 |
if response is not None: |
487 |
self.assertEqual('ok\nquit command requested... exiting\n', |
|
488 |
response) |
|
11149.12.11
by John Arbash Meinel
Start trying to get some subprocess style testing for the service as well. |
489 |
|
11149.12.23
by John Arbash Meinel
Create a command that just replays content that it read back to its output. |
490 |
def _get_fork_handles(self, path): |
11149.12.25
by John Arbash Meinel
Trying to spawn multiple sessions seems to show some failures. |
491 |
trace.mutter('getting handles for: %s' % (path,)) |
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
492 |
stdin_path = os.path.join(path, 'stdin') |
493 |
stdout_path = os.path.join(path, 'stdout') |
|
494 |
stderr_path = os.path.join(path, 'stderr') |
|
11149.12.83
by John Arbash Meinel
Review feedback from Michael Hudson. |
495 |
# The ordering must match the ordering of the service or we get a
|
496 |
# deadlock.
|
|
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
497 |
child_stdin = open(stdin_path, 'wb', 0) |
498 |
child_stdout = open(stdout_path, 'rb', 0) |
|
499 |
child_stderr = open(stderr_path, 'rb', 0) |
|
11149.12.23
by John Arbash Meinel
Create a command that just replays content that it read back to its output. |
500 |
return child_stdin, child_stdout, child_stderr |
501 |
||
502 |
def communicate_with_fork(self, path, stdin=None): |
|
503 |
child_stdin, child_stdout, child_stderr = self._get_fork_handles(path) |
|
11149.12.21
by John Arbash Meinel
Unfortunately 'bzr rocks' test still takes 2s, though lp-serve takes 3.6s. |
504 |
if stdin is not None: |
505 |
child_stdin.write(stdin) |
|
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
506 |
child_stdin.close() |
507 |
stdout_content = child_stdout.read() |
|
508 |
stderr_content = child_stderr.read() |
|
11149.12.21
by John Arbash Meinel
Unfortunately 'bzr rocks' test still takes 2s, though lp-serve takes 3.6s. |
509 |
return stdout_content, stderr_content |
510 |
||
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
511 |
def assertReturnCode(self, expected_code, sock): |
512 |
"""Assert that we get the expected return code as a message."""
|
|
513 |
response = sock.recv(1024) |
|
11149.12.41
by John Arbash Meinel
Test suite passing again, this time with the master process |
514 |
self.assertStartsWith(response, 'exited\n') |
515 |
code = int(response.split('\n', 1)[1]) |
|
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
516 |
self.assertEqual(expected_code, code) |
517 |
||
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
518 |
|
519 |
class TestLPServiceInSubprocess(TestCaseWithLPForkingServiceSubprocess): |
|
520 |
||
11149.12.23
by John Arbash Meinel
Create a command that just replays content that it read back to its output. |
521 |
def test_fork_lp_serve_hello(self): |
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
522 |
path, _, sock = self.send_fork_request('lp-serve --inet 2') |
11149.12.21
by John Arbash Meinel
Unfortunately 'bzr rocks' test still takes 2s, though lp-serve takes 3.6s. |
523 |
stdout_content, stderr_content = self.communicate_with_fork(path, |
524 |
'hello\n') |
|
525 |
self.assertEqual('ok\x012\n', stdout_content) |
|
11149.12.12
by John Arbash Meinel
Do lots of dancing to get it to work. |
526 |
self.assertEqual('', stderr_content) |
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
527 |
self.assertReturnCode(0, sock) |
11149.12.23
by John Arbash Meinel
Create a command that just replays content that it read back to its output. |
528 |
|
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
529 |
def DONT_test_fork_lp_serve_multiple_hello(self): |
530 |
# This ensures that the fifos are all set to blocking mode
|
|
531 |
# We can't actually run this test, because by default 'bzr serve
|
|
532 |
# --inet' does not flush after each message. So we end up blocking
|
|
533 |
# forever waiting for the server to finish responding to the first
|
|
534 |
# request.
|
|
535 |
path, _, sock = self.send_fork_request('lp-serve --inet 2') |
|
536 |
child_stdin, child_stdout, child_stderr = self._get_fork_handles(path) |
|
537 |
child_stdin.write('hello\n') |
|
538 |
child_stdin.flush() |
|
539 |
self.assertEqual('ok\x012\n', child_stdout.read()) |
|
540 |
child_stdin.write('hello\n') |
|
541 |
self.assertEqual('ok\x012\n', child_stdout.read()) |
|
542 |
child_stdin.close() |
|
543 |
self.assertEqual('', child_stderr.read()) |
|
544 |
child_stdout.close() |
|
545 |
child_stderr.close() |
|
546 |
||
11149.12.23
by John Arbash Meinel
Create a command that just replays content that it read back to its output. |
547 |
def test_fork_replay(self): |
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
548 |
path, _, sock = self.send_fork_request('launchpad-replay') |
11149.12.23
by John Arbash Meinel
Create a command that just replays content that it read back to its output. |
549 |
stdout_content, stderr_content = self.communicate_with_fork(path, |
550 |
'1 hello\n2 goodbye\n1 maybe\n') |
|
551 |
self.assertEqualDiff('hello\nmaybe\n', stdout_content) |
|
552 |
self.assertEqualDiff('goodbye\n', stderr_content) |
|
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
553 |
self.assertReturnCode(0, sock) |
11149.12.24
by John Arbash Meinel
it only takes 907ms to start and stop the service, we may need to look closer at |
554 |
|
555 |
def test_just_run_service(self): |
|
556 |
# Start and stop are defined in setUp()
|
|
557 |
pass
|
|
11149.12.25
by John Arbash Meinel
Trying to spawn multiple sessions seems to show some failures. |
558 |
|
559 |
def test_fork_multiple_children(self): |
|
560 |
paths = [] |
|
561 |
for idx in range(4): |
|
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
562 |
paths.append(self.send_fork_request('launchpad-replay')) |
11149.12.81
by John Arbash Meinel
Respond to Andrew's feedback. |
563 |
# Do them out of order, as order shouldn't matter.
|
11149.12.28
by John Arbash Meinel
Just make sure we can handle out-of-order communication as well. |
564 |
for idx in [3, 2, 0, 1]: |
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
565 |
p, pid, sock = paths[idx] |
11149.12.25
by John Arbash Meinel
Trying to spawn multiple sessions seems to show some failures. |
566 |
stdout_msg = 'hello %d\n' % (idx,) |
567 |
stderr_msg = 'goodbye %d\n' % (idx+1,) |
|
568 |
stdout, stderr = self.communicate_with_fork(p, |
|
569 |
'1 %s2 %s' % (stdout_msg, stderr_msg)) |
|
570 |
self.assertEqualDiff(stdout_msg, stdout) |
|
571 |
self.assertEqualDiff(stderr_msg, stderr) |
|
11149.12.40
by John Arbash Meinel
Start checking the returncode message. |
572 |
self.assertReturnCode(0, sock) |
11149.12.61
by John Arbash Meinel
Handle messages that take a while to get sent. |
573 |
|
574 |
def test_fork_respects_env_vars(self): |
|
575 |
path, pid, sock = self.send_fork_request('whoami', |
|
576 |
env={'BZR_EMAIL': 'this_test@example.com'}) |
|
577 |
stdout_content, stderr_content = self.communicate_with_fork(path) |
|
578 |
self.assertEqual('', stderr_content) |
|
579 |
self.assertEqual('this_test@example.com\n', stdout_content) |
|
11149.12.66
by John Arbash Meinel
SIGTERM is now handled gracefully as a normal shutdown, rather than crashing. |
580 |
|
11149.12.69
by John Arbash Meinel
Set the default configuration to not use the forking daemon, but the development version to use it. |
581 |
def _check_exits_nicely(self, sig_id): |
11149.12.66
by John Arbash Meinel
SIGTERM is now handled gracefully as a normal shutdown, rather than crashing. |
582 |
path, _, sock = self.send_fork_request('rocks') |
583 |
self.assertEqual(None, self.service_process.poll()) |
|
584 |
# Now when we send SIGTERM, it should wait for the child to exit,
|
|
585 |
# before it tries to exit itself.
|
|
586 |
# In python2.6+ we could use self.service_process.terminate()
|
|
11149.12.69
by John Arbash Meinel
Set the default configuration to not use the forking daemon, but the development version to use it. |
587 |
os.kill(self.service_process.pid, sig_id) |
11149.12.66
by John Arbash Meinel
SIGTERM is now handled gracefully as a normal shutdown, rather than crashing. |
588 |
self.assertEqual(None, self.service_process.poll()) |
589 |
# Now talk to the child, so the service can close
|
|
590 |
stdout_content, stderr_content = self.communicate_with_fork(path) |
|
591 |
self.assertEqual('It sure does!\n', stdout_content) |
|
592 |
self.assertEqual('', stderr_content) |
|
593 |
self.assertReturnCode(0, sock) |
|
594 |
# And the process should exit cleanly
|
|
595 |
self.assertEqual(0, self.service_process.wait()) |
|
11149.12.69
by John Arbash Meinel
Set the default configuration to not use the forking daemon, but the development version to use it. |
596 |
|
597 |
def test_sigterm_exits_nicely(self): |
|
598 |
self._check_exits_nicely(signal.SIGTERM) |
|
599 |
||
600 |
def test_sigint_exits_nicely(self): |
|
601 |
self._check_exits_nicely(signal.SIGINT) |
|
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
602 |
|
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
603 |
def test_child_exits_eventually(self): |
604 |
# We won't ever bind to the socket the child wants, and after some
|
|
605 |
# time, the child should exit cleanly.
|
|
606 |
# First, tell the subprocess that we want children to exit quickly.
|
|
12344.5.4
by John Arbash Meinel
Change the code to use signal.alarm() instead of another python thread. |
607 |
# *sigh* signal.alarm only has 1s resolution, so this test is slow.
|
608 |
response = self.send_message_to_service('child_connect_timeout 1\n') |
|
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
609 |
self.assertEqual('ok\n', response) |
610 |
# Now request a fork
|
|
611 |
path, pid, sock = self.send_fork_request('rocks') |
|
612 |
# # Open one handle, but not all of them
|
|
613 |
stdin_path = os.path.join(path, 'stdin') |
|
614 |
stdout_path = os.path.join(path, 'stdout') |
|
615 |
stderr_path = os.path.join(path, 'stderr') |
|
616 |
child_stdin = open(stdin_path, 'wb') |
|
12344.5.4
by John Arbash Meinel
Change the code to use signal.alarm() instead of another python thread. |
617 |
# We started opening the child, but stop before we get all handles
|
618 |
# open. After 1 second, the child should get signaled and die.
|
|
619 |
# The master process should notice, and tell us the status of the
|
|
620 |
# exited child.
|
|
621 |
val = sock.recv(4096) |
|
622 |
self.assertEqual('exited\n%s\n' % (signal.SIGALRM,), val) |
|
623 |
# The master process should clean up after the now deceased child.
|
|
624 |
self.failIfExists(path) |
|
12344.5.1
by John Arbash Meinel
Change how the children connect to their fifos. |
625 |
|
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
626 |
|
627 |
class TestCaseWithLPForkingServiceDaemon( |
|
628 |
TestCaseWithLPForkingServiceSubprocess): |
|
629 |
"""Test LPForkingService interaction, when run in daemon mode."""
|
|
630 |
||
631 |
def _cleanup_daemon(self, pid, pid_filename): |
|
632 |
try: |
|
633 |
os.kill(pid, signal.SIGKILL) |
|
634 |
except (OSError, IOError), e: |
|
635 |
trace.mutter('failed to kill pid %d, might be already dead: %s' |
|
636 |
% (pid, e)) |
|
637 |
try: |
|
638 |
os.remove(pid_filename) |
|
639 |
except (OSError, IOError), e: |
|
640 |
if e.errno != errno.ENOENT: |
|
641 |
trace.mutter('failed to remove %r: %s' |
|
642 |
% (pid_filename, e)) |
|
643 |
||
644 |
def _start_subprocess(self, path, env_changes): |
|
645 |
fd, pid_filename = tempfile.mkstemp(prefix='tmp-lp-forking-service-', |
|
646 |
suffix='.pid') |
|
11869.11.2
by John Arbash Meinel
stop_service() now waits for the service to actually exit, and gets increasingly demanding about it. |
647 |
self.service_pid_filename = pid_filename |
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
648 |
os.close(fd) |
649 |
proc = self.start_bzr_subprocess( |
|
650 |
['lp-service', '--path', path, '--no-preload', |
|
651 |
'--children-timeout=1', '--pid-file', pid_filename], |
|
652 |
env_changes=env_changes) |
|
653 |
trace.mutter('started lp-service daemon') |
|
654 |
# We wait for the spawned process to exit, expecting it to report the
|
|
655 |
# final pid into the pid_filename.
|
|
656 |
tnow = time.time() |
|
657 |
tstop_waiting = tnow + 1.0 |
|
11869.11.4
by John Arbash Meinel
Reduce the chance of a race condition by waiting for the socket, rather than the pid file. |
658 |
# When this returns, the first fork has completed and the parent has
|
659 |
# exited.
|
|
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
660 |
proc.wait() |
661 |
while tnow < tstop_waiting: |
|
11869.11.4
by John Arbash Meinel
Reduce the chance of a race condition by waiting for the socket, rather than the pid file. |
662 |
# Wait for the socket to become available
|
663 |
if os.path.exists(path): |
|
664 |
# The service has created the socket for us to communicate
|
|
665 |
break
|
|
666 |
time.sleep(0.1) |
|
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
667 |
tnow = time.time() |
11869.11.4
by John Arbash Meinel
Reduce the chance of a race condition by waiting for the socket, rather than the pid file. |
668 |
|
669 |
with open(pid_filename, 'rb') as f: |
|
670 |
pid = f.read() |
|
671 |
trace.mutter('found pid: %r' % (pid,)) |
|
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
672 |
pid = int(pid.strip()) |
673 |
# This is now the pid of the final daemon
|
|
674 |
trace.mutter('lp-forking-service daemon at pid %s' % (pid,)) |
|
675 |
# Because nothing else will clean this up, add this final handler to
|
|
676 |
# clean up if all else fails.
|
|
677 |
self.addCleanup(self._cleanup_daemon, pid, pid_filename) |
|
678 |
# self.service_process will now be the pid of the daemon, rather than a
|
|
679 |
# Popen object.
|
|
680 |
return pid |
|
681 |
||
682 |
def stop_service(self): |
|
683 |
if self.service_process is None: |
|
684 |
# Already stopped
|
|
685 |
return
|
|
686 |
# First, try to stop the service gracefully, by sending a 'quit'
|
|
687 |
# message
|
|
688 |
try: |
|
689 |
response = self.send_message_to_service('quit\n') |
|
690 |
except socket.error, e: |
|
691 |
# Ignore a failure to connect, the service must be stopping/stopped
|
|
692 |
# already
|
|
693 |
response = None |
|
11869.11.2
by John Arbash Meinel
stop_service() now waits for the service to actually exit, and gets increasingly demanding about it. |
694 |
if response is not None: |
695 |
self.assertEqual('ok\nquit command requested... exiting\n', |
|
696 |
response) |
|
697 |
# Wait for the process to actually exit, or force it if necessary.
|
|
698 |
tnow = time.time() |
|
699 |
tend = tnow + 2.0 |
|
700 |
# We'll be nice a couple of times, and then get mean
|
|
701 |
attempts = [None, None, None, signal.SIGTERM, signal.SIGKILL] |
|
702 |
stopped = False |
|
703 |
unclean = False |
|
704 |
while tnow < tend: |
|
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
705 |
try: |
11869.11.2
by John Arbash Meinel
stop_service() now waits for the service to actually exit, and gets increasingly demanding about it. |
706 |
os.kill(self.service_process, 0) |
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
707 |
except (OSError, IOError), e: |
11869.11.2
by John Arbash Meinel
stop_service() now waits for the service to actually exit, and gets increasingly demanding about it. |
708 |
if e.errno == errno.ESRCH: |
709 |
# The process has successfully exited
|
|
710 |
stopped = True |
|
711 |
break
|
|
712 |
raise
|
|
713 |
else: |
|
714 |
# The process has not exited yet
|
|
715 |
time.sleep(0.1) |
|
716 |
if attempts: |
|
717 |
sig = attempts.pop(0) |
|
718 |
if sig is not None: |
|
719 |
unclean = True |
|
720 |
try: |
|
721 |
os.kill(self.service_process, sig) |
|
722 |
except (OSError, IOError), e: |
|
723 |
if e.errno == errno.ESRCH: |
|
724 |
stopped = True |
|
725 |
break
|
|
726 |
raise
|
|
727 |
if not stopped: |
|
728 |
self.fail('Unable to stop the daemon process (pid %s) after 2.0s' |
|
729 |
% (self.service_process,)) |
|
730 |
elif unclean: |
|
731 |
self.fail('Process (pid %s) had to be shut-down' |
|
732 |
% (self.service_process,)) |
|
11869.11.1
by John Arbash Meinel
Start working on a --pid-file option. |
733 |
self.service_process = None |
734 |
||
735 |
def test_simple_start_and_stop(self): |
|
736 |
pass # All the work is done in setUp() |
|
11869.11.2
by John Arbash Meinel
stop_service() now waits for the service to actually exit, and gets increasingly demanding about it. |
737 |
|
738 |
def test_starts_and_cleans_up(self): |
|
739 |
# The service should be up and responsive
|
|
740 |
response = self.send_message_to_service('hello\n') |
|
741 |
self.assertEqual('ok\nyep, still alive\n', response) |
|
742 |
self.failUnless(os.path.isfile(self.service_pid_filename)) |
|
743 |
with open(self.service_pid_filename, 'rb') as f: |
|
744 |
content = f.read() |
|
745 |
self.assertEqualDiff('%d\n' % (self.service_process,), content) |
|
746 |
# We're done, shut it down
|
|
747 |
self.stop_service() |
|
748 |
self.failIf(os.path.isfile(self.service_pid_filename)) |