added completly new version for haslach 2025

This commit is contained in:
fotobox
2025-03-17 03:47:13 +01:00
parent 152832515c
commit 769ab91da8
2333 changed files with 409208 additions and 341 deletions

View File

@@ -0,0 +1,30 @@
# coding: utf-8
# flake8: noqa
"""
The package asynctest is built on top of the standard :mod:`unittest` module
and cuts down boilerplate code when testing libraries for :mod:`asyncio`.
asynctest imports the standard unittest package, overrides some of its features
and adds new ones. A test author can import asynctest in place of
:mod:`unittest` safely.
It is divided in submodules, but they are all imported at the top level,
so :class:`asynctest.case.TestCase` is equivalent to :class:`asynctest.TestCase`.
Currently, asynctest targets the "selector" model. Hence, some features will
not (yet) work with Windows' proactor.
"""
import unittest
from unittest import *
# Shadows unittest with our enhanced classes
from .case import *
from .mock import *
# And load or own tools
from ._fail_on import *
from .helpers import *
from .selector import *
__all__ = unittest.__all__

View File

@@ -0,0 +1,146 @@
# coding: utf-8
"""
:class:`asynctest.TestCase` decorator which controls checks performed after
tests.
This module is separated from :mod:`asynctest.case` to avoid circular imports
in modules registering new checks.
To implement new checks:
* its name must be added in the ``DEFAULTS`` dict,
* a static method of the same name must be added to the :class:`_fail_on`
class,
* an optional static method named ``before_[name of the check]`` can be
added to :class:`_fail_on` to implement some set-up before the test runs.
A check may be only available on some platforms, activated by a conditional
import. In this case, ``DEFAULT`` and :class:`_fail_on` can be updated in the
module. There is an example in the :mod:`asynctest.selector` module.
"""
from asyncio import TimerHandle
_FAIL_ON_ATTR = "_asynctest_fail_on"
#: Default value of the arguments of @fail_on, the name of the argument matches
#: the name of the static method performing the check in the :class:`_fail_on`.
#: The value is True when the check is enabled by default, False otherwise.
DEFAULTS = {
"unused_loop": False,
"active_handles": False,
}
class _fail_on:
def __init__(self, checks=None):
self.checks = checks or {}
self._computed_checks = None
def __call__(self, func):
checker = getattr(func, _FAIL_ON_ATTR, None)
if checker:
checker = checker.copy()
checker.update(self.checks)
else:
checker = self.copy()
setattr(func, _FAIL_ON_ATTR, checker)
return func
def update(self, checks, override=True):
if override:
self.checks.update(checks)
else:
for check, value in checks.items():
self.checks.setdefault(check, value)
def copy(self):
return _fail_on(self.checks.copy())
def get_checks(self, case):
# cache the result so it's consistent across calls to get_checks()
if self._computed_checks is None:
checks = DEFAULTS.copy()
try:
checks.update(getattr(case, _FAIL_ON_ATTR, None).checks)
except AttributeError:
pass
checks.update(self.checks)
self._computed_checks = checks
return self._computed_checks
def before_test(self, case):
checks = self.get_checks(case)
for check in filter(checks.get, checks):
try:
getattr(self, "before_test_" + check)(case)
except (AttributeError, TypeError):
pass
def check_test(self, case):
checks = self.get_checks(case)
for check in filter(checks.get, checks):
getattr(self, check)(case)
# checks
@staticmethod
def unused_loop(case):
if not case.loop._asynctest_ran:
case.fail("Loop did not run during the test")
@staticmethod
def _is_live_timer_handle(handle):
return isinstance(handle, TimerHandle) and not handle._cancelled
@classmethod
def _live_timer_handles(cls, loop):
return filter(cls._is_live_timer_handle, loop._scheduled)
@classmethod
def active_handles(cls, case):
handles = tuple(cls._live_timer_handles(case.loop))
if handles:
case.fail("Loop contained unfinished work {!r}".format(handles))
def fail_on(**kwargs):
"""
Enable checks on the loop state after a test ran to help testers to
identify common mistakes.
"""
# documented in asynctest.case.rst
for kwarg in kwargs:
if kwarg not in DEFAULTS:
raise TypeError("fail_on() got an unexpected keyword argument "
"'{}'".format(kwarg))
return _fail_on(kwargs)
def _fail_on_all(flag, func):
checker = _fail_on(dict((arg, flag) for arg in DEFAULTS))
return checker if func is None else checker(func)
def strict(func=None):
"""
Activate strict checking of the state of the loop after a test ran.
"""
# documented in asynctest.case.rst
return _fail_on_all(True, func)
def lenient(func=None):
"""
Deactivate all checks after a test ran.
"""
# documented in asynctest.case.rst
return _fail_on_all(False, func)

