~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to lib/test/networktest.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import email.Utils
 
2
import socket
 
3
 
 
4
from miro import download_utils
 
5
from miro import net
 
6
from miro.test.framework import EventLoopTest, MiroTestCase
 
7
 
 
8
class TestingConnectionHandler(net.ConnectionHandler):
 
9
    def __init__(self, test):
 
10
        super(TestingConnectionHandler, self).__init__()
 
11
        self.states['foo'] = self.handleFoo
 
12
        self.states['bar'] = self.handleBar
 
13
        self.states['noread'] = None
 
14
        self.fooData = ''
 
15
        self.barData = ''
 
16
        self.gotHandleClose = False
 
17
        self.closeType = None
 
18
        self.test = test
 
19
 
 
20
    def handleFoo(self):
 
21
        data = self.buffer.read()
 
22
        self.fooData += data
 
23
        self.test.stopEventLoop(False)
 
24
 
 
25
    def handleBar(self):
 
26
        data = self.buffer.read()
 
27
        self.barData += data
 
28
        self.test.stopEventLoop(False)
 
29
 
 
30
    def handle_close(self, type):
 
31
        self.gotHandleClose = True
 
32
        self.closeType = type
 
33
        self.test.stopEventLoop(False)
 
34
 
 
35
class FakeStream:
 
36
    def __init__(self, closeCallback=None):
 
37
        self.open = False
 
38
        self.paused = False
 
39
        self.readCallback = None
 
40
        self.closeCallback = closeCallback
 
41
        self.timedOut = False
 
42
        self.connectionErrback = None
 
43
        self.name = ""
 
44
        self.output = ''
 
45
        self.unprocessed = ''
 
46
        self.input = ''
 
47
        self.pendingOutput = ''
 
48
        self.timedOut = False
 
49
        # to add a new page response, add the uri in the appropriate
 
50
        # host, then add the appropriate response in _generateResponse
 
51
        self.pages = {
 
52
            'pculture.org':
 
53
                {'/normalpage.txt': 'I AM A NORMAL PAGE\n',
 
54
                 '/normalpage2.txt': 'I AM A NORMAL PAGE\n',
 
55
                 '/normalpage3.txt': 'I AM A NORMAL PAGE\n',
 
56
                 '/nohead.php': 'DYNAMIC CONTENT',
 
57
                 '/cookie.php': 'normal page',
 
58
                 '/etag.txt': 'normal page',
 
59
                 '/BasicAuthentication/': 'normal page',
 
60
                 '/DigestAuthentication/': 'normal page',
 
61
                 '/secure.txt': 'Normal',
 
62
                 },
 
63
            'www.foo.com':
 
64
                {'/': "Normal", '/2': "Blah"},
 
65
            'www.bar.com':
 
66
                {'/': "Normal", '/2': "Blah"},
 
67
            'www.baz.com':
 
68
                {'/': "Normal", '/2': "Blah"},
 
69
            'www.froz.com':
 
70
                {'/': "Normal", '/2': "Blah"},
 
71
            'www.qux.com':
 
72
                {'/': "Normal", '/2': "Blah"},
 
73
            }
 
74
 
 
75
    def _tryReadCallback(self):
 
76
        if ((len(self.pendingOutput) > 0 and self.readCallback
 
77
             and not self.paused)):
 
78
            response = self.pendingOutput
 
79
            self.pendingOutput = ''
 
80
            self.readCallback(response)
 
81
 
 
82
    def pause(self):
 
83
        self.paused = True
 
84
 
 
85
    def unpause(self):
 
86
        self.paused = False
 
87
        self._tryReadCallback()
 
88
 
 
89
    def unpause_momentarily(self):
 
90
        self.paused = False
 
91
        self._tryReadCallback()
 
92
        self.paused = True
 
93
 
 
94
    def _generateResponse(self, method, uri, version, headers):
 
95
        text = None
 
96
        now = email.Utils.formatdate(usegmt=True)
 
97
        if not self.pages.has_key(headers["Host"]):
 
98
            self.errback(net.ConnectionError("Can't connect"))
 
99
            return None
 
100
 
 
101
        host_pages = self.pages[headers["Host"]]
 
