~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/launchpad_loggerhead/tests.py

  • Committer: Robert Collins
  • Date: 2011-08-18 03:32:33 UTC
  • mto: This revision was merged to the branch mainline in revision 13728.
  • Revision ID: robertc@robertcollins.net-20110818033233-u345xu9cl57t2pkw
Migrate the launchpad_loggerhead implementation of oops_middleware to that within oops_wsgi.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
from canonical.launchpad.webapp.vhosts import allvhosts
21
21
from canonical.testing.layers import DatabaseFunctionalLayer
22
22
from launchpad_loggerhead.app import (
23
 
    _oops_html_template,
24
 
    oops_middleware,
25
23
    RootApp,
26
24
    )
27
25
from launchpad_loggerhead.session import SessionHandler
150
148
        self.assertEqual(self.browser.url, dummy_root + '+logout')
151
149
        self.assertEqual(self.browser.contents,
152
150
                         'This is a dummy destination.\n')
153
 
 
154
 
 
155
 
class TestOopsMiddleware(TestCase):
156
 
 
157
 
    def setUp(self):
158
 
        super(TestOopsMiddleware, self).setUp()
159
 
        self.start_response_called = False
160
 
 
161
 
    def assertContainsRe(self, haystack, needle_re, flags=0):
162
 
        """Assert that a contains something matching a regular expression."""
163
 
        # There is: self.assertTextMatchesExpressionIgnoreWhitespace
164
 
        #           but it does weird things with whitespace, and gives
165
 
        #           unhelpful error messages when it fails, so this is copied
166
 
        #           from bzrlib
167
 
        if not re.search(needle_re, haystack, flags):
168
 
            if '\n' in haystack or len(haystack) > 60:
169
 
                # a long string, format it in a more readable way
170
 
                raise AssertionError(
171
 
                        'pattern "%s" not found in\n"""\\\n%s"""\n'
172
 
                        % (needle_re, haystack))
173
 
            else:
174
 
                raise AssertionError('pattern "%s" not found in "%s"'
175
 
                        % (needle_re, haystack))
176
 
 
177
 
    def catchLogEvents(self):
178
 
        """Any log events that are triggered get written to self.log_stream"""
179
 
        logger = logging.getLogger('lp-loggerhead')
180
 
        logger.setLevel(logging.DEBUG)
181
 
        self.log_stream = cStringIO.StringIO()
182
 
        handler = logging.StreamHandler(self.log_stream)
183
 
        handler.setLevel(logging.DEBUG)
184
 
        logger.addHandler(handler)
185
 
        self.addCleanup(logger.removeHandler, handler)
186
 
 
187
 
    def runtime_failing_app(self, environ, start_response):
188
 
        if False:
189
 
            yield None
190
 
        raise RuntimeError('just a generic runtime error.')
191
 
 
192
 
    def socket_failing_app(self, environ, start_response):
193
 
        if False:
194
 
            yield None
195
 
        raise socket.error(errno.EPIPE, 'Connection closed')
196
 
 
197
 
    def logging_start_response(self, status, response_headers, exc_info=None):
198
 
        self._response_chunks = []
199
 
        def _write(chunk):
200
 
            self._response_chunks.append(chunk)
201
 
        self.start_response_called = True
202
 
        return _write
203
 
 
204
 
    def success_app(self, environ, start_response):
205
 
        writer = start_response('200 OK', {})
206
 
        writer('Successfull\n')
207
 
        return []
208
 
 
209
 
    def failing_start_response(self, status, response_headers, exc_info=None):
210
 
        def fail_write(chunk):
211
 
            raise socket.error(errno.EPIPE, 'Connection closed')
212
 
        self.start_response_called = True
213
 
        return fail_write
214
 
 
215
 
    def multi_yielding_app(self, environ, start_response):
216
 
        writer = start_response('200 OK', {})
217
 
        yield 'content\n'
218
 
        yield 'I want\n'
219
 
        yield 'to give to the user\n'
220
 
 
221
 
    def no_body_app(self, environ, start_response):
222
 
        writer = start_response('200 OK', {})
223
 
        return []
