~launchpad-pqm/launchpad/devel

« back to all changes in this revision

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

  • Committer: Launchpad Patch Queue Manager
  • Date: 2008-06-05 03:26:13 UTC
  • mfrom: (6343.3.10 appserver-layer)
  • Revision ID: launchpad@pqm.canonical.com-20080605032613-n7auocswwz5lpdh6
[r=flacoste][!log] Added an AppServerLayer which fires off the app
        server in a child process,
        now providing a framework for tests which need to talk to a real live
        HTTP server (etc.).

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
"""
21
21
 
22
22
__metaclass__ = type
23
 
 
24
23
__all__ = [
 
24
    'AppServerLayer',
25
25
    'BaseLayer',
26
26
    'DatabaseLayer',
27
27
    'ExperimentalLaunchpadZopelessLayer',
31
31
    'LaunchpadLayer',
32
32
    'LaunchpadScriptLayer',
33
33
    'LaunchpadZopelessLayer',
34
 
    'LayerConsistencyError',
35
34
    'LayerIsolationError',
36
35
    'LibrarianLayer',
37
36
    'PageTestLayer',
41
40
    'TwistedLaunchpadZopelessLayer'
42
41
    ]
43
42
 
 
43
import datetime
 
44
import errno
44
45
import gc
45
46
import logging
46
47
import os
47
48
import signal
48
49
import socket
49
50
import sys
50
 
from textwrap import dedent
51
51
import threading
52
52
import time
 
53
 
 
54
from textwrap import dedent
53
55
from unittest import TestCase, TestResult
54
56
from urllib import urlopen
55
57
 
64
66
from zope.security.simplepolicies import PermissiveSecurityPolicy
65
67
from zope.server.logger.pythonlogger import PythonLogger
66
68
 
 
69
from canonical import pidfile
67
70
from canonical.config import config
68
71
from canonical.database.sqlbase import ZopelessTransactionManager
69
72
from canonical.launchpad.interfaces import IMailBox, IOpenLaunchBag
75
78
    GoogleServiceTestSetup)
76
79
from canonical.launchpad.webapp.servers import (
77
80
    LaunchpadAccessLogger, register_launchpad_request_publication_factories)
 
81
from canonical.lazr.config import ImplicitTypeSchema
78
82
from canonical.lazr.timeout import (
79
83
    get_default_timeout_function, set_default_timeout_function)
80
84
from canonical.lp import initZopeless
84
88
 
85
89
 
86
90
orig__call__ = zope.app.testing.functional.HTTPCaller.__call__
 
91
COMMA = ','
87
92
 
88
93
 
89
94
class MockRootFolder:
133
138
        return True
134
139
 
135
140
 
 
141
def wait_children(seconds=120):
 
142
    """Wait for all children to exit.
 
143
 
 
144
    :param seconds: Maximum number of seconds to wait.  If None, wait
 
145
        forever.
 
146
    """
 
147
    now = datetime.datetime.now
 
148
    if seconds is None:
 
149
        until = None
 
150
    else:
 
151
        until = now() + datetime.timedelta(seconds=seconds)
 
152
    while True:
 
153
        try:
 
154
            os.waitpid(-1, os.WNOHANG)
 
155
        except OSError, error:
 
156
            if error.errno != errno.ECHILD:
 
157
                raise
 
158
            break
 
159
        if until is not None and now() > until:
 
160
            break
 
161
 
 
162
 
136
163
class BaseLayer:
137
164
    """Base layer.
138
165
 
155
182
    @profiled
156
183
    def setUp(cls):
157
184
        BaseLayer.isSetUp = True
158
 
 
159
185
        # Kill any Librarian left running from a previous test run.
160
186
        LibrarianTestSetup().tearDown()
161
 
 
162
187
        # Kill any database left lying around from a previous test run.
163
188
        try:
164
189
            DatabaseLayer.connect().close()
178
203
        # Store currently running threads so we can detect if a test
179
204
        # leaves new threads running.
180
205
        BaseLayer._threads = threading.enumerate()
181
 
 
182
206
        BaseLayer.check()
183
 
 
184
207
        BaseLayer.original_working_directory = os.getcwd()
185
208
 
186
209
        # Tests and test infrastruture sometimes needs to know the test
198
221
    @classmethod
199
222
    @profiled
200
223
    def testTearDown(cls):
201
 
 
202
224
        # Get our current working directory, handling the case where it no
203
225
        # longer exists (!).
204
226
        try:
215
237
            os.chdir(BaseLayer.original_working_directory)
216
238
 
217
239
        BaseLayer.original_working_directory = None
218
 
 
219
240
        reset_logging()
220
 
 
221
241
        del canonical.launchpad.mail.stub.test_emails[:]
222
 
 
223
242
        BaseLayer.test_name = None
224
 
 
225
243
        BaseLayer.check()
226
244
 
227
245
        # Check for tests that leave live threads around early.
228
246
        # A live thread may be the cause of other failures, such as
229
247
        # uncollectable garbage.
230
248
        new_threads = [
231
 
                thread for thread in threading.enumerate()
232
 
                    if thread not in BaseLayer._threads and thread.isAlive()]
 
249
            thread for thread in threading.enumerate()
 
250
            if thread not in BaseLayer._threads and thread.isAlive()
 
251
            ]
 
252
 
233
253
        if new_threads:
234
254
            BaseLayer.flagTestIsolationFailure(
235
 
                    "Test left new live threads: %s" % repr(new_threads))
 
255
                "Test left new live threads: %s" % repr(new_threads))