102
        if host_pages.has_key(uri):
 
103
            text = host_pages[uri]
 
104
 
 
105
        if text is not None:
 
106
            if method == "GET":
 
107
                if ((uri == '/BasicAuthentication/'
 
108
                     and (not headers.has_key('Authorization')
 
109
                          or headers['Authorization'] != 'Basic Z3Vlc3Q6Z3Vlc3Q='))):
 
110
                    text = "Not authorized"
 
111
                    return "\r\n".join([
 
112
                            "HTTP/1.1 401 Unauthorized",
 
113
                            "WWW-Authenticate: Basic realm=\"test\"",
 
114
                            "Content-Type: text/html; charset=UTF-8",
 
115
                            "Date: %s" % now,
 
116
                            "Content-Length: %d" % len(text),
 
117
                            "",
 
118
                            text])
 
119
 
 
120
                elif ((uri == '/DigestAuthentication/'
 
121
                       and (not headers.has_key('Authorization')
 
122
                            or (headers['Authorization'] != 'FOO')))):
 
123
                    text = "Not authorized"
 
124
                    return "\r\n".join([
 
125
                            "HTTP/1.1 401 Unauthorized",
 
126
                            "WWW-Authenticate: Digest realm=\"test\",domain=\"/DigestAuthentication\",nonce=\"13dc6f6b70fec989c0d5bd5956818b33\"",
 
127
                            "Content-Type: text/html; charset=UTF-8",
 
128
                            "Date: %s" % now,
 
129
                            "Content-Length: %d" % len(text),
 
130
                            "",
 
131
                            text])
 
132
 
 
133
                elif uri == '/etag.txt':
 
134
                    etag = "\"1262547188.66\""
 
135
                    if headers.get("If-None-Match") == etag:
 
136
                        return "\r\n".join([
 
137
                                "HTTP/1.1 304 Not Modified",
 
138
                                "ETag: %s" % etag,
 
139
                                "",
 
140
                                ""])
 
141
                    else:
 
142
                        return "\r\n".join([
 
143
                                "HTTP/1.1 200 OK",
 
144
                                "ETag: %s" % etag,
 
145
                                "Last-Modified: Sun, 03 Jan 2010 19:33:08 GMT",
 
146
                                "Content-Length: %d" % len(text),
 
147
                                "Content-Type: text/plain",
 
148
                                "",
 
149
                                text])
 
150
 
 
151
                elif uri == '/cookie.php':
 
152
                    if "Cookie" in headers:
 
153
                        text += "\n%s" % headers["Cookie"]
 
154
                    return "\r\n".join([
 
155
                            "HTTP/1.1 200 OK",
 
156
                            "Content-Type: text/plain; charset=UTF-8",
 
157
                            "Set-Cookie: MiroTestCookie=foobar; path=/; domain=pculture.org",
 
158
                            "Last-Modified: %s" % now,
 
159
                            "Date: %s" % now,
 
160
                            "Content-Length: %d" % len(text),
 
161
                            "",
 
162
                            text])
 
163
 
 
164
                else:
 
165
                    return "\r\n".join([
 
166
                            "HTTP/1.1 200 OK",
 
167
                            "Content-Type: text/plain; charset=UTF-8",
 
168
                            "Last-Modified: %s" % now,
 
169
                            "Date: %s" % now,
 
170
                            "Content-Length: %d" % len(text),
 
171
                            "",
 
172
                            text])
 
173
 
 
174
            elif method == "HEAD":
 
175
                if uri == '/nohead.php':
 
176
                    return "\r\n".join([
 
177
                            "HTTP/1.1 405 NOT ALLOWED",
 
178
                            "Date: %s" % now,
 
179
                            "",
 
180
                            ""])
 
181
                else:
 
182
                    return "\r\n".join([
 
183
                            "HTTP/1.1 200 OK",
 
184
                            "Content-Type: text/plain; charset=UTF-8",
 
185
                            "Last-Modified: %s" % now,
 
186
                            "Date: %s" % now,
 
187
                            "Content-Length: %d" % len(text),
 
188
                            "",
 
189
                            ""])
 
