~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/web/test/test_wsgi.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
 
2
# See LICENSE for details.
 
3
 
 
4
"""
 
5
Tests for L{twisted.web.wsgi}.
 
6
"""
 
7
 
 
8
__metaclass__ = type
 
9
 
 
10
from sys import exc_info
 
11
from urllib import quote
 
12
from thread import get_ident
 
13
import StringIO, cStringIO, tempfile
 
14
 
 
15
from zope.interface.verify import verifyObject
 
16
 
 
17
from twisted.python.compat import set
 
18
from twisted.python.log import addObserver, removeObserver, err
 
19
from twisted.python.failure import Failure
 
20
from twisted.python.threadpool import ThreadPool
 
21
from twisted.internet.defer import Deferred, gatherResults
 
22
from twisted.internet import reactor
 
23
from twisted.internet.error import ConnectionLost
 
24
from twisted.trial.unittest import TestCase
 
25
from twisted.web import http
 
26
from twisted.web.resource import IResource, Resource
 
27
from twisted.web.server import Request, Site, version
 
28
from twisted.web.wsgi import WSGIResource
 
29
from twisted.web.test.test_web import DummyChannel
 
30
 
 
31
 
 
32
class SynchronousThreadPool:
 
33
    """
 
34
    A single-threaded implementation of part of the L{ThreadPool} interface.
 
35
    This implementation calls functions synchronously rather than running
 
36
    them in a thread pool.  It is used to make the tests which are not
 
37
    directly for thread-related behavior deterministic.
 
38
    """
 
39
    def callInThread(self, f, *a, **kw):
 
40
        """
 
41
        Call C{f(*a, **kw)} in this thread rather than scheduling it to be
 
42
        called in a thread.
 
43
        """
 
44
        try:
 
45
            f(*a, **kw)
 
46
        except:
 
47
            # callInThread doesn't let exceptions propagate to the caller.
 
48
            # None is always returned and any exception raised gets logged
 
49
            # later on.
 
50
            err(None, "Callable passed to SynchronousThreadPool.callInThread failed")
 
51
 
 
52
 
 
53
 
 
54
class SynchronousReactorThreads:
 
55
    """
 
56
    A single-threaded implementation of part of the L{IReactorThreads}
 
57
    interface.  This implementation assumes that it will only be invoked
 
58
    from the reactor thread, so it calls functions synchronously rather than
 
59
    trying to schedule them to run in the reactor thread.  It is used in
 
60
    conjunction with L{SynchronousThreadPool} to make the tests which are
 
61
    not directly for thread-related behavior deterministic.
 
62
    """
 
63
    def callFromThread(self, f, *a, **kw):
 
64
        """
 
65
        Call C{f(*a, **kw)} in this thread which should also be the reactor
 
66
        thread.
 
67
        """
 
68
        f(*a, **kw)
 
69
 
 
70
 
 
71
 
 
72
class WSGIResourceTests(TestCase):
 
73
    def setUp(self):
 
74
        """
 
75
        Create a L{WSGIResource} with synchronous threading objects and a no-op
 
76
        application object.  This is useful for testing certain things about
 
77
        the resource implementation which are unrelated to WSGI.
 
78
        """
 
79
        self.resource = WSGIResource(
 
80
            SynchronousReactorThreads(), SynchronousThreadPool(),
 
81
            lambda environ, startResponse: None)
 
82
 
 
83
 
 
84
    def test_interfaces(self):
 
85
        """
 
86
        L{WSGIResource} implements L{IResource} and stops resource traversal.
 
87
        """
 
88
        verifyObject(IResource, self.resource)
 
89
        self.assertTrue(self.resource.isLeaf)
 
90
 
 
91
 
 
92
    def test_unsupported(self):
 
93
        """
 
94
        A L{WSGIResource} cannot have L{IResource} children.  Its
 
95
        C{getChildWithDefault} and C{putChild} methods raise L{RuntimeError}.
 
96
        """
 
97
        self.assertRaises(
 
98
            RuntimeError,
 
99
            self.resource.getChildWithDefault,
 
100
            "foo", Request(DummyChannel(), False))
 
101
        self.assertRaises(
 
102
            RuntimeError,
 
103
            self.resource.putChild,
 
104
            "foo", Resource())
 
105
 
 
106
 
 
107
class WSGITestsMixin:
 
108
    """
 
109
    @ivar channelFactory: A no-argument callable which will be invoked to
 
110
        create a new HTTP channel to associate with request objects.
 
111
    """
 
112
    channelFactory = DummyChannel
 
113
 
 
114
    def setUp(self):
 
115
        self.threadpool = SynchronousThreadPool()
 
116
        self.reactor = SynchronousReactorThreads()
 
117
 
 
118
 
 
119
    def lowLevelRender(
 
120
        self, requestFactory, applicationFactory, channelFactory, method,
 
121
        version, resourceSegments, requestSegments, query=None, headers=[],
 
122
        body=None, safe=''):
 
123
        """
 
124
        @param method: A C{str} giving the request method to use.
 
125
 
 
126
        @param version: A C{str} like C{'1.1'} giving the request version.
 
127
 
 
128
        @param resourceSegments: A C{list} of unencoded path segments which
 
129
            specifies the location in the resource hierarchy at which the
 
130
            L{WSGIResource} will be placed, eg C{['']} for I{/}, C{['foo',
 
131
            'bar', '']} for I{/foo/bar/}, etc.
 
132
 
 
133
        @param requestSegments: A C{list} of unencoded path segments giving the
 
134
            request URI.
 
135
 
 
136
        @param query: A C{list} of two-tuples of C{str} giving unencoded query
 
137
            argument keys and values.
 
138
 
 
139
        @param headers: A C{list} of two-tuples of C{str} giving request header
 
140
            names and corresponding values.
 
141
 
 
142
        @param safe: A C{str} giving the bytes which are to be considered
 
143
            I{safe} for inclusion in the request URI and not quoted.
 
144
 
 
145
        @return: A L{Deferred} which will be called back with a two-tuple of
 
146
            the arguments passed which would be passed to the WSGI application
 
147
            object for this configuration and request (ie, the environment and
 
148
            start_response callable).
 
149
        """
 
150
        root = WSGIResource(
 
151
            self.reactor, self.threadpool, applicationFactory())
 
152
        resourceSegments.reverse()
 
153
        for seg in resourceSegments:
 
154
            tmp = Resource()
 
155
            tmp.putChild(seg, root)
 
156
            root = tmp
 
157
 
 
158
        channel = channelFactory()
 
159
        channel.site = Site(root)
 
160
        request = requestFactory(channel, False)
 
161
        for k, v in headers:
 
162
            request.requestHeaders.addRawHeader(k, v)
 
163
        request.gotLength(0)
 
164
        if body:
 
165
            request.content.write(body)
 
166
            request.content.seek(0)
 
167
        uri = '/' + '/'.join([quote(seg, safe) for seg in requestSegments])
 
168
        if query is not None:
 
169
            uri += '?' + '&'.join(['='.join([quote(k, safe), quote(v, safe)])
 
170
                                   for (k, v) in query])
 
171
        request.requestReceived(method, uri, 'HTTP/' + version)
 
172
        return request
 
173
 
 
174
 
 
175
    def render(self, *a, **kw):
 
176
        result = Deferred()
 
177
        def applicationFactory():
 
178
            def application(*args):
 
179
                environ, startResponse = args
 
180
                result.callback(args)
 
181
                startResponse('200 OK', [])
 
182
                return iter(())
 
183
            return application
 
184
        self.lowLevelRender(
 
185
            Request, applicationFactory, self.channelFactory, *a, **kw)
 
186
        return result
 
187
 
 
188
 
 
189
    def requestFactoryFactory(self, requestClass=Request):
 
190
        d = Deferred()
 
191
        def requestFactory(*a, **kw):
 
192
            request = requestClass(*a, **kw)
 
193
            # If notifyFinish is called after lowLevelRender returns, it won't
 
194
            # do the right thing, because the request will have already
 
195
            # finished.  One might argue that this is a bug in
 
196
            # Request.notifyFinish.
 
197
            request.notifyFinish().chainDeferred(d)
 
