~0x44/nova/extdoc

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/web/test/test_distrib.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-2010 Twisted Matrix Laboratories.
 
2
# See LICENSE for details.
 
3
 
 
4
"""
 
5
Tests for L{twisted.web.distrib}.
 
6
"""
 
7
 
 
8
from os.path import abspath
 
9
from xml.dom.minidom import parseString
 
10
try:
 
11
    import pwd
 
12
except ImportError:
 
13
    pwd = None
 
14
 
 
15
from zope.interface.verify import verifyObject
 
16
 
 
17
from twisted.python import log, filepath
 
18
from twisted.internet import reactor, defer
 
19
from twisted.trial import unittest
 
20
from twisted.spread import pb
 
21
from twisted.spread.banana import SIZE_LIMIT
 
22
from twisted.web import http, distrib, client, resource, static, server
 
23
from twisted.web.test.test_web import DummyRequest
 
24
from twisted.web.test._util import _render
 
25
 
 
26
 
 
27
class MySite(server.Site):
 
28
    def stopFactory(self):
 
29
        if hasattr(self, "logFile"):
 
30
            if self.logFile != log.logfile:
 
31
                self.logFile.close()
 
32
            del self.logFile
 
33
 
 
34
 
 
35
 
 
36
class PBServerFactory(pb.PBServerFactory):
 
37
    """
 
38
    A PB server factory which keeps track of the most recent protocol it
 
39
    created.
 
40
 
 
41
    @ivar proto: L{None} or the L{Broker} instance most recently returned
 
42
        from C{buildProtocol}.
 
43
    """
 
44
    proto = None
 
45
 
 
46
    def buildProtocol(self, addr):
 
47
        self.proto = pb.PBServerFactory.buildProtocol(self, addr)
 
48
        return self.proto
 
49
 
 
50
 
 
51
 
 
52
class DistribTest(unittest.TestCase):
 
53
    port1 = None
 
54
    port2 = None
 
55
    sub = None
 
56
    f1 = None
 
57
 
 
58
    def tearDown(self):
 
59
        """
 
60
        Clean up all the event sources left behind by either directly by
 
61
        test methods or indirectly via some distrib API.
 
62
        """
 
63
        dl = [defer.Deferred(), defer.Deferred()]
 
64
        if self.f1 is not None and self.f1.proto is not None:
 
65
            self.f1.proto.notifyOnDisconnect(lambda: dl[0].callback(None))
 
66
        else:
 
67
            dl[0].callback(None)
 
68
        if self.sub is not None and self.sub.publisher is not None:
 
69
            self.sub.publisher.broker.notifyOnDisconnect(
 
70
                lambda: dl[1].callback(None))
 
71
            self.sub.publisher.broker.transport.loseConnection()
 
72
        else:
 
73
            dl[1].callback(None)
 
74
        http._logDateTimeStop()
 
75
        if self.port1 is not None:
 
76
            dl.append(self.port1.stopListening())
 
77
        if self.port2 is not None:
 
78
            dl.append(self.port2.stopListening())
 
79
        return defer.gatherResults(dl)
 
80
 
 
81
 
 
82
    def testDistrib(self):
 
83
        # site1 is the publisher
 
84
        r1 = resource.Resource()
 
85
        r1.putChild("there", static.Data("root", "text/plain"))
 
86
        site1 = server.Site(r1)
 
87
        self.f1 = PBServerFactory(distrib.ResourcePublisher(site1))
 
88
        self.port1 = reactor.listenTCP(0, self.f1)
 
89
        self.sub = distrib.ResourceSubscription("127.0.0.1",
 
90
                                                self.port1.getHost().port)
 
91
        r2 = resource.Resource()
 
92
        r2.putChild("here", self.sub)
 
93
        f2 = MySite(r2)
 
94
        self.port2 = reactor.listenTCP(0, f2)
 
95
        d = client.getPage("http://127.0.0.1:%d/here/there" % \
 
96
                           self.port2.getHost().port)
 
97
        d.addCallback(self.failUnlessEqual, 'root')
 
98
        return d
 
99
 
 
100
 
 
101
    def _requestTest(self, child, **kwargs):
 
102
        """
 
103
        Set up a resource on a distrib site using L{ResourcePublisher} and
 
104
        then retrieve it from a L{ResourceSubscription} via an HTTP client.
 
105
 
 
106
        @param child: The resource to publish using distrib.
 
107
        @param **kwargs: Extra keyword arguments to pass to L{getPage} when
 
108
            requesting the resource.
 
109
 
 
110
        @return: A L{Deferred} which fires with the result of the request.
 
111
        """
 
112
        distribRoot = resource.Resource()
 
113
        distribRoot.putChild("child", child)
 
114
        distribSite = server.Site(distribRoot)
 
115
        self.f1 = distribFactory = PBServerFactory(
 
116
            distrib.ResourcePublisher(distribSite))
 
117
        distribPort = reactor.listenTCP(
 
118
            0, distribFactory, interface="127.0.0.1")
 
119
        self.addCleanup(distribPort.stopListening)
 
120
        addr = distribPort.getHost()
 