190
 
 
191
        text = "<h1>Not found</h1>"
 
192
        return "\r\n".join([
 
193
                "HTTP/1.1 404 Not Founde",
 
194
                "Content-Type: text/html; charset=UTF-8",
 
195
                "Date: %s" % now,
 
196
                "Content-Length: %d" % len(text),
 
197
                "",
 
198
                text])
 
199
 
 
200
    def _processRequest(self, method, uri, version, headers):
 
201
        response = self._generateResponse(method,uri, version, headers)
 
202
        if response is not None:
 
203
            self.pendingOutput += response
 
204
            self._tryReadCallback()
 
205
 
 
206
    def _processData(self, data):
 
207
        self.unprocessed += data
 
208
        while self.unprocessed.find("\r\n\r\n") != -1:
 
209
            requests = self.unprocessed.split("\r\n\r\n", 1)
 
210
            self.unprocessed = requests[1]
 
211
            headers = requests[0].split("\r\n")
 
212
            (req_method, req_uri, req_version) =  headers.pop(0).split(' ')
 
213
            headers = dict([x.split(': ', 1) for x in headers])
 
214
            self._processRequest(req_method, req_uri, req_version, headers)
 
215
 
 
216
    def __str__(self):
 
217
        if self.name:
 
218
            return "%s: %s" % (type(self).__name__, self.name)
 
219
        else:
 
220
            return "Unknown %s" % type(self).__name__
 
221
 
 
222
    def startReadTimeout(self):
 
223
        pass
 
224
 
 
225
    def stopReadTimeout(self):
 
226
        pass
 
227
 
 
228
    def open_connection(self, host, port, callback, errback,
 
229
                        disabledReadTimeout=None):
 
230
        self.name = "Outgoing %s:%s" % (host, port)
 
231
        self.output = ''
 
232
        self.host = host
 
233
        self.port = port
 
234
        self.open = True
 
235
        self.errback = errback
 
236
        self.dsiabledReadTimeout = disabledReadTimeout
 
237
        callback(self)
 
238
 
 
239
    def acceptConnection(self, host, port, callback, errback):
 
240
        errback()
 
241
 
 
242
    def close_connection(self):
 
243
        self.open = False
 
244
 
 
245
    def isOpen(self):
 
246
        return self.open
 
247
 
 
248
    def send_data(self, data, callback = None):
 
249
        if not self.isOpen():
 
250
            raise ValueError("Socket not connected")
 
251
        self.output += data
 
252
        self._processData(data)
 
253
 
 
254
    def startReading(self, readCallback):
 
255
        if not self.isOpen():
 
256
            raise ValueError("Socket not connected")
 
257
        self.readCallback = readCallback
 
258
        self._tryReadCallback()
 
259
 
 
260
    def stopReading(self):
 
261
        """Stop reading from the socket."""
 
262
        if not self.isOpen():
 
263
            raise ValueError("Socket not connected")
 
264
        self.readCallback = None
 
265
 
 
266
    def onReadTimeout(self):
 
267
        raise IOError("Read Timeout")
 
268
 
 
269
    def handleSocketError(self, code, msg, operation):
 
270
        raise IOError("Socket Error")
 
271
 
 
272
class DumbFakeStream(FakeStream):
 
273
    def _generateResponse(self, method, uri, version, headers):
 
274
        return None
 
275
 
 
276
class AsyncSocketTest(EventLoopTest):
 
277
    def setUp(self):
 
278
        self.data = None
 
279
        self.errbackCalled = False
 
280
        self.callbackCalled = False
 
281
        self.fakeCallbackError = False
 
282
        EventLoopTest.setUp(self)
 
283
 
 
284
    def callback(self, data):
 
285
        if self.fakeCallbackError:
 
286
            1/0
 
287
        self.data = data
 
288
        self.callbackCalled = True
 
289
        self.stopEventLoop(False)
 
290
 
 
291
    def errback(self, error):
 
292
        self.data = error
 
293
        self.errbackCalled = True
 
294
        self.stopEventLoop(False)
 
295
 
 
296
class NetworkBufferTest(MiroTestCase):
 
297
    def setUp(self):
 