198
            return request
 
199
        return d, requestFactory
 
200
 
 
201
 
 
202
    def getContentFromResponse(self, response):
 
203
        return response.split('\r\n\r\n', 1)[1]
 
204
 
 
205
 
 
206
 
 
207
class EnvironTests(WSGITestsMixin, TestCase):
 
208
    """
 
209
    Tests for the values in the C{environ} C{dict} passed to the application
 
210
    object by L{twisted.web.wsgi.WSGIResource}.
 
211
    """
 
212
    def environKeyEqual(self, key, value):
 
213
        def assertEnvironKeyEqual((environ, startResponse)):
 
214
            self.assertEqual(environ[key], value)
 
215
        return assertEnvironKeyEqual
 
216
 
 
217
 
 
218
    def test_environIsDict(self):
 
219
        """
 
220
        L{WSGIResource} calls the application object with an C{environ}
 
221
        parameter which is exactly of type C{dict}.
 
222
        """
 
223
        d = self.render('GET', '1.1', [], [''])
 
224
        def cbRendered((environ, startResponse)):
 
225
            self.assertIdentical(type(environ), dict)
 
226
        d.addCallback(cbRendered)
 
227
        return d
 
228
 
 
229
 
 
230
    def test_requestMethod(self):
 
231
        """
 
232
        The C{'REQUEST_METHOD'} key of the C{environ} C{dict} passed to the
 
233
        application contains the HTTP method in the request (RFC 3875, section
 
234
        4.1.12).
 
235
        """
 
236
        get = self.render('GET', '1.1', [], [''])
 
237
        get.addCallback(self.environKeyEqual('REQUEST_METHOD', 'GET'))
 
238
 
 
239
        # Also make sure a different request method shows up as a different
 
240
        # value in the environ dict.
 
241
        post = self.render('POST', '1.1', [], [''])
 
242
        post.addCallback(self.environKeyEqual('REQUEST_METHOD', 'POST'))
 
243
 
 
244
        return gatherResults([get, post])
 
245
 
 
246
 
 
247
    def test_scriptName(self):
 
248
        """
 
249
        The C{'SCRIPT_NAME'} key of the C{environ} C{dict} passed to the
 
250
        application contains the I{abs_path} (RFC 2396, section 3) to this
 
251
        resource (RFC 3875, section 4.1.13).
 
252
        """
 
253
        root = self.render('GET', '1.1', [], [''])
 
254
        root.addCallback(self.environKeyEqual('SCRIPT_NAME', ''))
 
255
 
 
256
        emptyChild = self.render('GET', '1.1', [''], [''])
 
257
        emptyChild.addCallback(self.environKeyEqual('SCRIPT_NAME', '/'))
 
258
 
 
259
        leaf = self.render('GET', '1.1', ['foo'], ['foo'])
 
260
        leaf.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
 
261
 
 
262
        container = self.render('GET', '1.1', ['foo', ''], ['foo', ''])
 
263
        container.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo/'))
 
264
 
 
265
        internal = self.render('GET', '1.1', ['foo'], ['foo', 'bar'])
 
266
        internal.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
 
267
 
 
268
        unencoded = self.render(
 
269
            'GET', '1.1', ['foo', '/', 'bar\xff'], ['foo', '/', 'bar\xff'])
 
270
        # The RFC says "(not URL-encoded)", even though that makes
 
271
        # interpretation of SCRIPT_NAME ambiguous.
 
272
        unencoded.addCallback(
 
273
            self.environKeyEqual('SCRIPT_NAME', '/foo///bar\xff'))
 
274
 
 
275
        return gatherResults([
 
276
                root, emptyChild, leaf, container, internal, unencoded])
 
277
 
 
278
 
 
279
    def test_pathInfo(self):
 
280
        """
 
281
        The C{'PATH_INFO'} key of the C{environ} C{dict} passed to the
 
282
        application contains the suffix of the request URI path which is not
 
283
        included in the value for the C{'SCRIPT_NAME'} key (RFC 3875, section
 
284
        4.1.5).
 
285
        """
 
286
        assertKeyEmpty = self.environKeyEqual('PATH_INFO', '')
 
287
 
 
288
        root = self.render('GET', '1.1', [], [''])
 
289
        root.addCallback(self.environKeyEqual('PATH_INFO', '/'))
 
290
 
 
291
        emptyChild = self.render('GET', '1.1', [''], [''])
 
292
        emptyChild.addCallback(assertKeyEmpty)
 
293
 
 
294
        leaf = self.render('GET', '1.1', ['foo'], ['foo'])
 
295
        leaf.addCallback(assertKeyEmpty)
 
296
 
 
297
        container = self.render('GET', '1.1', ['foo', ''], ['foo', ''])
 
298
        container.addCallback(assertKeyEmpty)
 
299
 
 
300
        internalLeaf = self.render('GET', '1.1', ['foo'], ['foo', 'bar'])
 
301
        internalLeaf.addCallback(self.environKeyEqual('PATH_INFO', '/bar'))
 
302
 
 
303
        internalContainer = self.render('GET', '1.1', ['foo'], ['foo', ''])
 
304
        internalContainer.addCallback(self.environKeyEqual('PATH_INFO', '/'))
 
305
 
 
306
        unencoded = self.render('GET', '1.1', [], ['foo', '/', 'bar\xff'])
 
307
        unencoded.addCallback(
 
308
            self.environKeyEqual('PATH_INFO', '/foo///bar\xff'))
 
309
 
 
310
        return gatherResults([
 
311
                root, leaf, container, internalLeaf,
 
312
                internalContainer, unencoded])
 
313
 
 
314
 
 
315
    def test_queryString(self):
 
316
        """
 
317
        The C{'QUERY_STRING'} key of the C{environ} C{dict} passed to the
 
318
        application contains the portion of the request URI after the first
 
319
        I{?} (RFC 3875, section 4.1.7).
 
320
        """
 
321
        missing = self.render('GET', '1.1', [], [''], None)
 
322
        missing.addCallback(self.environKeyEqual('QUERY_STRING', ''))
 
323
 
 
324
        empty = self.render('GET', '1.1', [], [''], [])
 
325
        empty.addCallback(self.environKeyEqual('QUERY_STRING', ''))
 
326
 
 
327
        present = self.render('GET', '1.1', [], [''], [('foo', 'bar')])
 
328
        present.addCallback(self.environKeyEqual('QUERY_STRING', 'foo=bar'))
 
329
 
 
330
        unencoded = self.render('GET', '1.1', [], [''], [('/', '/')])
 
331
        unencoded.addCallback(self.environKeyEqual('QUERY_STRING', '%2F=%2F'))
 
332
 
 
333
        # "?" is reserved in the <searchpart> portion of a URL.  However, it
 
334
        # seems to be a common mistake of clients to forget to quote it.  So,
 
335
        # make sure we handle that invalid case.
 
336
        doubleQuestion = self.render(
 
337
            'GET', '1.1', [], [''], [('foo', '?bar')], safe='?')
 
338
        doubleQuestion.addCallback(
 
339
            self.environKeyEqual('QUERY_STRING', 'foo=?bar'))
 
340
 
 
341
        return gatherResults([
 
342
            missing, empty, present, unencoded, doubleQuestion])
 
343
 
 
344
 
 
345
    def test_contentType(self):
 
346
        """
 
347
        The C{'CONTENT_TYPE'} key of the C{environ} C{dict} passed to the
 
348
        application contains the value of the I{Content-Type} request header
 
349
        (RFC 3875, section 4.1.3).
 
350
        """
 
351
        missing = self.render('GET', '1.1', [], [''])
 
352
        missing.addCallback(self.environKeyEqual('CONTENT_TYPE', ''))
 
353
 
 
354
        present = self.render(
 
355
            'GET', '1.1', [], [''], None, [('content-type', 'x-foo/bar')])
 
356
        present.addCallback(self.environKeyEqual('CONTENT_TYPE', 'x-foo/bar'))
 
357
 
 
358
        return gatherResults([missing, present])
 
359
 
 
360
 
 
361
    def test_contentLength(self):
 