121
 
 
122
        self.sub = mainRoot = distrib.ResourceSubscription(
 
123
            addr.host, addr.port)
 
124
        mainSite = server.Site(mainRoot)
 
125
        mainPort = reactor.listenTCP(0, mainSite, interface="127.0.0.1")
 
126
        self.addCleanup(mainPort.stopListening)
 
127
        mainAddr = mainPort.getHost()
 
128
 
 
129
        return client.getPage("http://%s:%s/child" % (
 
130
            mainAddr.host, mainAddr.port), **kwargs)
 
131
 
 
132
 
 
133
 
 
134
    def test_requestHeaders(self):
 
135
        """
 
136
        The request headers are available on the request object passed to a
 
137
        distributed resource's C{render} method.
 
138
        """
 
139
        requestHeaders = {}
 
140
 
 
141
        class ReportRequestHeaders(resource.Resource):
 
142
            def render(self, request):
 
143
                requestHeaders.update(dict(
 
144
                    request.requestHeaders.getAllRawHeaders()))
 
145
                return ""
 
146
 
 
147
        request = self._requestTest(
 
148
            ReportRequestHeaders(), headers={'foo': 'bar'})
 
149
        def cbRequested(result):
 
150
            self.assertEquals(requestHeaders['Foo'], ['bar'])
 
151
        request.addCallback(cbRequested)
 
152
        return request
 
153
 
 
154
 
 
155
    def test_largeWrite(self):
 
156
        """
 
157
        If a string longer than the Banana size limit is passed to the
 
158
        L{distrib.Request} passed to the remote resource, it is broken into
 
159
        smaller strings to be transported over the PB connection.
 
160
        """
 
161
        class LargeWrite(resource.Resource):
 
162
            def render(self, request):
 
163
                request.write('x' * SIZE_LIMIT + 'y')
 
164
                request.finish()
 
165
                return server.NOT_DONE_YET
 
166
 
 
167
        request = self._requestTest(LargeWrite())
 
168
        request.addCallback(self.assertEquals, 'x' * SIZE_LIMIT + 'y')
 
169
        return request
 
170
 
 
171
 
 
172
    def test_largeReturn(self):
 
173
        """
 
174
        Like L{test_largeWrite}, but for the case where C{render} returns a
 
175
        long string rather than explicitly passing it to L{Request.write}.
 
176
        """
 
177
        class LargeReturn(resource.Resource):
 
178
            def render(self, request):
 
179
                return 'x' * SIZE_LIMIT + 'y'
 
180
 
 
181
        request = self._requestTest(LargeReturn())
 
182
        request.addCallback(self.assertEquals, 'x' * SIZE_LIMIT + 'y')
 
183
        return request
 
184
 
 
185
 
 
186
    def test_connectionLost(self):
 
187
        """
 
188
        If there is an error issuing the request to the remote publisher, an
 
189
        error response is returned.
 
190
        """
 
191
        # Using pb.Root as a publisher will cause request calls to fail with an
 
192
        # error every time.  Just what we want to test.
 
193
        self.f1 = serverFactory = PBServerFactory(pb.Root())
 
194
        self.port1 = serverPort = reactor.listenTCP(0, serverFactory)
 
195
 
 
196
        self.sub = subscription = distrib.ResourceSubscription(
 
197
            "127.0.0.1", serverPort.getHost().port)
 
198
        request = DummyRequest([''])
 
199
        d = _render(subscription, request)
 
200
        def cbRendered(ignored):
 
201
            self.assertEqual(request.responseCode, 500)
 
202
            # This is the error we caused the request to fail with.  It should
 
203
            # have been logged.
 
204
            self.assertEqual(len(self.flushLoggedErrors(pb.NoSuchMethod)), 1)
 
205
        d.addCallback(cbRendered)
 
206
        return d
 
207
 
 
208
 
 
209
 
 
210
class _PasswordDatabase:
 
211
    def __init__(self, users):
 
212
        self._users = users
 
213
 
 
214
 
 
215
    def getpwall(self):
 
216
        return iter(self._users)
 
217
 
 
218
 
 
219
    def getpwnam(self, username):
 
220
        for user in self._users:
 
221
            if user[0] == username:
 
222
                return user
 
223
        raise KeyError()
 
224
 
 
225
 
 
226
 
 
227
class UserDirectoryTests(unittest.TestCase):
 
228
    """
 
229
    Tests for L{UserDirectory}, a resource for listing all user resources
 
230
    available on a system.
 
231
    """
 
232
    def setUp(self):
 
233
        self.alice = ('alice', 'x', 123, 456, 'Alice,,,', self.mktemp(), '/bin/sh')
 
234
        self.bob = ('bob', 'x', 234, 567, 'Bob,,,', self.mktemp(), '/bin/sh')
 
235
        self.database = _PasswordDatabase([self.alice, self.bob])
 
236
        self.directory = distrib.UserDirectory(self.database)
 
237
 
 
238
 
 
239
    def test_interface(self):
 
240
        """
 
241
        L{UserDirectory} instances provide L{resource.IResource}.
 
242
        """
 
243
        self.assertTrue(verifyObject(resource.IResource, self.directory))
 