View File

@@ -0,0 +1,505 @@
# coding: utf-8
"""
Module ``case``
---------------
Enhance :class:`unittest.TestCase`:
* a new loop is issued and set as the default loop before each test, and
closed and disposed after,
* if the loop uses a selector, it will be wrapped with
:class:`asynctest.TestSelector`,
* a test method in a TestCase identified as a coroutine function or returning
a coroutine will run on the loop,
* :meth:`~TestCase.setUp()` and :meth:`~TestCase.tearDown()` methods can be
coroutine functions,
* cleanup functions registered with :meth:`~TestCase.addCleanup()` can be
coroutine functions,
* a test fails if the loop did not run during the test.
class-level set-up
~~~~~~~~~~~~~~~~~~
Since each test runs in its own loop, it is not possible to run
:meth:`~TestCase.setUpClass()` and :meth:`~TestCase.tearDownClass()` as
coroutines.
If one needs to perform set-up actions at the class level (meaning
once for all tests in the class), it should be done using a loop created for
this sole purpose and that is not shared with the tests. Ideally, the loop
shall be closed in the method which creates it.
If one really needs to share a loop between tests,
:attr:`TestCase.use_default_loop` can be set to ``True`` (as a class
attribute). The test case will use the loop returned by
:meth:`asyncio.get_event_loop()` instead of creating a new loop for each test.
This way, the event loop or event loop policy can be set during class-level
set-up and tear down.
"""
import asyncio
import functools
import types
import unittest
import sys
import warnings
from unittest.case import * # NOQA
import asynctest.selector
import asynctest._fail_on
class _Policy(asyncio.AbstractEventLoopPolicy):
def __init__(self, original_policy, loop, forbid_get_event_loop):
self.original_policy = original_policy
self.forbid_get_event_loop = forbid_get_event_loop
self.loop = loop
self.watcher = None
# we override the loop from the original policy because we don't want to
# instantiate a "default loop" that may be never closed (usually, we only
# run the test, so the "original default loop" is not even created by the
# policy).
def get_event_loop(self):
if self.forbid_get_event_loop:
raise AssertionError("TestCase.forbid_get_event_loop is True, "
"asyncio.get_event_loop() must not be called")
elif self.loop:
return self.loop
else:
return self.original_policy.get_event_loop()
def new_event_loop(self):
return self.original_policy.new_event_loop()
def set_event_loop(self, loop):
result = self.original_policy.set_event_loop(loop)
self.loop = loop
return result
def _check_unix(self):
if not hasattr(asyncio, "SafeChildWatcher"):
raise NotImplementedError
def get_child_watcher(self):
self._check_unix()
if self.loop:
if self.watcher is None:
self.watcher = asyncio.SafeChildWatcher()
self.watcher.attach_loop(self.loop)
return self.watcher
else:
return self.original_policy.get_child_watcher()
def set_child_watcher(self, watcher):
self._check_unix()
if self.loop:
result = self.original_policy.set_child_watcher(watcher)
self.watcher = watcher
return result
else:
return self.original_policy.set_child_watcher(watcher)
def reset_watcher(self):
if self.watcher:
self.watcher.close()
# force the original policy to reissue a child watcher next time
# get_child_watcher() is called, which effectively attach the loop
# to the new watcher. That's the best we can do so far
self.original_policy.set_child_watcher(None)
class TestCase(unittest.TestCase):
"""
A test which is a coroutine function or which returns a coroutine will run
on the loop.
Once the test returned, one or more assertions are checked. For instance,
a test fails if the loop didn't run. These checks can be enabled or
disabled using the :func:`~asynctest.fail_on` decorator.
By default, a new loop is created and is set as the default loop before
each test. Test authors can retrieve this loop with
:attr:`~asynctest.TestCase.loop`.
If :attr:`~asynctest.TestCase.use_default_loop` is set to ``True``, the
current default event loop is used instead. In this case, it is up to the
test author to deal with the state of the loop in each test: the loop might
be closed, callbacks and tasks may be scheduled by previous tests. It is
also up to the test author to close the loop and dispose the related
resources.
If :attr:`~asynctest.TestCase.forbid_get_event_loop` is set to ``True``,
a call to :func:`asyncio.get_event_loop()` will raise an
:exc:`AssertionError`. Since Python 3.6, calling
:func:`asyncio.get_event_loop()` from a callback or a coroutine will return
the running loop (instead of raising an exception).
These behaviors should be configured when defining the test case class::
class With_Reusable_Loop_TestCase(asynctest.TestCase):
use_default_loop = True
forbid_get_event_loop = False
def test_something(self):
pass
If :meth:`setUp()` and :meth:`tearDown()` are coroutine functions, they
will run on the loop. Note that :meth:`setUpClass()` and
:meth:`tearDownClass()` can not be coroutines.
.. versionadded:: 0.5
attribute :attr:`~asynctest.TestCase.use_default_loop`.
.. versionadded:: 0.7
attribute :attr:`~asynctest.TestCase.forbid_get_event_loop`.
In any case, the default loop is now reset to its original state
outside a test function.
.. versionadded:: 0.8
``ignore_loop`` has been deprecated in favor of the extensible
:func:`~asynctest.fail_on` decorator.
"""
#: If true, the loop used by the test case is the current default event
#: loop returned by :func:`asyncio.get_event_loop()`. The loop will not be
#: closed and recreated between tests.
use_default_loop = False
#: If true, the value returned by :func:`asyncio.get_event_loop()` will be
#: set to ``None`` during the test. It allows to ensure that tested code
#: use a loop object explicitly passed around.
forbid_get_event_loop = False
#: Event loop created and set as default event loop during the test.
loop = None
def _init_loop(self):
if self.use_default_loop:
self.loop = asyncio.get_event_loop()
loop = None
else:
loop = self.loop = asyncio.new_event_loop()
policy = _Policy(asyncio.get_event_loop_policy(),
loop, self.forbid_get_event_loop)
asyncio.set_event_loop_policy(policy)
self.loop = self._patch_loop(self.loop)
def _unset_loop(self):
policy = asyncio.get_event_loop_policy()
if not self.use_default_loop:
if sys.version_info >= (3, 6):
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
self.loop.close()
policy.reset_watcher()
asyncio.set_event_loop_policy(policy.original_policy)
self.loop = None
def _patch_loop(self, loop):
if hasattr(loop, '_asynctest_ran'):
# The loop is already patched
return loop
loop._asynctest_ran = False
def wraps(method):
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
try:
return method(*args, **kwargs)
finally:
loop._asynctest_ran = True
return types.MethodType(wrapper, loop)
for method in ('run_forever', 'run_until_complete', ):
setattr(loop, method, wraps(getattr(loop, method)))
if isinstance(loop, asyncio.selector_events.BaseSelectorEventLoop):
loop._selector = asynctest.selector.TestSelector(loop._selector)
return loop
def _setUp(self):
self._init_loop()
# initialize post-test checks
test = getattr(self, self._testMethodName)
checker = getattr(test, asynctest._fail_on._FAIL_ON_ATTR, None)
self._checker = checker or asynctest._fail_on._fail_on()
self._checker.before_test(self)
if asyncio.iscoroutinefunction(self.setUp):
self.loop.run_until_complete(self.setUp())
else:
self.setUp()
# don't take into account if the loop ran during setUp
self.loop._asynctest_ran = False
def _tearDown(self):
if asyncio.iscoroutinefunction(self.tearDown):
self.loop.run_until_complete(self.tearDown())
else:
self.tearDown()
# post-test checks
self._checker.check_test(self)
# Override unittest.TestCase methods which call setUp() and tearDown()
def run(self, result=None):
orig_result = result
if result is None:
result = self.defaultTestResult()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun()
result.startTest(self)
testMethod = getattr(self, self._testMethodName)
if (getattr(self.__class__, "__unittest_skip__", False) or
getattr(testMethod, "__unittest_skip__", False)):
# If the class or method was skipped.
try:
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') or
getattr(testMethod, '__unittest_skip_why__', ''))
self._addSkip(result, self, skip_why)
finally:
result.stopTest(self)
return
expecting_failure = getattr(testMethod,
"__unittest_expecting_failure__", False)
outcome = unittest.case._Outcome(result)
try:
self._outcome = outcome
with outcome.testPartExecutor(self):
self._setUp()
if outcome.success:
outcome.expecting_failure = expecting_failure
with outcome.testPartExecutor(self, isTest=True):
self._run_test_method(testMethod)
outcome.expecting_failure = False
with outcome.testPartExecutor(self):
self._tearDown()
self.loop.run_until_complete(self.doCleanups())
self._unset_loop()
for test, reason in outcome.skipped:
self._addSkip(result, test, reason)
self._feedErrorsToResult(result, outcome.errors)
if outcome.success:
if expecting_failure:
if outcome.expectedFailure:
self._addExpectedFailure(result, outcome.expectedFailure)
else:
self._addUnexpectedSuccess(result)
else:
result.addSuccess(self)
return result
finally:
result.stopTest(self)
if orig_result is None:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
# explicitly break reference cycles:
# outcome.errors -> frame -> outcome -> outcome.errors
# outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure
outcome.errors.clear()
outcome.expectedFailure = None
# clear the outcome, no more needed
self._outcome = None
def debug(self):
self._setUp()
try:
self._run_test_method(getattr(self, self._testMethodName))
self._tearDown()
while self._cleanups:
function, args, kwargs = self._cleanups.pop(-1)
if asyncio.iscoroutinefunction(function):
self.loop.run_until_complete(function(*args, **kwargs))
else:
function(*args, **kwargs)
except Exception:
raise
finally:
self._unset_loop()
def _run_test_method(self, method):
# If the method is a coroutine or returns a coroutine, run it on the
# loop
result = method()
if asyncio.iscoroutine(result):
self.loop.run_until_complete(result)
@asyncio.coroutine
def doCleanups(self):
"""
Execute all cleanup functions. Normally called for you after tearDown.
"""
outcome = self._outcome or unittest.mock._Outcome()
while self._cleanups:
function, args, kwargs = self._cleanups.pop()
with outcome.testPartExecutor(self):
if asyncio.iscoroutinefunction(function):
yield from function(*args, **kwargs)
else:
function(*args, **kwargs)
return outcome.success
def addCleanup(self, function, *args, **kwargs):
"""
Add a function, with arguments, to be called when the test is
completed. If function is a coroutine function, it will run on the loop
before it's cleaned.
"""
return super().addCleanup(function, *args, **kwargs)
@asyncio.coroutine
def assertAsyncRaises(self, exception, awaitable):
"""
Test that an exception of type ``exception`` is raised when an
exception is raised when awaiting ``awaitable``, a future or coroutine.
:see: :meth:`unittest.TestCase.assertRaises()`
"""
with self.assertRaises(exception):
return (yield from awaitable)
@asyncio.coroutine
def assertAsyncRaisesRegex(self, exception, regex, awaitable):
"""
Like :meth:`assertAsyncRaises()` but also tests that ``regex`` matches
on the string representation of the raised exception.
:see: :meth:`unittest.TestCase.assertRaisesRegex()`
"""
with self.assertRaisesRegex(exception, regex):
return (yield from awaitable)
@asyncio.coroutine
def assertAsyncWarns(self, warning, awaitable):
"""
Test that a warning is triggered when awaiting ``awaitable``, a future
or a coroutine.
:see: :meth:`unittest.TestCase.assertWarns()`
"""
with self.assertWarns(warning):
return (yield from awaitable)
@asyncio.coroutine
def assertAsyncWarnsRegex(self, warning, regex, awaitable):
"""
Like :meth:`assertAsyncWarns()` but also tests that ``regex`` matches
on the message of the triggered warning.
:see: :meth:`unittest.TestCase.assertWarnsRegex()`
"""
with self.assertWarnsRegex(warning, regex):
return (yield from awaitable)
class FunctionTestCase(TestCase, unittest.FunctionTestCase):
"""
Enables the same features as :class:`~asynctest.TestCase`, but for
:class:`~asynctest.FunctionTestCase`.
"""
class ClockedTestCase(TestCase):
"""
Subclass of :class:`~asynctest.TestCase` with a controlled loop clock,
useful for testing timer based behaviour without slowing test run time.
The clock will only advance when :meth:`advance()` is called.
"""
def _init_loop(self):
super()._init_loop()
self.loop.time = functools.wraps(self.loop.time)(lambda: self._time)
self._time = 0
@asyncio.coroutine
def advance(self, seconds):
"""
Fast forward time by a number of ``seconds``.
Callbacks scheduled to run up to the destination clock time will be
executed on time:
>>> self.loop.call_later(1, print_time)
>>> self.loop.call_later(2, self.loop.call_later, 1, print_time)
>>> await self.advance(3)
1
3
In this example, the third callback is scheduled at ``t = 2`` to be
executed at ``t + 1``. Hence, it will run at ``t = 3``. The callback as
been called on time.
"""
if seconds < 0:
raise ValueError(
'Cannot go back in time ({} seconds)'.format(seconds))
yield from self._drain_loop()
target_time = self._time + seconds
while True:
next_time = self._next_scheduled()
if next_time is None or next_time > target_time:
break
self._time = next_time
yield from self._drain_loop()
self._time = target_time
yield from self._drain_loop()
def _next_scheduled(self):
try:
return self.loop._scheduled[0]._when
except IndexError:
return None
@asyncio.coroutine
def _drain_loop(self):
while True:
next_time = self._next_scheduled()
if not self.loop._ready and (next_time is None or
next_time > self._time):
break
yield from asyncio.sleep(0)
self.loop._TestCase_asynctest_ran = True
def ignore_loop(func=None):
"""
Ignore the error case where the loop did not run during the test.
"""
warnings.warn("ignore_loop() is deprecated in favor of "
"fail_on(unused_loop=False)", DeprecationWarning)
checker = asynctest._fail_on._fail_on({"unused_loop": False})
return checker if func is None else checker(func)