362
        """
 
363
        The C{'CONTENT_LENGTH'} key of the C{environ} C{dict} passed to the
 
364
        application contains the value of the I{Content-Length} request header
 
365
        (RFC 3875, section 4.1.2).
 
366
        """
 
367
        missing = self.render('GET', '1.1', [], [''])
 
368
        missing.addCallback(self.environKeyEqual('CONTENT_LENGTH', ''))
 
369
 
 
370
        present = self.render(
 
371
            'GET', '1.1', [], [''], None, [('content-length', '1234')])
 
372
        present.addCallback(self.environKeyEqual('CONTENT_LENGTH', '1234'))
 
373
 
 
374
        return gatherResults([missing, present])
 
375
 
 
376
 
 
377
    def test_serverName(self):
 
378
        """
 
379
        The C{'SERVER_NAME'} key of the C{environ} C{dict} passed to the
 
380
        application contains the best determination of the server hostname
 
381
        possible, using either the value of the I{Host} header in the request
 
382
        or the address the server is listening on if that header is not
 
383
        present (RFC 3875, section 4.1.14).
 
384
        """
 
385
        missing = self.render('GET', '1.1', [], [''])
 
386
        # 10.0.0.1 value comes from a bit far away -
 
387
        # twisted.test.test_web.DummyChannel.transport.getHost().host
 
388
        missing.addCallback(self.environKeyEqual('SERVER_NAME', '10.0.0.1'))
 
389
 
 
390
        present = self.render(
 
391
            'GET', '1.1', [], [''], None, [('host', 'example.org')])
 
392
        present.addCallback(self.environKeyEqual('SERVER_NAME', 'example.org'))
 
393
 
 
394
        return gatherResults([missing, present])
 
395
 
 
396
 
 
397
    def test_serverPort(self):
 
398
        """
 
399
        The C{'SERVER_PORT'} key of the C{environ} C{dict} passed to the
 
400
        application contains the port number of the server which received the
 
401
        request (RFC 3875, section 4.1.15).
 
402
        """
 
403
        portNumber = 12354
 
404
        def makeChannel():
 
405
            channel = DummyChannel()
 
406
            channel.transport = DummyChannel.TCP()
 
407
            channel.transport.port = portNumber
 
408
            return channel
 
409
        self.channelFactory = makeChannel
 
410
 
 
411
        d = self.render('GET', '1.1', [], [''])
 
412
        d.addCallback(self.environKeyEqual('SERVER_PORT', str(portNumber)))
 
413
        return d
 
414
 
 
415
 
 
416
    def test_serverProtocol(self):
 
417
        """
 
418
        The C{'SERVER_PROTOCOL'} key of the C{environ} C{dict} passed to the
 
419
        application contains the HTTP version number received in the request
 
420
        (RFC 3875, section 4.1.16).
 
421
        """
 
422
        old = self.render('GET', '1.0', [], [''])
 
423
        old.addCallback(self.environKeyEqual('SERVER_PROTOCOL', 'HTTP/1.0'))
 
424
 
 
425
        new = self.render('GET', '1.1', [], [''])
 
426
        new.addCallback(self.environKeyEqual('SERVER_PROTOCOL', 'HTTP/1.1'))
 
427
 
 
428
        return gatherResults([old, new])
 
429
 
 
430
 
 
431
    def test_remoteAddr(self):
 
432
        """
 
433
        The C{'REMOTE_ADDR'} key of the C{environ} C{dict} passed to the
 
434
        application contains the address of the client making the request.
 
435
        """
 
436
        d = self.render('GET', '1.1', [], [''])
 
437
        d.addCallback(self.environKeyEqual('REMOTE_ADDR', '192.168.1.1'))
 
438
 
 
439
        return d
 
440
 
 
441
    def test_headers(self):
 
442
        """
 
443
        HTTP request headers are copied into the C{environ} C{dict} passed to
 
444
        the application with a C{HTTP_} prefix added to their names.
 
445
        """
 
446
        singleValue = self.render(
 
447
            'GET', '1.1', [], [''], None, [('foo', 'bar'), ('baz', 'quux')])
 
448
        def cbRendered((environ, startResponse)):
 
449
            self.assertEqual(environ['HTTP_FOO'], 'bar')
 
450
            self.assertEqual(environ['HTTP_BAZ'], 'quux')
 
451
        singleValue.addCallback(cbRendered)
 
452
 
 
453
        multiValue = self.render(
 
454
            'GET', '1.1', [], [''], None, [('foo', 'bar'), ('foo', 'baz')])
 
455
        multiValue.addCallback(self.environKeyEqual('HTTP_FOO', 'bar,baz'))
 
456
 
 
457
        withHyphen = self.render(
 
458
            'GET', '1.1', [], [''], None, [('foo-bar', 'baz')])
 
459
        withHyphen.addCallback(self.environKeyEqual('HTTP_FOO_BAR', 'baz'))
 
460
 
 
461
        multiLine = self.render(
 
462
            'GET', '1.1', [], [''], None, [('foo', 'bar\n\tbaz')])
 
463
        multiLine.addCallback(self.environKeyEqual('HTTP_FOO', 'bar \tbaz'))
 
464
 
 
465
        return gatherResults([singleValue, multiValue, withHyphen, multiLine])
 
466
 
 
467
 
 
468
    def test_wsgiVersion(self):
 
469
        """
 
470
        The C{'wsgi.version'} key of the C{environ} C{dict} passed to the
 
471
        application has the value C{(1, 0)} indicating that this is a WSGI 1.0
 
472
        container.
 
473
        """
 
474
        versionDeferred = self.render('GET', '1.1', [], [''])
 
475
        versionDeferred.addCallback(self.environKeyEqual('wsgi.version', (1, 0)))
 
476
        return versionDeferred
 
477
 
 
478
 
 
479
    def test_wsgiRunOnce(self):
 
480
        """
 
481
        The C{'wsgi.run_once'} key of the C{environ} C{dict} passed to the
 
482
        application is set to C{False}.
 
483
        """
 
484
        once = self.render('GET', '1.1', [], [''])
 
485
        once.addCallback(self.environKeyEqual('wsgi.run_once', False))
 
486
        return once
 
487
 
 
488
 
 
489
    def test_wsgiMultithread(self):
 
490
        """
 
491
        The C{'wsgi.multithread'} key of the C{environ} C{dict} passed to the
 
492
        application is set to C{True}.
 
493
        """
 
494
        thread = self.render('GET', '1.1', [], [''])
 
495
        thread.addCallback(self.environKeyEqual('wsgi.multithread', True))
 
496
        return thread
 
497
 
 
498
 
 
499
    def test_wsgiMultiprocess(self):
 
500
        """
 
501
        The C{'wsgi.multiprocess'} key of the C{environ} C{dict} passed to the
 
502
        application is set to C{False}.
 
503
        """
 
504
        process = self.render('GET', '1.1', [], [''])
 
505
        process.addCallback(self.environKeyEqual('wsgi.multiprocess', False))
 
506
        return process
 
507
 
 
508
 
 
509
    def test_wsgiURLScheme(self):
 
510
        """
 
511
        The C{'wsgi.url_scheme'} key of the C{environ} C{dict} passed to the
 
512
        application has the request URL scheme.
 
513
        """
 
514
        # XXX Does this need to be different if the request is for an absolute
 
515
        # URL?
 
516
        def channelFactory():
 
517
            channel = DummyChannel()
 
518
            channel.transport = DummyChannel.SSL()
 
519
            return channel
 
520
 
 
521
        self.channelFactory = DummyChannel
 
522
        httpDeferred = self.render('GET', '1.1', [], [''])
 
523
        httpDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'http'))
 
524
 
 
525
        self.channelFactory = channelFactory
 
526
        httpsDeferred = self.render('GET', '1.1', [], [''])
 
527
        httpsDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'https'))
 
528
 
 
529
        return gatherResults([httpDeferred, httpsDeferred])
 
530
 
 
531
 
 
532
    def test_wsgiErrors(self):
 