224
 
 
225
 
    def _get_default_environ(self):
226
 
        return {'wsgi.version': (1, 0),
227
 
                'wsgi.url_scheme': 'http',
228
 
                'PATH_INFO': '/test/path',
229
 
                'REQUEST_METHOD': 'GET',
230
 
                'SERVER_NAME': 'localhost',
231
 
                'SERVER_PORT': '8080',
232
 
               }
233
 
 
234
 
    def wrap_and_run(self, app, failing_write=False):
235
 
        app = oops_middleware(app)
236
 
        # Just random env data, rather than setting up a whole wsgi stack just
237
 
        # to pass in values for this dict
238
 
        environ = self._get_default_environ()
239
 
        if failing_write:
240
 
            result = list(app(environ, self.failing_start_response))
241
 
        else:
242
 
            result = list(app(environ, self.logging_start_response))
243
 
        return result
244
 
 
245
 
    def test_exception_triggers_oops(self):
246
 
        res = self.wrap_and_run(self.runtime_failing_app)
247
 
        # After the exception was raised, we should also have gotten an oops
248
 
        # event
249
 
        self.assertEqual(1, len(self.oopses))
250
 
        oops = self.oopses[0]
251
 
        self.assertEqual('RuntimeError', oops['type'])
252
 
        # runtime_failing_app doesn't call start_response, but oops_middleware
253
 
        # does because it tries to send the OOPS information to the user.
254
 
        self.assertTrue(self.start_response_called)
255
 
        self.assertEqual(_oops_html_template % {'oopsid': oops['id']},
256
 
                         ''.join(self._response_chunks))
257
 
 
258
 
    def test_ignores_socket_exceptions(self):
259
 
        self.catchLogEvents()
260
 
        res = self.wrap_and_run(self.socket_failing_app)
261
 
        self.assertEqual(0, len(self.oopses))
262
 
        self.assertContainsRe(self.log_stream.getvalue(),
263
 
            'Caught socket exception from <unknown>:.*Connection closed')
264
 
        # start_response doesn't get called because the app fails first,
265
 
        # and oops_middleware knows not to do anything with a closed socket.
266
 
        self.assertFalse(self.start_response_called)
267
 
 
268
 
    def test_ignores_writer_failures(self):
269
 
        self.catchLogEvents()
270
 
        res = self.wrap_and_run(self.success_app, failing_write=True)
271
 
        self.assertEqual(0, len(self.oopses))
272
 
        self.assertContainsRe(self.log_stream.getvalue(),
273
 
            'Caught socket exception from <unknown>:.*Connection closed')
274
 
        # success_app calls start_response, so this should get passed on.
275
 
        self.assertTrue(self.start_response_called)
276
 
 
277
 
    def test_stopping_early_no_oops(self):
278
 
        # See bug #726985.
279
 
        # If content is being streamed, and the pipe closes, we'll get a
280
 
        # 'GeneratorExit', because it is closed before finishing. This doesn't
281
 
        # need to become an OOPS.
282
 
        self.catchLogEvents()
283
 
        app = oops_middleware(self.multi_yielding_app)
284
 
        environ = self._get_default_environ()
285
 
        result = app(environ, self.logging_start_response)
286
 
        self.assertEqual('content\n', result.next())
287
 
        # At this point, we intentionally kill the app and the response, so
288
 
        # that they will get GeneratorExit
289
 
        del app, result
290
 
        self.assertEqual([], self.oopses)
291
 
        self.assertContainsRe(self.log_stream.getvalue(),
292
 
            'Caught GeneratorExit from <unknown>')
293
 
        # Body content was yielded, we must have called start_response
294
 
        self.assertTrue(self.start_response_called)
295
 
 
296
 
    def test_no_body_calls_start_response(self):
297
 
        # See bug #732481, even if we don't have a body, if we have headers to
298
 
        # send, we must call start_response
299
 
        result = self.wrap_and_run(self.no_body_app)
300
 
        self.assertEqual([], result)
301
 
        self.assertTrue(self.start_response_called)
302
 
        # Output content is empty because of no_body_app
303
 
        self.assertEqual('', ''.join(self._response_chunks))