298
        self.buffer = net.NetworkBuffer()
 
299
        MiroTestCase.setUp(self)
 
300
 
 
301
    def test_read_line(self):
 
302
        self.buffer.addData("HEL")
 
303
        self.assertEquals(self.buffer.readline(), None)
 
304
        self.buffer.addData("LO\r\n")
 
305
        self.assertEquals(self.buffer.readline(), 'HELLO')
 
306
        self.buffer.addData("HOWS\r\nIT\nGOING\r\nCRONLY\rDOESNTBREAK")
 
307
        self.assertEquals(self.buffer.readline(), 'HOWS')
 
308
        self.assertEquals(self.buffer.readline(), 'IT')
 
309
        self.assertEquals(self.buffer.readline(), 'GOING')
 
310
        self.assertEquals(self.buffer.readline(), None)
 
311
        self.assertEquals(self.buffer.read(), "CRONLY\rDOESNTBREAK")
 
312
 
 
313
    def test_read(self):
 
314
        self.buffer.addData("12345678901234567890")
 
315
        self.assertEquals(self.buffer.read(4), "1234")
 
316
        self.assertEquals(self.buffer.read(6), "567890")
 
317
        self.buffer.addData("CARBOAT")
 
318
        self.assertEquals(self.buffer.read(), "1234567890CARBOAT")
 
319
 
 
320
    def test_length(self):
 
321
        self.buffer.addData("ONE\r\nTWO")
 
322
        self.assertEquals(self.buffer.length, 8)
 
323
        self.buffer.readline()
 
324
        self.assertEquals(self.buffer.length, 3)
 
325
        self.buffer.read(1)
 
326
        self.assertEquals(self.buffer.length, 2)
 
327
        self.buffer.unread("AAA")
 
328
        self.assertEquals(self.buffer.length, 5)
 
329
        self.buffer.addData("MORE")
 
330
        self.assertEquals(self.buffer.length, 9)
 
331
 
 
332
    def test_get_value(self):
 
333
        self.buffer.addData("ONE")
 
334
        self.buffer.addData("TWO")
 
335
        self.buffer.addData("THREE")
 
336
        self.assertEquals(self.buffer.getValue(), "ONETWOTHREE")
 
337
        # check to make sure the value doesn't change as a result
 
338
        self.assertEquals(self.buffer.getValue(), "ONETWOTHREE")
 
339
 
 
340
 
 
341
class WeirdCloseConnectionTest(AsyncSocketTest):
 
342
    def test_close_during_open_connection(self):
 
343
        """
 
344
        Test opening a connection, then closing the HTTPConnection
 
345
        before it happens.  The open_connection callback shouldn't be
 
346
        called.
 
347
 
 
348
        Open a socket on localhost and try to connect to that, this
 
349
        should be pretty much instantaneous, so we don't need a long
 
350
        timeout to runEventLoop.
 
351
        """
 
352
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
353
        sock.bind( ('127.0.0.1', 0))
 
354
        sock.listen(1)
 
355
        host, port = sock.getsockname()
 
356
        try:
 
357
            conn = net.AsyncSocket()
 
358
            conn.open_connection(host, port, self.callback, self.errback)
 
359
            conn.close_connection()
 
360
            self.runEventLoop(timeout=1, timeoutNormal=True)
 
361
            self.assert_(not self.callbackCalled)
 
362
            self.assert_(self.errbackCalled)
 
363
        finally:
 
364
            sock.close()
 
365
 
 
366
    def test_close_during_accept_connection(self):
 
367
        """
 
368
        Test opening a connection, then closing the HTTPConnection
 
369
        before it happens.  The open_connection callback shouldn't be
 
370
        called.
 
371
 
 
372
        Open a socket on localhost and try to connect to that, this
 
373
        should be pretty much instantaneous, so we don't need a long
 
374
        timeout to runEventLoop.
 
375
        """
 
376
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
377
        try:
 
378
            conn = net.AsyncSocket()
 
379
            conn.acceptConnection('127.0.0.1', 0, self.callback, self.errback)
 
380
            sock.connect((conn.addr, conn.port))
 
381
            conn.close_connection()
 