533
        """
 
534
        The C{'wsgi.errors'} key of the C{environ} C{dict} passed to the
 
535
        application is a file-like object (as defined in the U{Input and Errors
 
536
        Streams<http://www.python.org/dev/peps/pep-0333/#input-and-error-streams>}
 
537
        section of PEP 333) which converts bytes written to it into events for
 
538
        the logging system.
 
539
        """
 
540
        events = []
 
541
        addObserver(events.append)
 
542
        self.addCleanup(removeObserver, events.append)
 
543
 
 
544
        errors = self.render('GET', '1.1', [], [''])
 
545
        def cbErrors((environ, startApplication)):
 
546
            errors = environ['wsgi.errors']
 
547
            errors.write('some message\n')
 
548
            errors.writelines(['another\nmessage\n'])
 
549
            errors.flush()
 
550
            self.assertEqual(events[0]['message'], ('some message\n',))
 
551
            self.assertEqual(events[0]['system'], 'wsgi')
 
552
            self.assertTrue(events[0]['isError'])
 
553
            self.assertEqual(events[1]['message'], ('another\nmessage\n',))
 
554
            self.assertEqual(events[1]['system'], 'wsgi')
 
555
            self.assertTrue(events[1]['isError'])
 
556
            self.assertEqual(len(events), 2)
 
557
        errors.addCallback(cbErrors)
 
558
        return errors
 
559
 
 
560
 
 
561
class InputStreamTestMixin(WSGITestsMixin):
 
562
    """
 
563
    A mixin for L{TestCase} subclasses which defines a number of tests against
 
564
    L{_InputStream}.  The subclass is expected to create a file-like object to
 
565
    be wrapped by an L{_InputStream} under test.
 
566
    """
 
567
    def getFileType(self):
 
568
        raise NotImplementedError(
 
569
            "%s.getFile must be implemented" % (self.__class__.__name__,))
 
570
 
 
571
 
 
572
    def _renderAndReturnReaderResult(self, reader, content):
 
573
        contentType = self.getFileType()
 
574
        class CustomizedRequest(Request):
 
575
            def gotLength(self, length):
 
576
                # Always allocate a file of the specified type, instead of
 
577
                # using the base behavior of selecting one depending on the
 
578
                # length.
 
579
                self.content = contentType()
 
580
 
 
581
        def appFactoryFactory(reader):
 
582
            result = Deferred()
 
583
            def applicationFactory():
 
584
                def application(*args):
 
585
                    environ, startResponse = args
 
586
                    result.callback(reader(environ['wsgi.input']))
 
587
                    startResponse('200 OK', [])
 
588
                    return iter(())
 
589
                return application
 
590
            return result, applicationFactory
 
591
        d, appFactory = appFactoryFactory(reader)
 
592
        self.lowLevelRender(
 
593
            CustomizedRequest, appFactory, DummyChannel,
 
594
            'PUT', '1.1', [], [''], None, [],
 
595
            content)
 
596
        return d
 
597
 
 
598
 
 
599
    def test_readAll(self):
 
600
        """
 
601
        Calling L{_InputStream.read} with no arguments returns the entire input
 
602
        stream.
 
603
        """
 
604
        bytes = "some bytes are here"
 
605
        d = self._renderAndReturnReaderResult(lambda input: input.read(), bytes)
 
606
        d.addCallback(self.assertEquals, bytes)
 
607
        return d
 
608
 
 
609
 
 
610
    def test_readSome(self):
 
611
        """
 
612
        Calling L{_InputStream.read} with an integer returns that many bytes
 
613
        from the input stream, as long as it is less than or equal to the total
 
614
        number of bytes available.
 
615
        """
 
616
        bytes = "hello, world."
 
617
        d = self._renderAndReturnReaderResult(lambda input: input.read(3), bytes)
 
618
        d.addCallback(self.assertEquals, "hel")
 
619
        return d
 
620
 
 
621
 
 
622
    def test_readMoreThan(self):
 
623
        """
 
624
        Calling L{_InputStream.read} with an integer that is greater than the
 
625
        total number of bytes in the input stream returns all bytes in the
 
626
        input stream.
 
627
        """
 
628
        bytes = "some bytes are here"
 
629
        d = self._renderAndReturnReaderResult(
 
630
            lambda input: input.read(len(bytes) + 3), bytes)
 
631
        d.addCallback(self.assertEquals, bytes)
 
632
        return d
 
633
 
 
634
 
 
635
    def test_readTwice(self):
 
636
        """
 
637
        Calling L{_InputStream.read} a second time returns bytes starting from
 
638
        the position after the last byte returned by the previous read.
 
639
        """
 
640
        bytes = "some bytes, hello"
 
641
        def read(input):
 
642
            input.read(3)
 
643
            return input.read()
 
644
        d = self._renderAndReturnReaderResult(read, bytes)
 
645
        d.addCallback(self.assertEquals, bytes[3:])
 
646
        return d
 
647
 
 
648
 
 
649
    def test_readNone(self):
 
650
        """
 
651
        Calling L{_InputStream.read} with C{None} as an argument returns all
 
652
        bytes in the input stream.
 
653
        """
 
654
        bytes = "the entire stream"
 
655
        d = self._renderAndReturnReaderResult(
 
656
            lambda input: input.read(None), bytes)
 
657
        d.addCallback(self.assertEquals, bytes)
 
658
        return d
 
659
 
 
660
 
 
661
    def test_readNegative(self):
 
662
        """
 
663
        Calling L{_InputStream.read} with a negative integer as an argument
 
664
        returns all bytes in the input stream.
 
665
        """
 
666
        bytes = "all of the input"
 
667
        d = self._renderAndReturnReaderResult(
 
668
            lambda input: input.read(-1), bytes)
 
669
        d.addCallback(self.assertEquals, bytes)
 
670
        return d
 
671
 
 
672
 
 
673
    def test_readline(self):
 
674
        """
 
675
        Calling L{_InputStream.readline} with no argument returns one line from
 
676
        the input stream.
 
677
        """
 
678
        bytes = "hello\nworld"
 
679
        d = self._renderAndReturnReaderResult(
 
680
            lambda input: input.readline(), bytes)
 
681
        d.addCallback(self.assertEquals, "hello\n")
 
682
        return d
 
683
 
 
684
 
 
685
    def test_readlineSome(self):
 
686
        """
 
687
        Calling L{_InputStream.readline} with an integer returns at most that
 
688
        many bytes, even if it is not enough to make up a complete line.
 
689
 
 
690
        COMPATIBILITY NOTE: the size argument is excluded from the WSGI
 
691
        specification, but is provided here anyhow, because useful libraries
 
692
        such as python stdlib's cgi.py assume their input file-like-object
 
693
        supports readline with a size argument. If you use it, be aware your
 
694
        application may not be portable to other conformant WSGI servers.
 
695
        """
 
696
        bytes = "goodbye\nworld"
 
697
        d = self._renderAndReturnReaderResult(
 
698
            lambda input: input.readline(3), bytes)
 
699
        d.addCallback(self.assertEquals, "goo")
 
700
        return d
 
701
 
 
702
 
 
703
    def test_readlineMoreThan(self):
 
704
        """
 
705
        Calling L{_InputStream.readline} with an integer which is greater than
 
706
        the number of bytes in the next line returns only the next line.
 
707
        """
 
708
        bytes = "some lines\nof text"
 
709
        d = self._renderAndReturnReaderResult(
 
710
            lambda input: input.readline(20), bytes)
 
711
        d.addCallback(self.assertEquals, "some lines\n")
 
712
        return d
 
713
 
 
714
 
 
715
    def test_readlineTwice(self):
 
716
        """
 
717
        Calling L{_InputStream.readline} a second time returns the line
 
718
        following the line returned by the first call.
 
719
        """
 
720
        bytes = "first line\nsecond line\nlast line"
 
721
        def readline(input):
 
722
            input.readline()
 
723
            return input.readline()
 
724
        d = self._renderAndReturnReaderResult(readline, bytes)
 
725
        d.addCallback(self.assertEquals, "second line\n")
 
726
        return d
 
727
 
 
728
 
 
729
    def test_readlineNone(self):
 
730
        """
 
731
        Calling L{_InputStream.readline} with C{None} as an argument returns
 
732
        one line from the input stream.
 
733
        """
 
