blob: 8170533188c370436646530bf825e8a99a07cc89 [file] [log] [blame]
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07001"""Utilities shared by tests."""
2
3import collections
4import contextlib
5import io
Victor Stinner1cae9ec2014-07-14 22:26:34 +02006import logging
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07007import os
Yury Selivanovff827f02014-02-18 18:02:19 -05008import re
Yury Selivanov88a5bf02014-02-18 12:15:06 -05009import socket
10import socketserver
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070011import sys
Yury Selivanov88a5bf02014-02-18 12:15:06 -050012import tempfile
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070013import threading
Antoine Pitroud20afad2013-10-20 01:51:25 +020014import time
Victor Stinnerc73701d2014-06-18 01:36:32 +020015import unittest
Victor Stinner24ba2032014-02-26 10:25:02 +010016from unittest import mock
Yury Selivanov88a5bf02014-02-18 12:15:06 -050017
18from http.server import HTTPServer
Victor Stinnerda492a82014-02-20 10:37:27 +010019from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
Yury Selivanov88a5bf02014-02-18 12:15:06 -050020
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070021try:
22 import ssl
23except ImportError: # pragma: no cover
24 ssl = None
25
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070026from . import base_events
Yury Selivanov0f3c9762015-11-20 12:57:34 -050027from . import compat
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070028from . import events
Victor Stinnere6a53792014-03-06 01:00:36 +010029from . import futures
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070030from . import selectors
Victor Stinnere6a53792014-03-06 01:00:36 +010031from . import tasks
Victor Stinnerf951d282014-06-29 00:46:45 +020032from .coroutines import coroutine
Victor Stinner1cae9ec2014-07-14 22:26:34 +020033from .log import logger
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070034
35
36if sys.platform == 'win32': # pragma: no cover
37 from .windows_utils import socketpair
38else:
39 from socket import socketpair # pragma: no cover
40
41
42def dummy_ssl_context():
43 if ssl is None:
44 return None
45 else:
46 return ssl.SSLContext(ssl.PROTOCOL_SSLv23)
47
48
49def run_briefly(loop):
Victor Stinnerf951d282014-06-29 00:46:45 +020050 @coroutine
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070051 def once():
52 pass
53 gen = once()
Victor Stinner896a25a2014-07-08 11:29:25 +020054 t = loop.create_task(gen)
Victor Stinner98b63912014-06-30 14:51:04 +020055 # Don't log a warning if the task is not done after run_until_complete().
56 # It occurs if the loop is stopped or if a task raises a BaseException.
57 t._log_destroy_pending = False
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070058 try:
59 loop.run_until_complete(t)
60 finally:
61 gen.close()
62
63
Victor Stinnere6a53792014-03-06 01:00:36 +010064def run_until(loop, pred, timeout=30):
65 deadline = time.time() + timeout
Antoine Pitroud20afad2013-10-20 01:51:25 +020066 while not pred():
67 if timeout is not None:
68 timeout = deadline - time.time()
69 if timeout <= 0:
Victor Stinnere6a53792014-03-06 01:00:36 +010070 raise futures.TimeoutError()
71 loop.run_until_complete(tasks.sleep(0.001, loop=loop))
Antoine Pitroud20afad2013-10-20 01:51:25 +020072
73
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070074def run_once(loop):
Guido van Rossum41f69f42015-11-19 13:28:47 -080075 """Legacy API to run once through the event loop.
76
77 This is the recommended pattern for test code. It will poll the
78 selector once and run all callbacks scheduled in response to I/O
79 events.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070080 """
Guido van Rossum41f69f42015-11-19 13:28:47 -080081 loop.call_soon(loop.stop)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070082 loop.run_forever()
83
84
Yury Selivanov88a5bf02014-02-18 12:15:06 -050085class SilentWSGIRequestHandler(WSGIRequestHandler):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070086
Yury Selivanov88a5bf02014-02-18 12:15:06 -050087 def get_stderr(self):
88 return io.StringIO()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070089
Yury Selivanov88a5bf02014-02-18 12:15:06 -050090 def log_message(self, format, *args):
91 pass
92
93
94class SilentWSGIServer(WSGIServer):
95
Antoine Pitroufd39a892014-10-15 16:58:21 +020096 request_timeout = 2
97
98 def get_request(self):
99 request, client_addr = super().get_request()
100 request.settimeout(self.request_timeout)
101 return request, client_addr
102
Yury Selivanov88a5bf02014-02-18 12:15:06 -0500103 def handle_error(self, request, client_address):
104 pass
105
106
107class SSLWSGIServerMixin:
108
109 def finish_request(self, request, client_address):
110 # The relative location of our test directory (which
111 # contains the ssl key and certificate files) differs
112 # between the stdlib and stand-alone asyncio.
113 # Prefer our own if we can find it.
114 here = os.path.join(os.path.dirname(__file__), '..', 'tests')
115 if not os.path.isdir(here):
116 here = os.path.join(os.path.dirname(os.__file__),
117 'test', 'test_asyncio')
118 keyfile = os.path.join(here, 'ssl_key.pem')
119 certfile = os.path.join(here, 'ssl_cert.pem')
120 ssock = ssl.wrap_socket(request,
121 keyfile=keyfile,
122 certfile=certfile,
123 server_side=True)
124 try:
125 self.RequestHandlerClass(ssock, client_address, self)
126 ssock.close()
127 except OSError:
128 # maybe socket has been closed by peer
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700129 pass
130
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700131
Yury Selivanov88a5bf02014-02-18 12:15:06 -0500132class SSLWSGIServer(SSLWSGIServerMixin, SilentWSGIServer):
133 pass
134
135
136def _run_test_server(*, address, use_ssl=False, server_cls, server_ssl_cls):
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700137
138 def app(environ, start_response):
139 status = '200 OK'
140 headers = [('Content-type', 'text/plain')]
141 start_response(status, headers)
142 return [b'Test message']
143
144 # Run the test WSGI server in a separate thread in order not to
145 # interfere with event handling in the main thread
Yury Selivanov88a5bf02014-02-18 12:15:06 -0500146 server_class = server_ssl_cls if use_ssl else server_cls
147 httpd = server_class(address, SilentWSGIRequestHandler)
148 httpd.set_app(app)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700149 httpd.address = httpd.server_address
Antoine Pitroufd39a892014-10-15 16:58:21 +0200150 server_thread = threading.Thread(
151 target=lambda: httpd.serve_forever(poll_interval=0.05))
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700152 server_thread.start()
153 try:
154 yield httpd
155 finally:
156 httpd.shutdown()
Antoine Pitroua7a150c2013-10-20 23:26:23 +0200157 httpd.server_close()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700158 server_thread.join()
159
160
Yury Selivanov88a5bf02014-02-18 12:15:06 -0500161if hasattr(socket, 'AF_UNIX'):
162
163 class UnixHTTPServer(socketserver.UnixStreamServer, HTTPServer):
164
165 def server_bind(self):
166 socketserver.UnixStreamServer.server_bind(self)
167 self.server_name = '127.0.0.1'
168 self.server_port = 80
169
170
171 class UnixWSGIServer(UnixHTTPServer, WSGIServer):
172
Antoine Pitroufd39a892014-10-15 16:58:21 +0200173 request_timeout = 2
174
Yury Selivanov88a5bf02014-02-18 12:15:06 -0500175 def server_bind(self):
176 UnixHTTPServer.server_bind(self)
177 self.setup_environ()
178
179 def get_request(self):
180 request, client_addr = super().get_request()
Antoine Pitroufd39a892014-10-15 16:58:21 +0200181 request.settimeout(self.request_timeout)
Yury Selivanov88a5bf02014-02-18 12:15:06 -0500182 # Code in the stdlib expects that get_request
183 # will return a socket and a tuple (host, port).
184 # However, this isn't true for UNIX sockets,
185 # as the second return value will be a path;
186 # hence we return some fake data sufficient
187 # to get the tests going
188 return request, ('127.0.0.1', '')
189
190
191 class SilentUnixWSGIServer(UnixWSGIServer):
192
193 def handle_error(self, request, client_address):
194 pass
195
196
197 class UnixSSLWSGIServer(SSLWSGIServerMixin, SilentUnixWSGIServer):
198 pass
199
200
201 def gen_unix_socket_path():
202 with tempfile.NamedTemporaryFile() as file:
203 return file.name
204
205
206 @contextlib.contextmanager
207 def unix_socket_path():
208 path = gen_unix_socket_path()
209 try:
210 yield path
211 finally:
212 try:
213 os.unlink(path)
214 except OSError:
215 pass
216
217
218 @contextlib.contextmanager
219 def run_test_unix_server(*, use_ssl=False):
220 with unix_socket_path() as path:
221 yield from _run_test_server(address=path, use_ssl=use_ssl,
222 server_cls=SilentUnixWSGIServer,
223 server_ssl_cls=UnixSSLWSGIServer)
224
225
226@contextlib.contextmanager
227def run_test_server(*, host='127.0.0.1', port=0, use_ssl=False):
228 yield from _run_test_server(address=(host, port), use_ssl=use_ssl,
229 server_cls=SilentWSGIServer,
230 server_ssl_cls=SSLWSGIServer)
231
232
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700233def make_test_protocol(base):
234 dct = {}
235 for name in dir(base):
236 if name.startswith('__') and name.endswith('__'):
237 # skip magic names
238 continue
Victor Stinnera1254972014-02-11 11:34:30 +0100239 dct[name] = MockCallback(return_value=None)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700240 return type('TestProtocol', (base,) + base.__bases__, dct)()
241
242
243class TestSelector(selectors.BaseSelector):
244
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100245 def __init__(self):
246 self.keys = {}
247
248 def register(self, fileobj, events, data=None):
249 key = selectors.SelectorKey(fileobj, 0, events, data)
250 self.keys[fileobj] = key
251 return key
252
253 def unregister(self, fileobj):
254 return self.keys.pop(fileobj)
255
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700256 def select(self, timeout):
257 return []
258
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100259 def get_map(self):
260 return self.keys
261
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700262
263class TestLoop(base_events.BaseEventLoop):
264 """Loop for unittests.
265
266 It manages self time directly.
267 If something scheduled to be executed later then
268 on next loop iteration after all ready handlers done
269 generator passed to __init__ is calling.
270
271 Generator should be like this:
272
273 def gen():
274 ...
275 when = yield ...
276 ... = yield time_advance
277
Yury Selivanovb0b0e622014-02-18 22:27:48 -0500278 Value returned by yield is absolute time of next scheduled handler.
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700279 Value passed to yield is time advance to move loop's time forward.
280 """
281
282 def __init__(self, gen=None):
283 super().__init__()
284
285 if gen is None:
286 def gen():
287 yield
288 self._check_on_close = False
289 else:
290 self._check_on_close = True
291
292 self._gen = gen()
293 next(self._gen)
294 self._time = 0
Victor Stinner06847d92014-02-11 09:03:47 +0100295 self._clock_resolution = 1e-9
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700296 self._timers = []
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700297 self._selector = TestSelector()
298
299 self.readers = {}
300 self.writers = {}
301 self.reset_counters()
302
303 def time(self):
304 return self._time
305
306 def advance_time(self, advance):
307 """Move test time forward."""
308 if advance:
309 self._time += advance
310
311 def close(self):
Victor Stinner29ad0112015-01-15 00:04:21 +0100312 super().close()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700313 if self._check_on_close:
314 try:
315 self._gen.send(0)
316 except StopIteration:
317 pass
318 else: # pragma: no cover
319 raise AssertionError("Time generator is not finished")
320
321 def add_reader(self, fd, callback, *args):
Yury Selivanovff827f02014-02-18 18:02:19 -0500322 self.readers[fd] = events.Handle(callback, args, self)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700323
324 def remove_reader(self, fd):
325 self.remove_reader_count[fd] += 1
326 if fd in self.readers:
327 del self.readers[fd]
328 return True
329 else:
330 return False
331
332 def assert_reader(self, fd, callback, *args):
333 assert fd in self.readers, 'fd {} is not registered'.format(fd)
334 handle = self.readers[fd]
335 assert handle._callback == callback, '{!r} != {!r}'.format(
336 handle._callback, callback)
337 assert handle._args == args, '{!r} != {!r}'.format(
338 handle._args, args)
339
340 def add_writer(self, fd, callback, *args):
Yury Selivanovff827f02014-02-18 18:02:19 -0500341 self.writers[fd] = events.Handle(callback, args, self)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700342
343 def remove_writer(self, fd):
344 self.remove_writer_count[fd] += 1
345 if fd in self.writers:
346 del self.writers[fd]
347 return True
348 else:
349 return False
350
351 def assert_writer(self, fd, callback, *args):
352 assert fd in self.writers, 'fd {} is not registered'.format(fd)
353 handle = self.writers[fd]
354 assert handle._callback == callback, '{!r} != {!r}'.format(
355 handle._callback, callback)
356 assert handle._args == args, '{!r} != {!r}'.format(
357 handle._args, args)
358
359 def reset_counters(self):
360 self.remove_reader_count = collections.defaultdict(int)
361 self.remove_writer_count = collections.defaultdict(int)
362
363 def _run_once(self):
364 super()._run_once()
365 for when in self._timers:
366 advance = self._gen.send(when)
367 self.advance_time(advance)
368 self._timers = []
369
370 def call_at(self, when, callback, *args):
371 self._timers.append(when)
372 return super().call_at(when, callback, *args)
373
374 def _process_events(self, event_list):
375 return
376
377 def _write_to_self(self):
378 pass
Victor Stinnera1254972014-02-11 11:34:30 +0100379
Yury Selivanov88a5bf02014-02-18 12:15:06 -0500380
Victor Stinnera1254972014-02-11 11:34:30 +0100381def MockCallback(**kwargs):
Victor Stinner24ba2032014-02-26 10:25:02 +0100382 return mock.Mock(spec=['__call__'], **kwargs)
Yury Selivanovff827f02014-02-18 18:02:19 -0500383
384
385class MockPattern(str):
386 """A regex based str with a fuzzy __eq__.
387
388 Use this helper with 'mock.assert_called_with', or anywhere
Yury Selivanovb0b0e622014-02-18 22:27:48 -0500389 where a regex comparison between strings is needed.
Yury Selivanovff827f02014-02-18 18:02:19 -0500390
391 For instance:
392 mock_call.assert_called_with(MockPattern('spam.*ham'))
393 """
394 def __eq__(self, other):
395 return bool(re.search(str(self), other, re.S))
Victor Stinner307bccc2014-06-12 18:39:26 +0200396
397
398def get_function_source(func):
399 source = events._get_function_source(func)
400 if source is None:
401 raise ValueError("unable to get the source of %r" % (func,))
402 return source
Victor Stinnerc73701d2014-06-18 01:36:32 +0200403
404
405class TestCase(unittest.TestCase):
406 def set_event_loop(self, loop, *, cleanup=True):
407 assert loop is not None
408 # ensure that the event loop is passed explicitly in asyncio
409 events.set_event_loop(None)
410 if cleanup:
411 self.addCleanup(loop.close)
412
413 def new_test_loop(self, gen=None):
414 loop = TestLoop(gen)
415 self.set_event_loop(loop)
416 return loop
417
418 def tearDown(self):
419 events.set_event_loop(None)
Victor Stinner1cae9ec2014-07-14 22:26:34 +0200420
Victor Stinner5d44c082015-02-02 18:36:31 +0100421 # Detect CPython bug #23353: ensure that yield/yield-from is not used
422 # in an except block of a generator
423 self.assertEqual(sys.exc_info(), (None, None, None))
424
Yury Selivanov0f3c9762015-11-20 12:57:34 -0500425 if not compat.PY34:
426 # Python 3.3 compatibility
427 def subTest(self, *args, **kwargs):
428 class EmptyCM:
429 def __enter__(self):
430 pass
431 def __exit__(self, *exc):
432 pass
433 return EmptyCM()
434
Victor Stinner1cae9ec2014-07-14 22:26:34 +0200435
436@contextlib.contextmanager
437def disable_logger():
438 """Context manager to disable asyncio logger.
439
440 For example, it can be used to ignore warnings in debug mode.
441 """
442 old_level = logger.level
443 try:
444 logger.setLevel(logging.CRITICAL+1)
445 yield
446 finally:
447 logger.setLevel(old_level)
Victor Stinnerb2614752014-08-25 23:20:52 +0200448
449def mock_nonblocking_socket():
450 """Create a mock of a non-blocking socket."""
451 sock = mock.Mock(socket.socket)
452 sock.gettimeout.return_value = 0.0
453 return sock
Victor Stinner231b4042015-01-14 00:19:09 +0100454
455
456def force_legacy_ssl_support():
457 return mock.patch('asyncio.sslproto._is_sslproto_available',
458 return_value=False)