382
            self.runEventLoop(timeout=1, timeoutNormal=True)
 
383
            self.assert_(not self.callbackCalled)
 
384
            self.assert_(self.errbackCalled)
 
385
        finally:
 
386
            sock.close()
 
387
 
 
388
class ConnectionHandlerTest(EventLoopTest):
 
389
    def setUp(self):
 
390
        EventLoopTest.setUp(self)
 
391
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
392
        server.bind( ('127.0.0.1', 0) )
 
393
        server.listen(1)
 
394
        address = server.getsockname()
 
395
 
 
396
        self.connectionHandler = TestingConnectionHandler(self)
 
397
        def stopEventLoop(conn):
 
398
            self.stopEventLoop(False)
 
399
        self.connectionHandler.open_connection(address[0], address[1],
 
400
                stopEventLoop, stopEventLoop)
 
401
        self.runEventLoop()
 
402
        self.remoteSocket, address = server.accept()
 
403
        self.remoteSocket.setblocking(False)
 
404
 
 
405
    def test_send(self):
 
406
        data = 'abcabc' * 1024  * 64
 
407
        self.connectionHandler.send_data(data)
 
408
        self.received = net.NetworkBuffer()
 
409
        def readData():
 
410
            try:
 
411
                readIn = self.remoteSocket.recv(1024 * 1024)
 
412
            except:
 
413
                readIn = ''
 
414
            self.received.addData(readIn)
 
415
            if self.received.length == len(data):
 
416
                self.stopEventLoop(False)
 
417
            else:
 
418
                self.add_timeout(0.1, readData, 'test')
 
419
        self.add_timeout(0.1, readData, 'test')
 
420
        self.runEventLoop()
 
421
        self.assert_(self.received.read() == data)
 
422
 
 
423
    def test_read(self):
 
424
        self.connectionHandler.change_state('foo')
 
425
        self.remoteSocket.send('abc')
 
426
        self.runEventLoop(timeout=1)
 
427
        self.assertEquals(self.connectionHandler.fooData, 'abc')
 
428
        self.connectionHandler.change_state('bar')
 
429
        self.remoteSocket.send('def')
 
430
        self.runEventLoop(timeout=1)
 
431
        self.assertEquals(self.connectionHandler.barData, 'def')
 
432
        self.remoteSocket.send('ghi')
 
433
        self.connectionHandler.change_state('noread')
 
434
        self.runEventLoop(timeout=0.1, timeoutNormal=True)
 
435
        self.assertEquals(self.connectionHandler.fooData, 'abc')
 
436
        self.assertEquals(self.connectionHandler.barData, 'def')
 
437
        self.connectionHandler.change_state('foo')
 
438
        self.runEventLoop(timeout=1)
 
439
        self.assertEquals(self.connectionHandler.fooData, 'abcghi')
 
440
 
 
441
    def test_close(self):
 
442
        self.connectionHandler.close_connection()
 
443
        self.assert_(not self.connectionHandler.stream.isOpen())
 
444
        # second close shouldn't throw any exceptions
 
445
        self.connectionHandler.close_connection()
 
446
 
 
447
    def test_remote_close(self):
 
448
        self.connectionHandler.change_state('foo')
 
449
        self.remoteSocket.shutdown(socket.SHUT_WR)
 
450
        self.runEventLoop()
 
451
        self.assertEquals(self.connectionHandler.gotHandleClose, True)
 
452
 
 
453
    # FIXME - this test fails on Windows.
 
454
    def test_remote_close2(self):
 
455
        self.remoteSocket.shutdown(socket.SHUT_RD)
 
456
        self.remoteSocket.close()
 
457
        # Note: we have to send enough data so that the OS won't
 
458
        # buffer the entire send call.  Otherwise we may miss that the
 
459
        # socket has closed.
 
460
        self.connectionHandler.send_data("A" * 1024 * 1024)
 
461
        self.runEventLoop(timeout=1)
 
462
        self.assertEquals(self.connectionHandler.gotHandleClose, True)
 
463
 
 
464
    def test_string(self):
 
465
        # just make sure it doesn't throw an exception
 
466
        str(self.connectionHandler)
 
467
        self.assert_(True)