244
 
 
245
 
 
246
    def _404Test(self, name):
 
247
        """
 
248
        Verify that requesting the C{name} child of C{self.directory} results
 
249
        in a 404 response.
 
250
        """
 
251
        request = DummyRequest([name])
 
252
        result = self.directory.getChild(name, request)
 
253
        d = _render(result, request)
 
254
        def cbRendered(ignored):
 
255
            self.assertEqual(request.responseCode, 404)
 
256
        d.addCallback(cbRendered)
 
257
        return d
 
258
 
 
259
 
 
260
    def test_getInvalidUser(self):
 
261
        """
 
262
        L{UserDirectory.getChild} returns a resource which renders a 404
 
263
        response when passed a string which does not correspond to any known
 
264
        user.
 
265
        """
 
266
        return self._404Test('carol')
 
267
 
 
268
 
 
269
    def test_getUserWithoutResource(self):
 
270
        """
 
271
        L{UserDirectory.getChild} returns a resource which renders a 404
 
272
        response when passed a string which corresponds to a known user who has
 
273
        neither a user directory nor a user distrib socket.
 
274
        """
 
275
        return self._404Test('alice')
 
276
 
 
277
 
 
278
    def test_getPublicHTMLChild(self):
 
279
        """
 
280
        L{UserDirectory.getChild} returns a L{static.File} instance when passed
 
281
        the name of a user with a home directory containing a I{public_html}
 
282
        directory.
 
283
        """
 
284
        home = filepath.FilePath(self.bob[-2])
 
285
        public_html = home.child('public_html')
 
286
        public_html.makedirs()
 
287
        request = DummyRequest(['bob'])
 
288
        result = self.directory.getChild('bob', request)
 
289
        self.assertIsInstance(result, static.File)
 
290
        self.assertEqual(result.path, public_html.path)
 
291
 
 
292
 
 
293
    def test_getDistribChild(self):
 
294
        """
 
295
        L{UserDirectory.getChild} returns a L{ResourceSubscription} instance
 
296
        when passed the name of a user suffixed with C{".twistd"} who has a
 
297
        home directory containing a I{.twistd-web-pb} socket.
 
298
        """
 
299
        home = filepath.FilePath(self.bob[-2])
 
300
        home.makedirs()
 
301
        web = home.child('.twistd-web-pb')
 
302
        request = DummyRequest(['bob'])
 
303
        result = self.directory.getChild('bob.twistd', request)
 
304
        self.assertIsInstance(result, distrib.ResourceSubscription)
 
305
        self.assertEqual(result.host, 'unix')
 
306
        self.assertEqual(abspath(result.port), web.path)
 
307
 
 
308
 
 
309
    def test_invalidMethod(self):
 
310
        """
 
311
        L{UserDirectory.render} raises L{UnsupportedMethod} in response to a
 
312
        non-I{GET} request.
 
313
        """
 
314
        request = DummyRequest([''])
 
315
        request.method = 'POST'
 
316
        self.assertRaises(
 
317
            server.UnsupportedMethod, self.directory.render, request)
 
318
 
 
319
 
 
320
    def test_render(self):
 
321
        """
 
322
        L{UserDirectory} renders a list of links to available user content
 
323
        in response to a I{GET} request.
 
324
        """
 
325
        public_html = filepath.FilePath(self.alice[-2]).child('public_html')
 
326
        public_html.makedirs()
 
327
        web = filepath.FilePath(self.bob[-2])
 
328
        web.makedirs()
 
329
        # This really only works if it's a unix socket, but the implementation
 
330
        # doesn't currently check for that.  It probably should someday, and
 
331
        # then skip users with non-sockets.
 
332
        web.child('.twistd-web-pb').setContent("")
 
333
 
 
334
        request = DummyRequest([''])
 
335
        result = _render(self.directory, request)
 
336
        def cbRendered(ignored):
 
337
            document = parseString(''.join(request.written))
 
338
 
 
339
            # Each user should have an li with a link to their page.
 
340
            [alice, bob] = document.getElementsByTagName('li')
 
341
            self.assertEqual(alice.firstChild.tagName, 'a')
 
342
            self.assertEqual(alice.firstChild.getAttribute('href'), 'alice/')
 
343
            self.assertEqual(alice.firstChild.firstChild.data, 'Alice (file)')
 
344
            self.assertEqual(bob.firstChild.tagName, 'a')
 
345
            self.assertEqual(bob.firstChild.getAttribute('href'), 'bob.twistd/')
 
346
            self.assertEqual(bob.firstChild.firstChild.data, 'Bob (twistd)')
 
347
 
 
348
        result.addCallback(cbRendered)
 
349
        return result
 
350
 
 
351
 
 
352
    def test_passwordDatabase(self):
 
353
        """
 
354
        If L{UserDirectory} is instantiated with no arguments, it uses the
 
355
        L{pwd} module as its password database.
 
356
        """
 
357
        directory = distrib.UserDirectory()
 
358
        self.assertIdentical(directory._pwd, pwd)
 
359
    if pwd is None:
 
360
        test_passwordDatabase.skip = "pwd module required"
 
361