734
        bytes = "this is one line\nthis is another line"
 
735
        d = self._renderAndReturnReaderResult(
 
736
            lambda input: input.readline(None), bytes)
 
737
        d.addCallback(self.assertEquals, "this is one line\n")
 
738
        return d
 
739
 
 
740
 
 
741
    def test_readlineNegative(self):
 
742
        """
 
743
        Calling L{_InputStream.readline} with a negative integer as an argument
 
744
        returns one line from the input stream.
 
745
        """
 
746
        bytes = "input stream line one\nline two"
 
747
        d = self._renderAndReturnReaderResult(
 
748
            lambda input: input.readline(-1), bytes)
 
749
        d.addCallback(self.assertEquals, "input stream line one\n")
 
750
        return d
 
751
 
 
752
 
 
753
    def test_readlines(self):
 
754
        """
 
755
        Calling L{_InputStream.readlines} with no arguments returns a list of
 
756
        all lines from the input stream.
 
757
        """
 
758
        bytes = "alice\nbob\ncarol"
 
759
        d = self._renderAndReturnReaderResult(
 
760
            lambda input: input.readlines(), bytes)
 
761
        d.addCallback(self.assertEquals, ["alice\n", "bob\n", "carol"])
 
762
        return d
 
763
 
 
764
 
 
765
    def test_readlinesSome(self):
 
766
        """
 
767
        Calling L{_InputStream.readlines} with an integer as an argument
 
768
        returns a list of lines from the input stream with the argument serving
 
769
        as an approximate bound on the total number of bytes to read.
 
770
        """
 
771
        bytes = "123\n456\n789\n0"
 
772
        d = self._renderAndReturnReaderResult(
 
773
            lambda input: input.readlines(5), bytes)
 
774
        def cbLines(lines):
 
775
            # Make sure we got enough lines to make 5 bytes.  Anything beyond
 
776
            # that is fine too.
 
777
            self.assertEquals(lines[:2], ["123\n", "456\n"])
 
778
        d.addCallback(cbLines)
 
779
        return d
 
780
 
 
781
 
 
782
    def test_readlinesMoreThan(self):
 
783
        """
 
784
        Calling L{_InputStream.readlines} with an integer which is greater than
 
785
        the total number of bytes in the input stream returns a list of all
 
786
        lines from the input.
 
787
        """
 
788
        bytes = "one potato\ntwo potato\nthree potato"
 
789
        d = self._renderAndReturnReaderResult(
 
790
            lambda input: input.readlines(100), bytes)
 
791
        d.addCallback(
 
792
            self.assertEquals,
 
793
            ["one potato\n", "two potato\n", "three potato"])
 
794
        return d
 
795
 
 
796
 
 
797
    def test_readlinesAfterRead(self):
 
798
        """
 
799
        Calling L{_InputStream.readlines} after a call to L{_InputStream.read}
 
800
        returns lines starting at the byte after the last byte returned by the
 
801
        C{read} call.
 
802
        """
 
803
        bytes = "hello\nworld\nfoo"
 
804
        def readlines(input):
 
805
            input.read(7)
 
806
            return input.readlines()
 
807
        d = self._renderAndReturnReaderResult(readlines, bytes)
 
808
        d.addCallback(self.assertEquals, ["orld\n", "foo"])
 
809
        return d
 
810
 
 
811
 
 
812
    def test_readlinesNone(self):
 
813
        """
 
814
        Calling L{_InputStream.readlines} with C{None} as an argument returns
 
815
        all lines from the input.
 
816
        """
 
817
        bytes = "one fish\ntwo fish\n"
 
818
        d = self._renderAndReturnReaderResult(
 
819
            lambda input: input.readlines(None), bytes)
 
820
        d.addCallback(self.assertEquals, ["one fish\n", "two fish\n"])
 
821
        return d
 
822
 
 
823
 
 
824
    def test_readlinesNegative(self):
 
825
        """
 
826
        Calling L{_InputStream.readlines} with a negative integer as an
 
827
        argument returns a list of all lines from the input.
 
828
        """
 
829
        bytes = "red fish\nblue fish\n"
 
830
        d = self._renderAndReturnReaderResult(
 
831
            lambda input: input.readlines(-1), bytes)
 
832
        d.addCallback(self.assertEquals, ["red fish\n", "blue fish\n"])
 
833
        return d
 
834
 
 
835
 
 
836
    def test_iterable(self):
 
837
        """
 
838
        Iterating over L{_InputStream} produces lines from the input stream.
 
839
        """
 
840
        bytes = "green eggs\nand ham\n"
 
841
        d = self._renderAndReturnReaderResult(lambda input: list(input), bytes)
 
842
        d.addCallback(self.assertEquals, ["green eggs\n", "and ham\n"])
 
843
        return d
 
844
 
 
845
 
 
846
    def test_iterableAfterRead(self):
 
847
        """
 
848
        Iterating over L{_InputStream} after calling L{_InputStream.read}
 
849
        produces lines from the input stream starting from the first byte after
 
850
        the last byte returned by the C{read} call.
 
851
        """
 
852
        bytes = "green eggs\nand ham\n"
 
853
        def iterate(input):
 
854
            input.read(3)
 
855
            return list(input)
 
856
        d = self._renderAndReturnReaderResult(iterate, bytes)
 
857
        d.addCallback(self.assertEquals, ["en eggs\n", "and ham\n"])
 
858
        return d
 
859
 
 
860
 
 
861
 
 
862
class InputStreamStringIOTests(InputStreamTestMixin, TestCase):
 
863
    """
 
864
    Tests for L{_InputStream} when it is wrapped around a L{StringIO.StringIO}.
 
865
    """
 
866
    def getFileType(self):
 
867
        return StringIO.StringIO
 
868
 
 
869
 
 
870
 
 
871
class InputStreamCStringIOTests(InputStreamTestMixin, TestCase):
 
872
    """
 
873
    Tests for L{_InputStream} when it is wrapped around a
 
874
    L{cStringIO.StringIO}.
 
875
    """
 
876
    def getFileType(self):
 
877
        return cStringIO.StringIO
 
878
 
 
879
 
 
880
 
 
881
class InputStreamTemporaryFileTests(InputStreamTestMixin, TestCase):
 
882
    """
 
883
    Tests for L{_InputStream} when it is wrapped around a L{tempfile.TemporaryFile}.
 
884
    """
 
885
    def getFileType(self):
 
886
        return tempfile.TemporaryFile
 
887
 
 
888
 
 
889
 
 
890
class StartResponseTests(WSGITestsMixin, TestCase):
 
891
    """
 
892
    Tests for the I{start_response} parameter passed to the application object
 
893
    by L{WSGIResource}.
 
894
    """
 
895
    def test_status(self):
 
896
        """
 
897
        The response status passed to the I{start_response} callable is written
 
898
        as the status of the response to the request.
 
899
        """
 
900
        channel = DummyChannel()
 
901
 
 
902
        def applicationFactory():
 
903
            def application(environ, startResponse):
 
904
                startResponse('107 Strange message', [])
 
905
                return iter(())
 
906
            return application
 
907
 
 
908
        d, requestFactory = self.requestFactoryFactory()
 
909
        def cbRendered(ignored):
 
910
            self.assertTrue(
 
911
                channel.transport.written.getvalue().startswith(
 
912
                    'HTTP/1.1 107 Strange message'))
 
913
        d.addCallback(cbRendered)
 