View File

@@ -0,0 +1,24 @@
# coding: utf-8
"""
Module ``helpers``
------------------
Helper functions and coroutines for :mod:`asynctest`.
"""
import asyncio
@asyncio.coroutine
def exhaust_callbacks(loop):
"""
Run the loop until all ready callbacks are executed.
The coroutine doesn't wait for callbacks scheduled in the future with
:meth:`~asyncio.BaseEventLoop.call_at()` or
:meth:`~asyncio.BaseEventLoop.call_later()`.
:param loop: event loop
"""
while loop._ready:
yield from asyncio.sleep(0, loop=loop)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,378 @@
# coding: utf-8
"""
Module ``selector``
-------------------
Mock of :mod:`selectors` and compatible objects performing asynchronous IO.
This module provides classes to mock objects performing IO (files, sockets,
etc). These mocks are compatible with :class:`~asynctest.TestSelector`, which
can simulate the behavior of a selector on the mock objects, or forward actual
work to a real selector.
"""
import asyncio
try:
import selectors
except ImportError:
# In the case of Python 3.3, attempt to use the selectors
# modules from within the asyncio package
import asyncio.selectors as selectors
import socket
try:
import ssl
except ImportError:
# allow python to be compiled without ssl
ssl = None
from . import mock
from . import _fail_on
class FileDescriptor(int):
"""
A subclass of int which allows to identify the virtual file-descriptor of a
:class:`~asynctest.FileMock`.
If :class:`~asynctest.FileDescriptor()` without argument, its value will be
the value of :data:`~FileDescriptor.next_fd`.
When an object is created, :data:`~FileDescriptor.next_fd` is set to the
highest value for a :class:`~asynctest.FileDescriptor` object + 1.
"""
next_fd = 0
def __new__(cls, *args, **kwargs):
if not args and not kwargs:
s = super().__new__(cls, FileDescriptor.next_fd)
else:
s = super().__new__(cls, *args, **kwargs)
FileDescriptor.next_fd = max(FileDescriptor.next_fd + 1, s + 1)
return s
def __hash__(self):
# Return a different hash than the int so we can register both a
# FileDescriptor object and an int of the same value
return hash('__FileDescriptor_{}'.format(self))
def fd(fileobj):
"""
Return the :class:`~asynctest.FileDescriptor` value of ``fileobj``.
If ``fileobj`` is a :class:`~asynctest.FileDescriptor`, ``fileobj`` is
returned, else ``fileobj.fileno()`` is returned instead.
Note that if fileobj is an int, :exc:`ValueError` is raised.
:raise ValueError: if ``fileobj`` is not a :class:`~asynctest.FileMock`,
a file-like object or
a :class:`~asynctest.FileDescriptor`.
"""
try:
return fileobj if isinstance(fileobj, FileDescriptor) else fileobj.fileno()
except Exception:
raise ValueError
def isfilemock(obj):
"""
Return ``True`` if the ``obj`` or ``obj.fileno()`` is
a :class:`asynctest.FileDescriptor`.
"""
try:
return (isinstance(obj, FileDescriptor) or
isinstance(obj.fileno(), FileDescriptor))
except AttributeError:
# obj has no attribute fileno()
return False
class FileMock(mock.Mock):
"""
Mock a file-like object.
A FileMock is an intelligent mock which can work with TestSelector to
simulate IO events during tests.
.. method:: fileno()
Return a :class:`~asynctest.FileDescriptor` object.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fileno.return_value = FileDescriptor()
def _get_child_mock(self, *args, **kwargs):
# A FileMock returns a Mock by default, not a FileMock
return mock.Mock(**kwargs)
class SocketMock(FileMock):
"""
Mock a socket.
See :class:`~asynctest.FileMock`.
"""
def __init__(self, side_effect=None, return_value=mock.DEFAULT,
wraps=None, name=None, spec_set=None, parent=None,
**kwargs):
super().__init__(socket.socket, side_effect=side_effect,
return_value=return_value, wraps=wraps, name=name,
spec_set=spec_set, parent=parent, **kwargs)
if ssl:
class SSLSocketMock(SocketMock):
"""
Mock a socket wrapped by the :mod:`ssl` module.
See :class:`~asynctest.FileMock`.
.. versionadded:: 0.5
"""
def __init__(self, side_effect=None, return_value=mock.DEFAULT,
wraps=None, name=None, spec_set=None, parent=None,
**kwargs):
FileMock.__init__(self, ssl.SSLSocket, side_effect=side_effect,
return_value=return_value, wraps=wraps, name=name,
spec_set=spec_set, parent=parent, **kwargs)
def _set_event_ready(fileobj, loop, event):
selector = loop._selector
fd = selector._fileobj_lookup(fileobj)
if fd in selector._fd_to_key:
loop._process_events([(selector._fd_to_key[fd], event)])
def set_read_ready(fileobj, loop):
"""
Schedule callbacks registered on ``loop`` as if the selector notified that
data is ready to be read on ``fileobj``.
:param fileobj: file object or :class:`~asynctest.FileMock` on which the
event is mocked.
:param loop: :class:`asyncio.SelectorEventLoop` watching for events on
``fileobj``.
::
mock = asynctest.SocketMock()
mock.recv.return_value = b"Data"
def read_ready(sock):
print("received:", sock.recv(1024))
loop.add_reader(mock, read_ready, mock)
set_read_ready(mock, loop)
loop.run_forever() # prints received: b"Data"
.. versionadded:: 0.4
"""
# since the selector would notify of events at the beginning of the next
# iteration, we let this iteration finish before actually scheduling the
# reader (hence the call_soon)
loop.call_soon_threadsafe(_set_event_ready, fileobj, loop, selectors.EVENT_READ)
def set_write_ready(fileobj, loop):
"""
Schedule callbacks registered on ``loop`` as if the selector notified that
data can be written to ``fileobj``.
:param fileobj: file object or :class:`~asynctest.FileMock` on which th
event is mocked.
:param loop: :class:`asyncio.SelectorEventLoop` watching for events on
``fileobj``.
.. versionadded:: 0.4
"""
loop.call_soon_threadsafe(_set_event_ready, fileobj, loop, selectors.EVENT_WRITE)
class TestSelector(selectors._BaseSelectorImpl):
"""
A selector which supports IOMock objects.
It can wrap an actual implementation of a selector, so the selector will
work both with mocks and real file-like objects.
A common use case is to patch the selector loop::
loop._selector = asynctest.TestSelector(loop._selector)
:param selector: optional, if provided, this selector will be used to work
with real file-like objects.
"""
def __init__(self, selector=None):
super().__init__()
self._selector = selector
def _fileobj_lookup(self, fileobj):
if isfilemock(fileobj):
return fd(fileobj)
return super()._fileobj_lookup(fileobj)
def register(self, fileobj, events, data=None):
"""
Register a file object or a :class:`~asynctest.FileMock`.
If a real selector object has been supplied to the
:class:`~asynctest.TestSelector` object and ``fileobj`` is not
a :class:`~asynctest.FileMock` or a :class:`~asynctest.FileDescriptor`
returned by :meth:`FileMock.fileno()`, the object will be registered to
the real selector.
See :meth:`selectors.BaseSelector.register`.
"""
if isfilemock(fileobj) or self._selector is None:
key = super().register(fileobj, events, data)
else:
key = self._selector.register(fileobj, events, data)
if key:
self._fd_to_key[key.fd] = key
return key
def unregister(self, fileobj):
"""
Unregister a file object or a :class:`~asynctest.FileMock`.
See :meth:`selectors.BaseSelector.unregister`.
"""
if isfilemock(fileobj) or self._selector is None:
key = super().unregister(fileobj)
else:
key = self._selector.unregister(fileobj)
if key and key.fd in self._fd_to_key:
del self._fd_to_key[key.fd]
return key
def modify(self, fileobj, events, data=None):
"""
Shortcut when calling :meth:`TestSelector.unregister` then
:meth:`TestSelector.register` to update the registration of a an object
to the selector.
See :meth:`selectors.BaseSelector.modify`.
"""
if isfilemock(fileobj) or self._selector is None:
key = super().modify(fileobj, events, data)
else:
# del the key first because modify() fails if events is incorrect
fd = self._fileobj_lookup(fileobj)
if fd in self._fd_to_key:
del self._fd_to_key[fd]
key = self._selector.modify(fileobj, events, data)
if key:
self._fd_to_key[key.fd] = key
return key
def select(self, timeout=None):
"""
Perform the selection.
This method is a no-op if no actual selector has been supplied.
See :meth:`selectors.BaseSelector.select`.
"""
if self._selector is None:
return []
return self._selector.select(timeout)
def close(self):
"""
Close the selector.
Close the actual selector if supplied, unregister all mocks.
See :meth:`selectors.BaseSelector.close`.
"""
if self._selector is not None:
self._selector.close()
super().close()
def get_registered_events(selector):
watched_events = []
for event in selector.get_map().values():
watched_events.append(event)
if selector._selector is not None:
# this is our TestSelector, wrapping a true selector object
for event in selector._selector.get_map().values():
watched_events.append(event)
return set(watched_events)
if hasattr(asyncio, "format_helpers"):
# Python 3.7+
def _format_callback(handle):
return asyncio.format_helpers._format_callback(handle._callback,
handle._args, None)
elif hasattr(asyncio.events, "_format_args_and_kwargs"):
# Python 3.5, 3.6
def _format_callback(handle):
return asyncio.events._format_callback(handle._callback, handle._args,
None)
else:
# Python 3.4
def _format_callback(handle):
return asyncio.events._format_callback(handle._callback, handle._args)
def _format_event(event):
callbacks = []
if event.events & selectors.EVENT_READ:
callbacks.append("add_reader({}, {})".format(
event.fileobj, _format_callback(event.data[0])))
if event.events & selectors.EVENT_WRITE:
callbacks.append("add_writer({}, {})".format(
event.fileobj, _format_callback(event.data[1])))
return callbacks
def fail_on_before_test_active_selector_callbacks(case):
case._active_selector_callbacks = get_registered_events(
case.loop._selector)
def fail_on_active_selector_callbacks(case):
ignored_events = case._active_selector_callbacks
active_events = get_registered_events(case.loop._selector)
output = ["some events watched during the tests were not removed:"]
for c in map(_format_event, active_events - ignored_events):
output.extend(c)
if len(output) > 1:
case.fail("\n - ".join(output))
_fail_on.DEFAULTS["active_selector_callbacks"] = False
_fail_on._fail_on.active_selector_callbacks = staticmethod(fail_on_active_selector_callbacks)
_fail_on._fail_on.before_test_active_selector_callbacks = \
staticmethod(fail_on_before_test_active_selector_callbacks)