236
256
        del BaseLayer._threads
237
257
 
238
258
        # Objects with __del__ methods cannot participate in refence cycles.
257
277
        """
258
278
        if FunctionalLayer.isSetUp and ZopelessLayer.isSetUp:
259
279
            raise LayerInvariantError(
260
 
                "Both Zopefull and Zopeless CA environments setup"
261
 
                )
 
280
                "Both Zopefull and Zopeless CA environments setup")
262
281
 
263
282
        # Detect a test that causes the component architecture to be loaded.
264
283
        # This breaks test isolation, as it cannot be torn down.
265
 
        if (is_ca_available() and not FunctionalLayer.isSetUp
266
 
                and not ZopelessLayer.isSetUp):
 
284
        if (is_ca_available()
 
285
            and not FunctionalLayer.isSetUp
 
286
            and not ZopelessLayer.isSetUp):
267
287
            raise LayerIsolationError(
268
288
                "Component architecture should not be loaded by tests. "
269
289
                "This should only be loaded by the Layer."
274
294
        # but it is better for the tear down to be explicit.
275
295
        if ZopelessTransactionManager._installed is not None:
276
296
            raise LayerIsolationError(
277
 
                    "Zopeless environment was setup and not torn down."
278
 
                    )
 
297
                "Zopeless environment was setup and not torn down.")
279
298
 
280
299
        # Detect a test that forgot to reset the default socket timeout.
281
300
        # This safety belt is cheap and protects us from very nasty
1125
1144
 
1126
1145
class TwistedLaunchpadZopelessLayer(TwistedLayer, LaunchpadZopelessLayer):
1127
1146
    """A layer for cleaning up the Twisted thread pool."""
 
1147
 
 
1148
 
 
1149
class AppServerLayer(LaunchpadLayer):
 
1150
    """Environment for starting and stopping the app server."""
 
1151
 
 
1152
    services = ('librarian', 'restricted-librarian')
 
1153
    LPCONFIG = 'testrunner-appserver'
 
1154
 
 
1155
    @classmethod
 
1156
    @profiled
 
1157
    def setUp(cls):
 
1158
        cls.stopAllServices()
 
1159
        # Get the child process's pid file.
 
1160
        path = os.path.join(config.root, 'configs', cls.LPCONFIG,
 
1161
                            'launchpad-lazr.conf')
 
1162
        schema = ImplicitTypeSchema(config.schema.filename)
 
1163
        child_config = schema.load(path)
 
1164
        # lazr.config doesn't set this attribute.
 
1165
        child_config.instance_name = cls.LPCONFIG
 
1166
        pid = pidfile.get_pid('launchpad', child_config)
 
1167
        if pid is not None:
 
1168
            # Don't worry if the process no longer exists.
 
1169
            try:
 
1170
                os.kill(pid, signal.SIGTERM)
 
1171
            except OSError, error:
 
1172
                if error.errno != errno.ESRCH:
 
1173
                    raise
 
1174
            pidfile.remove_pidfile('launchpad', child_config)
 
1175
        pid = os.fork()
 
1176
        if pid == 0:
 
1177
            # The child.
 
1178
            os.execlp('make', 'make', '-i' '-s', 'LPCONFIG=%s' % cls.LPCONFIG,
 
1179
                      'run_all_quickly_and_quietly')
 
1180
            # Should never get here...
 
1181
            os._exit()
 
1182
        # The parent.  Wait until the app server is responsive, but not
 
1183
        # forever.  Make sure the test database is set up.
 
1184
        from canonical.launchpad.ftests.harness import LaunchpadTestSetup
 
1185
        LaunchpadTestSetup().setUp()
 
1186
        until = time.time() + 60
 
1187
        while time.time() < until:
 
1188
            try:
 
1189
                connection = urlopen('http://launchpad.dev:8085')
 
1190
                connection.read()
 
1191
            except IOError, (error_message, error):
 
1192
                if error.args[0] != errno.ECONNREFUSED:
 
1193
                    raise
 
1194
                time.sleep(0.5)
 
1195
            else:
 
1196
                connection.close()
 
1197
                break
 
1198
        else:
 
1199
            cls.stopAllServices()
 
1200
 
 
1201
    @classmethod
 
1202
    @profiled
 
1203
    def tearDown(cls):
 
1204
        # Force the database to reset.
 
1205
        from canonical.launchpad.ftests.harness import LaunchpadTestSetup
 
1206
        LaunchpadTestSetup().tearDown()
 
1207
        cls.stopAllServices()
 
1208
        # Ensure that there are no child processes still running.
 
1209
        try:
 
1210
            os.waitpid(-1, os.WNOHANG)
 
1211
        except OSError, error:
 
1212
            if error.errno != errno.ECHILD:
 
1213
                raise
 
1214
        else:
 
1215
            cls.stopAllServices()
 
1216
            raise LayerIsolationError('Child processes have leaked through.')
 
1217
 
 
1218
    @classmethod
 
1219
    @profiled
 
1220
    def testSetUp(cls):
 
1221
        pass
 
1222
 
 
1223
    @classmethod
 
1224
    @profiled
 
1225
    def testTearDown(cls):
 
1226
        DatabaseLayer.force_dirty_database()
 
1227
 
 
1228
    @classmethod
 
1229
    @profiled
 
1230
    def stopAllServices(cls):
 
1231
        os.system('make -i -s LPCONFIG=%s stop_quickly_and_quietly'
 
1232
                  % cls.LPCONFIG)
 
1233
        wait_children()