914
 
 
915
        request = self.lowLevelRender(
 
916
            requestFactory, applicationFactory,
 
917
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
918
 
 
919
        return d
 
920
 
 
921
 
 
922
    def _headersTest(self, appHeaders, expectedHeaders):
 
923
        """
 
924
        Verify that if the response headers given by C{appHeaders} are passed
 
925
        to the I{start_response} callable, then the response header lines given
 
926
        by C{expectedHeaders} plus I{Server} and I{Date} header lines are
 
927
        included in the response.
 
928
        """
 
929
        # Make the Date header value deterministic
 
930
        self.patch(http, 'datetimeToString', lambda: 'Tuesday')
 
931
 
 
932
        channel = DummyChannel()
 
933
 
 
934
        def applicationFactory():
 
935
            def application(environ, startResponse):
 
936
                startResponse('200 OK', appHeaders)
 
937
                return iter(())
 
938
            return application
 
939
 
 
940
        d, requestFactory = self.requestFactoryFactory()
 
941
        def cbRendered(ignored):
 
942
            response = channel.transport.written.getvalue()
 
943
            headers, rest = response.split('\r\n\r\n', 1)
 
944
            headerLines = headers.split('\r\n')[1:]
 
945
            headerLines.sort()
 
946
            allExpectedHeaders = expectedHeaders + [
 
947
                'Date: Tuesday',
 
948
                'Server: ' + version,
 
949
                'Transfer-Encoding: chunked']
 
950
            allExpectedHeaders.sort()
 
951
            self.assertEqual(headerLines, allExpectedHeaders)
 
952
 
 
953
        d.addCallback(cbRendered)
 
954
 
 
955
        request = self.lowLevelRender(
 
956
            requestFactory, applicationFactory,
 
957
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
958
        return d
 
959
 
 
960
 
 
961
    def test_headers(self):
 
962
        """
 
963
        The headers passed to the I{start_response} callable are included in
 
964
        the response as are the required I{Date} and I{Server} headers and the
 
965
        necessary connection (hop to hop) header I{Transfer-Encoding}.
 
966
        """
 
967
        return self._headersTest(
 
968
            [('foo', 'bar'), ('baz', 'quux')],
 
969
            ['Baz: quux', 'Foo: bar'])
 
970
 
 
971
 
 
972
    def test_applicationProvidedContentType(self):
 
973
        """
 
974
        If I{Content-Type} is included in the headers passed to the
 
975
        I{start_response} callable, one I{Content-Type} header is included in
 
976
        the response.
 
977
        """
 
978
        return self._headersTest(
 
979
            [('content-type', 'monkeys are great')],
 
980
            ['Content-Type: monkeys are great'])
 
981
 
 
982
 
 
983
    def test_applicationProvidedServerAndDate(self):
 
984
        """
 
985
        If either I{Server} or I{Date} is included in the headers passed to the
 
986
        I{start_response} callable, they are disregarded.
 
987
        """
 
988
        return self._headersTest(
 
989
            [('server', 'foo'), ('Server', 'foo'),
 
990
             ('date', 'bar'), ('dATE', 'bar')],
 
991
            [])
 
992
 
 
993
 
 
994
    def test_delayedUntilReturn(self):
 
995
        """
 
996
        Nothing is written in response to a request when the I{start_response}
 
997
        callable is invoked.  If the iterator returned by the application
 
998
        object produces only empty strings, the response is written after the
 
999
        last element is produced.
 
1000
        """
 
1001
        channel = DummyChannel()
 
1002
 
 
1003
        intermediateValues = []
 
1004
        def record():
 
1005
            intermediateValues.append(channel.transport.written.getvalue())
 
1006
 
 
1007
        def applicationFactory():
 
1008
            def application(environ, startResponse):
 
1009
                startResponse('200 OK', [('foo', 'bar'), ('baz', 'quux')])
 
1010
                yield ''
 
1011
                record()
 
1012
            return application
 
1013
 
 
1014
        d, requestFactory = self.requestFactoryFactory()
 
1015
        def cbRendered(ignored):
 
1016
            self.assertEqual(intermediateValues, [''])
 
1017
        d.addCallback(cbRendered)
 
1018
 
 
1019
        request = self.lowLevelRender(
 
1020
            requestFactory, applicationFactory,
 
1021
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
1022
 
 
1023
        return d
 
1024
 
 
1025
 
 
1026
    def test_delayedUntilContent(self):
 
1027
        """
 
1028
        Nothing is written in response to a request when the I{start_response}
 
1029
        callable is invoked.  Once a non-empty string has been produced by the
 
1030
        iterator returned by the application object, the response status and
 
1031
        headers are written.
 
1032
        """
 
1033
        channel = DummyChannel()
 
1034
 
 
1035
        intermediateValues = []
 
1036
        def record():
 
1037
            intermediateValues.append(channel.transport.written.getvalue())
 
1038
 
 
1039
        def applicationFactory():
 
1040
            def application(environ, startResponse):
 
1041
                startResponse('200 OK', [('foo', 'bar')])
 
1042
                yield ''
 
1043
                record()
 
1044
                yield 'foo'
 
1045
                record()
 
1046
            return application
 
1047
 
 
1048
        d, requestFactory = self.requestFactoryFactory()
 
1049
        def cbRendered(ignored):
 
1050
            self.assertFalse(intermediateValues[0])
 
1051
            self.assertTrue(intermediateValues[1])
 
1052
        d.addCallback(cbRendered)
 
1053
 
 
1054
        request = self.lowLevelRender(
 
1055
            requestFactory, applicationFactory,
 
1056
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
1057
 
 
1058
        return d
 
1059
 
 
1060
 
 
1061
    def test_content(self):
 
1062
        """
 
1063
        Content produced by the iterator returned by the application object is
 
1064
        written to the request as it is produced.
 
1065
        """
 
1066
        channel = DummyChannel()
 
1067
 
 
1068
        intermediateValues = []
 
1069
        def record():
 
1070
            intermediateValues.append(channel.transport.written.getvalue())
 
1071
 
 
1072
        def applicationFactory():
 
1073
            def application(environ, startResponse):
 
1074
                startResponse('200 OK', [('content-length', '6')])
 
1075
                yield 'foo'
 
1076
                record()
 
1077
                yield 'bar'
 
1078
                record()
 
1079
            return application
 
1080
 
 
1081
        d, requestFactory = self.requestFactoryFactory()
 
1082
        def cbRendered(ignored):
 
1083
            self.assertEqual(
 
1084
                self.getContentFromResponse(intermediateValues[0]),
 
1085
                'foo')
 
1086
            self.assertEqual(
 
1087
                self.getContentFromResponse(intermediateValues[1]),
 
1088
                'foobar')
 
1089
        d.addCallback(cbRendered)
 
1090
 
 
1091
        request = self.lowLevelRender(
 
1092
            requestFactory, applicationFactory,
 
1093
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
1094
 
 
1095
        return d
 
1096
 
 
1097
 
 
1098
    def test_multipleStartResponse(self):
 
1099
        """
 
1100
        If the I{start_response} callable is invoked multiple times before a
 
1101
        data for the response body is produced, the values from the last call
 
1102
        are used.
 
1103
        """
 
1104
        channel = DummyChannel()
 
1105
 
 
1106
        def applicationFactory():
 
1107
            def application(environ, startResponse):
 
1108
                startResponse('100 Foo', [])
 
1109
                startResponse('200 Bar', [])
 
1110
                return iter(())
 
1111
            return application
 
1112
 
 
1113
        d, requestFactory = self.requestFactoryFactory()
 
1114
        def cbRendered(ignored):
 
1115
            self.assertTrue(
 
1116
                channel.transport.written.getvalue().startswith(
 
1117
                    'HTTP/1.1 200 Bar\r\n'))
 
1118
        d.addCallback(cbRendered)
 
1119
 
 
1120
        request = self.lowLevelRender(
 
1121
            requestFactory, applicationFactory,
 
1122
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
1123
 
 
1124
        return d
 
1125
 
 
1126
 
 
1127
    def test_startResponseWithException(self):
 
1128
        """
 
1129
        If the I{start_response} callable is invoked with a third positional
 
1130
        argument before the status and headers have been written to the
 
1131
        response, the status and headers become the newly supplied values.
 
1132
        """
 
1133
        channel = DummyChannel()
 
1134
 
 
1135
        def applicationFactory():
 
1136
            def application(environ, startResponse):
 
1137
                startResponse('100 Foo', [], (Exception, Exception("foo"), None))
 
1138
                return iter(())
 
1139
            return application
 
1140
 
 
1141
        d, requestFactory = self.requestFactoryFactory()
 
1142
        def cbRendered(ignored):
 
1143
            self.assertTrue(
 
1144
                channel.transport.written.getvalue().startswith(
 
1145
                    'HTTP/1.1 100 Foo\r\n'))
 
1146
        d.addCallback(cbRendered)
 
1147
 
 
1148
        request = self.lowLevelRender(
 
1149
            requestFactory, applicationFactory,
 
1150
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
1151
 
 
1152
        return d
 
1153
 
 
1154
 
 
1155
    def test_startResponseWithExceptionTooLate(self):
 
1156
        """
 
1157
        If the I{start_response} callable is invoked with a third positional
 
1158
        argument after the status and headers have been written to the
 
1159
        response, the supplied I{exc_info} values are re-raised to the
 
1160
        application.
 
1161
        """
 
1162
        channel = DummyChannel()
 
1163
 
 
1164
        class SomeException(Exception):
 
1165
            pass
 
1166
 
 
1167
        try:
 
1168
            raise SomeException()
 
1169
        except:
 
1170
            excInfo = exc_info()
 
1171
 
 
1172
        reraised = []
 
1173
 
 
1174
        def applicationFactory():
 
1175
            def application(environ, startResponse):
 
1176
                startResponse('200 OK', [])
 
1177
                yield 'foo'
 
1178
                try:
 
1179
                    startResponse('500 ERR', [], excInfo)
 
1180
                except:
 
1181
                    reraised.append(exc_info())
 
1182
            return application
 
1183
 
 
1184
        d, requestFactory = self.requestFactoryFactory()
 
1185
        def cbRendered(ignored):
 
1186
            self.assertTrue(
 
1187
                channel.transport.written.getvalue().startswith(
 
1188
                    'HTTP/1.1 200 OK\r\n'))
 
1189
            self.assertEqual(reraised[0][0], excInfo[0])
 
1190
            self.assertEqual(reraised[0][1], excInfo[1])
 
1191
            self.assertEqual(reraised[0][2].tb_next, excInfo[2])
 
1192
 
 
1193
        d.addCallback(cbRendered)
 
1194
 
 
1195
        request = self.lowLevelRender(
 
1196
            requestFactory, applicationFactory,
 
1197
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
1198
 
 
1199
        return d
 
1200
 
 
1201
 
 
1202
    def test_write(self):
 
1203
        """
 
1204
        I{start_response} returns the I{write} callable which can be used to
 
1205
        write bytes to the response body without buffering.
 
1206
        """
 
1207
        channel = DummyChannel()
 
1208
 
 
1209
        intermediateValues = []
 
1210
        def record():
 
1211
            intermediateValues.append(channel.transport.written.getvalue())
 
1212
 
 
1213
        def applicationFactory():
 
1214
            def application(environ, startResponse):
 
1215
                write = startResponse('100 Foo', [('content-length', '6')])
 
1216
                write('foo')
 
1217
                record()
 
1218
                write('bar')
 
1219
                record()
 
1220
                return iter(())
 
1221
            return application
 
1222
 
 
1223
        d, requestFactory = self.requestFactoryFactory()
 
1224
        def cbRendered(ignored):
 
1225
            self.assertEqual(
 
1226
                self.getContentFromResponse(intermediateValues[0]),
 
1227
                'foo')
 
1228
            self.assertEqual(
 
1229
                self.getContentFromResponse(intermediateValues[1]),
 
1230
                'foobar')
 
1231
        d.addCallback(cbRendered)
 
1232
 
 
1233
        request = self.lowLevelRender(
 
1234
            requestFactory, applicationFactory,
 
1235
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
1236
 
 
1237
        return d
 
1238
 
 
1239
 
 
1240
 
 
1241
class ApplicationTests(WSGITestsMixin, TestCase):
 
1242
    """
 
1243
    Tests for things which are done to the application object and the iterator
 
1244
    it returns.
 
1245
    """
 
1246
    def enableThreads(self):
 
1247
        self.reactor = reactor
 
1248
        self.threadpool = ThreadPool()
 
1249
        self.threadpool.start()
 
1250
        self.addCleanup(self.threadpool.stop)
 
1251
 
 
1252
 
 
1253
    def test_close(self):
 
1254
        """
 
1255
        If the application object returns an iterator which also has a I{close}
 
1256
        method, that method is called after iteration is complete.
 
1257
        """
 
1258
        channel = DummyChannel()
 
1259
 
 
1260
        class Result:
 
1261
            def __init__(self):
 
1262
                self.open = True
 
1263
 
 
1264
            def __iter__(self):
 
1265
                for i in range(3):
 
1266
                    if self.open:
 
1267
                        yield str(i)
 
1268
 
 
1269
            def close(self):
 
1270
                self.open = False
 
1271
 
 
1272
        result = Result()
 
1273
        def applicationFactory():
 
1274
            def application(environ, startResponse):
 
1275
                startResponse('200 OK', [('content-length', '3')])
 
1276
                return result
 
1277
            return application
 
1278
 
 
1279
        d, requestFactory = self.requestFactoryFactory()
 
1280
        def cbRendered(ignored):
 
1281
            self.assertEqual(
 
1282
                self.getContentFromResponse(
 
1283
                    channel.transport.written.getvalue()),
 
1284
                '012')
 
1285
            self.assertFalse(result.open)
 
1286
        d.addCallback(cbRendered)
 
1287
 
 
1288
        self.lowLevelRender(
 
1289
            requestFactory, applicationFactory,
 
1290
            lambda: channel, 'GET', '1.1', [], [''])
 
1291
 
 
1292
        return d
 
1293
 
 
1294
 
 
1295
    def test_applicationCalledInThread(self):
 
1296
        """
 
1297
        The application object is invoked and iterated in a thread which is not
 
1298
        the reactor thread.
 
1299
        """
 
1300
        self.enableThreads()
 
1301
        invoked = []
 
1302
 
 
1303
        def applicationFactory():
 
1304
            def application(environ, startResponse):
 
1305
                def result():
 
1306
                    for i in range(3):
 
1307
                        invoked.append(get_ident())
 
1308
                        yield str(i)
 
1309
                invoked.append(get_ident())
 
1310
                startResponse('200 OK', [('content-length', '3')])
 
1311
                return result()
 
1312
            return application
 
1313
 
 
1314
        d, requestFactory = self.requestFactoryFactory()
 
1315
        def cbRendered(ignored):
 
1316
            self.assertNotIn(get_ident(), invoked)
 
1317
            self.assertEqual(len(set(invoked)), 1)
 
1318
        d.addCallback(cbRendered)
 
1319
 
 
1320
        self.lowLevelRender(
 
1321
            requestFactory, applicationFactory,
 
1322
            DummyChannel, 'GET', '1.1', [], [''])
 
1323
 
 
1324
        return d
 
1325
 
 
1326
 
 
1327
    def test_writeCalledFromThread(self):
 
1328
        """
 
1329
        The I{write} callable returned by I{start_response} calls the request's
 
1330
        C{write} method in the reactor thread.
 
1331
        """
 
1332
        self.enableThreads()
 
1333
        invoked = []
 
1334
 
 
1335
        class ThreadVerifier(Request):
 
1336
            def write(self, bytes):
 
1337
                invoked.append(get_ident())
 
1338
                return Request.write(self, bytes)
 
1339
 
 
1340
        def applicationFactory():
 
1341
            def application(environ, startResponse):
 
1342
                write = startResponse('200 OK', [])
 
1343
                write('foo')
 
1344
                return iter(())
 
1345
            return application
 
1346
 
 
1347
        d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
 
1348
        def cbRendered(ignored):
 
1349
            self.assertEqual(set(invoked), set([get_ident()]))
 
1350
        d.addCallback(cbRendered)
 
1351
 
 
1352
        self.lowLevelRender(
 
1353
            requestFactory, applicationFactory, DummyChannel,
 
1354
            'GET', '1.1', [], [''])
 
1355
 
 
1356
        return d
 
1357
 
 
1358
 
 
1359
    def test_iteratedValuesWrittenFromThread(self):
 
1360
        """
 
1361
        Strings produced by the iterator returned by the application object are
 
1362
        written to the request in the reactor thread.
 
1363
        """
 
1364
        self.enableThreads()
 
1365
        invoked = []
 
1366
 
 
1367
        class ThreadVerifier(Request):
 
1368
            def write(self, bytes):
 
1369
                invoked.append(get_ident())
 
1370
                return Request.write(self, bytes)
 
1371
 
 
1372
        def applicationFactory():
 
1373
            def application(environ, startResponse):
 
1374
                startResponse('200 OK', [])
 
1375
                yield 'foo'
 
1376
            return application
 
1377
 
 
1378
        d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
 
1379
        def cbRendered(ignored):
 
1380
            self.assertEqual(set(invoked), set([get_ident()]))
 
1381
        d.addCallback(cbRendered)
 
1382
 
 
1383
        self.lowLevelRender(
 
1384
            requestFactory, applicationFactory, DummyChannel,
 
1385
            'GET', '1.1', [], [''])
 
1386
 
 
1387
        return d
 
1388
 
 
1389
 
 
1390
    def test_statusWrittenFromThread(self):
 
1391
        """
 
1392
        The response status is set on the request object in the reactor thread.
 
1393
        """
 
1394
        self.enableThreads()
 
1395
        invoked = []
 
1396
 
 
1397
        class ThreadVerifier(Request):
 
1398
            def setResponseCode(self, code, message):
 
1399
                invoked.append(get_ident())
 
1400
                return Request.setResponseCode(self, code, message)
 
1401
 
 
1402
        def applicationFactory():
 
1403
            def application(environ, startResponse):
 
1404
                startResponse('200 OK', [])
 
1405
                return iter(())
 
1406
            return application
 
1407
 
 
1408
        d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
 
1409
        def cbRendered(ignored):
 
1410
            self.assertEqual(set(invoked), set([get_ident()]))
 
1411
        d.addCallback(cbRendered)
 
1412
 
 
1413
        self.lowLevelRender(
 
1414
            requestFactory, applicationFactory, DummyChannel,
 
1415
            'GET', '1.1', [], [''])
 
1416
 
 
1417
        return d
 
1418
 
 
1419
 
 
1420
    def test_connectionClosedDuringIteration(self):
 
1421
        """
 
1422
        If the request connection is lost while the application object is being
 
1423
        iterated, iteration is stopped.
 
1424
        """
 
1425
        class UnreliableConnection(Request):
 
1426
            """
 
1427
            This is a request which pretends its connection is lost immediately
 
1428
            after the first write is done to it.
 
1429
            """
 
1430
            def write(self, bytes):
 
1431
                self.connectionLost(Failure(ConnectionLost("No more connection")))
 
1432
 
 
1433
        self.badIter = False
 
1434
        def appIter():
 
1435
            yield "foo"
 
1436
            self.badIter = True
 
1437
            raise Exception("Should not have gotten here")
 
1438
 
 
1439
        def applicationFactory():
 
1440
            def application(environ, startResponse):
 
1441
                startResponse('200 OK', [])
 
1442
                return appIter()
 
1443
            return application
 
1444
 
 
1445
        d, requestFactory = self.requestFactoryFactory(UnreliableConnection)
 
1446
        def cbRendered(ignored):
 
1447
            self.assertFalse(self.badIter, "Should not have resumed iteration")
 
1448
        d.addCallback(cbRendered)
 
1449
 
 
1450
        self.lowLevelRender(
 
1451
            requestFactory, applicationFactory, DummyChannel,
 
1452
            'GET', '1.1', [], [''])
 
1453
 
 
1454
        return self.assertFailure(d, ConnectionLost)
 
1455
 
 
1456
 
 
1457
    def _internalServerErrorTest(self, application):
 
1458
        channel = DummyChannel()
 
1459
 
 
1460
        def applicationFactory():
 
1461
            return application
 
1462
 
 
1463
        d, requestFactory = self.requestFactoryFactory()
 
1464
        def cbRendered(ignored):
 
1465
            errors = self.flushLoggedErrors(RuntimeError)
 
1466
            self.assertEquals(len(errors), 1)
 
1467
 
 
1468
            self.assertTrue(
 
1469
                channel.transport.written.getvalue().startswith(
 
1470
                    'HTTP/1.1 500 Internal Server Error'))
 
1471
        d.addCallback(cbRendered)
 
1472
 
 
1473
        request = self.lowLevelRender(
 
1474
            requestFactory, applicationFactory,
 
1475
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
1476
 
 
1477
        return d
 
1478
 
 
1479
 
 
1480
    def test_applicationExceptionBeforeStartResponse(self):
 
1481
        """
 
1482
        If the application raises an exception before calling I{start_response}
 
1483
        then the response status is I{500} and the exception is logged.
 
1484
        """
 
1485
        def application(environ, startResponse):
 
1486
            raise RuntimeError("This application had some error.")
 
1487
        return self._internalServerErrorTest(application)
 
1488
 
 
1489
 
 
1490
    def test_applicationExceptionAfterStartResponse(self):
 
1491
        """
 
1492
        If the application calls I{start_response} but then raises an exception
 
1493
        before any data is written to the response then the response status is
 
1494
        I{500} and the exception is logged.
 
1495
        """
 
1496
        def application(environ, startResponse):
 
1497
            startResponse('200 OK', [])
 
1498
            raise RuntimeError("This application had some error.")
 
1499
        return self._internalServerErrorTest(application)
 
1500
 
 
1501
 
 
1502
    def _connectionClosedTest(self, application, responseContent):
 
1503
        channel = DummyChannel()
 
1504
 
 
1505
        def applicationFactory():
 
1506
            return application
 
1507
 
 
1508
        d, requestFactory = self.requestFactoryFactory()
 
1509
 
 
1510
        # Capture the request so we can disconnect it later on.
 
1511
        requests = []
 
1512
        def requestFactoryWrapper(*a, **kw):
 
1513
            requests.append(requestFactory(*a, **kw))
 
1514
            return requests[-1]
 
1515
 
 
1516
        def ebRendered(ignored):
 
1517
            errors = self.flushLoggedErrors(RuntimeError)
 
1518
            self.assertEquals(len(errors), 1)
 
1519
 
 
1520
            response = channel.transport.written.getvalue()
 
1521
            self.assertTrue(response.startswith('HTTP/1.1 200 OK'))
 
1522
            # Chunked transfer-encoding makes this a little messy.
 
1523
            self.assertIn(responseContent, response)
 
1524
        d.addErrback(ebRendered)
 
1525
 
 
1526
        request = self.lowLevelRender(
 
1527
            requestFactoryWrapper, applicationFactory,
 
1528
            lambda: channel, 'GET', '1.1', [], [''], None, [])
 
1529
 
 
1530
        # By now the connection should be closed.
 
1531
        self.assertTrue(channel.transport.disconnected)
 
1532
        # Give it a little push to go the rest of the way.
 
1533
        requests[0].connectionLost(Failure(ConnectionLost("All gone")))
 
1534
 
 
1535
        return d
 
1536
 
 
1537
 
 
1538
    def test_applicationExceptionAfterWrite(self):
 
1539
        """
 
1540
        If the application raises an exception after the response status has
 
1541
        already been sent then the connection is closed and the exception is
 
1542
        logged.
 
1543
        """
 
1544
        responseContent = (
 
1545
            'Some bytes, triggering the server to start sending the response')
 
1546
 
 
1547
        def application(environ, startResponse):
 
1548
            startResponse('200 OK', [])
 
1549
            yield responseContent
 
1550
            raise RuntimeError("This application had some error.")
 
1551
        return self._connectionClosedTest(application, responseContent)
 
1552
 
 
1553
 
 
1554
    def test_applicationCloseException(self):
 
1555
        """
 
1556
        If the application returns a closeable iterator and the C{close} method
 
1557
        raises an exception when called then the connection is still closed and
 
1558
        the exception is logged.
 
1559
        """
 
1560
        responseContent = 'foo'
 
1561
 
 
1562
        class Application(object):
 
1563
            def __init__(self, environ, startResponse):
 
1564
                startResponse('200 OK', [])
 
1565
 
 
1566
            def __iter__(self):
 
1567
                yield responseContent
 
1568
 
 
1569
            def close(self):
 
1570
                raise RuntimeError("This application had some error.")
 
1571
 
 
1572
        return self._connectionClosedTest(Application, responseContent)