~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/canonical/testing/layers.py

merge appserver branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
"""
21
21
 
22
22
__metaclass__ = type
23
 
 
24
23
__all__ = [
25
 
    'BaseLayer', 'DatabaseLayer', 'LibrarianLayer', 'FunctionalLayer',
26
 
    'LaunchpadLayer', 'ZopelessLayer', 'LaunchpadFunctionalLayer',
27
 
    'LaunchpadZopelessLayer', 'LaunchpadScriptLayer', 'PageTestLayer',
28
 
    'LayerConsistencyError', 'LayerIsolationError', 'TwistedLayer',
29
 
    'ExperimentalLaunchpadZopelessLayer', 'TwistedLaunchpadZopelessLayer'
 
24
    'AppServerLayer',
 
25
    'BaseLayer',
 
26
    'DatabaseLayer',
 
27
    'ExperimentalLaunchpadZopelessLayer',
 
28
    'FunctionalLayer',
 
29
    'LaunchpadFunctionalLayer',
 
30
    'LaunchpadLayer',
 
31
    'LaunchpadScriptLayer',
 
32
    'LaunchpadZopelessLayer',
 
33
    'LayerIsolationError',
 
34
    'LibrarianLayer',
 
35
    'PageTestLayer',
 
36
    'TwistedLaunchpadZopelessLayer',
 
37
    'TwistedLayer',
 
38
    'ZopelessLayer',
30
39
    ]
31
40
 
 
41
import datetime
 
42
import errno
32
43
import gc
33
44
import logging
34
45
import os
35
46
import signal
36
47
import socket
37
48
import sys
38
 
from textwrap import dedent
39
49
import threading
40
50
import time
 
51
 
 
52
from textwrap import dedent
41
53
from unittest import TestCase, TestResult
42
54
from urllib import urlopen
43
55
 
68
80
 
69
81
 
70
82
orig__call__ = zope.app.testing.functional.HTTPCaller.__call__
 
83
COMMA = ','
71
84
 
72
85
 
73
86
class MockRootFolder:
117
130
        return True
118
131
 
119
132
 
 
133
def wait_children(seconds=120):
 
134
    """Wait for all children to exit.
 
135
 
 
136
    :param seconds: Maximum number of seconds to wait.  If None, wait
 
137
        forever.
 
138
    """
 
139
    now = datetime.datetime.now
 
140
    if seconds is None:
 
141
        until = None
 
142
    else:
 
143
        until = now() + datetime.timedelta(seconds=seconds)
 
144
    while True:
 
145
        try:
 
146
            os.waitpid(-1, os.WNOHANG)
 
147
        except OSError, error:
 
148
            if error.errno != errno.ECHILD:
 
149
                raise
 
150
            break
 
151
        if until is not None and now() > until:
 
152
            break
 
153
 
 
154
 
120
155
class BaseLayer:
121
156
    """Base layer.
122
157
 
139
174
    @profiled
140
175
    def setUp(cls):
141
176
        BaseLayer.isSetUp = True
142
 
 
143
177
        # Kill any Librarian left running from a previous test run.
144
178
        LibrarianTestSetup().tearDown()
145
 
 
146
179
        # Kill any database left lying around from a previous test run.
147
180
        try:
148
181
            DatabaseLayer.connect().close()
162
195
        # Store currently running threads so we can detect if a test
163
196
        # leaves new threads running.
164
197
        BaseLayer._threads = threading.enumerate()
165
 
 
166
198
        BaseLayer.check()
167
 
 
168
199
        BaseLayer.original_working_directory = os.getcwd()
169
200
 
170
201
        # Tests and test infrastruture sometimes needs to know the test
182
213
    @classmethod
183
214
    @profiled
184
215
    def testTearDown(cls):
185
 
 
186
216
        # Get our current working directory, handling the case where it no
187
217
        # longer exists (!).
188
218
        try:
199
229
            os.chdir(BaseLayer.original_working_directory)
200
230
 
201
231
        BaseLayer.original_working_directory = None
202
 
 
203
232
        reset_logging()
204
 
 
205
233
        del canonical.launchpad.mail.stub.test_emails[:]
206
 
 
207
234
        BaseLayer.test_name = None
208
 
 
209
235
        BaseLayer.check()
210
236
 
211
237
        # Check for tests that leave live threads around early.
212
238
        # A live thread may be the cause of other failures, such as
213
239
        # uncollectable garbage.
214
240
        new_threads = [
215
 
                thread for thread in threading.enumerate()
216
 
                    if thread not in BaseLayer._threads and thread.isAlive()]
 
241
            thread for thread in threading.enumerate()
 
242
            if thread not in BaseLayer._threads and thread.isAlive()
 
243
            ]
 
244
 
217
245
        if new_threads:
218
246
            BaseLayer.flagTestIsolationFailure(
219
 
                    "Test left new live threads: %s" % repr(new_threads))
 
247
                "Test left new live threads: %s" % repr(new_threads))
220
248
        del BaseLayer._threads
221
249
 
222
250
        # Objects with __del__ methods cannot participate in refence cycles.
241
269
        """
242
270
        if FunctionalLayer.isSetUp and ZopelessLayer.isSetUp:
243
271
            raise LayerInvariantError(
244
 
                "Both Zopefull and Zopeless CA environments setup"
245
 
                )
 
272
                "Both Zopefull and Zopeless CA environments setup")
246
273
 
247
274
        # Detect a test that causes the component architecture to be loaded.
248
275
        # This breaks test isolation, as it cannot be torn down.
249
 
        if (is_ca_available() and not FunctionalLayer.isSetUp
250
 
                and not ZopelessLayer.isSetUp):
 
276
        if (is_ca_available()
 
277
            and not FunctionalLayer.isSetUp
 
278
            and not ZopelessLayer.isSetUp):
251
279
            raise LayerIsolationError(
252
280
                "Component architecture should not be loaded by tests. "
253
281
                "This should only be loaded by the Layer."
258
286
        # but it is better for the tear down to be explicit.
259
287
        if ZopelessTransactionManager._installed is not None:
260
288
            raise LayerIsolationError(
261
 
                    "Zopeless environment was setup and not torn down."
262
 
                    )
 
289
                "Zopeless environment was setup and not torn down.")
263
290
 
264
291
        # Detect a test that forgot to reset the default socket timeout.
265
292
        # This safety belt is cheap and protects us from very nasty
1072
1099
 
1073
1100
class TwistedLaunchpadZopelessLayer(TwistedLayer, LaunchpadZopelessLayer):
1074
1101
    """A layer for cleaning up the Twisted thread pool."""
 
1102
 
 
1103
 
 
1104
class AppServerLayer(LaunchpadLayer):
 
1105
    """Environment for starting and stopping the app server."""
 
1106
 
 
1107
    services = ('librarian', 'restricted-librarian')
 
1108
    LPCONFIG = 'testrunner-appserver'
 
1109
 
 
1110
    @classmethod
 
1111
    @profiled
 
1112
    def setUp(cls):
 
1113
        pid = os.fork()
 
1114
        if pid == 0:
 
1115
            # The child.
 
1116
            os.execlp('make', 'make', '-i' '-s', 'LPCONFIG=%s' % cls.LPCONFIG,
 
1117
                      'run_all_quickly_and_quietly')
 
1118
            # Should never get here...
 
1119
            os._exit()
 
1120
        # The parent.  Wait until the app server is responsive, but not
 
1121
        # forever.  Make sure the test database is set up.
 
1122
        from canonical.launchpad.ftests.harness import LaunchpadTestSetup
 
1123
        LaunchpadTestSetup().setUp()
 
1124
        until = time.time() + 60
 
1125
        while time.time() < until:
 
1126
            try:
 
1127
                connection = urlopen('http://launchpad.dev:8085')
 
1128
                connection.read()
 
1129
            except IOError, (error_message, error):
 
1130
                if error.args[0] != errno.ECONNREFUSED:
 
1131
                    raise
 
1132
                time.sleep(0.5)
 
1133
            else:
 
1134
                connection.close()
 
1135
                break
 
1136
        else:
 
1137
            cls.stopAllServices()
 
1138
 
 
1139
    @classmethod
 
1140
    @profiled
 
1141
    def tearDown(cls):
 
1142
        # Force the database to reset.
 
1143
        from canonical.launchpad.ftests.harness import LaunchpadTestSetup
 
1144
        LaunchpadTestSetup().tearDown()
 
1145
        cls.stopAllServices()
 
1146
        # Ensure that there are no child processes still running.
 
1147
        try:
 
1148
            os.waitpid(-1, os.WNOHANG)
 
1149
        except OSError, error:
 
1150
            if error.errno != errno.ECHILD:
 
1151
                raise
 
1152
        else:
 
1153
            cls.stopAllServices()
 
1154
            raise LayerIsolationError('Child processes have leaked through.')
 
1155
 
 
1156
    @classmethod
 
1157
    @profiled
 
1158
    def testSetUp(cls):
 
1159
        pass
 
1160
 
 
1161
    @classmethod
 
1162
    @profiled
 
1163
    def testTearDown(cls):
 
1164
        DatabaseLayer.force_dirty_database()
 
1165
 
 
1166
    @classmethod
 
1167
    @profiled
 
1168
    def stopAllServices(cls):
 
1169
        os.system('make -i -s LPCONFIG=%s stop_quickly_and_quietly'
 
1170
                  % cls.LPCONFIG)
 
1171
        wait_children()