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