22
20
from bzrlib.plugins import lpserve
21
from testtools import content
24
23
from canonical.config import config
25
from lp.codehosting import get_bzr_path, get_BZR_PLUGIN_PATH_for_subprocess
24
from lp.codehosting import (
26
get_BZR_PLUGIN_PATH_for_subprocess,
28
from lp.testing.fakemethod import FakeMethod
28
31
class TestingLPForkingServiceInAThread(lpserve.LPForkingService):
29
32
"""A test-double to run a "forking service" in a thread.
31
Note that we don't allow actually forking, but it does allow us to interact
32
with the service for other operations.
34
Note that we don't allow actually forking, but it does allow us to
35
interact with the service for other operations.
35
# For testing, we set the timeouts much lower, because we want the tests to
38
# For testing we set the timeouts much lower, because we want the
39
# tests to run quickly.
37
40
WAIT_FOR_CHILDREN_TIMEOUT = 0.5
38
41
SOCKET_TIMEOUT = 0.01
39
42
SLEEP_FOR_CHILDREN_TIMEOUT = 0.01
40
43
WAIT_FOR_REQUEST_TIMEOUT = 0.1
42
# We're running in a thread as part of the test suite, blow up if we try to
45
# We're running in a thread as part of the test suite. Blow up at
46
# any attempt to fork.
44
47
_fork_function = None
46
49
def __init__(self, path, perms=None):
52
55
path=path, perms=None)
54
57
def _register_signals(self):
55
pass # Don't register it for the test suite
58
# Don't register it for the test suite.
57
61
def _unregister_signals(self):
58
pass # We don't fork, and didn't register, so don't unregister
62
# We don't fork, and didn't register, so don't unregister.
60
65
def _create_master_socket(self):
61
66
super(TestingLPForkingServiceInAThread, self)._create_master_socket()
131
136
def test_autostop(self):
132
137
# We shouldn't leak a thread here, as it should be part of the test
134
service = TestingLPForkingServiceInAThread.start_service(self)
139
TestingLPForkingServiceInAThread.start_service(self)
137
142
class TestCaseWithLPForkingService(tests.TestCaseWithTransport):
294
299
os.mkfifo(os.path.join(tempdir, 'stdin'))
295
300
os.mkfifo(os.path.join(tempdir, 'stdout'))
296
301
os.mkfifo(os.path.join(tempdir, 'stderr'))
297
303
# catch SIGALRM so we don't stop the test suite. It will still
298
304
# interupt the blocking open() calls.
299
def noop_on_alarm(signal, frame):
301
signal.signal(signal.SIGALRM, noop_on_alarm)
305
signal.signal(signal.SIGALRM, FakeMethod())
302
307
self.addCleanup(signal.signal, signal.SIGALRM, signal.SIG_DFL)
303
308
e = self.assertRaises(errors.BzrError,
304
309
self.service._open_handles, tempdir)
305
310
self.assertContainsRe(str(e), r'After \d+.\d+s we failed to open.*')
309
313
class TestCaseWithSubprocess(tests.TestCaseWithTransport):
310
314
"""Override the bzr start_bzr_subprocess command.
312
The launchpad infrastructure requires a fair amount of configuration to get
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.
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.
318
322
def get_python_path(self):
373
377
class TestCaseWithLPForkingServiceSubprocess(TestCaseWithSubprocess):
374
378
"""Tests will get a separate process to communicate to.
376
The number of these tests should be small, because it is expensive to start
380
The number of these tests should be small, because it is expensive to
381
start and stop the daemon.
379
383
TODO: This should probably use testresources, or layers somehow...
458
462
service_fd, path = tempfile.mkstemp(prefix='tmp-lp-service-',
460
464
os.close(service_fd)
461
os.remove(path) # service wants create it as a socket
462
env_changes = {'BZR_PLUGIN_PATH': lpserve.__path__[0],
465
# The service wants to create this file as a socket.
468
'BZR_PLUGIN_PATH': lpserve.__path__[0],
464
471
proc = self._start_subprocess(path, env_changes)
465
472
return proc, path
467
474
def stop_service(self):
468
475
if self.service_process is None:
471
478
# First, try to stop the service gracefully, by sending a 'quit'
474
481
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
483
# Ignore a failure to connect; the service must be
484
# stopping/stopped already.
479
486
tend = time.time() + 10.0
480
487
while self.service_process.poll() is None:
564
571
for idx in [3, 2, 0, 1]:
565
572
p, pid, sock = paths[idx]
566
573
stdout_msg = 'hello %d\n' % (idx,)
567
stderr_msg = 'goodbye %d\n' % (idx+1,)
574
stderr_msg = 'goodbye %d\n' % (idx + 1,)
568
575
stdout, stderr = self.communicate_with_fork(p,
569
576
'1 %s2 %s' % (stdout_msg, stderr_msg))
570
577
self.assertEqualDiff(stdout_msg, stdout)
607
614
# *sigh* signal.alarm only has 1s resolution, so this test is slow.
608
615
response = self.send_message_to_service('child_connect_timeout 1\n')
609
616
self.assertEqual('ok\n', response)
617
# Now request a fork.
611
618
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')
617
619
# We started opening the child, but stop before we get all handles
618
620
# open. After 1 second, the child should get signaled and die.
619
621
# The master process should notice, and tell us the status of the
675
677
# Because nothing else will clean this up, add this final handler to
676
678
# clean up if all else fails.
677
679
self.addCleanup(self._cleanup_daemon, pid, pid_filename)
678
# self.service_process will now be the pid of the daemon, rather than a
680
# self.service_process will now be the pid of the daemon,
681
# rather than a Popen object.
682
684
def stop_service(self):
689
691
response = self.send_message_to_service('quit\n')
690
692
except socket.error, e:
691
# Ignore a failure to connect, the service must be stopping/stopped
693
# Ignore a failure to connect; the service must be
694
# stopping/stopped already.
694
696
if response is not None:
695
697
self.assertEqual('ok\nquit command requested... exiting\n',
733
735
self.service_process = None
735
737
def test_simple_start_and_stop(self):
736
pass # All the work is done in setUp()
738
# All the work is done in setUp().
738
741
def test_starts_and_cleans_up(self):
739
# The service should be up and responsive
742
# The service should be up and responsive.
740
743
response = self.send_message_to_service('hello\n')
741
744
self.assertEqual('ok\nyep, still alive\n', response)
742
745
self.failUnless(os.path.isfile(self.service_pid_filename))
743
746
with open(self.service_pid_filename, 'rb') as f:
744
747
content = f.read()
745
748
self.assertEqualDiff('%d\n' % (self.service_process,), content)
746
# We're done, shut it down
749
# We're done. Shut it down.
747
750
self.stop_service()
748
751
self.failIf(os.path.isfile(self.service_pid_filename))