Add python-eventlet package to MOS 9.0 repository
[packages/trusty/python-eventlet.git] / python-eventlet / tests / isolated / wsgi_connection_timeout.py
1 """Issue #143 - Socket timeouts in wsgi server not caught.
2 https://bitbucket.org/eventlet/eventlet/issue/143/
3
4 This file intentionally ignored by nose.
5 Caller process (tests.wsgi_test.TestWsgiConnTimeout) handles success / failure
6
7
8 Simulate server connection socket timeout without actually waiting.
9 Logs 'timed out' if server debug=True (similar to 'accepted' logging)
10
11 FAIL: if log (ie, _spawn_n_impl 'except:' catches timeout, logs TB)
12 NOTE: timeouts are NOT on server_sock, but on the conn sockets produced
13 by the socket.accept() call
14
15 server's socket.listen() sock - NaughtySocketAcceptWrap
16     /  |  \
17     |  |  |   (1 - many)
18     V  V  V
19 server / client accept() conn - ExplodingConnectionWrap
20     /  |  \
21     |  |  |   (1 - many)
22     V  V  V
23 connection makefile() file objects - ExplodingSocketFile <-- these raise
24 """
25 import socket
26
27 import eventlet
28 from eventlet.support import six
29 import tests.wsgi_test
30
31
32 # no standard tests in this file, ignore
33 __test__ = False
34
35
36 TAG_BOOM = "=== ~* BOOM *~ ==="
37
38 output_buffer = []
39
40
41 class BufferLog(object):
42     @staticmethod
43     def write(s):
44         output_buffer.append(s.rstrip())
45         return len(s)
46
47
48 # This test might make you wince
49 class NaughtySocketAcceptWrap(object):
50     # server's socket.accept(); patches resulting connection sockets
51
52     def __init__(self, sock):
53         self.sock = sock
54         self.sock._really_accept = self.sock.accept
55         self.sock.accept = self
56         self.conn_reg = []
57
58     def unwrap(self):
59         self.sock.accept = self.sock._really_accept
60         del self.sock._really_accept
61         for conn_wrap in self.conn_reg:
62             conn_wrap.unwrap()
63
64     def arm(self):
65         output_buffer.append("ca-click")
66         for i in self.conn_reg:
67             i.arm()
68
69     def __call__(self):
70         output_buffer.append(self.__class__.__name__ + ".__call__")
71         conn, addr = self.sock._really_accept()
72         self.conn_reg.append(ExplodingConnectionWrap(conn))
73         return conn, addr
74
75
76 class ExplodingConnectionWrap(object):
77     # new connection's socket.makefile
78     # eventlet *tends* to use socket.makefile, not raw socket methods.
79     # need to patch file operations
80
81     def __init__(self, conn):
82         self.conn = conn
83         self.conn._really_makefile = self.conn.makefile
84         self.conn.makefile = self
85         self.armed = False
86         self.file_reg = []
87
88     def unwrap(self):
89         self.conn.makefile = self.conn._really_makefile
90         del self.conn._really_makefile
91
92     def arm(self):
93         output_buffer.append("tick")
94         for i in self.file_reg:
95             i.arm()
96
97     def __call__(self, mode='r', bufsize=-1):
98         output_buffer.append(self.__class__.__name__ + ".__call__")
99         # file_obj = self.conn._really_makefile(*args, **kwargs)
100         file_obj = ExplodingSocketFile(self.conn._sock, mode, bufsize)
101         self.file_reg.append(file_obj)
102         return file_obj
103
104
105 class ExplodingSocketFile(eventlet.greenio._fileobject):
106
107     def __init__(self, sock, mode='rb', bufsize=-1, close=False):
108         args = [bufsize, close] if six.PY2 else []
109         super(self.__class__, self).__init__(sock, mode, *args)
110         self.armed = False
111
112     def arm(self):
113         output_buffer.append("beep")
114         self.armed = True
115
116     def _fuse(self):
117         if self.armed:
118             output_buffer.append(TAG_BOOM)
119             raise socket.timeout("timed out")
120
121     def readline(self, *args, **kwargs):
122         output_buffer.append(self.__class__.__name__ + ".readline")
123         self._fuse()
124         return super(self.__class__, self).readline(*args, **kwargs)
125
126
127 def step(debug):
128     output_buffer[:] = []
129
130     server_sock = eventlet.listen(('localhost', 0))
131     server_addr = server_sock.getsockname()
132     sock_wrap = NaughtySocketAcceptWrap(server_sock)
133
134     eventlet.spawn_n(
135         eventlet.wsgi.server,
136         debug=debug,
137         log=BufferLog,
138         max_size=128,
139         site=tests.wsgi_test.Site(),
140         sock=server_sock,
141     )
142
143     try:
144         # req #1 - normal
145         sock1 = eventlet.connect(server_addr)
146         sock1.settimeout(0.1)
147         fd1 = sock1.makefile('rwb')
148         fd1.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
149         fd1.flush()
150         tests.wsgi_test.read_http(sock1)
151
152         # let the server socket ops catch up, set bomb
153         eventlet.sleep(0)
154         output_buffer.append("arming...")
155         sock_wrap.arm()
156
157         # req #2 - old conn, post-arm - timeout
158         fd1.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
159         fd1.flush()
160         try:
161             tests.wsgi_test.read_http(sock1)
162             assert False, 'Expected ConnectionClosed exception'
163         except tests.wsgi_test.ConnectionClosed:
164             pass
165
166         fd1.close()
167         sock1.close()
168     finally:
169         # reset streams, then output trapped tracebacks
170         sock_wrap.unwrap()
171     # check output asserts in tests.wsgi_test.TestHttpd
172     # test_143_server_connection_timeout_exception
173
174     return output_buffer[:]
175
176
177 def main():
178     output_normal = step(debug=False)
179     output_debug = step(debug=True)
180
181     assert "timed out" in output_debug[-1], repr(output_debug)
182     # if the BOOM check fails, it's because our timeout didn't happen
183     # (if eventlet stops using file.readline() to read HTTP headers,
184     # for instance)
185     assert TAG_BOOM == output_debug[-2], repr(output_debug)
186     assert TAG_BOOM == output_normal[-1], repr(output_normal)
187     assert "Traceback" not in output_debug, repr(output_debug)
188     assert "Traceback" not in output_normal, repr(output_normal)
189     print("pass")
190
191 if __name__ == '__main__':
192     main()