pax_global_header00006660000000000000000000000064134665645450014534gustar00rootroot0000000000000052 comment=8a0cb9b0837cfab3d920c607cea19dca7ad23a80 asynctest-0.13.0/000077500000000000000000000000001346656454500136325ustar00rootroot00000000000000asynctest-0.13.0/.appveyor.yml000066400000000000000000000014341346656454500163020ustar00rootroot00000000000000environment: matrix: - PYTHON: "C:\\Python35" - PYTHON: "C:\\Python35" PYTHONASYNCIODEBUG: "1" - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python35-x64" PYTHONASYNCIODEBUG: "1" - PYTHON: "C:\\Python36" - PYTHON: "C:\\Python36" PYTHONASYNCIODEBUG: "1" - PYTHON: "C:\\Python36-x64" - PYTHON: "C:\\Python36-x64" PYTHONASYNCIODEBUG: "1" - PYTHON: "C:\\Python37" - PYTHON: "C:\\Python37" PYTHONASYNCIODEBUG: "1" - PYTHON: "C:\\Python37-x64" - PYTHON: "C:\\Python37-x64" PYTHONASYNCIODEBUG: "1" install: - "%PYTHON%\\python.exe -m pip install --upgrade pip" - "%PYTHON%\\python.exe -m pip install --upgrade wheel>=0.30.0 setuptools>=36.6.0" build: off test_script: - "%PYTHON%\\python.exe -m unittest test" asynctest-0.13.0/.gitignore000066400000000000000000000001211346656454500156140ustar00rootroot00000000000000*.py[oc] __pycache__ build asynctest.egg-info dist doc/_build virtualenv/ venv*/ asynctest-0.13.0/.travis.yml000066400000000000000000000101111346656454500157350ustar00rootroot00000000000000cache: pip matrix: fast_finish: true include: - os: osx language: generic env: NAME="Python 3.5" before_install: - brew update - brew upgrade pyenv || brew install pyenv || true - pyenv install 3.5.6 - pyenv local 3.5.6 - eval "$(pyenv init -)" - python3 -m venv env - pyenv local --unset - source env/bin/activate - os: osx language: generic env: NAME="Python 3.6" before_install: - brew update - brew upgrade pyenv || brew install pyenv || true - pyenv install 3.6.6 - pyenv local 3.6.6 - eval "$(pyenv init -)" - python3 -m venv env - pyenv local --unset - source env/bin/activate - os: osx language: generic env: NAME="Python 3.7" before_install: - brew update - brew upgrade pyenv || brew install pyenv || true - pyenv install 3.7.0 - pyenv local 3.7.0 - eval "$(pyenv init -)" - python3 -m venv env - pyenv local --unset - source env/bin/activate - os: osx language: generic env: NAME="Python pypy3" before_install: - brew update - brew upgrade pyenv || brew install pyenv || true - pyenv install pypy3.5-6.0.0 - pyenv local pypy3.5-6.0.0 - eval "$(pyenv init -)" - pypy3 -m venv env - pyenv local --unset - source env/bin/activate - os: osx language: generic env: NAME="Python 3.6-dev" before_install: - brew update - brew upgrade pyenv || brew install pyenv || true - pyenv install 3.6-dev - pyenv local 3.6-dev - eval "$(pyenv init -)" - python3 -m venv env - pyenv local --unset - source env/bin/activate - os: osx language: generic env: NAME="Python 3.7-dev" before_install: - brew update - brew upgrade pyenv || brew install pyenv || true - pyenv install 3.7-dev - pyenv local 3.7-dev - eval "$(pyenv init -)" - python3 -m venv env - pyenv local --unset - source env/bin/activate - os: osx language: generic env: NAME="Python 3.8-dev" before_install: - brew update - brew upgrade pyenv || brew install pyenv || true - pyenv install 3.8-dev - pyenv local 3.8-dev - eval "$(pyenv init -)" - python3 -m venv env - pyenv local --unset - source env/bin/activate - os: linux language: python python: 3.5 - os: linux language: python python: 3.6 - os: linux language: python python: &python_major_ver 3.7 dist: xenial sudo: true - os: linux language: python python: 3.6-dev - os: linux language: python python: 3.7-dev dist: xenial sudo: true - os: linux language: python python: 3.8-dev dist: xenial sudo: true - os: linux language: python python: pypy3 allow_failures: - os: osx env: NAME="Python 3.6-dev" - os: osx env: NAME="Python 3.7-dev" - os: osx env: NAME="Python 3.8-dev" - os: linux python: 3.6-dev - os: linux python: 3.7-dev - os: linux python: 3.8-dev install: - curl https://bootstrap.pypa.io/get-pip.py | python - python -m pip install --upgrade wheel>=0.30.0 setuptools>=36.6.0 script: - python -m unittest --verbose test - "[[ $(python -c \"import platform; print(platform.python_implementation())\") == \"CPython\" ]] && PYTHONASYNCIODEBUG=1 python -m unittest test || true" deploy: provider: pypi user: martius password: secure: "UP31EYyU/X5NlCStj80Jc9j2DKsJDe9ErsEaqaPkVi1rV8AASt38gO8qA/hcW+f56qRE34qjeaYL+vHJ6nT7wRcWWu1HU4gp4+C6THEOlNVdgB4AzaqOtNF1T6ArPXoMrKWO2sPwcLrAIls8DCzYkdYGmbtFOXGDl4n7ZubJoM8=" distributions: sdist bdist_wheel on: # Only when a tag is on master and matches vX.Y.Z python: *python_major_ver tags: true branch: master condition: '$TRAVIS_TAG =~ ^v([0-9]+\.){2}[0-9]+$' asynctest-0.13.0/AUTHORS000066400000000000000000000006451346656454500147070ustar00rootroot00000000000000Austin Cormier David Honour Emmanuel Ilya Kulakov Lukasz Jernas Martin Richard - Maintainer of the library Pyotr Ermishkin Random User Rémi Cardona Sviatoslav Sydorenko asynctest-0.13.0/LICENSE.md000066400000000000000000000010661346656454500152410ustar00rootroot00000000000000**Copyright 2015 Martin Richard** * * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. asynctest-0.13.0/MANIFEST.in000066400000000000000000000001251346656454500153660ustar00rootroot00000000000000include LICENSE.md include README.rst include test/__init__.py include test/utils.py asynctest-0.13.0/Notes000066400000000000000000000055061346656454500146530ustar00rootroot00000000000000Aiotest I would like to write an asyncio.unittest package providing a wrapper on top of the unittest package. Currently, supported features are described in the README and docstrings. The next features I plan to add are mocking features: * when spec is specified, methods decorated with @asyncio.coroutine will be coroutine mocks, Does it make sense to detect when a mock is called via "yield from" so we can make the mock behave like a coroutine even if the decorator is missing? The idea is to add a simple and comprehensive unit testing framework for asyncio projects and libraries, aiming at reducing the boilerplate, as well as unittest does for regular packages. Some of the things that will be useful: * when mocking, * ensure a coroutine or callback won't block for too long (and other features available through the debug mode of asyncio). Some of the things that will _probably_ be useful: * allow to mock parts of the loop when required, such as: - mock the selector, - allow triggering fake selector events, - control the loop clock (thus allowing to fast-forward to run callbacks in the future) * specific assertions such as "assert_scheduled", "assert_done", "assert_cancelled", etc, * make a test fail if the loop don't end in a clean state (uncaught exception, unfreed resource, etc), * an asyncio.unittest.mock module, which can mock a coroutine easily (the trivial way is to mock a coroutine with a future which is done, for instance). Some more complex/high level things I'd like to do, but which may not be included in the base "package" * a mock system allowing to play an exact scenario of events which are supposed to happen concurrently while the loop run (for instance, complex network cases involving ConnectionReset, BrokenPipes, etc) - Currently in my tests, I use actual sockets and yield from asyncio.sleep() to ensure events happen in the order I expect on the loop, I don't really like it, * on the other hand, some parts of the asyncio package currently never yield, but may in the future, I'd like to find a way to test that my code doesn't use assumptions like that (thus resulting in possible future race conditions), maybe what I want is a kind of assert_future_done, assert_event_occured, etc * a collection of stream_pair() functions which would return a par of streams connected to each other, * a way to mock a program call (allowing to mock asynchronous process spawning) - actually I'm not sure how I would do this. Most of those features are features I implemented or should have implemented in order to get a clean unit test codebase for my project(s) using asyncio. I am looking for a wishlist of features and feedbacks about what you missed when writting tests for asyncio libraries, or that made you not write tests. asynctest-0.13.0/README.rst000066400000000000000000000106161346656454500153250ustar00rootroot00000000000000.. image:: https://img.shields.io/pypi/v/asynctest.svg :target: https://pypi.python.org/pypi/asynctest :alt: PyPI .. image:: https://travis-ci.org/Martiusweb/asynctest.svg?branch=master :target: https://travis-ci.org/Martiusweb/asynctest :alt: Travis .. image:: https://ci.appveyor.com/api/projects/status/github/Martiusweb/asynctest?branch=master&svg=true :target: https://ci.appveyor.com/project/Martiusweb/asynctest/branch/master :alt: AppVeyor .. image:: https://img.shields.io/pypi/pyversions/asynctest.svg :target: https://github.com/Martiusweb/asynctest :alt: Supported Python versions ========= asynctest ========= The package asynctest is built on top of the standard unittest module and cuts down boilerplate code when testing libraries for asyncio. Currently, asynctest targets the "selector" model, hence, some features will not (yet?) work with Windows' proactor. .. warning:: Since asynctest 0.13, Python 3.4 is not supported anymore. Author & license ---------------- Authored by Martin Richard and licensed under the Apache 2 license. See the AUTHORS file for a comprehensive list of the authors. Documentation ------------- .. image:: https://readthedocs.org/projects/asynctest/badge/ :target: http://asynctest.readthedocs.org/en/latest/ Full documentation is available at http://asynctest.readthedocs.org/en/latest/. It includes a tutorial with tested examples of how to use ``TestCase`` or mocks. Features -------- TestCases ~~~~~~~~~ - Initialize and close a loop created for each test (it can be configurated), if the loop uses a selector, it will be updated with a TestSelector object wrapping the original selector (see below), - if the test function is a coroutine function or returns a coroutine, it will run on the loop, - TestCase.setUp() and TestCase.tearDown() can be coroutine functions, - control post-test checks with `@fail_on`, for instance, the test fail if the loop didn't run, some optional checks can be activated, - ClockedTestCase allows to control the loop clock and run timed events without waiting the wall clock. Mock and CoroutineMock ~~~~~~~~~~~~~~~~~~~~~~ - CoroutineMock is a new Mock class which mocks a coroutine function, and returns a coroutine when called, - MagicMock supports asynchronous context managers and asynchronous iterators, - NonCallableMock, Mock and CoroutineMock can return CoroutineMock objects when its attributes are get if there is a matching attribute in the spec (or spec_set) object which is a coroutine function, - patch(), patch.object(), patch.multiple() return a MagickMock or CoroutineMock object by default, according to the patched target, - patch(), patch.object(), patch.multiple() handle generators and coroutines and their behavior can be controled when the generator or coroutine pauses, - all the patch() methods can decorate coroutine functions, - mock_open() returns a MagickMock object by default. - return_once() can be used with Mock.side_effect to return a value only once when a mock is called. Selectors ~~~~~~~~~ The module asynctest.selector provides classes to mock objects performing IO (files, sockets, etc). - FileMock is a special type of mock which represents a file. FileMock.fileno() returns a special value which allows to identify uniquely the mock, - SocketMock is a special type of FileMock which uses socket.socket as spec, - TestSelector is a custom selector able to wrap a real selector implementation and deal with FileMock objects, it can replace a selector loop by calling `loop._selector = TestSelector(loop._selector)`, and will intercept mock so they don't get registered to the actual selector. - set_read_ready() and set_write_ready() to force read and write event callbacks to be scheduled on the loop, as if the selector scheduled them. Helpers ~~~~~~~ - the coroutine exhaust_callbacks(loop) returns once all the callbacks which should be called immediately are executed, which is useful when the test author needs to assert things which are not yet executed by the loop. Roadmap ------- I hope I will find time to develop and release the following features: - set of warnings against common mistakes - proactor support Tests ----- asynctest is unit tested. You can run asynctest test suite with this command:: $ PYTHONPATH=. python -m unittest test asynctest-0.13.0/asynctest/000077500000000000000000000000001346656454500156475ustar00rootroot00000000000000asynctest-0.13.0/asynctest/__init__.py000066400000000000000000000015641346656454500177660ustar00rootroot00000000000000# 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__ asynctest-0.13.0/asynctest/_fail_on.py000066400000000000000000000102511346656454500177660ustar00rootroot00000000000000# 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) asynctest-0.13.0/asynctest/case.py000066400000000000000000000423651346656454500171460ustar00rootroot00000000000000# 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) asynctest-0.13.0/asynctest/helpers.py000066400000000000000000000010171346656454500176620ustar00rootroot00000000000000# 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) asynctest-0.13.0/asynctest/mock.py000066400000000000000000001374401346656454500171630ustar00rootroot00000000000000# coding: utf-8 """ Module ``mock`` --------------- Wrapper to unittest.mock reducing the boilerplate when testing asyncio powered code. A mock can behave as a coroutine, as specified in the documentation of :class:`~asynctest.mock.Mock`. """ import asyncio import asyncio.coroutines import contextlib import enum import functools import inspect import sys import types import unittest.mock # From python 3.6, a sentinel object is used to mark coroutines (rather than # a boolean) to prevent a mock/proxy object to return a truthy value. # see: https://github.com/python/asyncio/commit/ea776a11f632a975ad3ebbb07d8981804aa292db try: _is_coroutine = asyncio.coroutines._is_coroutine except AttributeError: _is_coroutine = True class _AsyncIterator: """ Wraps an iterator in an asynchronous iterator. """ def __init__(self, iterator): self.iterator = iterator def __aiter__(self): return self async def __anext__(self): try: return next(self.iterator) except StopIteration: pass raise StopAsyncIteration # magic methods which must be coroutine functions async_magic_coroutines = ("__aenter__", "__aexit__", "__anext__") # all magic methods used in an async context _async_magics = async_magic_coroutines + ("__aiter__", ) # We use unittest.mock.MagicProxy which works well, but it's not aware that # we want __aexit__ to return a falsy value by default. # We add the entry in unittest internal dict as it will not change the # normal behavior of unittest. unittest.mock._return_values["__aexit__"] = False def _get_async_iter(mock): """ Factory of ``__aiter__`` magic methods for a MagicMock. It creates a function which returns an asynchronous iterator based on the return value of ``mock.__aiter__``. Since __aiter__ used could be a coroutine in Python 3.5 and 3.6, we also support this case. See: https://www.python.org/dev/peps/pep-0525/#id23 """ def __aiter__(): return_value = mock.__aiter__._mock_return_value if return_value is DEFAULT: iterator = iter([]) else: iterator = iter(return_value) return _AsyncIterator(iterator) if asyncio.iscoroutinefunction(mock.__aiter__): return asyncio.coroutine(__aiter__) return __aiter__ unittest.mock._side_effect_methods["__aiter__"] = _get_async_iter async_magic_coroutines = set(async_magic_coroutines) _async_magics = set(_async_magics) # This changes the behavior of unittest, but the change is minor and is # probably better than overriding __set/get/del attr__ everywhere. unittest.mock._all_magics |= _async_magics def _raise(exception): raise exception def _make_native_coroutine(coroutine): """ Wrap a coroutine (or any function returning an awaitable) in a native coroutine. """ if inspect.iscoroutinefunction(coroutine): # Nothing to do. return coroutine @functools.wraps(coroutine) async def wrapper(*args, **kwargs): return await coroutine(*args, **kwargs) return wrapper def _is_started(patching): if isinstance(patching, _patch_dict): return patching._is_started else: return unittest.mock._is_started(patching) class FakeInheritanceMeta(type): """ A metaclass which recreates the original inheritance model from unittest.mock. - NonCallableMock > NonCallableMagicMock - NonCallable > Mock - Mock > MagicMock """ def __init__(self, name, bases, attrs): attrs['__new__'] = types.MethodType(self.__new, self) super().__init__(name, bases, attrs) @staticmethod def __new(cls, *args, **kwargs): new = type(cls.__name__, (cls, ), {'__doc__': cls.__doc__}) return object.__new__(new, *args, **kwargs) def __instancecheck__(cls, obj): # That's tricky, each type(mock) is actually a subclass of the actual # Mock type (see __new__) if super().__instancecheck__(obj): return True _type = type(obj) if issubclass(cls, NonCallableMock): if issubclass(_type, (NonCallableMagicMock, Mock, )): return True if issubclass(cls, Mock) and not issubclass(cls, CoroutineMock): if issubclass(_type, (MagicMock, )): return True return False def _get_is_coroutine(self): return self.__dict__['_mock_is_coroutine'] def _set_is_coroutine(self, value): # property setters and getters are overridden by Mock(), we need to # update the dict to add values value = _is_coroutine if bool(value) else False self.__dict__['_mock_is_coroutine'] = value # _mock_add_spec() is the actual private implementation in unittest.mock, we # override it to support coroutines in the metaclass. def _mock_add_spec(self, spec, *args, **kwargs): unittest.mock.NonCallableMock._mock_add_spec(self, spec, *args, **kwargs) _spec_coroutines = [] for attr in dir(spec): if asyncio.iscoroutinefunction(getattr(spec, attr)): _spec_coroutines.append(attr) self.__dict__['_spec_coroutines'] = _spec_coroutines def _get_child_mock(self, *args, **kwargs): _new_name = kwargs.get("_new_name") if _new_name in self.__dict__['_spec_coroutines']: return CoroutineMock(*args, **kwargs) _type = type(self) if issubclass(_type, MagicMock) and _new_name in async_magic_coroutines: klass = CoroutineMock elif issubclass(_type, CoroutineMock): klass = MagicMock elif not issubclass(_type, unittest.mock.CallableMixin): if issubclass(_type, unittest.mock.NonCallableMagicMock): klass = MagicMock elif issubclass(_type, NonCallableMock): klass = Mock else: klass = _type.__mro__[1] return klass(*args, **kwargs) class MockMetaMixin(FakeInheritanceMeta): def __new__(meta, name, base, namespace): if not any((isinstance(baseclass, meta) for baseclass in base)): # this ensures that inspect.iscoroutinefunction() doesn't return # True when testing a mock. code_mock = unittest.mock.NonCallableMock(spec_set=types.CodeType) code_mock.co_flags = 0 namespace.update({ '_mock_add_spec': _mock_add_spec, '_get_child_mock': _get_child_mock, '__code__': code_mock, }) return super().__new__(meta, name, base, namespace) class IsCoroutineArgMeta(MockMetaMixin): def __new__(meta, name, base, namespace): if not any((isinstance(baseclass, meta) for baseclass in base)): namespace.update({ '_asynctest_get_is_coroutine': _get_is_coroutine, '_asynctest_set_is_coroutine': _set_is_coroutine, 'is_coroutine': property(_get_is_coroutine, _set_is_coroutine, doc="True if the object mocked is a coroutine"), '_is_coroutine': property(_get_is_coroutine), }) wrapped_setattr = namespace.get("__setattr__", base[0].__setattr__) def __setattr__(self, attrname, value): if attrname == 'is_coroutine': self._asynctest_set_is_coroutine(value) else: return wrapped_setattr(self, attrname, value) namespace['__setattr__'] = __setattr__ return super().__new__(meta, name, base, namespace) class AsyncMagicMixin: """ Add support for async magic methods to :class:`MagicMock` and :class:`NonCallableMagicMock`. Actually, it's a shameless copy-paste of :class:`unittest.mock.MagicMixin`: when added to our classes, it will just do exactly what its :mod:`unittest` counterpart does, but for magic methods. It adds some behavior but should be compatible with future additions of :class:`MagicMock`. """ # Magic methods are invoked as type(obj).__magic__(obj), as seen in # PEP-343 (with) and PEP-492 (async with) def __init__(self, *args, **kwargs): self._mock_set_async_magics() # make magic work for kwargs in init unittest.mock._safe_super(AsyncMagicMixin, self).__init__(*args, **kwargs) self._mock_set_async_magics() # fix magic broken by upper level init def _mock_set_async_magics(self): these_magics = _async_magics if getattr(self, "_mock_methods", None) is not None: these_magics = _async_magics.intersection(self._mock_methods) remove_magics = _async_magics - these_magics for entry in remove_magics: if entry in type(self).__dict__: # remove unneeded magic methods delattr(self, entry) # don't overwrite existing attributes if called a second time these_magics = these_magics - set(type(self).__dict__) _type = type(self) for entry in these_magics: setattr(_type, entry, unittest.mock.MagicProxy(entry, self)) def mock_add_spec(self, *args, **kwargs): unittest.mock.MagicMock.mock_add_spec(self, *args, **kwargs) self._mock_set_async_magics() def __setattr__(self, name, value): _mock_methods = getattr(self, '_mock_methods', None) if _mock_methods is None or name in _mock_methods: if name in _async_magics: if not unittest.mock._is_instance_mock(value): setattr(type(self), name, unittest.mock._get_method(name, value)) original = value def value(*args, **kwargs): return original(self, *args, **kwargs) else: unittest.mock._check_and_set_parent(self, value, None, name) setattr(type(self), name, value) self._mock_children[name] = value return object.__setattr__(self, name, value) unittest.mock._safe_super(AsyncMagicMixin, self).__setattr__(name, value) # Notes about unittest.mock: # - MagicMock > Mock > NonCallableMock (where ">" means inherits from) # - when a mock instance is created, a new class (type) is created # dynamically, # - we *must* use magic or object's internals when we want to add our own # properties, and often override __getattr__/__setattr__ which are used # in unittest.mock.NonCallableMock. class NonCallableMock(unittest.mock.NonCallableMock, metaclass=IsCoroutineArgMeta): """ Enhance :class:`unittest.mock.NonCallableMock` with features allowing to mock a coroutine function. If ``is_coroutine`` is set to ``True``, the :class:`NonCallableMock` object will behave so :func:`asyncio.iscoroutinefunction` will return ``True`` with ``mock`` as parameter. If ``spec`` or ``spec_set`` is defined and an attribute is get, :class:`~asynctest.CoroutineMock` is returned instead of :class:`~asynctest.Mock` when the matching spec attribute is a coroutine function. The test author can also specify a wrapped object with ``wraps``. In this case, the :class:`~asynctest.Mock` object behavior is the same as with an :class:`unittest.mock.Mock` object: the wrapped object may have methods defined as coroutine functions. See :class:`unittest.mock.NonCallableMock` """ def __init__(self, spec=None, wraps=None, name=None, spec_set=None, is_coroutine=None, parent=None, **kwargs): super().__init__(spec=spec, wraps=wraps, name=name, spec_set=spec_set, parent=parent, **kwargs) self._asynctest_set_is_coroutine(is_coroutine) class NonCallableMagicMock(AsyncMagicMixin, unittest.mock.NonCallableMagicMock, metaclass=IsCoroutineArgMeta): """ A version of :class:`~asynctest.MagicMock` that isn't callable. """ def __init__(self, spec=None, wraps=None, name=None, spec_set=None, is_coroutine=None, parent=None, **kwargs): super().__init__(spec=spec, wraps=wraps, name=name, spec_set=spec_set, parent=parent, **kwargs) self._asynctest_set_is_coroutine(is_coroutine) class Mock(unittest.mock.Mock, metaclass=MockMetaMixin): """ Enhance :class:`unittest.mock.Mock` so it returns a :class:`~asynctest.CoroutineMock` object instead of a :class:`~asynctest.Mock` object where a method on a ``spec`` or ``spec_set`` object is a coroutine. For instance: >>> class Foo: ... @asyncio.coroutine ... def foo(self): ... pass ... ... def bar(self): ... pass >>> type(asynctest.mock.Mock(Foo()).foo) >>> type(asynctest.mock.Mock(Foo()).bar) The test author can also specify a wrapped object with ``wraps``. In this case, the :class:`~asynctest.Mock` object behavior is the same as with an :class:`unittest.mock.Mock` object: the wrapped object may have methods defined as coroutine functions. If you want to mock a coroutine function, use :class:`CoroutineMock` instead. See :class:`~asynctest.NonCallableMock` for details about :mod:`asynctest` features, and :mod:`unittest.mock` for the comprehensive documentation about mocking. """ class MagicMock(AsyncMagicMixin, unittest.mock.MagicMock, metaclass=MockMetaMixin): """ Enhance :class:`unittest.mock.MagicMock` so it returns a :class:`~asynctest.CoroutineMock` object instead of a :class:`~asynctest.Mock` object where a method on a ``spec`` or ``spec_set`` object is a coroutine. If you want to mock a coroutine function, use :class:`CoroutineMock` instead. :class:`MagicMock` allows to mock ``__aenter__``, ``__aexit__``, ``__aiter__`` and ``__anext__``. When mocking an asynchronous iterator, you can set the ``return_value`` of ``__aiter__`` to an iterable to define the list of values to be returned during iteration. You can not mock ``__await__``. If you want to mock an object implementing __await__, :class:`CoroutineMock` will likely be sufficient. see :class:`~asynctest.Mock`. .. versionadded:: 0.11 support of asynchronous iterators and asynchronous context managers. """ class _AwaitEvent: def __init__(self, mock): self._mock = mock self._condition = None @asyncio.coroutine def wait(self, skip=0): """ Wait for await. :param skip: How many awaits will be skipped. As a result, the mock should be awaited at least ``skip + 1`` times. """ def predicate(mock): return mock.await_count > skip return (yield from self.wait_for(predicate)) @asyncio.coroutine def wait_next(self, skip=0): """ Wait for the next await. Unlike :meth:`wait` that counts any await, mock has to be awaited once more, disregarding to the current :attr:`asynctest.CoroutineMock.await_count`. :param skip: How many awaits will be skipped. As a result, the mock should be awaited at least ``skip + 1`` more times. """ await_count = self._mock.await_count def predicate(mock): return mock.await_count > await_count + skip return (yield from self.wait_for(predicate)) @asyncio.coroutine def wait_for(self, predicate): """ Wait for a given predicate to become True. :param predicate: A callable that receives mock which result will be interpreted as a boolean value. The final predicate value is the return value. """ condition = self._get_condition() try: yield from condition.acquire() def _predicate(): return predicate(self._mock) return (yield from condition.wait_for(_predicate)) finally: condition.release() @asyncio.coroutine def _notify(self): condition = self._get_condition() try: yield from condition.acquire() condition.notify_all() finally: condition.release() def _get_condition(self): """ Creation of condition is delayed, to minimize the change of using the wrong loop. A user may create a mock with _AwaitEvent before selecting the execution loop. Requiring a user to delay creation is error-prone and inflexible. Instead, condition is created when user actually starts to use the mock. """ # No synchronization is needed: # - asyncio is thread unsafe # - there are no awaits here, method will be executed without # switching asyncio context. if self._condition is None: self._condition = asyncio.Condition() return self._condition def __bool__(self): return self._mock.await_count != 0 class CoroutineMock(Mock): """ Enhance :class:`~asynctest.mock.Mock` with features allowing to mock a coroutine function. The :class:`~asynctest.CoroutineMock` object will behave so the object is recognized as coroutine function, and the result of a call as a coroutine: >>> mock = CoroutineMock() >>> asyncio.iscoroutinefunction(mock) True >>> asyncio.iscoroutine(mock()) True The result of ``mock()`` is a coroutine which will have the outcome of ``side_effect`` or ``return_value``: - if ``side_effect`` is a function, the coroutine will return the result of that function, - if ``side_effect`` is an exception, the coroutine will raise the exception, - if ``side_effect`` is an iterable, the coroutine will return the next value of the iterable, however, if the sequence of result is exhausted, ``StopIteration`` is raised immediately, - if ``side_effect`` is not defined, the coroutine will return the value defined by ``return_value``, hence, by default, the coroutine returns a new :class:`~asynctest.CoroutineMock` object. If the outcome of ``side_effect`` or ``return_value`` is a coroutine, the mock coroutine obtained when the mock object is called will be this coroutine itself (and not a coroutine returning a coroutine). The test author can also specify a wrapped object with ``wraps``. In this case, the :class:`~asynctest.Mock` object behavior is the same as with an :class:`unittest.mock.Mock` object: the wrapped object may have methods defined as coroutine functions. """ #: Property which is set when the mock is awaited. Its ``wait`` and #: ``wait_next`` coroutine methods can be used to synchronize execution. #: #: .. versionadded:: 0.12 awaited = unittest.mock._delegating_property('awaited') #: Number of times the mock has been awaited. #: #: .. versionadded:: 0.12 await_count = unittest.mock._delegating_property('await_count') await_args = unittest.mock._delegating_property('await_args') await_args_list = unittest.mock._delegating_property('await_args_list') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # asyncio.iscoroutinefunction() checks this property to say if an # object is a coroutine # It is set through __dict__ because when spec_set is True, this # attribute is likely undefined. self.__dict__['_is_coroutine'] = _is_coroutine self.__dict__['_mock_awaited'] = _AwaitEvent(self) self.__dict__['_mock_await_count'] = 0 self.__dict__['_mock_await_args'] = None self.__dict__['_mock_await_args_list'] = unittest.mock._CallList() def _mock_call(_mock_self, *args, **kwargs): try: result = super()._mock_call(*args, **kwargs) except StopIteration as e: side_effect = _mock_self.side_effect if side_effect is not None and not callable(side_effect): raise result = asyncio.coroutine(_raise)(e) except BaseException as e: result = asyncio.coroutine(_raise)(e) _call = _mock_self.call_args @asyncio.coroutine def proxy(): try: if inspect.isawaitable(result): return (yield from result) else: return result finally: _mock_self.await_count += 1 _mock_self.await_args = _call _mock_self.await_args_list.append(_call) yield from _mock_self.awaited._notify() return proxy() def assert_awaited(_mock_self): """ Assert that the mock was awaited at least once. .. versionadded:: 0.12 """ self = _mock_self if self.await_count == 0: msg = ("Expected '%s' to have been awaited." % self._mock_name or 'mock') raise AssertionError(msg) def assert_awaited_once(_mock_self, *args, **kwargs): """ Assert that the mock was awaited exactly once. .. versionadded:: 0.12 """ self = _mock_self if not self.await_count == 1: msg = ("Expected '%s' to have been awaited once. Awaited %s times." % (self._mock_name or 'mock', self.await_count)) raise AssertionError(msg) def assert_awaited_with(_mock_self, *args, **kwargs): """ Assert that the last await was with the specified arguments. .. versionadded:: 0.12 """ self = _mock_self if self.await_args is None: expected = self._format_mock_call_signature(args, kwargs) raise AssertionError('Expected await: %s\nNot awaited' % (expected,)) def _error_message(): msg = self._format_mock_failure_message(args, kwargs) return msg expected = self._call_matcher((args, kwargs)) actual = self._call_matcher(self.await_args) if expected != actual: cause = expected if isinstance(expected, Exception) else None raise AssertionError(_error_message()) from cause def assert_awaited_once_with(_mock_self, *args, **kwargs): """ Assert that the mock was awaited exactly once and with the specified arguments. .. versionadded:: 0.12 """ self = _mock_self if not self.await_count == 1: msg = ("Expected '%s' to be awaited once. Awaited %s times." % (self._mock_name or 'mock', self.await_count)) raise AssertionError(msg) return self.assert_awaited_with(*args, **kwargs) def assert_any_await(_mock_self, *args, **kwargs): """ Assert the mock has ever been awaited with the specified arguments. .. versionadded:: 0.12 """ self = _mock_self expected = self._call_matcher((args, kwargs)) actual = [self._call_matcher(c) for c in self.await_args_list] if expected not in actual: cause = expected if isinstance(expected, Exception) else None expected_string = self._format_mock_call_signature(args, kwargs) raise AssertionError( '%s await not found' % expected_string ) from cause def assert_has_awaits(_mock_self, calls, any_order=False): """ Assert the mock has been awaited with the specified calls. The :attr:`await_args_list` list is checked for the awaits. If `any_order` is False (the default) then the awaits must be sequential. There can be extra calls before or after the specified awaits. If `any_order` is True then the awaits can be in any order, but they must all appear in :attr:`await_args_list`. .. versionadded:: 0.12 """ self = _mock_self expected = [self._call_matcher(c) for c in calls] cause = expected if isinstance(expected, Exception) else None all_awaits = unittest.mock._CallList(self._call_matcher(c) for c in self.await_args_list) if not any_order: if expected not in all_awaits: raise AssertionError( 'Awaits not found.\nExpected: %r\n' 'Actual: %r' % (unittest.mock._CallList(calls), self.await_args_list) ) from cause return all_awaits = list(all_awaits) not_found = [] for kall in expected: try: all_awaits.remove(kall) except ValueError: not_found.append(kall) if not_found: raise AssertionError( '%r not all found in await list' % (tuple(not_found),) ) from cause def assert_not_awaited(_mock_self): """ Assert that the mock was never awaited. .. versionadded:: 0.12 """ self = _mock_self if self.await_count != 0: msg = ("Expected '%s' to not have been awaited. Awaited %s times." % (self._mock_name or 'mock', self.await_count)) raise AssertionError(msg) def reset_mock(self, *args, **kwargs): """ See :func:`unittest.mock.Mock.reset_mock()` """ super().reset_mock(*args, **kwargs) self.awaited = _AwaitEvent(self) self.await_count = 0 self.await_args = None self.await_args_list = unittest.mock._CallList() def create_autospec(spec, spec_set=False, instance=False, _parent=None, _name=None, **kwargs): """ Create a mock object using another object as a spec. Attributes on the mock will use the corresponding attribute on the spec object as their spec. ``spec`` can be a coroutine function, a class or object with coroutine functions as attributes. If ``spec`` is a coroutine function, and ``instance`` is not ``False``, a :exc:`RuntimeError` is raised. .. versionadded:: 0.12 """ if unittest.mock._is_list(spec): spec = type(spec) is_type = isinstance(spec, type) is_coroutine_func = asyncio.iscoroutinefunction(spec) _kwargs = {'spec': spec} if spec_set: _kwargs = {'spec_set': spec} elif spec is None: # None we mock with a normal mock without a spec _kwargs = {} if _kwargs and instance: _kwargs['_spec_as_instance'] = True _kwargs.update(kwargs) Klass = MagicMock if inspect.isdatadescriptor(spec): _kwargs = {} elif is_coroutine_func: if instance: raise RuntimeError("Instance can not be True when create_autospec " "is mocking a coroutine function") Klass = CoroutineMock elif not unittest.mock._callable(spec): Klass = NonCallableMagicMock elif is_type and instance and not unittest.mock._instance_callable(spec): Klass = NonCallableMagicMock _name = _kwargs.pop('name', _name) _new_name = _name if _parent is None: _new_name = '' mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name, name=_name, **_kwargs) if isinstance(spec, unittest.mock.FunctionTypes): wrapped_mock = mock # _set_signature returns an object wrapping the mock, not the mock # itself. mock = unittest.mock._set_signature(mock, spec) if is_coroutine_func: # Can't wrap the mock with asyncio.coroutine because it doesn't # detect a CoroWrapper as an awaitable in debug mode. # It is safe to do so because the mock object wrapped by # _set_signature returns the result of the CoroutineMock itself, # which is a Coroutine (as defined in CoroutineMock._mock_call) mock._is_coroutine = _is_coroutine mock.awaited = _AwaitEvent(mock) mock.await_count = 0 mock.await_args = None mock.await_args_list = unittest.mock._CallList() for a in ('assert_awaited', 'assert_awaited_once', 'assert_awaited_with', 'assert_awaited_once_with', 'assert_any_await', 'assert_has_awaits', 'assert_not_awaited'): setattr(mock, a, getattr(wrapped_mock, a)) else: unittest.mock._check_signature(spec, mock, is_type, instance) if _parent is not None and not instance: _parent._mock_children[_name] = mock if is_type and not instance and 'return_value' not in kwargs: mock.return_value = create_autospec(spec, spec_set, instance=True, _name='()', _parent=mock) for entry in dir(spec): if unittest.mock._is_magic(entry): continue try: original = getattr(spec, entry) except AttributeError: continue kwargs = {'spec': original} if spec_set: kwargs = {'spec_set': original} if not isinstance(original, unittest.mock.FunctionTypes): new = unittest.mock._SpecState(original, spec_set, mock, entry, instance) mock._mock_children[entry] = new else: parent = mock if isinstance(spec, unittest.mock.FunctionTypes): parent = mock.mock skipfirst = unittest.mock._must_skip(spec, entry, is_type) kwargs['_eat_self'] = skipfirst if asyncio.iscoroutinefunction(original): child_klass = CoroutineMock else: child_klass = MagicMock new = child_klass(parent=parent, name=entry, _new_name=entry, _new_parent=parent, **kwargs) mock._mock_children[entry] = new unittest.mock._check_signature(original, new, skipfirst=skipfirst) if isinstance(new, unittest.mock.FunctionTypes): setattr(mock, entry, new) return mock def mock_open(mock=None, read_data=''): """ A helper function to create a mock to replace the use of :func:`open()`. It works for :func:`open()` called directly or used as a context manager. :param mock: mock object to configure, by default a :class:`~asynctest.MagicMock` object is created with the API limited to methods or attributes available on standard file handles. :param read_data: string for the :func:`read()` and :func:`readlines()` of the file handle to return. This is an empty string by default. """ if mock is None: mock = MagicMock(name='open', spec=open) return unittest.mock.mock_open(mock, read_data) ANY = unittest.mock.ANY DEFAULT = unittest.mock.sentinel.DEFAULT def _update_new_callable(patcher, new, new_callable): if new == DEFAULT and not new_callable: original = patcher.get_original()[0] if isinstance(original, (classmethod, staticmethod)): # the original object is the raw descriptor, if it's a classmethod # or a static method, we need to unwrap it original = original.__get__(None, object) if asyncio.iscoroutinefunction(original): patcher.new_callable = CoroutineMock else: patcher.new_callable = MagicMock return patcher # Documented in doc/asynctest.mock.rst PatchScope = enum.Enum('PatchScope', 'LIMITED GLOBAL') LIMITED = PatchScope.LIMITED GLOBAL = PatchScope.GLOBAL def _decorate_coroutine_callable(func, new_patching): if hasattr(func, 'patchings'): func.patchings.append(new_patching) return func # Python 3.5 returns True for is_generator_func(new_style_coroutine) if # there is an "await" statement in the function body, which is wrong. It is # fixed in 3.6, but I can't find which commit fixes this. # The only way to work correctly with 3.5 and 3.6 seems to use # inspect.iscoroutinefunction() is_generator_func = inspect.isgeneratorfunction(func) is_coroutine_func = asyncio.iscoroutinefunction(func) try: is_native_coroutine_func = inspect.iscoroutinefunction(func) except AttributeError: is_native_coroutine_func = False if not (is_generator_func or is_coroutine_func): return None patchings = [new_patching] def patched_factory(*args, **kwargs): extra_args = [] patchers_to_exit = [] patch_dict_with_limited_scope = [] exc_info = tuple() try: for patching in patchings: arg = patching.__enter__() if patching.scope == LIMITED: patchers_to_exit.append(patching) if isinstance(patching, _patch_dict): if patching.scope == GLOBAL: for limited_patching in patch_dict_with_limited_scope: if limited_patching.in_dict is patching.in_dict: limited_patching._keep_global_patch(patching) else: patch_dict_with_limited_scope.append(patching) else: if patching.attribute_name is not None: kwargs.update(arg) if patching.new is DEFAULT: patching.new = arg[patching.attribute_name] elif patching.new is DEFAULT: patching.mock_to_reuse = arg extra_args.append(arg) args += tuple(extra_args) gen = func(*args, **kwargs) return _PatchedGenerator(gen, patchings, asyncio.iscoroutinefunction(func)) except BaseException: if patching not in patchers_to_exit and _is_started(patching): # the patcher may have been started, but an exception # raised whilst entering one of its additional_patchers patchers_to_exit.append(patching) # Pass the exception to __exit__ exc_info = sys.exc_info() # re-raise the exception raise finally: for patching in reversed(patchers_to_exit): patching.__exit__(*exc_info) # wrap the factory in a native coroutine or a generator to respect # introspection. if is_native_coroutine_func: # inspect.iscoroutinefunction() returns True patched = _make_native_coroutine(patched_factory) elif is_generator_func: # inspect.isgeneratorfunction() returns True def patched_generator(*args, **kwargs): return (yield from patched_factory(*args, **kwargs)) patched = patched_generator if is_coroutine_func: # asyncio.iscoroutinefunction() returns True patched = asyncio.coroutine(patched) else: patched = patched_factory patched.patchings = patchings return functools.wraps(func)(patched) class _PatchedGenerator(asyncio.coroutines.CoroWrapper): # Inheriting from asyncio.CoroWrapper gives us a comprehensive wrapper # implementing one or more workarounds for cpython bugs def __init__(self, gen, patchings, is_coroutine): self.gen = gen self._is_coroutine = is_coroutine self.__name__ = getattr(gen, '__name__', None) self.__qualname__ = getattr(gen, '__qualname__', None) self.patchings = patchings self.global_patchings = [p for p in patchings if p.scope == GLOBAL] self.limited_patchings = [p for p in patchings if p.scope == LIMITED] # GLOBAL patches have been started in the _patch/patched() wrapper def _limited_patchings_stack(self): with contextlib.ExitStack() as stack: for patching in self.limited_patchings: stack.enter_context(patching) return stack.pop_all() def _stop_global_patchings(self): for patching in reversed(self.global_patchings): if _is_started(patching): patching.stop() def __repr__(self): return repr(self.generator) def __next__(self): try: with self._limited_patchings_stack(): return self.gen.send(None) except BaseException: # the generator/coroutine terminated, stop the patchings self._stop_global_patchings() raise def send(self, value): with self._limited_patchings_stack(): return super().send(value) def throw(self, exc, value=None, traceback=None): with self._limited_patchings_stack(): return self.gen.throw(exc, value, traceback) def close(self): try: with self._limited_patchings_stack(): return self.gen.close() finally: self._stop_global_patchings() def __del__(self): # The generator/coroutine is deleted before it terminated, we must # still stop the patchings self._stop_global_patchings() class _patch(unittest.mock._patch): def __init__(self, *args, scope=GLOBAL, **kwargs): super().__init__(*args, **kwargs) self.scope = scope self.mock_to_reuse = None def copy(self): patcher = _patch( self.getter, self.attribute, self.new, self.spec, self.create, self.spec_set, self.autospec, self.new_callable, self.kwargs, scope=self.scope) patcher.attribute_name = self.attribute_name patcher.additional_patchers = [ p.copy() for p in self.additional_patchers ] return patcher def __enter__(self): # When patching a coroutine, we reuse the same mock object if self.mock_to_reuse is not None: self.target = self.getter() self.temp_original, self.is_local = self.get_original() setattr(self.target, self.attribute, self.mock_to_reuse) if self.attribute_name is not None: for patching in self.additional_patchers: patching.__enter__() return self.mock_to_reuse else: return self._perform_patch() def _perform_patch(self): # This will intercept the result of super().__enter__() if we need to # override the default behavior (ie: we need to use our own autospec). original, local = self.get_original() result = super().__enter__() if self.autospec is None or not self.autospec: # no need to override the default behavior return result if self.autospec is True: autospec = original else: autospec = self.autospec new = create_autospec(autospec, spec_set=bool(self.spec_set), _name=self.attribute, **self.kwargs) self.temp_original = original self.is_local = local setattr(self.target, self.attribute, new) if self.attribute_name is not None: if self.new is DEFAULT: result[self.attribute_name] = new return result return new def decorate_callable(self, func): wrapped = _decorate_coroutine_callable(func, self) if wrapped is None: return super().decorate_callable(func) else: return wrapped def patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, scope=GLOBAL, **kwargs): """ A context manager, function decorator or class decorator which patches the target with the value given by the ``new`` argument. ``new`` specifies which object will replace the ``target`` when the patch is applied. By default, the target will be patched with an instance of :class:`~asynctest.CoroutineMock` if it is a coroutine, or a :class:`~asynctest.MagicMock` object. It is a replacement to :func:`unittest.mock.patch`, but using :mod:`asynctest.mock` objects. When a generator or a coroutine is patched using the decorator, the patch is activated or deactivated according to the ``scope`` argument value: * :const:`asynctest.GLOBAL`: the default, enables the patch until the generator or the coroutine finishes (returns or raises an exception), * :const:`asynctest.LIMITED`: the patch will be activated when the generator or coroutine is being executed, and deactivated when it yields a value and pauses its execution (with ``yield``, ``yield from`` or ``await``). The behavior differs from :func:`unittest.mock.patch` for generators. When used as a context manager, the patch is still active even if the generator or coroutine is paused, which may affect concurrent tasks:: @asyncio.coroutine def coro(): with asynctest.mock.patch("module.function"): yield from asyncio.get_event_loop().sleep(1) @asyncio.coroutine def independent_coro(): assert not isinstance(module.function, asynctest.mock.Mock) asyncio.create_task(coro()) asyncio.create_task(independent_coro()) # this will raise an AssertionError(coro() is scheduled first)! loop.run_forever() :param scope: :const:`asynctest.GLOBAL` or :const:`asynctest.LIMITED`, controls when the patch is activated on generators and coroutines When used as a decorator with a generator based coroutine, the order of the decorators matters. The order of the ``@patch()`` decorators is in the reverse order of the parameters produced by these patches for the patched function. And the ``@asyncio.coroutine`` decorator should be the last since ``@patch()`` conceptually patches the coroutine, not the function:: @patch("module.function2") @patch("module.function1") @asyncio.coroutine def test_coro(self, mock_function1, mock_function2): yield from asyncio.get_event_loop().sleep(1) see :func:`unittest.mock.patch()`. .. versionadded:: 0.6 patch into generators and coroutines with a decorator. """ getter, attribute = unittest.mock._get_target(target) patcher = _patch(getter, attribute, new, spec, create, spec_set, autospec, new_callable, kwargs, scope=scope) return _update_new_callable(patcher, new, new_callable) def _patch_object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, scope=GLOBAL, **kwargs): patcher = _patch(lambda: target, attribute, new, spec, create, spec_set, autospec, new_callable, kwargs, scope=scope) return _update_new_callable(patcher, new, new_callable) def _patch_multiple(target, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, scope=GLOBAL, **kwargs): if type(target) is str: def getter(): return unittest.mock._importer(target) else: def getter(): return target if not kwargs: raise ValueError('Must supply at least one keyword argument with ' 'patch.multiple') items = list(kwargs.items()) attribute, new = items[0] patcher = _patch(getter, attribute, new, spec, create, spec_set, autospec, new_callable, {}, scope=scope) patcher.attribute_name = attribute for attribute, new in items[1:]: this_patcher = _patch(getter, attribute, new, spec, create, spec_set, autospec, new_callable, {}, scope=scope) this_patcher.attribute_name = attribute patcher.additional_patchers.append(this_patcher) def _update(patcher): return _update_new_callable(patcher, patcher.new, new_callable) patcher = _update(patcher) patcher.additional_patchers = list(map(_update, patcher.additional_patchers)) return patcher class _patch_dict(unittest.mock._patch_dict): # documentation is in doc/asynctest.mock.rst def __init__(self, in_dict, values=(), clear=False, scope=GLOBAL, **kwargs): super().__init__(in_dict, values, clear, **kwargs) self.scope = scope self._is_started = False self._global_patchings = [] def _keep_global_patch(self, other_patching): self._global_patchings.append(other_patching) def decorate_class(self, klass): for attr in dir(klass): attr_value = getattr(klass, attr) if (attr.startswith(patch.TEST_PREFIX) and hasattr(attr_value, "__call__")): decorator = _patch_dict(self.in_dict, self.values, self.clear) decorated = decorator(attr_value) setattr(klass, attr, decorated) return klass def __call__(self, func): if isinstance(func, type): return self.decorate_class(func) wrapper = _decorate_coroutine_callable(func, self) if wrapper is None: return super().__call__(func) else: return wrapper def _patch_dict(self): self._is_started = True # Since Python 3.7.3, the moment when a dict specified by a target # string has been corrected. (see #115) if isinstance(self.in_dict, str): self.in_dict = unittest.mock._importer(self.in_dict) try: self._original = self.in_dict.copy() except AttributeError: # dict like object with no copy method # must support iteration over keys self._original = {} for key in self.in_dict: self._original[key] = self.in_dict[key] if self.clear: _clear_dict(self.in_dict) try: self.in_dict.update(self.values) except AttributeError: # dict like object with no update method for key in self.values: self.in_dict[key] = self.values[key] def _unpatch_dict(self): self._is_started = False if self.scope == LIMITED: # add to self.values the updated values which where not in # the original dict, as the patch may be reactivated for key in self.in_dict: if (key not in self._original or self._original[key] is not self.in_dict[key]): self.values[key] = self.in_dict[key] _clear_dict(self.in_dict) originals = [self._original] for patching in self._global_patchings: if patching._is_started: # keep the values of global patches originals.append(patching.values) for original in originals: try: self.in_dict.update(original) except AttributeError: for key in original: self.in_dict[key] = original[key] _clear_dict = unittest.mock._clear_dict patch.object = _patch_object patch.dict = _patch_dict patch.multiple = _patch_multiple patch.stopall = unittest.mock._patch_stopall patch.TEST_PREFIX = 'test' sentinel = unittest.mock.sentinel call = unittest.mock.call PropertyMock = unittest.mock.PropertyMock def return_once(value, then=None): """ Helper to use with ``side_effect``, so a mock will return a given value only once, then return another value. When used as a ``side_effect`` value, if one of ``value`` or ``then`` is an :class:`Exception` type, an instance of this exception will be raised. >>> mock.recv = Mock(side_effect=return_once(b"data")) >>> mock.recv() b"data" >>> repr(mock.recv()) 'None' >>> repr(mock.recv()) 'None' >>> mock.recv = Mock(side_effect=return_once(b"data", then=BlockingIOError)) >>> mock.recv() b"data" >>> mock.recv() Traceback BlockingIOError :param value: value to be returned once by the mock when called. :param then: value returned for any subsequent call. .. versionadded:: 0.4 """ yield value while True: yield then asynctest-0.13.0/asynctest/selector.py000066400000000000000000000266541346656454500200560ustar00rootroot00000000000000# 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) asynctest-0.13.0/doc/000077500000000000000000000000001346656454500143775ustar00rootroot00000000000000asynctest-0.13.0/doc/Makefile000066400000000000000000000163751346656454500160530ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/asynctest.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/asynctest.qhc" applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/asynctest" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/asynctest" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." asynctest-0.13.0/doc/asynctest.case.rst000066400000000000000000000100161346656454500200560ustar00rootroot00000000000000.. automodule:: asynctest.case .. toctree:: :maxdepth: 2 .. py:currentmodule:: asynctest TestCases ~~~~~~~~~ .. autoclass:: TestCase :members: :undoc-members: :exclude-members: debug, run .. method:: setUp() Method or coroutine called to prepare the test fixture. see :py:meth:`unittest.TestCase.setUp()` .. method:: tearDown() Method called immediately after the test method has been called and the result recorded. see :py:meth:`unittest.TestCase.tearDown()` .. autoclass:: FunctionTestCase :members: :undoc-members: .. autoclass:: ClockedTestCase :members: :undoc-members: :exclude-members: setUp Decorators ~~~~~~~~~~ .. decorator:: fail_on(**checks) Enable checks on the loop state after a test ran to help testers to identify common mistakes. Enable or disable a check using a keywork argument with a boolean value:: @asynctest.fail_on(unused_loop=True) class TestCase(asynctest.TestCase): ... Available checks are: * ``unused_loop``: disabled by default, checks that the loop ran at least once during the test. This check can not fail if the test method is a coroutine. This allows to detect cases where a test author assume its test will run tasks or callbacks on the loop, but it actually didn't. * ``active_selector_callbacks``: enabled by default, checks that any registered reader or writer callback on a selector loop (with ``add_reader()`` or ``add_writer()``) is later explicitly unregistered (with ``remove_reader()`` or ``remove_writer()``) before the end of the test. * ``active_handles``: disabled by default, checks that there is not scheduled callback left to be executed on the loop at the end of the test. The helper :func:`~asynctest.helpers.exhaust_callbacks()` can help to give a chance to the loop to run pending callbacks. The decorator of a method has a greater priority than the decorator of a class. When :func:`~asynctest.fail_on` decorates a class and one of its methods with conflicting arguments, those of the class are overriden. Subclasses of a decorated :class:`~asynctest.TestCase` inherit of the checks enabled on the parent class. .. versionadded:: 0.8 .. versionadded:: 0.9 ``active_handles`` .. versionadded:: 0.12 ``unused_loop`` is now deactivated by default to maintain compatibility with non-async test inherited from :class:`unittest.TestCase`. This check is especially useful to track missing ``@asyncio.coroutine`` decorators in a codebase that must be compatbible with Python 3.4. .. decorator:: strict Activate strict checking of the state of the loop after a test ran. It is a shortcut to :func:`~asynctest.fail_on` with all checks set to ``True``. Note that by definition, the behavior of :func:`strict` will change in the future when new checks will be added, and may break existing tests with new errors after an update of the library. .. versionadded:: 0.8 .. decorator:: lenient Deactivate all checks performed after a test ran. It is a shortcut to :func:`~asynctest.fail_on` with all checks set to ``False``. .. versionadded:: 0.8 .. decorator:: ignore_loop By default, a test fails if the loop did not run during the test (including set up and tear down), unless the :class:`~asynctest.TestCase` class or test function is decorated by :func:`~asynctest.ignore_loop`. .. deprecated:: 0.8 Use :func:`~asynctest.fail_on` with ``unused_loop=False`` instead. asynctest-0.13.0/doc/asynctest.helpers.rst000066400000000000000000000001041346656454500206020ustar00rootroot00000000000000.. automodule:: asynctest.helpers :members: :undoc-members: asynctest-0.13.0/doc/asynctest.mock.rst000066400000000000000000000057371346656454500201120ustar00rootroot00000000000000.. automodule:: asynctest.mock .. toctree:: :maxdepth: 2 .. py:currentmodule:: asynctest Mock classes ~~~~~~~~~~~~ .. autoclass:: Mock :members: :undoc-members: .. autoclass:: NonCallableMock :members: :undoc-members: .. autoclass:: MagicMock :members: :undoc-members: .. autoclass:: CoroutineMock :members: :undoc-members: Autospeccing ~~~~~~~~~~~~ .. autofunction:: create_autospec Patch ~~~~~ .. data:: GLOBAL Value of ``scope``, activating a patch until the decorated generator or coroutine returns or raises an exception. .. data:: LIMITED Value of ``scope``, deactivating a patch when a decorated generator or a coroutine pauses (``yield`` or ``await``). .. autofunction:: patch .. py:currentmodule:: asynctest.patch .. function:: object(target, attribute, new=DEFAULT, \ spec=None, create=False, spec_set=None, autospec=None, \ new_callable=None, scope=asynctest.GLOBAL, **kwargs) Patch the named member (``attribute``) on an object (``target``) with a mock object, in the same fashion as :func:`~asynctest.patch`. See :func:`~asynctest.patch` and :func:`unittest.mock.patch.object`. .. function:: multiple(target, spec=None, create=False, \ spec_set=None, autospec=None, new_callable=None, \ scope=asynctest.global, **kwargs) Perform multiple patches in a single call. It takes the object to be patched (either as an object or a string to fetch the object by importing) and keyword arguments for the patches. See :func:`~asynctest.patch` and :func:`unittest.mock.patch.multiple`. .. function:: dict(in_dict, values=(), clear=False, \ scope=asynctest.GLOBAL, **kwargs) Patch a dictionary, or dictionary like object, and restore the dictionary to its original state after the test. Its behavior can be controlled on decorated generators and coroutines with ``scope``. .. versionadded:: 0.8 patch into generators and coroutines with a decorator. :param in_dict: dictionary like object, or string referencing the object to patch. :param values: a dictionary of values or an iterable of (key, value) pairs to set in the dictionary. :param clear: if ``True``, in_dict will be cleared before the new values are set. :param scope: :const:`asynctest.GLOBAL` or :const:`asynctest.LIMITED`, controls when the patch is activated on generators and coroutines :see: :func:`~asynctest.patch` (details about ``scope``) and :func:`unittest.mock.patch.dict`. .. py:currentmodule:: asynctest Helpers ~~~~~~~ .. autofunction:: mock_open .. autofunction:: return_once asynctest-0.13.0/doc/asynctest.selector.rst000066400000000000000000000016231346656454500207670ustar00rootroot00000000000000.. automodule:: asynctest.selector .. toctree:: :maxdepth: 2 Mocking file-like objects ~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: asynctest.FileMock :members: :undoc-members: .. autoclass:: asynctest.SocketMock :members: :undoc-members: :show-inheritance: .. autoclass:: asynctest.SSLSocketMock :members: :undoc-members: :show-inheritance: .. autoclass:: asynctest.FileDescriptor :members: :undoc-members: :show-inheritance: Helpers ####### .. autofunction:: asynctest.fd .. autofunction:: asynctest.isfilemock Mocking the selector ~~~~~~~~~~~~~~~~~~~~ .. autoclass:: asynctest.TestSelector :members: :undoc-members: Helpers ####### .. autofunction:: asynctest.set_read_ready .. autofunction:: asynctest.set_write_ready asynctest-0.13.0/doc/conf.py000066400000000000000000000231151346656454500157000ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # asynctest documentation build configuration file, created by # sphinx-quickstart. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # Import asynctest source directory sys.path.insert(0, os.path.abspath('..')) # NOQA try: from setuptools.config import read_configuration cfg_path = os.path.join(os.path.dirname(__file__), '..', 'setup.cfg') setup_args = read_configuration(cfg_path)['metadata'] except ImportError: from setup import args as setup_args, read_version setup_args["author"] = "Martin Richard" setup_args["description"] = "" setup_args["version"] = read_version() # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = setup_args["name"] author = copyright = setup_args["author"] # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. release = setup_args["version"] # The short X.Y version. version = ".".join(release.split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. on_rtd = os.environ.get('READTHEDOCS', None) == 'True' html_theme = 'default' if on_rtd else 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', # Latex figure (float) alignment # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [(master_doc, project + '.tex', project + ' Documentation', author, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, project, project + ' Documentation', [author], 1), ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [(master_doc, project, project + ' Documentation', author, project, setup_args["description"], 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/3': None} asynctest-0.13.0/doc/examples/000077500000000000000000000000001346656454500162155ustar00rootroot00000000000000asynctest-0.13.0/doc/examples/index.rst000066400000000000000000000002631346656454500200570ustar00rootroot00000000000000List of code examples --------------------- .. toctree:: tutorial/clock.py tutorial/mocking.py tutorial/mocking_io.py tutorial/patching.py tutorial/test_cases.py asynctest-0.13.0/doc/examples/tutorial/000077500000000000000000000000001346656454500200605ustar00rootroot00000000000000asynctest-0.13.0/doc/examples/tutorial/clock.py000066400000000000000000000027411346656454500215310ustar00rootroot00000000000000# coding: utf-8 import time import asynctest def is_time_around(expected_time, loop=None, delta=.01): """ Checks that the time is equal to ``expected_time`` (within the range of +/- ``delta``). If ``loop`` is provided, the clock of the loop is used. """ now = loop.time() if loop else time.time() return (expected_time - delta) <= now <= (expected_time + delta) class TestAdvanceTime(asynctest.ClockedTestCase): async def test_advance_time(self): base_loop_time = self.loop.time() base_wall_time = time.time() await self.advance(10) self.assertEqual(base_loop_time + 10, self.loop.time()) self.assertTrue(is_time_around(base_wall_time)) class TestWithClockAndCallbacks(asynctest.ClockedTestCase): results = None def runs_at(self, expected_time): self.results.append(is_time_around(expected_time, self.loop)) @asynctest.fail_on(active_handles=True) async def test_callbacks_executed_when_expected(self): self.results = [] base_time = self.loop.time() self.loop.call_later(1, self.runs_at, base_time + 1) self.loop.call_at(base_time + 7, self.runs_at, base_time + 7) # This shows that the callback didn't run yet self.assertEqual(0, len(self.results)) await self.advance(10) # This shows that the callbacks ran... self.assertEqual(2, len(self.results)) # ...when expected self.assertTrue(all(self.results)) asynctest-0.13.0/doc/examples/tutorial/clock.py.rst000066400000000000000000000001121346656454500223260ustar00rootroot00000000000000``tutorial/clock.py`` --------------------- .. literalinclude:: clock.py asynctest-0.13.0/doc/examples/tutorial/mocking.py000066400000000000000000000304111346656454500220600ustar00rootroot00000000000000# coding: utf-8 # pylama: ignore=C0103, ignore camel case variable name (AsyncClientMock) import asyncio import collections import itertools import logging import asynctest class Client: def add_user(self, user): raise NotImplementedError def get_users(self): raise NotImplementedError def increase_nb_users_cached(self, nb_cached): raise NotImplementedError class AsyncClient: async def add_user(self, user, transaction=None): raise NotImplementedError async def get_users(self, transaction=None): raise NotImplementedError async def increase_nb_users_cached(self, nb_cached, transaction=None): raise NotImplementedError def get_users_cursor(self, transaction=None): return self.Cursor(transaction or self) def new_transaction(self): return self.Transaction(self) class Transaction: def __init__(self, client): self.client = client def __call__(self, funcname, *args, **kwargs): """ Forwards the call to the client, with the argument ``transaction `` set. """ method = getattr(self.client, funcname) return method(*args, transaction=self, **kwargs) async def __aenter__(self): return self async def __aexit__(self, *args): pass class Cursor: def __init__(self, transaction): self.transaction = transaction def __aiter__(self): return self async def __anext__(self): # if the request has not been started, do it there raise NotImplementedError def cache_users(client, cache): """ Load the list of users from a distant server accessed with ``client``, add them to ``cache``. Notify the server about the number of new users put in the cache, and returns this number. :param client: a connection to the distant server :param cache: a dict-like object """ users = client.get_users() nb_users_cached = 0 for user in users: if user.id not in cache: nb_users_cached += 1 cache[user.id] = user client.increase_nb_users_cached(nb_users_cached) logging.debug("added %d users to the cache %r", nb_users_cached, cache) return nb_users_cached class StubClient: User = collections.namedtuple("User", "id username") def __init__(self, *users_to_return): self.users_to_return = [] self.users_to_return.extend(users_to_return) self.nb_users_cached = 0 def add_user(self, user): self.users_to_return.append(user) def get_users(self): return self.users_to_return def increase_nb_users_cached(self, nb_cached): self.nb_users_cached += nb_cached class TestUsingStub(asynctest.TestCase): def test_one_user_added_to_cache(self): user = StubClient.User(1, "a.dmin") client = StubClient(user) cache = {} # The user has been added to the cache nb_added = cache_users(client, cache) self.assertEqual(nb_added, 1) self.assertEqual(cache[1], user) # The user was already there nb_added = cache_users(client, cache) self.assertEqual(nb_added, 0) self.assertEqual(cache[1], user) def test_no_users_to_add(self): cache = {} nb_added = cache_users(StubClient(), cache) self.assertEqual(nb_added, 0) self.assertEqual(len(cache), 0) class TestUsingMock(asynctest.TestCase): def test_no_users_to_add(self): client = asynctest.Mock(Client()) client.get_users.return_value = [] cache = {} nb_added = cache_users(client, cache) client.get_users.assert_called() self.assertEqual(nb_added, 0) self.assertEqual(len(cache), 0) client.increase_nb_users_cached.assert_called_once_with(0) async def cache_users_async(client, cache): users = await client.get_users() nb_users_cached = 0 for user in users: if user.id not in cache: nb_users_cached += 1 cache[user.id] = user await client.increase_nb_users_cached(nb_users_cached) logging.debug("added %d users to the cache %r", nb_users_cached, cache) return nb_users_cached class TestUsingFuture(asynctest.TestCase): async def test_no_users_to_add(self): client = asynctest.Mock(Client()) client.get_users.return_value = asyncio.Future() client.get_users.return_value.set_result([]) client.increase_nb_users_cached.return_value = asyncio.Future() client.increase_nb_users_cached.return_value.set_result(None) cache = {} nb_added = await cache_users_async(client, cache) client.get_users.assert_called() self.assertEqual(nb_added, 0) self.assertEqual(len(cache), 0) client.increase_nb_users_cached.assert_called_once_with(0) class TestUsingCoroutineMock(asynctest.TestCase): async def test_no_users_to_add(self): client = asynctest.Mock(Client()) client.get_users = asynctest.CoroutineMock(return_value=[]) client.increase_nb_users_cached = asynctest.CoroutineMock() cache = {} nb_added = await cache_users_async(client, cache) client.get_users.assert_awaited() self.assertEqual(nb_added, 0) self.assertEqual(len(cache), 0) client.increase_nb_users_cached.assert_awaited_once_with(0) class TestUsingCoroutineMockAndSpec(asynctest.TestCase): async def test_no_users_to_add(self): client = asynctest.Mock(AsyncClient()) client.get_users.return_value = [] cache = {} nb_added = await cache_users_async(client, cache) client.get_users.assert_awaited() self.assertEqual(nb_added, 0) self.assertEqual(len(cache), 0) client.increase_nb_users_cached.assert_awaited_once_with(0) class TestAutoSpec(asynctest.TestCase): async def test_functions_and_coroutines_arguments_are_checked(self): client = asynctest.Mock(Client()) cache = {} cache_users_mock = asynctest.create_autospec(cache_users_async) with self.subTest("create_autospec returns a regular mock"): await cache_users_mock(client, cache) cache_users_mock.assert_awaited_once_with(client, cache) with self.subTest("an exception is raised when the mock is called " "with the wrong number of arguments"): with self.assertRaises(TypeError): await cache_users_mock("wrong", "number", "of", "args") async def test_create_autospec_on_a_class(self): AsyncClientMock = asynctest.create_autospec(AsyncClient) client = AsyncClientMock() with self.subTest("the mock of a class returns a mock instance of " "the class"): self.assertIsInstance(client, AsyncClient) with self.subTest("attributes of the mock instance are correctly " "mocked as coroutines"): await client.increase_nb_users_cached(1) class TestCoroutineMockResult(asynctest.TestCase): async def test_result_set_with_return_value(self): coroutine_mock = asynctest.CoroutineMock() result = object() coroutine_mock.return_value = result # return the expected result self.assertIs(result, await coroutine_mock()) # always return the same result self.assertIs(await coroutine_mock(), await coroutine_mock()) async def test_result_with_side_effect_function(self): def uppercase_all(*args): return tuple(arg.upper() for arg in args) coroutine_mock = asynctest.CoroutineMock() coroutine_mock.side_effect = uppercase_all self.assertEqual(("FIRST", "CALL"), await coroutine_mock("first", "call")) self.assertEqual(("A", "SECOND", "CALL"), await coroutine_mock("a", "second", "call")) async def test_result_with_side_effect_exception(self): coroutine_mock = asynctest.CoroutineMock() coroutine_mock.side_effect = NotImplementedError # Raise an exception of the configured type with self.assertRaises(NotImplementedError): await coroutine_mock("any", "number", "of", "args") coroutine_mock.side_effect = Exception("an instance of exception") # Raise the exact specified object with self.assertRaises(Exception) as context: await coroutine_mock() self.assertIs(coroutine_mock.side_effect, context.exception) async def test_result_with_side_effect_iterable(self): coroutine_mock = asynctest.CoroutineMock() coroutine_mock.side_effect = ["one", "two", "three"] self.assertEqual("one", await coroutine_mock()) self.assertEqual("two", await coroutine_mock()) self.assertEqual("three", await coroutine_mock()) coroutine_mock.side_effect = itertools.cycle(["odd", "even"]) self.assertEqual("odd", await coroutine_mock()) self.assertEqual("even", await coroutine_mock()) self.assertEqual("odd", await coroutine_mock()) self.assertEqual("even", await coroutine_mock()) async def test_result_with_wrapped_object(self): stub = StubClient() mock = asynctest.Mock(stub, wraps=stub) cache = {} stub.add_user(StubClient.User(1, "a.dmin")) cache_users(mock, cache) mock.get_users.assert_called() self.assertEqual(stub.users_to_return, mock.get_users()) async def cache_users_with_cursor(client, cache): nb_users_cached = 0 async with client.new_transaction() as transaction: users_cursor = transaction.get_users_cursor() async for user in users_cursor: if user.id not in cache: nb_users_cached += 1 cache[user.id] = user await transaction.increase_nb_users_cached(nb_users_cached) logging.debug("added %d users to the cache %r", nb_users_cached, cache) return nb_users_cached class TestWithMagicMethods(asynctest.TestCase): async def test_context_manager(self): with self.assertRaises(AssertionError): async with asynctest.MagicMock() as context: # context is a MagicMock context.assert_called() async def test_empty_iterable(self): loop_iterations = 0 async for _ in asynctest.MagicMock(): loop_iterations += 1 self.assertEqual(0, loop_iterations) async def test_iterable(self): loop_iterations = 0 mock = asynctest.MagicMock() mock.__aiter__.return_value = range(5) async for _ in mock: loop_iterations += 1 self.assertEqual(5, loop_iterations) class TestCacheWithMagicMethods(asynctest.TestCase): async def test_one_user_added_to_cache(self): user = StubClient.User(1, "a.dmin") AsyncClientMock = asynctest.create_autospec(AsyncClient) transaction = asynctest.MagicMock() transaction.__aenter__.side_effect = AsyncClientMock cursor = asynctest.MagicMock() cursor.__aiter__.return_value = [user] client = AsyncClientMock() client.new_transaction.return_value = transaction client.get_users_cursor.return_value = cursor cache = {} # The user has been added to the cache nb_added = await cache_users_with_cursor(client, cache) self.assertEqual(nb_added, 1) self.assertEqual(cache[1], user) # The user was already there nb_added = await cache_users_with_cursor(client, cache) self.assertEqual(nb_added, 0) self.assertEqual(cache[1], user) class TestCachingIsLogged(asynctest.TestCase): async def test_with_context_manager(self): client = asynctest.Mock(AsyncClient()) cache = {} with asynctest.patch("logging.debug") as debug_mock: await cache_users_async(client, cache) debug_mock.assert_called() @asynctest.patch("logging.error") @asynctest.patch("logging.debug") async def test_with_decorator(self, debug_mock, error_mock): client = asynctest.Mock(AsyncClient()) cache = {} await cache_users_async(client, cache) debug_mock.assert_called() error_mock.assert_not_called() asynctest-0.13.0/doc/examples/tutorial/mocking.py.rst000066400000000000000000000001201346656454500226610ustar00rootroot00000000000000``tutorial/mocking.py`` ----------------------- .. literalinclude:: mocking.py asynctest-0.13.0/doc/examples/tutorial/mocking_io.py000066400000000000000000000030421346656454500225470ustar00rootroot00000000000000# coding: utf-8 import asyncio import socket import asynctest class TestMockASocket(asynctest.TestCase): async def test_read_and_write_from_socket(self): socket_mock = asynctest.SocketMock() socket_mock.type = socket.SOCK_STREAM recv_data = iter(( b"some data read", b"some other", b" ...and the last", )) recv_buffer = bytearray() def recv_side_effect(max_bytes): nonlocal recv_buffer if not recv_buffer: try: recv_buffer.extend(next(recv_data)) asynctest.set_read_ready(socket_mock, self.loop) except StopIteration: # nothing left pass data = recv_buffer[:max_bytes] recv_buffer = recv_buffer[max_bytes:] if recv_buffer: # Some more data to read asynctest.set_read_ready(socket_mock, self.loop) return data def send_side_effect(data): asynctest.set_read_ready(socket_mock, self.loop) return len(data) socket_mock.recv.side_effect = recv_side_effect socket_mock.send.side_effect = send_side_effect reader, writer = await asyncio.open_connection(sock=socket_mock) writer.write(b"a request?") self.assertEqual(b"some", await reader.read(4)) self.assertEqual(b" data read", await reader.read(10)) self.assertEqual(b"some other ...and the last", await reader.read()) asynctest-0.13.0/doc/examples/tutorial/mocking_io.py.rst000066400000000000000000000001311346656454500233520ustar00rootroot00000000000000``tutorial/mocking_io.py`` -------------------------- .. literalinclude:: mocking_io.py asynctest-0.13.0/doc/examples/tutorial/patching.py000066400000000000000000000044401346656454500222310ustar00rootroot00000000000000# coding: utf-8 import asyncio import unittest.mock import asynctest class MustBePatched: async def is_patched(self): """ return ``False``, unless patched. """ return False async def crash_if_patched(self, ran_event): """ Verify that the method is not patched. The coroutine is put to sleep for a duration of 0, meaning it let the loop schedule other coroutines concurrently. Each time the check is performed, ``ran_event`` is set. """ try: while True: try: is_patched = await self.is_patched() assert not is_patched await asyncio.sleep(0) finally: ran_event.set() except asyncio.CancelledError: pass async def terminate_and_check_task(task): task.cancel() await task async def happened_once(event): await event.wait() event.clear() must_be_patched = MustBePatched() # noqa class TestMustBePatched(asynctest.TestCase): async def setUp(self): # Event used to track if the background task checked if the patch # is active self.checked = asyncio.Event() # This task checks if the object is patched continuously, and sets # the checked event everytime it does so. self.background_task = asyncio.create_task( must_be_patched.crash_if_patched(self.checked)) # Any test will fail if the background task raises an exception self.addCleanup(terminate_and_check_task, self.background_task) @asynctest.patch.object(must_be_patched, "is_patched", return_value=True) async def test_patching_conflicting(self, _): # This call blocks until the check happened once in background await happened_once(self.checked) self.assertTrue(await must_be_patched.is_patched()) await happened_once(self.checked) @asynctest.patch.object(must_be_patched, "is_patched", return_value=True, scope=asynctest.LIMITED) async def test_patching_not_conflicting(self, _): await happened_once(self.checked) self.assertTrue(await must_be_patched.is_patched()) await happened_once(self.checked) asynctest-0.13.0/doc/examples/tutorial/patching.py.rst000066400000000000000000000001231346656454500230320ustar00rootroot00000000000000``tutorial/patching.py`` ------------------------ .. literalinclude:: patching.py asynctest-0.13.0/doc/examples/tutorial/test_cases.py000066400000000000000000000045651346656454500226010ustar00rootroot00000000000000# coding: utf-8 import asyncio import asynctest class MinimalExample(asynctest.TestCase): def test_that_true_is_true(self): self.assertTrue(True) class AnExampleWithSetup(asynctest.TestCase): async def a_coroutine(self): return "I worked" def test_that_a_coroutine_runs(self): my_loop = asyncio.new_event_loop() try: result = my_loop.run_until_complete(self.a_coroutine()) self.assertIn("worked", result) finally: my_loop.close() class AnExampleWithSetupMethod(asynctest.TestCase): async def a_coroutine(self): return "I worked" def setUp(self): self.my_loop = asyncio.new_event_loop() def test_that_a_coroutine_runs(self): result = self.my_loop.run_until_complete(self.a_coroutine()) self.assertIn("worked", result) def tearDown(self): self.my_loop.close() class AnExampleWithSetupAndCleanup(asynctest.TestCase): async def a_coroutine(self): return "I worked" def setUp(self): self.my_loop = asyncio.new_event_loop() self.addCleanup(self.my_loop.close) def test_that_a_coroutine_runs(self): result = self.my_loop.run_until_complete(self.a_coroutine()) self.assertIn("worked", result) class AnExampleWithTestCaseLoop(asynctest.TestCase): async def a_coroutine(self): return "I worked" def test_that_a_coroutine_runs(self): result = self.loop.run_until_complete(self.a_coroutine()) self.assertIn("worked", result) class AnExampleWithTestCaseAndCoroutines(asynctest.TestCase): async def a_coroutine(self): return "I worked" async def test_that_a_coroutine_runs(self): self.assertIn("worked", await self.a_coroutine()) class AnExampleWithAsynchronousSetUp(asynctest.TestCase): async def setUp(self): self.queue = asyncio.Queue(maxsize=1) await self.queue.put("I worked") async def test_that_a_lock_is_acquired(self): self.assertTrue(self.queue.full()) async def tearDown(self): while not self.queue.empty(): await self.queue.get() class AnExempleWhichDetectsPendingCallbacks(asynctest.TestCase): def i_must_run(self): pass # do something @asynctest.fail_on(active_handles=True) async def test_missing_a_callback(self): self.loop.call_later(1, self.i_must_run) asynctest-0.13.0/doc/examples/tutorial/test_cases.py.rst000066400000000000000000000001311346656454500233710ustar00rootroot00000000000000``tutorial/test_cases.py`` -------------------------- .. literalinclude:: test_cases.py asynctest-0.13.0/doc/generate_examples_rst.py000066400000000000000000000041101346656454500213250ustar00rootroot00000000000000# coding: utf-8 import fnmatch import pathlib import os.path import re import logging logging.basicConfig(level=logging.INFO) INCLUDED_SOURCES = ("*.py", ) EXCLUDED_SOURCES = ("__*__.py", ) INCLUDED_SOURCES_REGEX = tuple(re.compile(fnmatch.translate(pattern)) for pattern in INCLUDED_SOURCES) EXCLUDED_SOURCES_REGEX = tuple(re.compile(fnmatch.translate(pattern)) for pattern in EXCLUDED_SOURCES) def include_file(filename): return (any(regex.match(filename) for regex in INCLUDED_SOURCES_REGEX) and not any(regex.match(filename) for regex in EXCLUDED_SOURCES_REGEX)) def list_examples(src_dir): examples = [] for dirname, _, filenames in os.walk(src_dir): for filename in filenames: if include_file(filename): examples.append((pathlib.Path(dirname), filename)) index_contents = [] return sorted(examples) def generate_examples_rst(src_dir="examples/"): examples = list_examples(src_dir) # Generate the index logging.info("Creating index file") with open(os.path.join(src_dir, "index.rst"), "w") as index: index.write( "List of code examples\n" "---------------------\n" "\n" ".. toctree::\n" "\n" ) for example_dirname, example_filename in examples: example_pathname = os.path.join( example_dirname.relative_to(src_dir), example_filename) rst_filename = os.path.join(src_dir, f"{example_pathname}.rst") index.write(f" {example_pathname}\n") logging.info("generating file for %s", example_pathname) with open(rst_filename, "w") as example_rst: example_rst.write( f"``{example_pathname}``\n" f"{'-' * (len(example_pathname) + 4)}\n\n" f".. literalinclude:: {example_filename}\n" ) logging.info("index and source file generated") if __name__ == "__main__": generate_examples_rst() asynctest-0.13.0/doc/index.rst000066400000000000000000000016051346656454500162420ustar00rootroot00000000000000.. asynctest documentation master file asynctest documentation ======================= .. automodule:: asynctest This documentation contains the reference of the classes and functions defined by asynctest, and an introduction guide. Tutorial --------- .. toctree:: :maxdepth: 2 tutorial.introduction tutorial.test_cases tutorial.mocking tutorial.advanced Reference --------- .. toctree:: :maxdepth: 2 asynctest.case asynctest.mock asynctest.selector asynctest.helpers Code examples ------------- .. toctree:: :maxdepth: 2 examples/index Contribute ---------- Development of :mod:`asynctest` is on github: `Martiusweb/asynctest `_. Patches, feature requests and bug reports are welcome. Documentation indices and tables -------------------------------- * :ref:`genindex` * :ref:`modindex` * :ref:`search` asynctest-0.13.0/doc/tutorial.advanced.rst000066400000000000000000000126671346656454500205540ustar00rootroot00000000000000Advanced Features ================= This chapter describes miscellaneous features of :mod:`asynctest` which can be leveraged in specific use cases. Controlling time ---------------- Tests running calls to :func:`asyncio.sleep` will take as long as the sum of all these calls. These calls are frequent when testing for timeouts, for instance. In many cases, this will add a useless delay to the execution of the test suite, and encourage us to deactivate or ignore these tests. :class:`~asynctest.ClockedTestCase` is a subclass of :class:`~asynctest.TestCase` which replaces the clock of the loop in a test with a manually-controlled one. The clock will only advance when calling :meth:`~asynctest.ClockedTestCase.advance`. This will not affect the wall clock: functions like :func:`time.time` or :meth:`datetime.datetime.now` will return the regular date and time of the system. .. literalinclude:: examples/tutorial/clock.py :pyobject: TestAdvanceTime This example is pretty self-explanatory: we verified that the clock of the loop advanced as expected, without awaiting 10 actual seconds and changing the time of the wall clock. Internally, :class:`~asynctest.ClockedTestCase` will ensure that the loop is executed as if time was passing *fast*, instead of jumping the clock to the target time. .. literalinclude:: examples/tutorial/clock.py :pyobject: TestWithClockAndCallbacks This example schedules function calls to be executed later by the loop. Each call will verify that it runs at the expected time. ``@fail_on(active_handles=True)`` ensures that the callbacks have been executed when the test finishes. The source code of ``is_time_around()`` can be found in the example file :doc:`examples/tutorial/clock.py`. Mocking I/O ----------- Testing libraries or functions dealing with low-level IO objects may be complex: these objects are outside of our control, since they are owned by the kernel. It can be impossible to exactly predict their behavior and simulate edge-cases, such as the ones happening in a real-world scenario in a large network. Even worse, using mocks in place of files will often raise :exc:`OSError` because these obhjects are not compatible with the features of the system used by the loop. :mod:`asynctest` provides special mocks which can be used in place of actual file-like objects. They are supported by the loop provided by :class:`~asynctest.TestCase` if the loop uses a standard implementation with a selector (Window's Proactor loop or uvloop are not supported). These mocks are configured with a spec matching common file-like objects. =================================== ======================================== Mock ``spec`` =================================== ======================================== :class:`~asynctest.FileMock` a file object, implements ``fileno()`` :class:`~asynctest.SocketMock` :class:`socket.socket` :class:`~asynctest.SSLSocketMock` :class:`ssl.SSLSocket` =================================== ======================================== We can use :func:`asynctest.isfilemock()` to differenciate mocks from regular objects. As of :mod:`asynctest` 0.12, these mocks don't provide other features, and must be configured to return expected values for calls to methods like ``read()`` or ``recv()``. When configured, we still need to force the loop to detect that I/O is possible on these mock files. This is done with :func:`~asynctest.set_read_ready()` and :func:`~asynctest.set_write_ready()`. .. literalinclude:: examples/tutorial/mocking_io.py :pyobject: TestMockASocket In this example, we configure a socket mock to simulate a simple request-response scenario with a TCP (stream) socket. Some data is available to read on the socket once a request has been written. ``recv_side_effect()`` makes as if the data is received in several packets, but it has no impact on the high level :class:`~asyncio.StreamReader`. It's common that while a read operation blocks until data is available, a write is often successful. Thus, we didn't bother simulating the case where the congestion control would block the write operation. Testing with event loop policies -------------------------------- Advanced users may not be able to use the loop provided by :class:`~asyncio.TestCase` because they use a customized event loop policy (see :py:ref:`asyncio-policies`). It is often the case when using an alternative implementation (like `uvloop `_) or if the tests are integrated within a framework hidding the scheduling and management of the loop. It is possible to force the :class:`~asynctest.TestCase` to use the loop provided by the policy by setting the class attribute :attr:`~asynctest.TestCase.use_default_loop`. Conversely, authors of libraries may not want to assume which loop they should use and let users explicitly pass the loop as argument to a function call. For instance, most of the high-level functions of :py:mod:`asyncio` (see :py:ref:`asyncio-streams`, for instance) allow the caller to specify the loop to use if it needs this kind of flexibility. :attr:`~asynctest.TestCase.forbid_get_event_loop` forbids the use of :meth:`asyncio.get_event_loop`. An exception is raised if the method is called while a test is running. It helps developers to ensure they don't rely on the default loop this their library. .. note:: The behavior of :meth:`asyncio.get_event_loop()` changed over time. Explicitly passing the loop is not the recommended practice anymore. asynctest-0.13.0/doc/tutorial.introduction.rst000066400000000000000000000016011346656454500215120ustar00rootroot00000000000000Introduction ============ Asynctest is a library which extends the standard pacakge :mod:`unittest` to support asyncio features. This tutorial aims at gathering examples showing how to use :mod:`asynctest`. It is not a comprehensive documentation and doesn't explain the concepts of :mod:`asyncio`. Some basic patterns of :mod:`unittest` are covered. However, if you are not familiar with :mod:`unittest`, it's probably a good idea to read its documentation first. .. note:: This documentation has not yet been reviewed. The code samples and examples have been tested by the author but probably deserve (at least) a second look. This tutorial can be improved and probably contains mistakes, typos and incorrect sentences. It can be considered as an "early release". We invite you to open `issues or pull-requests on Github `_. asynctest-0.13.0/doc/tutorial.mocking.rst000066400000000000000000000571661346656454500204410ustar00rootroot00000000000000Mocking ======= Mocks are objects whose behavior can be controlled and which record how they are used. They are very commonly used to write tests. The next section presents the concept of a mock with an example. The rest of the chapter presents the features of :mod:`asynctest.mock`. Using mocks ----------- Let's have a look at a function to be tested. .. literalinclude:: examples/tutorial/mocking.py :pyobject: cache_users Even if the implementation of this function is correct, it can fail. For instance, ``client.get_users()`` performs calls to a distant server, which can fail temporarily. It would also be complicated to create multiple test cases if the result of ``client.get_users()`` can't be controlled inside the tests. One can solve this problem by crafting a stub object: .. literalinclude:: examples/tutorial/mocking.py :pyobject: StubClient Tests can be written with this object. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestUsingStub This will work correctly but has a few downsides. One of them is very practical: each time the interface of the stubbed class change, the stub must be updated. There is also a bigger problem. In our example, ``test_no_users_to_add()`` might miss a bug. If ``cache_users()`` doesn't call ``client.get_users()``, no user is added to the cache, yet all the assertions in the test are checked. In this example, the bug would be detected thanks to the other test. However, it might not be the case with a more complex implementation. The key to write a a better test is to enforce all the assumtions and requirements stated in the documentation. Currently, the test can be described this way: knowing that: * ``client.get_users()`` will return an empty result, * and that the cache is empty, a call to ``cache_users()`` must leave the cache empty. Instead, it should be: knowing that: * ``client.get_users()`` will return an empty result, * and that the cache is empty, a call to ``cache_users()`` *must have queried the client* and must leaves the cache empty. Mocks solve both of the issues discussed above. A mock can be configured to act like an actual object, and provides assertion methods to verify how the object has been used. We can also leverage the mock to test another statement of the documentation and make the test even more accurate. We will verify that the server is indeed notified of the number of users added to the cache. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestUsingMock In this example, client is a :class:`~asynctest.Mock`. This mock will reproduce the interface of ``Client()`` (an instance of the ``Client`` class, ommited for simplicity, available in the example file :doc:`examples/tutorial/mocking.py`). By default, the attributes of a mock object are themselves mocks. We call them *child mocks*. In the above example, ``client.get_users`` is configured to return an empty list when called. By default, a new mock object would have been returned instead. Later, ``client.get_users.assert_called()`` verifies that the method has been called. ``client.increase_nb_users_cached.assert_called_once_with(1)`` verifies that this method has been called, and that the right arguments have been provided. Mocks are powerful and can be configured in many ways. Unfortunatly, they can be somewhat complex to use. The next sections of this chapter will present the features of :class:`asynctest.Mock` related to :mod:`asyncio`. It is recommended to be familiar with the module :mod:`unittest.mock` before reading the rest of this chapter. Mocking of coroutines --------------------- Let's rewrite the previous example using asyncio. .. literalinclude:: examples/tutorial/mocking.py :pyobject: cache_users_async A mock object can not be awaited (with the ``await`` keyword). There are several ways to make ``client.get_users()`` awaitable. One approach is to configure the mock to return a :class:`asyncio.Future` object: .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestUsingFuture ``client.get_users()`` returns is a future which yields an empty list. It works, but is fairly limited. For instance, if the original ``get_users()`` is a coroutine function, this is not the case of its mock counterpart. This test can also miss a new bug now: what if ``client.increase_nb_users_cached()`` is never awaited? The method has been called, and since the result is a :class:`~asyncio.Future`, this mistake will not be caught if the test runs with asyncio's :ref:`asyncio-debug-mode`. :class:`asynctest.CoroutineMock` is a type of mock which specializes in mocking coroutine functions (defined with ``async def``). A :class:`~asynctest.CoroutineMock` object is not awaitable, but it returns a coroutine instance when called. It provides assertion methods to ensure it has been awaited, as shown in this example: .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestUsingCoroutineMock All the features of :class:`asynctest.CoroutineMock` are decribed in the reference documentation. Mocking of other objects ------------------------ :class:`~asynctest.Mock` can be configured with the arguments of its constructor. The value of ``spec`` defines the list of attributes of the mock. :mod:`asynctest.Mock` will also detect which attributes are coroutine functions and mock these attributes accordingly. It means that in the previous example, it was not required to assign :class:`~asynctest.CoroutineMock` objects to ``get_users()`` and ``increase_nb_users_cached()``. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestUsingCoroutineMockAndSpec.test_no_users_to_add :dedent: 4 .. note:: :mod:`asynctest` will mock an attribute as a :class:`~asynctest.CoroutineMock` if the function is a native coroutine (``async def`` function) or a decorated generator (using :func:`asyncio.coroutine`, before Python 3.5). Some libraries document function or methods as coroutines, while they are actually implemented as simple functions returning an awaitable object (like :class:`asyncio.Future`). In this case, :mod:`asynctest` can not detect that it should be mocked with :class:`~asynctest.CoroutineMock`. ``spec`` defines the attributes of the mock, but isn't passed to child mocks. In particular, using a class as ``spec`` will not reproduce the behavior of a constructor:: >>> ClientMock = asynctest.Mock(Client) >>> ClientMock() >>> ClientMock().get_users In this example, ``ClientMock`` should mock the ``Client`` class, but ``ClientMock()`` doesn't return a mock specified as a ``Client`` instance, and thus, ``ClientMock().get_users`` is not mocked as a coroutine. We need autospeccing to fix this. Autospeccing ------------ As the documentation of :mod:`unittest` says it, :func:`~asynctest.create_autospec()` creates mock objects that have the same attributes and methods as the objects they are replacing. Any functions and methods (including constructors) have the same call signature as the real object. It is the best solution to configure mocks to behave accurately like the object they replace. The mock of a function or coroutine must be called with the right arguments: .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestAutoSpec.test_functions_and_coroutines_arguments_are_checked :dedent: 4 .. note:: This example also shows the use of :meth:`~unittest.TestCase.assertRaises()`, which is successful only if an exception is raised in the ``with`` block. :meth:`~unittest.TestCase.subTest()` is used to document in a human-readable format which case is tested. It doesn't change the outcome of the test. The message is displayed if an assertion fails, which is especially useful to understand faster which part of the test breaks. :func:`~asynctest.create_autospec()` will mock the constructor of a class as expected. When called, it returns a mock with the spec of the class: .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestAutoSpec.test_create_autospec_on_a_class :dedent: 4 Types of mocks -------------- There are several types of mocks with slightly different features: * :class:`~asynctest.Mock` is the base mock type. * :class:`~asynctest.MagicMock`, it is very similar to :class:`~asynctest.Mock`, except that magic methods are also mocks, and can be configured:: >>> asynctest.Mock().__hash__ >>> asynctest.MagicMock().__hash__ >>> asynctest.MagicMock().__hash__.return_value = "custom value" * :class:`~asynctest.NonCallableMock` and :class:`~asynctest.NonCallableMagicMock` are their non-callable counterparts. It's usually better to use them when mocking objects or values. * :class:`~asynctest.CoroutineMock` mocks a coroutine function (or, more generaly, any callable object returning an awaitable). As mentioned before, a *child mock* is a mock attached to another mock. The child mock is either an attribute of the parent mock, or the result of a call to the parent mock. This relationship enables some features documented in the documentation of :class:`unittest.mock.Mock`. Attaching a child mock is just a matter of setting the right attribute:: client_mock = asynctest.Mock() # manually attaching a child mock to get_users mock.get_users = asynctest.Mock() # manually attaching the returned child mock to get_users() mock.get_users.return_value = asynctest.NonCallableMock() By default, the child mock is the result of the factory method :meth:`~unittest.mock.Mock._get_child_mock()`, and its result depend on the type of mock: ========================================== ================================ parent mock child mock ========================================== ================================ :class:`~asynctest.Mock` :class:`~asynctest.Mock` :class:`~asynctest.MagicMock` :class:`~asynctest.MagicMock` :class:`~asynctest.NonCallableMock` :class:`~asynctest.Mock` :class:`~asynctest.NonCallableMagicMock` :class:`~asynctest.MagicMock` :class:`~asynctest.CoroutineMock` :class:`~asynctest.MagicMock` ========================================== ================================ Controlling the result of :class:`~asynctest.CoroutineMock` ----------------------------------------------------------- Calling a :class:`~asynctest.CoroutineMock` returns a coroutine which can be awaited. The result of this coroutine can be configured like the result of a call to a mock. ``return_value`` ~~~~~~~~~~~~~~~~ The simplest way to configure the result of a mock is to set its ``return_value`` attribute. This result will always be returned as it is. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestCoroutineMockResult.test_result_set_with_return_value :dedent: 4 ``side_effect`` ~~~~~~~~~~~~~~~ The ``side_effect`` attribute of a mock enables more control over the result of the mock. If set, it has priority over ``return_value``, which is ignored. The value of ``side_effect`` can be a function. In this case, the call to the mock is forwarded to this function, and its result is returned. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestCoroutineMockResult.test_result_with_side_effect_function :dedent: 4 If the side effect is an exception object or class, this exception is raised. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestCoroutineMockResult.test_result_with_side_effect_exception :dedent: 4 Last but not least, ``side_effect`` can be any iterable object. In this case, the mock will return each value once, until the iterator is exhausted and :exc:`StopIteration` is raised to the caller. :func:`itertools.cycle` allows to repeat the iterator. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestCoroutineMockResult.test_result_with_side_effect_iterable :dedent: 4 .. important:: If the value of ``side_effect`` is a coroutine function or a generator function, it is treated as a regular function. The result of a call to this mock will be an instance of the coroutine or generator. As of asynctest 0.12, specifying a coroutine function as the side effect of :class:`~asynctest.CoroutineMock` is undefined and should be avoided. See `Github issue #31 `_. Wrapped object ~~~~~~~~~~~~~~ A mock can also wrap an object. This wrapped object is defined as an argument passed to the constructor of the mock. When a mock or any of its attributes is called, the call is forwarded to the wrapped object, like if it was the value of ``side_effect``. If ``side_effect`` or ``return_value`` are set for the mock, they will have priority over the wrapper. In practice, this is equivalent to adding the features of a :class:`~asynctest.Mock` to a stub object. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestCoroutineMockResult.test_result_with_wrapped_object :dedent: 4 Asynchronous iterators and context managers ------------------------------------------- Python 3.5 introduced the support for asynchronous iterators and context managers. They can be implemented with the magic methods ``__aiter__()``, ``__anext__()``, ``__aenter__()``, ``__aexit__()`` as described in :pep:`0492#asynchronous-context-managers-and-async-with`. :class:`~asynctest.MagicMock` will mock these methods and greatly simplify their configuration. In the example we used so far, we assumed that ``client.get_users()`` loads all users from a database and store them in a list that it will return. This implementation may consume a lot of memory if there are a lot of users to return. We can instead use a *cursor*. A cursor is an object *pointing to* the result of the query *get all users* on the database. It keeps an open connection to the database and fetches the objects lazily (only when they are really needed). It allows to load the users one by one from the database, and avoid filling the memory with all users at once. It is also common to wrap several related queries to a database in a transaction to ensure the sequence of calls is consistent. A better implementation of ``cache_users()`` should keep the calls to ``get_users()`` and ``increase_nb_users_cached()`` in the same transaction. The ``cache_users()`` implementation will look like this: .. literalinclude:: examples/tutorial/mocking.py :pyobject: cache_users_with_cursor ``client.new_transaction()`` returns a transaction object. Under the hood, ``async with`` calls its coroutine method ``__aenter__()`` and the result is stored in the variable ``transaction``. ``users_cursor`` is an asynchronously iterable object. It implements the method ``__aiter__()``, which returns an asynchronous iterator. ``__aiter__()`` is a function, not a coroutine. For each iteration of the ``async for`` loop, the coroutine method ``__anext__()`` of the asynchronous iterator is called and its result is assigned to ``user``. When the interpreter leaves the ``async with`` block, ``__aexit__()`` is called. A partial implementation of this logic can be found in the example file :doc:`examples/tutorial/mocking.py`. The next sections show how to use :class:`~asynctest.MagicMock` to test this method. Asynchronous context manager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :class:`~asynctest.MagicMock` mocks ``__aenter__`` with a :class:`~asynctest.CoroutineMock` returning a new child mock. If an exception is raised in an ``async with`` block, this exception is passed to ``__aexit__()``. In this case, the return value defines wether the interpreter suppresses or propagates the exception, as described in the documentation of :meth:`object.__exit__`. :class:`~asynctest.MagicMock` mocks ``__aexit__`` with a :class:`~asynctest.CoroutineMock` returning ``False`` by default, which means that the exception is propagated. By default, we can use a :class:`~asynctest.MagicMock` in an ``async with`` block without configuration, exceptions raised in this block are propagated: .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestWithMagicMethods.test_context_manager :dedent: 4 However, in the example above, the ``transaction`` object exposes the same methods as ``client``. In particular, We must configure this mock so ``transaction.increase_nb_users_cached()`` is a coroutine. Asynchronous iterator ~~~~~~~~~~~~~~~~~~~~~ The method ``__aiter__()`` of a :class:`~asynctest.MagicMock` returns an asynchronous iterator. By default, this iterator is empty. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestWithMagicMethods.test_empty_iterable :dedent: 4 The values yielded by the iterator can be configured by setting the ``return_value`` of ``__aiter__``. This value must be an iterable object, such as a list or a generator: .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestWithMagicMethods.test_iterable :dedent: 4 .. note:: As of asynctest 0.12, it is not possible to use an asynchronously iterable object as ``return_value`` for ``__aiter__()``. Setting ``side_effect`` allows to override the behavior of :class:`~asynctest.MagicMock`. Putting it all together ~~~~~~~~~~~~~~~~~~~~~~~ We can leverage several features of :mod:`asynctest` when testing ``cache_users_with_cursor()``: .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestCacheWithMagicMethods This example deserve some explanation. First, we use :func:`~asynctest.create_autospec()` to build a mock of the class `AsyncClient`. ``transaction`` will be the object configured as a context manager. When called with ``async with``, it must return an object with an interface as ``client``. We set ``AsyncClientMock`` as a side effect to ``transaction.__aenter__``, which means that a new mock of an instance of ``AsyncClient`` will be issued each time ``transaction`` is used in an ``async width`` block. ``cursor`` will be used in the ``async for`` loop. The iterator will yield the values of ``cursor.__aiter__.return_value``. We set to a list containing a single ``User`` object. A new iterator is created each time an ``async for`` loop is called upon the cursor, it is safe to use this mock several times. We then create ``client``, a mock created from ``AsyncClientMock``. We configure it so the return values of ``client.new_transaction()`` and ``client.get_users_cursor()`` are the mocks we created above. Note that we configured the behavior of ``client``'s attributes, not those of ``AsyncClientMock``. This is because the child mock of an autospecced class will not inherit the behavior of the parent mock, only its spec. Patching -------- Patching is a mechanism allowing to temporarily replace a symbol (class, object, function, attribute, …) by a mock, in-place. It is especially useful when one need a mock, but can't pass it as a parameter of the function to be tested. For instance, if ``cache_users()`` didn't accept the ``client`` argument, but instead created a new client, it would not be possible to replace it by a mock like in all the previous examples. When an object is hard to mock, it sometimes shows a limitation in the design: a coupling that is too tight, the use of a global variable (or a singleton), etc. However, it's not always possible or desirable to change the code to accomodate the tests. A common situation where tight coupling is almost invisible is when performing logging or monitoring. In this case, patching will help. A :func:`~asynctest.patch` can be used as a context manager. It will replace the target (:func:`logging.debug`) with a mock during the lifetime of the ``with`` block. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestCachingIsLogged.test_with_context_manager :dedent: 4 Alternatively, :func:`~asynctest.patch` can be used to decorate a test or a test class (inheriting :class:`~asynctest.TestCase`). This second example is roughly equivalent to the previous one. The main difference is that for all tests affected by the patch (the decorated method or all test methods in a decorated test class) must accept an additional argument which will receive the mock object used by the patch. Note that when using multiple decorators on a single method, the order of the arguments is inversed compared to the order of the decorators. This is due to the way decorators work in Python, a topic which we don't cover in this documentation. .. literalinclude:: examples/tutorial/mocking.py :pyobject: TestCachingIsLogged.test_with_decorator :dedent: 4 .. note:: In practice, we should have used :meth:`unittest.TestCase.assertLogs()`. It asserts that a given message have been logged and makes more sense than manually patching :mod:`logging`. There are variants of :func:`~asynctest.patch`: * :func:`asynctest.patch.object` patches the attribute of a given object, * :func:`asynctest.patch.multiple` patches several attributes of a given object, * :func:`asynctest.patch.dict` patches the values in a ``dict`` for the given indices. The official python documentation provide extensive details about how to define the target of a patch in its section :ref:`where-to-patch`. Scope of the patch ~~~~~~~~~~~~~~~~~~ There is one hidden catch in the examples above: what happens to the patch when the interpreter reaches the ``await`` statement and pauses the coroutine? When patch is used as a context manager, the patch stays active until the interpreter reached the end of the ``with`` block. When used as a decorator, the patch is activated right before the function (or coroutine) is executed, and deactivated once it returned. This is equivalent to englobing the body of the function in a ``with`` statement instead of using the decorator. However, since couroutines are asynchronous, the work performed by the interpreter while the coroutine is paused is unpredictable. In some cases, the patch can conflict with something else, and must only be active when the patched coroutine is running. It is possible to control when a :func:`asynctest.patch` must be active when applied to a coroutine with the argument ``scope``. If ``scope`` is set to :data:`asynctest.LIMITED`, the patch is active only when the coroutine is running. This situation is illustrated in the example bellow. The test case ``TestMustBePatched`` runs a task in background which fails if some patch is active. It contains two tests: one which shows the test conflicting, and one which uses the :data:`~asynctest.LIMITED` ``scope`` to deactivate the patch outside of the test coroutine. .. literalinclude:: examples/tutorial/patching.py :pyobject: TestMustBePatched In this example, ``happened_once()`` pauses the coroutine until the background task checked once that the patch is not active. The code of ``must_be_patched``, ``happened_once()`` and ``terminate_and_check_task()`` is available in the example file :doc:`examples/tutorial/patching.py`. ``test_patching_conflicting()`` fails because the patch is still active when it is paused and aways the ``self.checked`` event. While paused, the background task runs, and crashes because the patch is still active. In ``test_patching_not_conflicting()``, the patch is set with a :data:`~asynctest.LIMITED` scope, and is active only when the coroutine runs. When ``await must_be_patched.is_patched()`` runs, the patch is still active. This coroutine runs in the scope of the outher coroutine (the test): indeed, ``must_be_patched.is_patched()`` is scheduled in the task running the test. Conclusion ---------- This chapter showed most of the concepts and features of mock relevant when testing asynchronous code. There are plenty of other features and subtleties which are covered in the documentation of :mod:`unittest.mock`. asynctest-0.13.0/doc/tutorial.test_cases.rst000066400000000000000000000170251346656454500211350ustar00rootroot00000000000000Test cases ========== Writing and running a first test -------------------------------- Tests are written in classes inheriting :class:`~asynctest.TestCase`. A test case consists of: * some set-up which prepares the environment and the resources required for the test to run, * a list of assertions, usually as a list of checks that must be verified to mark the test as successful, * some finalization code which cleans the resources used during the test. It should revert the environment back to its state before the set-up. Let's look at a minimal example: .. literalinclude:: examples/tutorial/test_cases.py :pyobject: MinimalExample In this example, we created a test which contains only one assertion: it ensures that ``True`` is, well, true. :meth:`~unittest.TestCase.assertTrue` is a method of :class:`~asynctest.TestCase`. If the test is successful, it does nothing. Else, it raises an :exc:`AssertionError`. The documentation of :mod:`unittest` lists :py:ref:`assertion methods ` implemented by :class:`unittest.TestCase`. :class:`asynctest.TestCase` adds some more for asynchronous code. We can run it by creating an instance of our test case, its constructor takes the name of the test method as argument: >>> test_case = MinimalExample("test_that_true_is_true") >>> test_case.run() To make things more convenient, :mod:`unittest` provides a test runner script. The runner discovers test methods in a module (or package, or class) by looking up methods with a name prefixed by ``test_`` in :class:`~unittest.TestCase` subclasses:: $ python -m unittest test_cases . ---------------------------------------------------------------------- Ran 1 test in 0.001s OK The runner will create and run an instance of the test case (as shown in the code above) for each method that it finds. This means that you can add as many test methods to your :class:`~asynctest.TestCase` class as you want. Test setup ---------- Let's work on a slightly more complex example: .. literalinclude:: examples/tutorial/test_cases.py :pyobject: AnExampleWithSetup Here, we create a loop that will run a coroutine, ensure that the result of this coroutine is as expected (it should return an object containing the string ``"worked"`` somewhere). Then we close the loop, even if an exception was raised. If we happen to write several test methods, the set-up and clean-up will likely be repeated several times. It's probably more convenient to move these parts into their own methods. We can override two methods of the :class:`~asynctest.TestCase` class: :meth:`~unittest.TestCase.setUp` and :meth:`~unittest.TestCase.tearDown` which will be respectively called before the test method and after the test method: .. literalinclude:: examples/tutorial/test_cases.py :pyobject: AnExampleWithSetupMethod Both examples are very similar: :class:`~asynctest.TestCase` will run :meth:`~asynctest.TestCase.tearDown` even if an exception is raised in the test method. However, if an exception is raised in :meth:`~unittest.TestCase.setUp`, the test execution is aborted and :meth:`~unittest.TestCase.tearDown` will never run. If the setup fails in between the initialization of several resources, some of them will never be cleaned. This problem can be solved by registering clean-up callbacks which will always be executed. A clean-up callback is a function without (required) arguments that is passed to :meth:`~unittest.TestCase.addCleanup`. Using this feature, we can rewrite our previous example: .. literalinclude:: examples/tutorial/test_cases.py :pyobject: AnExampleWithSetupAndCleanup Tests should always run isolated from the others, this is why tests should only rely on local resources created for the test itself. This ensures that a test will not impact the execution of other tests, and can greatly help to get an accurate diagnostic when debugging a failing test. It's also worth noting that the order in which tests are executed by the test runner is undefined. It can lead to unpredictable behaviors if tests share some resources. Testing asynchronous code ------------------------- Speaking of tests isolation, it's usually preferable to create one loop per test. If the loop is shared, one test could (for instance) schedule a task and never await its result, the task would then run (and possibly trigger unexpected side effects) in another test. :class:`asynctest.TestCase` will create (and clean) an event loop for each test that will run. This loop is set in the :class:`~asynctest.TestCase.loop` attribute. We can use this feature and rewrite the previous example: .. literalinclude:: examples/tutorial/test_cases.py :pyobject: AnExampleWithTestCaseLoop Tests functions can be coroutines. :class:`~asynctest.TestCase` will schedule them on the loop. .. literalinclude:: examples/tutorial/test_cases.py :pyobject: AnExampleWithTestCaseAndCoroutines :meth:`~unittest.TestCase.setUp` and :meth:`~unittest.TestCase.tearDown` can also be coroutines, they will all run in the same loop. .. literalinclude:: examples/tutorial/test_cases.py :pyobject: AnExampleWithAsynchronousSetUp .. note:: The functions :meth:`~unittest.TestCase.setUpClass`, :meth:`~unittest.setUpModule` and their ``tearDown`` counterparts can not be coroutine. This is because the loop only exists in an instance of :class:`~asynctest.TestCase`. In practice, these methods should be avoided because they will not allow to reset the environment between tests. Automated checks ---------------- Asynchronous code introduces a class of subtle bugs which can be hard to detect. In particular, clean-up of resources is often performed asynchronously and can be missed in tests. :class:`~asynctest.TestCase` can check and fail if some callbacks or resources are still pending at the end of a test. These checks can be configured with the decorator :meth:`~asynctest.fail_on`. .. literalinclude:: examples/tutorial/test_cases.py :pyobject: AnExempleWhichDetectsPendingCallbacks This test will fail because the test don't wait long enough or doesn't cancel the callback ``i_must_run()``, scheduled to run in 1 second:: ====================================================================== FAIL: test_missing_a_callback (tutorial.test_cases.AnExempleWhichDetectsPendingCallbacks) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/martius/Code/python/asynctest/asynctest/case.py", line 300, in run self._tearDown() File "/home/martius/Code/python/asynctest/asynctest/case.py", line 262, in _tearDown self._checker.check_test(self) File "/home/martius/Code/python/asynctest/asynctest/_fail_on.py", line 90, in check_test getattr(self, check)(case) File "/home/martius/Code/python/asynctest/asynctest/_fail_on.py", line 111, in active_handles case.fail("Loop contained unfinished work {!r}".format(handles)) AssertionError: Loop contained unfinished work (,) ---------------------------------------------------------------------- Some convenient decorators can be used to enable of disable all checks: :func:`~asynctest.strict` and :func:`~asynctest.lenient`. All decorators can be used on a class or test function. Conclusion ---------- :class:`~asynctest.TestCase` provides handy features to test coroutines and asynchronous code. In the next section, we will talk about mocks. Mocks are objects simulating the behavior of other objects. asynctest-0.13.0/pyproject.toml000066400000000000000000000001021346656454500165370ustar00rootroot00000000000000[build-system] requires = ["setuptools>=36.6.0", "wheel>=0.30.0"] asynctest-0.13.0/setup.cfg000066400000000000000000000020111346656454500154450ustar00rootroot00000000000000[metadata] name = asynctest version = 0.13.0 url = https://github.com/Martiusweb/asynctest/ author = Martin Richard author_email = martius@martiusweb.net classifiers = Development Status :: 3 - Alpha Intended Audience :: Developers Topic :: Software Development :: Testing License :: OSI Approved :: Apache Software License Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy license = Apache 2 license_file = LICENSE.md description = Enhance the standard unittest package with features for testing asyncio libraries long_description = file: README.rst keywords = unittest test testing asyncio tulip selectors async mock [options] packages = find: python_requires = >=3.5 zip_safe = True [pylama] format = pylint skip = */.tox/*,*/.env/* linters = pylint,mccabe,pep8 ignore = F0401,C0111,E731 asynctest-0.13.0/setup.py000066400000000000000000000004451346656454500153470ustar00rootroot00000000000000# coding: utf-8 """ asynctest setup script. We rely on setuptools >= 30.3.0 to read setup.cfg, but provide a minimal support with distutils. """ args = { "name": "asynctest", "packages": ["asynctest"], } if __name__ == "__main__": from setuptools import setup setup(**args) asynctest-0.13.0/test/000077500000000000000000000000001346656454500146115ustar00rootroot00000000000000asynctest-0.13.0/test/__init__.py000066400000000000000000000001531346656454500167210ustar00rootroot00000000000000from .test_case import * from .test_helpers import * from .test_mock import * from .test_selector import * asynctest-0.13.0/test/test_case.py000066400000000000000000000776471346656454500171620ustar00rootroot00000000000000# coding: utf-8 # pylama: ignore=E501 noqa import asyncio import itertools import logging import os import re import subprocess import sys import time import unittest import unittest.mock import asynctest class Test: class FooTestCase(asynctest.TestCase): def runTest(self): pass def test_foo(self): pass @asynctest.fail_on(unused_loop=False) class LoggingTestCase(asynctest.TestCase): def __init__(self, calls): super().__init__() self.calls = calls def setUp(self): self.calls.append('setUp') def test_basic(self): self.events.append('test_basic') def tearDown(self): self.events.append('tearDown') class StartWaitProcessTestCase(asynctest.TestCase): @staticmethod @asyncio.coroutine def start_wait_process(loop): process = yield from asyncio.create_subprocess_shell( "true", stdout=subprocess.PIPE, stderr=subprocess.PIPE, loop=loop) try: out, err = yield from asyncio.wait_for( process.communicate(), timeout=.1, loop=loop) except BaseException: process.kill() os.waitpid(process.pid, os.WNOHANG) raise @asyncio.coroutine def runTest(self): yield from self.start_wait_process(self.loop) class _TestCase(unittest.TestCase): run_methods = ('run', 'debug', ) def create_default_loop(self): default_loop = asyncio.new_event_loop() asyncio.set_event_loop(default_loop) self.addCleanup(default_loop.close) return default_loop class Test_TestCase(_TestCase): def test_init_and_close_loop_for_test(self): default_loop = self.create_default_loop() @asynctest.lenient class LoopTest(asynctest.TestCase): failing = False def runTest(self): try: self.assertIsNotNone(self.loop) self.assertFalse(self.loop.close.called) except Exception: self.failing = True raise def runFailingTest(self): self.runTest() raise SystemError() for method, test in itertools.product(self.run_methods, ('runTest', 'runFailingTest', )): with self.subTest(method=method, test=test), \ unittest.mock.patch('asyncio.new_event_loop') as mock: mock_loop = unittest.mock.Mock(asyncio.AbstractEventLoop) mock_loop.run_until_complete.side_effect = default_loop.run_until_complete if sys.version_info >= (3, 6): mock_loop.shutdown_asyncgens = asynctest.CoroutineMock() mock.return_value = mock_loop case = LoopTest(test) try: getattr(case, method)() except SystemError: pass mock.assert_called_with() mock_loop.close.assert_called_with() if sys.version_info >= (3, 6): mock_loop.shutdown_asyncgens.assert_awaited() # If failing is True, one of the assertions in runTest failed self.assertFalse(case.failing) # Also, ensure we didn't override the original loop self.assertIs(default_loop, asyncio.get_event_loop()) def test_default_loop_is_not_created_when_unused(self): policy = asyncio.get_event_loop_policy() @asynctest.fail_on(unused_loop=False) class Dummy_TestCase(Test.FooTestCase): pass for method in self.run_methods: with self.subTest(method=method): with unittest.mock.patch.object(policy, "get_event_loop") as mock: case = Dummy_TestCase() getattr(case, method)() self.assertFalse(mock.called) def test_update_default_loop_works(self): a_loop = asyncio.new_event_loop() self.addCleanup(a_loop.close) class Update_Default_Loop_TestCase(asynctest.TestCase): @asynctest.fail_on(unused_loop=False) def runTest(self): self.assertIs(self.loop, asyncio.get_event_loop()) asyncio.set_event_loop(a_loop) self.assertIs(a_loop, asyncio.get_event_loop()) for method in self.run_methods: with self.subTest(method=method): case = Update_Default_Loop_TestCase() result = getattr(case, method)() if result: self.assertTrue(result.wasSuccessful()) self.assertIs(a_loop, asyncio.get_event_loop()) def test_use_default_loop(self): default_loop = self.create_default_loop() class Using_Default_Loop_TestCase(asynctest.TestCase): use_default_loop = True @asynctest.fail_on(unused_loop=False) def runTest(self): self.assertIs(default_loop, self.loop) for method in self.run_methods: with self.subTest(method=method): case = Using_Default_Loop_TestCase() result = getattr(case, method)() # assert that the original loop is reset after the test self.assertIs(default_loop, asyncio.get_event_loop()) if result: self.assertTrue(result.wasSuccessful()) self.assertFalse(default_loop.is_closed()) def test_forbid_get_event_loop(self): default_loop = self.create_default_loop() # Changed in python 3.6: get_event_loop() returns the running loop in # a callback or a coroutine, so forbid_get_event_loop should only be # forbidden where the loop is not running. @asynctest.lenient class Forbid_get_event_loop_TestCase(asynctest.TestCase): forbid_get_event_loop = True def runTest(self): with self.assertRaises(AssertionError): asyncio.get_event_loop() for method in self.run_methods: with self.subTest(method=method): case = Forbid_get_event_loop_TestCase() result = getattr(case, method)() # assert that the original loop is reset after the test self.assertIs(default_loop, asyncio.get_event_loop()) if result: self.assertTrue(result.wasSuccessful()) def test_coroutinefunction_executed(self): class CoroutineFunctionTest(asynctest.TestCase): ran = False async def noop(self): pass @asyncio.coroutine def runTest(self): self.ran = True yield from self.noop() class NativeCoroutineFunctionTest(CoroutineFunctionTest): async def runTest(self): self.ran = True await self.noop() for method in self.run_methods: with self.subTest(method=method): for case in (CoroutineFunctionTest(), NativeCoroutineFunctionTest()): with self.subTest(case=case): case.ran = False getattr(case, method)() self.assertTrue(case.ran) def test_coroutine_returned_executed(self): class CoroutineTest(asynctest.TestCase): ran = False @asyncio.coroutine def set_ran(self): self.ran = True def runTest(self): return self.set_ran() class NativeCoroutineTest(CoroutineTest): async def set_ran(self): self.ran = True def runTest(self): return self.set_ran() for method in self.run_methods: with self.subTest(method=method): for case in (CoroutineTest(), NativeCoroutineTest()): with self.subTest(case=case): case.ran = False getattr(case, method)() self.assertTrue(case.ran) def test_fails_when_future_has_scheduled_calls(self): class CruftyTest(asynctest.TestCase): @asynctest.fail_on(active_handles=True, unused_loop=False) def runTest(instance): instance.loop.call_later(5, lambda: None) with self.subTest(method='debug'): with self.assertRaisesRegex(AssertionError, 'unfinished work'): CruftyTest().debug() with self.subTest(method='run'): result = CruftyTest().run() self.assertEqual(1, len(result.failures)) def test_setup_teardown_may_be_coroutines(self): @asynctest.fail_on(unused_loop=False) class WithSetupFunction(Test.FooTestCase): ran = False def setUp(self): WithSetupFunction.ran = True @asynctest.fail_on(unused_loop=False) class WithSetupCoroutine(Test.FooTestCase): ran = False @asyncio.coroutine def setUp(self): WithSetupCoroutine.ran = True @asynctest.fail_on(unused_loop=False) class WithTearDownFunction(Test.FooTestCase): ran = False def tearDown(self): WithTearDownFunction.ran = True @asynctest.fail_on(unused_loop=False) class WithTearDownCoroutine(Test.FooTestCase): ran = False def tearDown(self): WithTearDownCoroutine.ran = True for method in self.run_methods: for call_mode in (WithSetupFunction, WithSetupCoroutine, WithTearDownFunction, WithTearDownCoroutine): with self.subTest(method=method, case=call_mode.__name__): case = call_mode() call_mode.ran = False getattr(case, method)() self.assertTrue(case.ran) def test_cleanup_functions_can_be_coroutines(self): cleanup_normal_called = False cleanup_normal_called_too_soon = False cleanup_coro_called = False def cleanup_normal(): nonlocal cleanup_normal_called cleanup_normal_called = True @asyncio.coroutine def cleanup_coro(): nonlocal cleanup_coro_called cleanup_coro_called = True @asynctest.fail_on(unused_loop=False) class TestCase(Test.FooTestCase): def setUp(self): nonlocal cleanup_normal_called, cleanup_normal_called_too_soon nonlocal cleanup_coro_called cleanup_normal_called = cleanup_coro_called = False self.addCleanup(cleanup_normal) cleanup_normal_called_too_soon = cleanup_normal_called self.addCleanup(cleanup_coro) for method in self.run_methods: with self.subTest(method=method): getattr(TestCase(), method)() self.assertTrue(cleanup_normal_called) self.assertTrue(cleanup_coro_called) def test_loop_uses_TestSelector(self): @asynctest.fail_on(unused_loop=False) class CheckLoopTest(asynctest.TestCase): def runTest(self): # TestSelector is used self.assertIsInstance(self.loop._selector, asynctest.selector.TestSelector) # And wraps the original selector self.assertIsNotNone(self.loop._selector._selector) for method in self.run_methods: with self.subTest(method=method): case = CheckLoopTest() outcome = getattr(case, method)() if outcome: self.assertTrue(outcome.wasSuccessful()) @unittest.skipIf(sys.platform == "win32", "Tests specific to Unix") class Test_TestCase_and_ChildWatcher(_TestCase): def test_watched_process_is_awaited(self): for method in self.run_methods: with self.subTest(method=method): case = Test.StartWaitProcessTestCase() outcome = getattr(case, method)() if outcome: self.assertTrue(outcome.wasSuccessful()) def test_original_watcher_works_outside_loop(self): default_loop = self.create_default_loop() # check if we can spawn and wait a subprocess before an after a test for method in self.run_methods: with self.subTest(method=method): coro = Test.StartWaitProcessTestCase.start_wait_process( default_loop) default_loop.run_until_complete(coro) case = Test.StartWaitProcessTestCase() outcome = getattr(case, method)() if outcome: self.assertTrue(outcome.wasSuccessful()) coro = Test.StartWaitProcessTestCase.start_wait_process( default_loop) default_loop.run_until_complete(coro) class Test_ClockedTestCase(asynctest.ClockedTestCase): took_n_seconds = re.compile(r'took \d+\.\d{3} seconds') @asyncio.coroutine def advance(self, seconds): debug = self.loop.get_debug() try: self.loop.set_debug(True) with self.assertLogs(level=logging.WARNING) as log: yield from self.loop.create_task(super().advance(seconds)) self.assertTrue(any(filter(self.took_n_seconds.search, log.output))) finally: self.loop.set_debug(debug) @asyncio.coroutine def test_advance(self): f = asyncio.Future(loop=self.loop) g = asyncio.Future(loop=self.loop) started_wall_clock = time.monotonic() started_loop_clock = self.loop.time() self.loop.call_later(1, f.set_result, None) self.loop.call_later(2, g.set_result, None) self.assertFalse(f.done()) yield from self.advance(1) self.assertTrue(f.done()) self.assertFalse(g.done()) yield from self.advance(9) self.assertTrue(g) finished_wall_clock = time.monotonic() finished_loop_clock = self.loop.time() self.assertLess( finished_wall_clock - started_wall_clock, finished_loop_clock - started_loop_clock) def test_advance_with_run_until_complete(self): f = asyncio.Future(loop=self.loop) started_wall_clock = time.monotonic() started_loop_clock = self.loop.time() self.loop.call_later(1, f.set_result, None) self.loop.run_until_complete(self.advance(1)) self.assertTrue(f.done()) finished_wall_clock = time.monotonic() finished_loop_clock = self.loop.time() self.assertLess( finished_wall_clock - started_wall_clock, finished_loop_clock - started_loop_clock) @asyncio.coroutine def test_negative_advance(self): with self.assertRaisesRegex(ValueError, 'back in time'): yield from self.advance(-1) self.assertEqual(self.loop.time(), 0) @asyncio.coroutine def test_callbacks_are_called_on_time(self): def record(call_time): call_time.append(self.loop.time()) call_time = [] self.loop.call_later(0, record, call_time) self.loop.call_later(1, record, call_time) self.loop.call_later(2, record, call_time) self.loop.call_later(5, record, call_time) yield from self.advance(3) expected = list(range(3)) self.assertEqual(call_time, expected) yield from self.advance(2) expected.append(5) self.assertEqual(call_time, expected) class Test_ClockedTestCase_setUp(asynctest.ClockedTestCase): def setUp(self): # Deactivate debug mode if enabled, because it will warn us that the # advance() coroutine took 1s. debug = self.loop.get_debug() self.loop.set_debug(False) self.addCleanup(self.loop.set_debug, debug) @asyncio.coroutine def test_setUp(self): yield from self.advance(1) self.assertEqual(1, self.loop.time()) class Test_ClockedTestCase_async_setUp(asynctest.ClockedTestCase): @asyncio.coroutine def setUp(self): # Deactivate debug mode if enabled, because it will warn us that the # advance() coroutine took 1s. debug = self.loop.get_debug() self.loop.set_debug(False) self.addCleanup(self.loop.set_debug, debug) yield from self.advance(1) @asyncio.coroutine def test_setUp(self): self.assertEqual(1, self.loop.time()) @unittest.mock.patch.dict("asynctest._fail_on.DEFAULTS", values={"foo": False, "bar": True}, clear=True) class Test_fail_on_decorator(unittest.TestCase): def checks(self, obj, fatal=True): if isinstance(obj, asynctest.TestCase): case = obj else: case = obj.__self__ try: return getattr(obj, asynctest._fail_on._FAIL_ON_ATTR).get_checks(case) except AttributeError: if fatal: self.fail("{!r} not decorated".format(obj)) def assert_not_decorated(self, obj): if self.checks(obj, False) is not None: self.fail("{!r} is decorated".format(obj)) def assert_checks_equal(self, obj, **kwargs): self.assertEqual(self.checks(obj), kwargs) def assert_checks(self, obj, **kwargs): checks = self.checks(obj) for check in kwargs: if kwargs[check] != checks[check]: self.fail("check '{}' {} (expected: {})".format( check, "enabled" if checks[check] else "disabled", "enabled" if kwargs[check] else "disabled") ) def test_check_arguments(self): message = "got an unexpected keyword argument 'not_existing'" with self.assertRaisesRegex(TypeError, message): asynctest.fail_on(foo=True, not_existing=True) def test_decorate_method(self): class TestCase(asynctest.TestCase): @asynctest.fail_on(foo=True) def test_foo(self): pass instance = TestCase() self.assert_checks_equal(instance.test_foo, foo=True, bar=True) def test_decorate_class(self): @asynctest.fail_on(foo=True) class TestCase(asynctest.TestCase): def test_foo(self): pass @asynctest.fail_on(bar=False) def test_bar(self): pass @asynctest.fail_on(foo=False, bar=False) def test_baz(self): pass instance = TestCase() with self.subTest("class is decorated"): self.assert_checks_equal(instance, foo=True, bar=True) with self.subTest("method without decoration is not decorated"): self.assert_not_decorated(instance.test_foo) with self.subTest("method decorated inherits of class params"): self.assert_checks_equal(instance.test_bar, foo=True, bar=False) with self.subTest("method decorated overrides class params"): self.assert_checks_equal(instance.test_baz, foo=False, bar=False) def test_decorate_subclass_doesnt_affect_base_class(self): class TestCase(asynctest.TestCase): pass @asynctest.fail_on(foo=True) class SubTestCase(TestCase): pass self.assert_not_decorated(TestCase()) self.assert_checks(SubTestCase(), foo=True) def test_decorate_subclass_inherits_parent_params(self): @asynctest.fail_on(foo=True) class TestCase(asynctest.TestCase): pass @asynctest.fail_on(bar=False) class SubTestCase(TestCase): pass @asynctest.fail_on(foo=False, bar=False) class OverridingTestCase(TestCase): pass self.assert_checks_equal(TestCase(), foo=True, bar=True) self.assert_checks_equal(SubTestCase(), foo=True, bar=False) self.assert_checks_equal(OverridingTestCase(), foo=False, bar=False) def test_strict_decorator(self): @asynctest.strict class TestCase(asynctest.TestCase): pass self.assert_checks_equal(TestCase(), foo=True, bar=True) @asynctest.strict() class TestCase(asynctest.TestCase): pass self.assert_checks_equal(TestCase(), foo=True, bar=True) def test_lenient_decorator(self): @asynctest.lenient class TestCase(asynctest.TestCase): pass self.assert_checks_equal(TestCase(), foo=False, bar=False) @asynctest.lenient() class TestCase(asynctest.TestCase): pass self.assert_checks_equal(TestCase(), foo=False, bar=False) fail_on_defaults = {"default": True, "optional": False} @unittest.mock.patch.dict("asynctest._fail_on.DEFAULTS", values=fail_on_defaults, clear=True) class Test_fail_on(_TestCase): def setUp(self): self.mocks = {} for method in fail_on_defaults: mock = self.mocks[method] = unittest.mock.Mock() setattr(asynctest._fail_on._fail_on, method, mock) self.addCleanup(lambda: [delattr(asynctest._fail_on._fail_on, method) for method in fail_on_defaults]) def tearDown(self): self.mocks.clear() def assert_checked(self, *args): for check in args: if not self.mocks[check].called: self.fail("'{}' unexpectedly unchecked".format(check)) def assert_not_checked(self, *args): for check in args: if self.mocks[check].called: self.fail("'{}' unexpectedly checked".format(check)) def test_default_checks(self): for method in self.run_methods: with self.subTest(method=method): case = Test.FooTestCase() getattr(case, method)() self.assert_checked("default") self.assert_not_checked("optional") self.mocks["default"].assert_called_with(case) def test_checks_on_decorated_class(self): @asynctest.fail_on(optional=True) class Dummy_TestCase(Test.FooTestCase): pass for method in self.run_methods: with self.subTest(method=method): getattr(Dummy_TestCase(), method)() self.assert_checked("default", "optional") def test_check_after_tearDown(self): self.mocks['default'].side_effect = AssertionError class Dummy_TestCase(asynctest.TestCase): def tearDown(self): self.tearDown_called = True def runTest(self): pass with self.subTest(method="debug"): case = Dummy_TestCase() with self.assertRaises(AssertionError): case.debug() self.assertTrue(case.tearDown_called) case = Dummy_TestCase() result = case.run() self.assertEqual(1, len(result.failures)) self.assertTrue(case.tearDown_called) def test_before_test_called_before_user_setup(self): mock = unittest.mock.Mock() setattr(asynctest._fail_on._fail_on, "before_test_default", mock) self.addCleanup(delattr, asynctest._fail_on._fail_on, "before_test_default") class TestCase(asynctest.TestCase): def setUp(self): self.assertTrue(mock.called) def runTest(self): pass for method in self.run_methods: with self.subTest(method=method): case = Test.FooTestCase() getattr(case, method)() self.assert_checked("default") self.assertTrue(mock.called) mock.assert_called_with(case) def test_non_existing_before_test_wont_fail(self): # set something not callable for default, nothing for optional, the # test must not fail setattr(asynctest._fail_on._fail_on, "before_test_default", None) self.addCleanup(delattr, asynctest._fail_on._fail_on, "before_test_default") @asynctest.fail_on(default=True, optional=True) class TestCase(asynctest.TestCase): def runTest(self): pass for method in self.run_methods: with self.subTest(method=method): getattr(TestCase(), method)() self.assert_checked("default", "optional") def test_before_test_called_for_enabled_checks_only(self): for method in map(lambda m: "before_test_" + m, fail_on_defaults): mock = self.mocks[method] = unittest.mock.Mock() setattr(asynctest._fail_on._fail_on, method, mock) self.addCleanup(lambda: [delattr(asynctest._fail_on._fail_on, "before_test_" + method) for method in fail_on_defaults]) for method in self.run_methods: with self.subTest(method=method): getattr(Test.FooTestCase(), method)() self.assertTrue(self.mocks["before_test_default"].called) self.assertFalse(self.mocks["before_test_optional"].called) @unittest.mock.patch.dict( "asynctest._fail_on.DEFAULTS", clear=True, unused_loop=asynctest._fail_on.DEFAULTS['unused_loop']) class Test_fail_on_unused_loop(_TestCase): @asynctest.fail_on(unused_loop=True) class WithCheckTestCase(Test.FooTestCase): pass def test_fails_when_loop_didnt_run(self): with self.assertRaisesRegex(AssertionError, 'Loop did not run during the test'): self.WithCheckTestCase().debug() result = self.WithCheckTestCase().run() self.assertEqual(1, len(result.failures)) def test_fails_when_loop_didnt_run_using_default_loop(self): class TestCase(self.WithCheckTestCase): use_default_loop = True default_loop = self.create_default_loop() with self.assertRaisesRegex(AssertionError, 'Loop did not run during the test'): TestCase().debug() result = TestCase().run() self.assertEqual(1, len(result.failures)) default_loop.run_until_complete(asyncio.sleep(0, loop=default_loop)) with self.assertRaisesRegex(AssertionError, 'Loop did not run during the test'): TestCase().debug() default_loop.run_until_complete(asyncio.sleep(0, loop=default_loop)) result = TestCase().run() self.assertEqual(1, len(result.failures)) def test_passes_when_ignore_loop_or_loop_run(self): @asynctest.fail_on(unused_loop=False) class IgnoreLoopClassTest(Test.FooTestCase): pass @asynctest.fail_on(unused_loop=True) class WithCoroutineTest(asynctest.TestCase): @asyncio.coroutine def runTest(self): yield from [] @asynctest.fail_on(unused_loop=True) class WithFunctionCallingLoopTest(asynctest.TestCase): def runTest(self): fut = asyncio.Future() self.loop.call_soon(fut.set_result, None) self.loop.run_until_complete(fut) for test in (IgnoreLoopClassTest, WithCoroutineTest, WithFunctionCallingLoopTest): with self.subTest(test=test): test().debug() result = test().run() self.assertEqual(0, len(result.failures)) def test_fails_when_loop_ran_only_during_setup(self): for test_use_default_loop in (False, True): with self.subTest(use_default_loop=test_use_default_loop): if test_use_default_loop: self.create_default_loop() class TestCase(self.WithCheckTestCase): use_default_loop = test_use_default_loop def setUp(self): self.loop.run_until_complete( asyncio.sleep(0, loop=self.loop)) with self.assertRaisesRegex( AssertionError, 'Loop did not run during the test'): TestCase().debug() result = TestCase().run() self.assertEqual(1, len(result.failures)) def test_fails_when_loop_ran_only_during_cleanup(self): for test_use_default_loop in (False, True): with self.subTest(use_default_loop=test_use_default_loop): if test_use_default_loop: self.create_default_loop() class TestCase(self.WithCheckTestCase): use_default_loop = test_use_default_loop def setUp(self): self.addCleanup(asyncio.coroutine(lambda: None)) with self.assertRaisesRegex( AssertionError, 'Loop did not run during the test'): TestCase().debug() result = TestCase().run() self.assertEqual(1, len(result.failures)) class Test_assertAsyncRaises(asynctest.TestCase): class CustomException(Exception): def __str__(self): return "CustomException Message" @property def raising(self): awaitable = asyncio.Future() awaitable.set_exception(self.CustomException()) return awaitable @property def finishing(self): awaitable = asyncio.Future() awaitable.set_result(None) return awaitable @asyncio.coroutine def test_assertAsyncRaises(self): with self.subTest("exception raised"): yield from self.assertAsyncRaises(self.CustomException, self.raising) with self.subTest("no exception raised"): with self.assertRaises(AssertionError): yield from self.assertAsyncRaises(self.CustomException, self.finishing) @asyncio.coroutine def test_assertAsyncRaisesRegex(self): regex = "CustomException" with self.subTest("exception raised"): yield from self.assertAsyncRaisesRegex(self.CustomException, regex, self.raising) with self.subTest("no exception raised"): with self.assertRaises(AssertionError): yield from self.assertAsyncRaisesRegex(self.CustomException, regex, self.finishing) class Test_assertAsyncWarns(asynctest.TestCase): class CustomWarning(Warning): pass @classmethod @asyncio.coroutine def warns(cls): import warnings warnings.warn("asynctest warning message", cls.CustomWarning) @staticmethod def doesnt_warn(): awaitable = asyncio.Future() awaitable.set_result(None) return awaitable @asyncio.coroutine def test_assertAsyncWarns(self): with self.subTest("warning triggered"): yield from self.assertAsyncWarns(self.CustomWarning, self.warns()) with self.subTest("warning not triggered"): with self.assertRaises(AssertionError): yield from self.assertAsyncWarns(self.CustomWarning, self.doesnt_warn()) @asyncio.coroutine def test_assertAsyncWarnsRegex(self): regex = "asynctest warning" with self.subTest("warning triggered"): yield from self.assertAsyncWarnsRegex(self.CustomWarning, regex, self.warns()) with self.subTest("warning not triggered"): with self.assertRaises(AssertionError): yield from self.assertAsyncWarnsRegex( self.CustomWarning, regex, self.doesnt_warn()) if __name__ == "__main__": unittest.main() asynctest-0.13.0/test/test_helpers.py000066400000000000000000000024431346656454500176670ustar00rootroot00000000000000# coding: utf-8 import asyncio import unittest import asynctest class TestExhaust(asynctest.TestCase): @asyncio.coroutine def wait_for(self, coro): return (yield from asyncio.wait_for(coro, loop=self.loop, timeout=1)) def test_exhaust_callbacks_nothing_to_wait(self): # Nothing ready, do nothing (must not timeout) yield from self.wait_for(asynctest.helpers.exhaust_callbacks(self.loop)) def test_exhaust_callbacks_one_callback(self): fut = asyncio.Future(loop=self.loop) self.loop.call_soon(fut.set_result, None) # A callback has been scheduled yield from self.wait_for(asynctest.helpers.exhaust_callbacks(self.loop)) self.assertTrue(fut.done()) def test_exhaust_callbacks_cascading_callbacks(self): # A callback has been scheduled, then another (while the 1st is # running), we must wait for both fut = asyncio.Future(loop=self.loop) fut2 = asyncio.Future(loop=self.loop) fut.add_done_callback(lambda _: fut2.set_result(None)) self.loop.call_soon(fut.set_result, None) yield from self.wait_for(asynctest.helpers.exhaust_callbacks(self.loop)) self.assertTrue(fut.done()) self.assertTrue(fut2.done()) if __name__ == "__main__": unittest.main() asynctest-0.13.0/test/test_mock.py000066400000000000000000002407151346656454500171640ustar00rootroot00000000000000# coding: utf-8 # pylama: ignore=E501 noqa import asyncio import functools import inspect import platform import unittest import sys import warnings import asynctest from .utils import run_coroutine class Test: @asyncio.coroutine def a_coroutine(self): pass def a_function(self): pass def is_patched(self): return False def second_is_patched(self): return False a_dict = {'is_patched': False, 'second_is_patched': False} a_second_dict = {'is_patched': False} @asyncio.coroutine def a_coroutine_with_args(self, arg, arg2): return None @classmethod @asyncio.coroutine def a_classmethod_coroutine(cls): pass @staticmethod @asyncio.coroutine def a_staticmethod_coroutine(): pass async def an_async_coroutine(self): pass @classmethod async def an_async_classmethod_coroutine(self): pass @staticmethod async def an_async_staticmethod_coroutine(): pass class ProbeException(Exception): pass patch_is_patched = functools.partial(asynctest.mock.patch, 'test.test_mock.Test.is_patched', new=lambda self: True) patch_second_is_patched = functools.partial( asynctest.mock.patch, 'test.test_mock.Test.second_is_patched', new=lambda self: True) patch_dict_is_patched = functools.partial( asynctest.mock.patch.dict, 'test.test_mock.Test.a_dict', values={"is_patched": True}) patch_dict_second_is_patched = functools.partial( asynctest.mock.patch.dict, 'test.test_mock.Test.a_dict', values={"second_is_patched": True}) patch_dict_second_dict_is_patched = functools.partial( asynctest.mock.patch.dict, 'test.test_mock.Test.a_second_dict', values={"is_patched": True}) def inject_class(obj): # Decorate _Test_* mixin classes so we can retrieve the mock class to test # with the last argument of the test function ("klass"). if isinstance(obj, type): for attr_name in dir(obj): attr = getattr(obj, attr_name) if callable(attr) and attr_name.startswith('test_'): setattr(obj, attr_name, inject_class(attr)) return obj else: @functools.wraps(obj) def wrapper(self): return obj(self, getattr(asynctest, self.class_to_test)) return wrapper @inject_class class _Test_iscoroutinefunction: # Ensure that an instance of this mock type is seen as a coroutine function def test_asyncio_iscoroutinefunction(self, klass): with self.subTest(is_coroutine=False): mock = klass(is_coroutine=False) self.assertFalse(asyncio.iscoroutinefunction(mock)) with self.subTest(is_coroutine=False): mock = klass(is_coroutine=True) self.assertTrue(asyncio.iscoroutinefunction(mock)) @inject_class class _Test_is_coroutine_property: # Ensure an instance offers an is_coroutine property def test_is_coroutine_property(self, klass): mock = klass() self.assertFalse(mock.is_coroutine) mock.is_coroutine = True self.assertTrue(mock.is_coroutine) mock = klass(is_coroutine=True) self.assertTrue(mock.is_coroutine) @inject_class class _Test_subclass: # Ensure that the tested class is also a subclass of its counterpart in # the standard module unittest.mock def test_subclass(self, klass): unittest_klass = getattr(unittest.mock, self.class_to_test) self.assertTrue(issubclass(klass, unittest_klass)) self.assertTrue(isinstance(klass(), unittest_klass)) @inject_class class _Test_called_coroutine: # Ensure that an object mocking as a coroutine works def test_returns_coroutine(self, klass): mock = klass() coro = mock() # Suppress debug warning about non-running coroutine if isinstance(coro, asyncio.coroutines.CoroWrapper): coro.gen = None self.assertTrue(asyncio.iscoroutine(coro)) def test_returns_coroutine_from_return_value(self, klass): mock = klass() mock.return_value = 'ProbeValue' self.assertEqual('ProbeValue', mock.return_value) self.assertEqual(mock.return_value, run_coroutine(mock())) def test_returns_coroutine_with_return_value_being_a_coroutine(self, klass): mock = klass() coroutine = asyncio.coroutine(lambda: 'ProbeValue') mock.return_value = coroutine() self.assertEqual('ProbeValue', run_coroutine(mock())) def test_returns_coroutine_from_side_effect(self, klass): mock = klass() mock.side_effect = lambda: 'ProbeValue' self.assertEqual('ProbeValue', run_coroutine(mock())) def test_returns_coroutine_from_side_effect_being_a_coroutine(self, klass): mock = klass() mock.side_effect = asyncio.coroutine(lambda: 'ProbeValue') self.assertEqual('ProbeValue', run_coroutine(mock())) def test_exception_side_effect_raises_in_coroutine(self, klass): mock = klass() mock.side_effect = ProbeException coroutine = mock() with self.assertRaises(ProbeException): run_coroutine(coroutine) def test_returns_coroutine_from_side_effect_being_an_iterable(self, klass): mock = klass() side_effect = ['Probe1', 'Probe2', 'Probe3'] mock.side_effect = side_effect for expected in side_effect: self.assertEqual(expected, run_coroutine(mock())) with self.assertRaises(StopIteration): mock() @inject_class class _Test_Spec_Spec_Set_Returns_Coroutine_Mock: # Ensure that when a mock is configured with spec or spec_set, coroutines # are detected and mocked correctly def test_mock_returns_coroutine_according_to_spec(self, klass): spec = Test() for attr in ('spec', 'spec_set', ): with self.subTest(spec_type=attr): mock = klass(**{attr: spec}) self.assertIsInstance(mock.a_function, (asynctest.Mock, asynctest.MagicMock)) self.assertNotIsInstance(mock.a_function, asynctest.CoroutineMock) self.assertIsInstance(mock.a_coroutine, asynctest.CoroutineMock) self.assertIsInstance(mock.a_classmethod_coroutine, asynctest.CoroutineMock) self.assertIsInstance(mock.a_staticmethod_coroutine, asynctest.CoroutineMock) mock.a_coroutine.return_value = "PROBE" self.assertEqual("PROBE", run_coroutine(mock.a_coroutine())) self.assertIsInstance(mock.an_async_coroutine, asynctest.CoroutineMock) self.assertIsInstance(mock.an_async_classmethod_coroutine, asynctest.CoroutineMock) self.assertIsInstance(mock.an_async_staticmethod_coroutine, asynctest.CoroutineMock) mock.an_async_coroutine.return_value = "PROBE" self.assertEqual("PROBE", run_coroutine(mock.an_async_coroutine())) # Ensure the name of the mock is correctly set, tests bug #49. def test_mock_has_correct_name(self, klass): spec = Test() for attr in ('spec', 'spec_set', ): with self.subTest(spec_type=attr): mock = klass(**{attr: spec}) self.assertIn("{}='{}".format(attr, "Test"), repr(mock)) self.assertIn("name='mock.a_coroutine'", repr(mock.a_coroutine)) run_coroutine(mock.a_coroutine()) self.assertIn("name='mock.a_function()'", repr(mock.a_function())) self.assertEqual("call.a_coroutine()", repr(mock.mock_calls[0])) self.assertEqual("call.a_function()", repr(mock.mock_calls[1])) @inject_class class _Test_Spec_Spec_Set_Is_Function: def test_mock_is_not_coroutine_when_spec_is_function(self, klass): spec = Test.a_function for attr in ('spec', 'spec_set', ): with self.subTest(spec_type=attr): mock = klass(**{attr: spec}) self.assertFalse(asyncio.iscoroutinefunction(mock)) if hasattr(inspect, "iscoroutinefunction"): self.assertFalse(inspect.iscoroutinefunction(mock)) @inject_class class _Test_Future: # Ensure that a mocked Future is detected as a future def test_mock_a_future_is_a_future(self, klass): mock = klass(asyncio.Future()) self.assertIsInstance(mock, asyncio.Future) def test_mock_from_create_future(self, klass): loop = asyncio.new_event_loop() try: if not (hasattr(loop, "create_future") and hasattr(asyncio, "isfuture")): return mock = klass(loop.create_future()) self.assertTrue(asyncio.isfuture(mock)) finally: loop.close() @inject_class class _Test_Mock_Of_Async_Magic_Methods: class WithAsyncContextManager: def __init__(self): self.entered = False self.exited = False async def __aenter__(self, *args, **kwargs): self.entered = True return self async def __aexit__(self, *args, **kwargs): self.exited = True def test_mock_magic_methods_are_coroutine_mocks(self, klass): for spec in (None, self.WithAsyncContextManager()): with self.subTest(spec=spec): mock_instance = asynctest.mock.MagicMock(spec) self.assertIsInstance(mock_instance.__aenter__, asynctest.mock.CoroutineMock) self.assertIsInstance(mock_instance.__aexit__, asynctest.mock.CoroutineMock) def test_mock_supports_async_context_manager(self, klass): called = False instance = self.WithAsyncContextManager() mock_instance = asynctest.mock.MagicMock(instance) async def use_context_manager(): nonlocal called async with mock_instance as result: called = True return result result = run_coroutine(use_context_manager()) self.assertFalse(instance.entered) self.assertFalse(instance.exited) self.assertTrue(called) self.assertTrue(mock_instance.__aenter__.called) self.assertTrue(mock_instance.__aexit__.called) self.assertIsNot(mock_instance, result) self.assertIsInstance(result, asynctest.mock.MagicMock) def test_mock_customize_async_context_manager(self, klass): instance = self.WithAsyncContextManager() mock_instance = asynctest.mock.MagicMock(instance) expected_result = object() mock_instance.__aenter__.return_value = expected_result async def use_context_manager(): async with mock_instance as result: return result self.assertIs(run_coroutine(use_context_manager()), expected_result) def test_mock_customize_async_context_manager_with_coroutine(self, klass): enter_called = False exit_called = False async def enter_coroutine(*args): nonlocal enter_called enter_called = True async def exit_coroutine(*args): nonlocal exit_called exit_called = True instance = self.WithAsyncContextManager() mock_instance = asynctest.mock.MagicMock(instance) mock_instance.__aenter__ = enter_coroutine mock_instance.__aexit__ = exit_coroutine async def use_context_manager(): async with mock_instance: pass run_coroutine(use_context_manager()) self.assertTrue(enter_called) self.assertTrue(exit_called) def test_context_manager_raise_exception_by_default(self, klass): class InContextManagerException(Exception): pass async def raise_in(context_manager): async with context_manager: raise InContextManagerException() instance = self.WithAsyncContextManager() mock_instance = asynctest.mock.MagicMock(instance) with self.assertRaises(InContextManagerException): run_coroutine(raise_in(mock_instance)) class WithAsyncIterator: def __init__(self): self.iter_called = False self.next_called = False self.items = ["foo", "bar", "baz"] def __aiter__(self): return self async def __anext__(self): try: return self.items.pop() except IndexError: pass raise StopAsyncIteration class WithAsyncIteratorDeprecated(WithAsyncIterator): # Before python 3.5.2, __aiter__ is specified as a coroutine, but it's # a design error, it should be a plain function. async def __aiter__(self): return super().__aiter__() def get_async_iterator_classes(self): # We assume that __aiter__ as a coroutine will not be available in # python 3.7, see: pep-0525#aiter-and-anext-builtins if sys.version_info >= (3, 7): return (self.WithAsyncIterator, ) else: return (self.WithAsyncIterator, self.WithAsyncIteratorDeprecated, ) def test_mock_aiter_and_anext(self, klass): classes = self.get_async_iterator_classes() for iterator_class in classes: with self.subTest(iterator_class=iterator_class.__name__): instance = iterator_class() mock_instance = asynctest.MagicMock(instance) self.assertEqual(asyncio.iscoroutine(instance.__aiter__), asyncio.iscoroutine(mock_instance.__aiter__)) self.assertEqual(asyncio.iscoroutine(instance.__anext__), asyncio.iscoroutine(mock_instance.__anext__)) iterator = instance.__aiter__() if asyncio.iscoroutine(iterator): iterator = run_coroutine(iterator) mock_iterator = mock_instance.__aiter__() if asyncio.iscoroutine(mock_iterator): mock_iterator = run_coroutine(mock_iterator) self.assertEqual(asyncio.iscoroutine(iterator.__aiter__), asyncio.iscoroutine(mock_iterator.__aiter__)) self.assertEqual(asyncio.iscoroutine(iterator.__anext__), asyncio.iscoroutine(mock_iterator.__anext__)) def test_mock_async_for(self, klass): async def iterate(iterator): accumulator = [] # don't print the DeprecationWarning for __aiter__ with warnings.catch_warnings(): warnings.simplefilter("ignore") async for item in iterator: accumulator.append(item) return accumulator expected = ["FOO", "BAR", "BAZ"] specs = [None] specs.extend(i() for i in self.get_async_iterator_classes()) for spec in specs: with self.subTest("iterate through default value"): mock_instance = asynctest.MagicMock(spec) self.assertEqual([], run_coroutine(iterate(mock_instance))) with self.subTest("iterate through set return_value"): mock_instance = asynctest.MagicMock(spec) mock_instance.__aiter__.return_value = expected[:] self.assertEqual(expected, run_coroutine(iterate(mock_instance))) with self.subTest("iterate through set return_value iterator"): mock_instance = asynctest.MagicMock(spec) mock_instance.__aiter__.return_value = iter(expected[:]) self.assertEqual(expected, run_coroutine(iterate(mock_instance))) class Test_NonCallabableMock(unittest.TestCase, _Test_subclass, _Test_iscoroutinefunction, _Test_is_coroutine_property, _Test_Spec_Spec_Set_Returns_Coroutine_Mock, _Test_Spec_Spec_Set_Is_Function, _Test_Future): class_to_test = 'NonCallableMock' class Test_NonCallableMagicMock(unittest.TestCase, _Test_subclass, _Test_iscoroutinefunction, _Test_is_coroutine_property, _Test_Spec_Spec_Set_Returns_Coroutine_Mock, _Test_Spec_Spec_Set_Is_Function, _Test_Future, _Test_Mock_Of_Async_Magic_Methods): class_to_test = 'NonCallableMagicMock' class Test_Mock(unittest.TestCase, _Test_subclass, _Test_Spec_Spec_Set_Returns_Coroutine_Mock, _Test_Spec_Spec_Set_Is_Function, _Test_Future): class_to_test = 'Mock' class Test_MagicMock(unittest.TestCase, _Test_subclass, _Test_Spec_Spec_Set_Returns_Coroutine_Mock, _Test_Spec_Spec_Set_Is_Function, _Test_Future, _Test_Mock_Of_Async_Magic_Methods): class_to_test = 'MagicMock' class Test_CoroutineMock(unittest.TestCase, _Test_called_coroutine, _Test_Spec_Spec_Set_Returns_Coroutine_Mock): class_to_test = 'CoroutineMock' def test_asyncio_iscoroutinefunction(self): mock = asynctest.mock.CoroutineMock() self.assertTrue(asyncio.iscoroutinefunction(mock)) def test_called_CoroutineMock_returns_MagicMock(self): mock = asynctest.mock.CoroutineMock() self.assertIsInstance(run_coroutine(mock()), asynctest.mock.MagicMock) class Test_CoroutineMock_awaited(asynctest.TestCase): @asynctest.fail_on(unused_loop=False) def test_awaited_delays_creation_of_condition(self): mock = asynctest.mock.CoroutineMock() self.assertIsNone(mock.awaited._condition) coro = mock() condition_before_run = mock.awaited._condition run_coroutine(coro) condition_after_run = mock.awaited._condition self.assertIsNone(condition_before_run) self.assertIsNotNone(condition_after_run) @asyncio.coroutine def test_awaited_CoroutineMock_sets_awaited(self): mock = asynctest.mock.CoroutineMock() yield from mock() self.assertTrue(mock.awaited) mock.reset_mock() self.assertFalse(mock.awaited) @asyncio.coroutine def side_effect(): raise RuntimeError() mock = asynctest.mock.CoroutineMock(side_effect=side_effect) with self.assertRaises(RuntimeError): yield from mock() @asyncio.coroutine def test_awaited_CoroutineMock_counts(self): mock = asynctest.mock.CoroutineMock() yield from mock() yield from mock() self.assertEqual(mock.await_count, 2) mock.reset_mock() self.assertEqual(mock.await_count, 0) @asyncio.coroutine def side_effect(): raise RuntimeError() mock = asynctest.mock.CoroutineMock(side_effect=side_effect) with self.assertRaises(RuntimeError): yield from mock() self.assertEqual(mock.await_count, 1) mock.reset_mock() mock.side_effect = RuntimeError with self.assertRaises(RuntimeError): yield from mock() self.assertEqual(mock.await_count, 1) @asyncio.coroutine def test_awaited_from_autospec_mock(self): mock = asynctest.mock.create_autospec(Test) self.assertFalse(mock.a_coroutine.awaited) self.assertEqual(0, mock.a_coroutine.await_count) yield from mock.a_coroutine() self.assertTrue(mock.a_coroutine.awaited) self.assertEqual(1, mock.a_coroutine.await_count) @asyncio.coroutine def test_awaited_wait(self): mock = asynctest.mock.CoroutineMock() t = asyncio.ensure_future(mock.awaited.wait()) yield from mock() yield from mock() yield from t mock.reset_mock() t = asyncio.ensure_future(mock.awaited.wait(skip=1)) yield from mock() self.assertFalse(t.done()) yield from mock() yield from t @asyncio.coroutine def test_awaited_wait_next(self): mock = asynctest.mock.CoroutineMock() yield from mock() t = asyncio.ensure_future(mock.awaited.wait_next()) yield from asyncio.sleep(0.01) self.assertFalse(t.done()) yield from mock() yield from t mock.reset_mock() yield from mock() t = asyncio.ensure_future(mock.awaited.wait_next(skip=1)) yield from asyncio.sleep(0.01) yield from mock() self.assertFalse(t.done()) yield from mock() yield from t @asyncio.coroutine def test_await_args(self): with self.subTest('in order'): mock = asynctest.mock.CoroutineMock() t1 = mock('foo') t2 = mock('bar') yield from t1 yield from t2 self.assertEqual(mock.await_args, asynctest.call('bar')) with self.subTest('out of order'): mock = asynctest.mock.CoroutineMock() t1 = mock('foo') t2 = mock('bar') yield from t2 yield from t1 self.assertEqual(mock.await_args, asynctest.call('foo')) @asyncio.coroutine def test_await_args_list(self): with self.subTest('in order'): mock = asynctest.mock.CoroutineMock() t1 = mock('foo') t2 = mock('bar') yield from t1 yield from t2 self.assertEqual(mock.await_args_list, [asynctest.call('foo'), asynctest.call('bar')]) with self.subTest('out of order'): mock = asynctest.mock.CoroutineMock() t1 = mock('foo') t2 = mock('bar') yield from t2 yield from t1 self.assertEqual(mock.await_args_list, [asynctest.call('bar'), asynctest.call('foo')]) @asyncio.coroutine def test_assert_awaited(self): mock = asynctest.mock.CoroutineMock() with self.assertRaises(AssertionError): mock.assert_awaited() yield from mock() mock.assert_awaited() @asyncio.coroutine def test_assert_awaited_once(self): mock = asynctest.mock.CoroutineMock() with self.assertRaises(AssertionError): mock.assert_awaited_once() yield from mock() mock.assert_awaited_once() yield from mock() with self.assertRaises(AssertionError): mock.assert_awaited_once() @asyncio.coroutine def test_assert_awaited_with(self): mock = asynctest.mock.CoroutineMock() with self.assertRaises(AssertionError): mock.assert_awaited_with('foo') yield from mock('foo') mock.assert_awaited_with('foo') yield from mock('bar') with self.assertRaises(AssertionError): mock.assert_awaited_with('foo') @asyncio.coroutine def test_assert_awaited_once_with(self): mock = asynctest.mock.CoroutineMock() with self.assertRaises(AssertionError): mock.assert_awaited_once_with('foo') yield from mock('foo') mock.assert_awaited_once_with('foo') yield from mock('foo') with self.assertRaises(AssertionError): mock.assert_awaited_once_with('foo') @asyncio.coroutine def test_assert_any_wait(self): mock = asynctest.mock.CoroutineMock() with self.assertRaises(AssertionError): mock.assert_any_await('bar') yield from mock('foo') with self.assertRaises(AssertionError): mock.assert_any_await('bar') yield from mock('bar') mock.assert_any_await('bar') yield from mock('baz') mock.assert_any_await('bar') @asyncio.coroutine def test_assert_has_awaits(self): calls = [asynctest.call('bar'), asynctest.call('baz')] with self.subTest('any_order=False'): mock = asynctest.mock.CoroutineMock() with self.assertRaises(AssertionError): mock.assert_has_awaits(calls) yield from mock('foo') with self.assertRaises(AssertionError): mock.assert_has_awaits(calls) yield from mock('bar') with self.assertRaises(AssertionError): mock.assert_has_awaits(calls) yield from mock('baz') mock.assert_has_awaits(calls) yield from mock('qux') mock.assert_has_awaits(calls) with self.subTest('any_order=True'): mock = asynctest.mock.CoroutineMock() with self.assertRaises(AssertionError): mock.assert_has_awaits(calls, any_order=True) yield from mock('baz') with self.assertRaises(AssertionError): mock.assert_has_awaits(calls, any_order=True) yield from mock('foo') with self.assertRaises(AssertionError): mock.assert_has_awaits(calls, any_order=True) yield from mock('bar') mock.assert_has_awaits(calls, any_order=True) yield from mock('qux') mock.assert_has_awaits(calls, any_order=True) @asyncio.coroutine def test_assert_not_awaited(self): mock = asynctest.mock.CoroutineMock() mock.assert_not_awaited() yield from mock() with self.assertRaises(AssertionError): mock.assert_not_awaited() def test_create_autospec_on_coroutine_and_using_assert_methods(self): mock = asynctest.create_autospec(Test.a_coroutine_with_args) mock.assert_not_awaited() yield from mock("arg0", "arg1", "arg2") mock.assert_awaited() # calls assert not awaited mock.assert_awaited_once() mock.assert_awaited_with("arg0", "arg1", "arg2") mock.assert_awaited_once_with("arg0", "arg1", "arg2") mock.assert_any_await("arg0", "arg1", "arg2") mock.assert_has_awaits([asynctest.call("arg0", "arg1", "arg2")]) class TestMockInheritanceModel(unittest.TestCase): to_test = { 'NonCallableMagicMock': 'NonCallableMock', 'Mock': 'NonCallableMock', 'MagicMock': 'Mock', 'CoroutineMock': 'Mock', } def test_Mock_is_not_CoroutineMock(self): self.assertNotIsInstance(asynctest.mock.Mock(), asynctest.mock.CoroutineMock) def test_MagicMock_is_not_CoroutineMock(self): self.assertNotIsInstance(asynctest.mock.MagicMock(), asynctest.mock.CoroutineMock) @staticmethod def make_inheritance_test(child, parent): def test(self): # Works in the common case self.assertIsInstance(getattr(asynctest.mock, child)(), getattr(asynctest.mock, parent)) # Works with a custom spec self.assertIsInstance(getattr(asynctest.mock, child)(Test()), getattr(asynctest.mock, parent)) return test for child, parent in TestMockInheritanceModel.to_test.items(): setattr(TestMockInheritanceModel, 'test_{}_inherits_from_{}'.format(child, parent), TestMockInheritanceModel.make_inheritance_test(child, parent)) # # mock_open # class Test_mock_open(unittest.TestCase): def test_MagicMock_returned_by_default(self): self.assertIsInstance(asynctest.mock_open(), asynctest.MagicMock) # # Test patches # class Test_patch(unittest.TestCase): def test_patch_as_context_manager_uses_MagicMock(self): with asynctest.mock.patch('test.test_mock.Test') as mock: self.assertIsInstance(mock, asynctest.mock.MagicMock) with asynctest.mock.patch('test.test_mock.Test.a_function') as mock: self.assertIsInstance(mock, asynctest.mock.MagicMock) def test_patch_as_decorator_uses_MagicMock(self): called = [] @asynctest.mock.patch('test.test_mock.Test') def test_mock_class(mock): self.assertIsInstance(mock, asynctest.mock.MagicMock) called.append("test_mock_class") @asynctest.mock.patch('test.test_mock.Test.a_function') def test_mock_function(mock): self.assertIsInstance(mock, asynctest.mock.MagicMock) called.append("test_mock_function") test_mock_class() test_mock_function() self.assertIn("test_mock_class", called) self.assertIn("test_mock_function", called) def test_patch_as_decorator_uses_CoroutineMock_on_coroutine_function(self): called = False @asynctest.mock.patch('test.test_mock.Test.a_coroutine') def test_mock_coroutine(mock): nonlocal called self.assertIsInstance(mock, asynctest.mock.CoroutineMock) called = True test_mock_coroutine() self.assertTrue(called) def test_patch_as_decorator_uses_CoroutineMock_on_classmethod_coroutine_function(self): called = False @asynctest.mock.patch("test.test_mock.Test.a_classmethod_coroutine") def test_mock_coroutine(mock): nonlocal called self.assertIsInstance(mock, asynctest.mock.CoroutineMock) called = True test_mock_coroutine() self.assertTrue(called) def test_patch_as_decorator_uses_CoroutineMock_on_staticmethod_coroutine_function(self): called = False @asynctest.mock.patch("test.test_mock.Test.a_staticmethod_coroutine") def test_mock_coroutine(mock): nonlocal called self.assertIsInstance(mock, asynctest.mock.CoroutineMock) called = True test_mock_coroutine() self.assertTrue(called) def test_patch_as_context_manager_uses_CoroutineMock_on_coroutine_function(self): with asynctest.mock.patch('test.test_mock.Test.a_coroutine') as mock: import test.test_mock self.assertIs(test.test_mock.Test.a_coroutine, mock) self.assertIsInstance(mock, asynctest.mock.CoroutineMock) def test_patch_as_context_manager_uses_CoroutineMock_on_classmethod_coroutine_function(self): with asynctest.mock.patch('test.test_mock.Test.a_classmethod_coroutine') as mock: import test.test_mock self.assertIs(test.test_mock.Test.a_classmethod_coroutine, mock) self.assertIsInstance(mock, asynctest.mock.CoroutineMock) def test_patch_as_context_manager_uses_CoroutineMock_on_staticmethod_coroutine_function(self): with asynctest.mock.patch('test.test_mock.Test.a_staticmethod_coroutine') as mock: import test.test_mock self.assertIs(test.test_mock.Test.a_staticmethod_coroutine, mock) self.assertIsInstance(mock, asynctest.mock.CoroutineMock) def test_patch_as_context_manager_uses_CoroutineMock_on_async_coroutine_function(self): with asynctest.mock.patch('test.test_mock.Test.an_async_coroutine') as mock: import test.test_mock self.assertIs(test.test_mock.Test.an_async_coroutine, mock) self.assertIsInstance(mock, asynctest.mock.CoroutineMock) def test_patch_as_context_manager_uses_CoroutineMock_on_async_classmethod_coroutine_function(self): with asynctest.mock.patch('test.test_mock.Test.an_async_classmethod_coroutine') as mock: import test.test_mock self.assertIs(test.test_mock.Test.an_async_classmethod_coroutine, mock) self.assertIsInstance(mock, asynctest.mock.CoroutineMock) def test_patch_as_context_manager_uses_CoroutineMock_on_async_staticmethod_coroutine_function(self): with asynctest.mock.patch('test.test_mock.Test.an_async_staticmethod_coroutine') as mock: import test.test_mock self.assertIs(test.test_mock.Test.an_async_staticmethod_coroutine, mock) self.assertIsInstance(mock, asynctest.mock.CoroutineMock) def test_patch_as_decorator_uses_CoroutineMock_on_async_coroutine_function(self): called = False @asynctest.mock.patch('test.test_mock.Test.an_async_coroutine') def test_mock_coroutine(mock): nonlocal called self.assertIsInstance(mock, asynctest.mock.CoroutineMock) called = True test_mock_coroutine() self.assertTrue(called) def test_patch_as_decorator_uses_CoroutineMock_on_async_classmethod_coroutine_function(self): called = False @asynctest.mock.patch('test.test_mock.Test.an_async_classmethod_coroutine') def test_mock_coroutine(mock): nonlocal called self.assertIsInstance(mock, asynctest.mock.CoroutineMock) called = True test_mock_coroutine() self.assertTrue(called) def test_patch_as_decorator_uses_CoroutineMock_on_async_staticmethod_coroutine_function(self): called = False @asynctest.mock.patch('test.test_mock.Test.an_async_staticmethod_coroutine') def test_mock_coroutine(mock): nonlocal called self.assertIsInstance(mock, asynctest.mock.CoroutineMock) called = True test_mock_coroutine() self.assertTrue(called) def test_patch_is_enabled_when_running_decorated_coroutine(self): with self.subTest("old style coroutine"): @patch_is_patched() @asyncio.coroutine def a_coroutine(): import test.test_mock return test.test_mock.Test().is_patched() self.assertTrue(run_coroutine(a_coroutine())) with self.subTest("native coroutine"): @patch_is_patched() async def a_coroutine(): import test.test_mock return test.test_mock.Test().is_patched() self.assertTrue(run_coroutine(a_coroutine())) def test_patch_is_enabled_when_running_decorated_function(self): @patch_is_patched() def a_function(): import test.test_mock return test.test_mock.Test().is_patched() self.assertTrue(a_function()) class Test_patch_decorator_coroutine_or_generator(unittest.TestCase): def test_coroutine_type_when_patched(self): with self.subTest("old style coroutine"): a_coroutine = Test.a_staticmethod_coroutine a_patched_coroutine = patch_is_patched()(a_coroutine) self.assertEqual(asyncio.iscoroutinefunction(a_patched_coroutine), asyncio.iscoroutinefunction(a_coroutine)) self.assertEqual(inspect.isgeneratorfunction(a_patched_coroutine), inspect.isgeneratorfunction(a_coroutine)) coro = a_coroutine() patched_coro = a_patched_coroutine() try: self.assertEqual(asyncio.iscoroutine(patched_coro), asyncio.iscoroutine(coro)) finally: run_coroutine(coro) run_coroutine(patched_coro) with self.subTest("native coroutine"): a_coroutine = Test.an_async_staticmethod_coroutine a_patched_coroutine = patch_is_patched()(a_coroutine) self.assertEqual( asyncio.iscoroutinefunction(a_patched_coroutine), asyncio.iscoroutinefunction(a_coroutine)) coro = a_coroutine() patched_coro = a_patched_coroutine() try: self.assertEqual(asyncio.iscoroutine(patched_coro), asyncio.iscoroutine(coro)) finally: run_coroutine(coro) run_coroutine(patched_coro) def test_generator_arg_is_default_mock(self): @asynctest.mock.patch('test.test_mock.Test') def a_generator(mock): self.assertIsInstance(mock, asynctest.mock.Mock) yield import test.test_mock self.assertIs(mock, test.test_mock.Test) for _ in a_generator(): pass def test_coroutine_arg_is_default_mock(self): @asyncio.coroutine def tester(coroutine_function): loop = asyncio.get_event_loop() fut = asyncio.Future(loop=loop) loop.call_soon(fut.set_result, None) before, after = yield from coroutine_function(fut) self.assertTrue(before) self.assertTrue(after) def is_instance_of_mock(obj): return isinstance(obj, asynctest.mock.Mock) def is_same_mock(obj): import test.test_mock return obj is test.test_mock.Test with self.subTest("old style coroutine"): @asynctest.mock.patch('test.test_mock.Test') def a_coroutine(fut, mock): before = is_instance_of_mock(mock) yield from fut after = is_same_mock(mock) return before, after run_coroutine(tester(a_coroutine)) with self.subTest("native coroutine"): @asynctest.mock.patch('test.test_mock.Test') async def a_native_coroutine(fut, mock): before = is_instance_of_mock(mock) await fut after = is_same_mock(mock) return before, after run_coroutine(tester(a_native_coroutine)) class Test_patch_object(unittest.TestCase): def test_patch_with_MagicMock(self): with asynctest.mock.patch.object(Test(), 'a_function') as mock: self.assertIsInstance(mock, asynctest.mock.MagicMock) obj = Test() obj.test = Test() with asynctest.mock.patch.object(obj, 'test') as mock: self.assertIsInstance(mock, asynctest.mock.MagicMock) def test_patch_coroutine_function_with_CoroutineMock(self): with asynctest.mock.patch.object(Test(), 'a_coroutine') as mock: self.assertIsInstance(mock, asynctest.mock.CoroutineMock) with asynctest.mock.patch.object(Test(), 'an_async_coroutine') as mock: self.assertIsInstance(mock, asynctest.mock.CoroutineMock) def test_patch_decorates_coroutine(self): obj = Test() with self.subTest("old style coroutine"): @asynctest.patch.object(obj, "is_patched", new=lambda: True) @asyncio.coroutine def a_coroutine(): return obj.is_patched() self.assertTrue(run_coroutine(a_coroutine())) with self.subTest("native coroutine"): @asynctest.patch.object(obj, "is_patched", new=lambda: True) async def a_native_coroutine(): return obj.is_patched() self.assertTrue(run_coroutine(a_native_coroutine())) class Test_patch_multiple(unittest.TestCase): def test_patch_with_MagicMock(self): default = asynctest.mock.DEFAULT with asynctest.mock.patch.multiple('test.test_mock', Test=default): import test.test_mock self.assertIsInstance(test.test_mock.Test, asynctest.mock.MagicMock) def test_patch_coroutine_function_with_CoroutineMock(self): default = asynctest.mock.DEFAULT with asynctest.mock.patch.multiple('test.test_mock.Test', a_function=default, a_coroutine=default, an_async_coroutine=default): import test.test_mock obj = test.test_mock.Test() self.assertIsInstance(obj.a_function, asynctest.mock.MagicMock) self.assertIsInstance(obj.a_coroutine, asynctest.mock.CoroutineMock) self.assertIsInstance(obj.an_async_coroutine, asynctest.mock.CoroutineMock) def test_patch_decorates_coroutine(self): with self.subTest("old style coroutine"): @asynctest.mock.patch.multiple("test.test_mock.Test", is_patched=lambda self: True) @asyncio.coroutine def a_coroutine(): import test.test_mock return test.test_mock.Test().is_patched() self.assertTrue(run_coroutine(a_coroutine())) with self.subTest("native coroutine"): @asynctest.mock.patch.multiple("test.test_mock.Test", is_patched=lambda self: True) async def a_native_coroutine(): import test.test_mock return test.test_mock.Test().is_patched() self.assertTrue(run_coroutine(a_native_coroutine())) class Test_patch_dict(unittest.TestCase): def test_patch_decorates_coroutine(self): with self.subTest("old style coroutine"): @patch_dict_is_patched() @asyncio.coroutine def a_coroutine(): import test.test_mock return test.test_mock.Test().a_dict['is_patched'] self.assertTrue(run_coroutine(a_coroutine())) with self.subTest("native coroutine"): @patch_dict_is_patched() async def a_native_coroutine(): import test.test_mock return test.test_mock.Test().a_dict['is_patched'] self.assertTrue(run_coroutine(a_native_coroutine())) def test_patch_decorates_function(self): @patch_dict_is_patched() def a_function(): import test.test_mock return test.test_mock.Test().a_dict['is_patched'] self.assertTrue(a_function()) def test_patch_decorates_class(self): import test.test_mock @patch_dict_is_patched() class Patched: @asyncio.coroutine def test_a_coroutine(self): return test.test_mock.Test().a_dict['is_patched'] def test_a_function(self): return test.test_mock.Test().a_dict['is_patched'] instance = Patched() self.assertFalse(test.test_mock.Test().a_dict['is_patched']) self.assertTrue(instance.test_a_function()) self.assertFalse(test.test_mock.Test().a_dict['is_patched']) self.assertTrue(run_coroutine(instance.test_a_coroutine())) self.assertFalse(test.test_mock.Test().a_dict['is_patched']) class Test_patch_autospec(unittest.TestCase): test_class_path = "{}.Test".format(__name__) def test_autospec_coroutine(self): called = False @asynctest.mock.patch(self.test_class_path, autospec=True) def patched(mock): nonlocal called called = True self.assertIsInstance(mock.a_coroutine, asynctest.mock.CoroutineMock) self.assertIsInstance(mock().a_coroutine, asynctest.mock.CoroutineMock) self.assertIsInstance(mock.a_function, asynctest.mock.Mock) self.assertIsInstance(mock().a_function, asynctest.mock.Mock) self.assertIsInstance(mock.an_async_coroutine, asynctest.mock.CoroutineMock) self.assertIsInstance(mock().an_async_coroutine, asynctest.mock.CoroutineMock) patched() self.assertTrue(called) def test_patch_autospec_with_patches_on_top(self): called = False @asynctest.mock.patch("{}.{}".format(self.test_class_path, "is_patched"), return_value=True) @asynctest.mock.patch("{}.{}".format(self.test_class_path, "a_coroutine"), autospec=True) def patched_function(coroutine_mock, is_patched_mock): nonlocal called called = True self.assertIsInstance(Test.is_patched, asynctest.mock.Mock) self.assertTrue(Test.is_patched()) self.assertTrue(asyncio.iscoroutinefunction(coroutine_mock)) self.assertTrue(asyncio.iscoroutinefunction(Test.a_coroutine)) patched_function() self.assertTrue(called) def test_patch_autospec_with_patches_under(self): called = False @asynctest.mock.patch("{}.{}".format(self.test_class_path, "a_coroutine"), autospec=True) @asynctest.mock.patch("{}.{}".format(self.test_class_path, "is_patched"), return_value=True) def patched_function(is_patched_mock, coroutine_mock): nonlocal called called = True self.assertIsInstance(Test.is_patched, asynctest.mock.Mock) self.assertTrue(Test.is_patched()) self.assertTrue(asyncio.iscoroutinefunction(coroutine_mock)) self.assertTrue(asyncio.iscoroutinefunction(Test.a_coroutine)) patched_function() self.assertTrue(called) def test_patch_object_autospec(self): called = False @asynctest.mock.patch.object(Test, "a_coroutine_with_args", autospec=True) def patched_function(patched): nonlocal called called = True self.assertTrue(asyncio.iscoroutinefunction(Test.a_coroutine_with_args)) with self.assertRaisesRegex(TypeError, "arg2"): run_coroutine(Test().a_coroutine_with_args("arg")) self.assertTrue(run_coroutine(Test().a_coroutine_with_args("arg", "arg2"))) patched_function() self.assertTrue(called) def test_patch_multiple_autospec(self): called = False default = asynctest.mock.DEFAULT @asynctest.mock.patch.multiple(Test, autospec=True, a_coroutine=default, a_coroutine_with_args=default) def patched_function(**patched): nonlocal called called = True with self.assertRaisesRegex(TypeError, "arg2"): run_coroutine(Test().a_coroutine_with_args("arg")) test = Test() self.assertTrue(run_coroutine(test.a_coroutine())) self.assertTrue(run_coroutine(test.a_coroutine_with_args("arg", "arg2"))) patched_function() self.assertTrue(called) # # patch scopes # class patch_scope_TestCase(unittest.TestCase): def is_patched(self): import test.test_mock return test.test_mock.Test().is_patched() def second_is_patched(self): import test.test_mock return test.test_mock.Test().second_is_patched() def _test_deactivate_patch_when_generator_init_fails(self, scope): @patch_is_patched(scope=scope) def a_generator(wrong_number_of_args): yield try: gen = a_generator() next(gen) self.fail("Exception must raise") except TypeError: pass self.assertFalse(self.is_patched()) def _test_deactivate_patch_when_generator_exec_fails(self, scope): @patch_is_patched(scope=scope) @asyncio.coroutine def a_coroutine(missing_arg): return with self.subTest("old style coroutine"): @asyncio.coroutine def tester(): try: yield from a_coroutine() self.fail("Exception must raise") except TypeError: pass self.assertFalse(self.is_patched()) run_coroutine(tester()) with self.subTest("native coroutine"): @patch_is_patched(scope=scope) async def a_native_coroutine(missing_arg): return None @asyncio.coroutine def tester(): try: yield from a_native_coroutine() self.fail("Exception must raise") except TypeError: pass self.assertFalse(self.is_patched()) run_coroutine(tester()) class patch_dict_scope_TestCase(unittest.TestCase): def is_patched(self): import test.test_mock return test.test_mock.Test().a_dict['is_patched'] def second_is_patched(self): import test.test_mock return test.test_mock.Test().a_dict['second_is_patched'] def second_dict_is_patched(self): import test.test_mock return test.test_mock.Test().a_second_dict['is_patched'] class Test_patch_dict_decorator_coroutine_or_generator_scope( patch_dict_scope_TestCase): def test_default_scope_is_global(self): @patch_dict_is_patched() def a_generator(): yield self.is_patched() yield self.is_patched() gen = a_generator() self.addCleanup(gen.close) self.assertTrue(next(gen)) self.assertTrue(self.is_patched()) self.assertTrue(next(gen)) def test_scope_limited(self): @patch_dict_is_patched(scope=asynctest.LIMITED) def a_generator(): yield self.is_patched() yield self.is_patched() gen = a_generator() self.addCleanup(gen.close) self.assertTrue(next(gen)) self.assertFalse(self.is_patched()) self.assertTrue(next(gen)) def test_patch_generator_with_multiple_scopes(self): with self.subTest("Outer: GLOBAL, inner: LIMITED"): @patch_dict_is_patched(scope=asynctest.GLOBAL) @patch_dict_second_dict_is_patched(scope=asynctest.LIMITED) def a_generator(): yield (self.is_patched(), self.second_dict_is_patched()) yield (self.is_patched(), self.second_dict_is_patched()) gen = a_generator() try: self.assertEqual((True, True), next(gen)) self.assertEqual( (True, False), (self.is_patched(), self.second_dict_is_patched())) self.assertEqual((True, True), next(gen)) finally: gen.close() with self.subTest("Outer: LIMITED, inner: GLOBAL"): @patch_dict_is_patched(scope=asynctest.LIMITED) @patch_dict_second_dict_is_patched(scope=asynctest.GLOBAL) def a_generator(): yield (self.is_patched(), self.second_dict_is_patched()) yield (self.is_patched(), self.second_dict_is_patched()) gen = a_generator() try: self.assertEqual((True, True), next(gen)) self.assertEqual( (False, True), (self.is_patched(), self.second_dict_is_patched())) self.assertEqual((True, True), next(gen)) finally: gen.close() def test_patch_generator_with_multiple_scopes_on_same_dict(self): import test.test_mock def tester(): test.test_mock.Test.a_dict['overriden_value'] = True for _ in range(2): yield ( self.is_patched(), self.second_is_patched(), test.test_mock.Test.a_dict.get('overriden_value', False)) with self.subTest("Outer: GLOBAL, inner: LIMITED"): @patch_dict_is_patched(scope=asynctest.GLOBAL) @patch_dict_second_is_patched(scope=asynctest.LIMITED) def a_generator(): yield from tester() gen = a_generator() try: self.assertEqual((True, True, True), next(gen)) self.assertEqual((True, False), (self.is_patched(), self.second_is_patched())) self.assertNotIn('overriden_value', test.test_mock.Test.a_dict) self.assertEqual((True, True, True), next(gen)) finally: gen.close() with self.subTest("Outer: LIMITED, inner: GLOBAL"): @patch_dict_is_patched(scope=asynctest.LIMITED) @patch_dict_second_is_patched(scope=asynctest.GLOBAL) def a_generator(): yield from tester() gen = a_generator() try: self.assertEqual((True, True, True), next(gen)) self.assertEqual((False, True), (self.is_patched(), self.second_is_patched())) self.assertNotIn('overriden_value', test.test_mock.Test.a_dict) self.assertEqual((True, True, True), next(gen)) finally: gen.close() def test_patch_coroutine_with_multiple_scopes(self): def tester(): return (self.is_patched(), self.second_dict_is_patched()) @asyncio.coroutine def tester_coroutine(future): before = tester() yield from future after = tester() return before, after def run_test(a_coroutine): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: future = asyncio.Future(loop=loop) task = loop.create_task(a_coroutine(future)) loop.call_soon(lambda: future.set_result(tester())) before, after = loop.run_until_complete(task) finally: loop.close() return before, future.result(), after with self.subTest("old style coroutine - Outer: GLOBAL, inner: LIMITED"): @patch_dict_is_patched(scope=asynctest.GLOBAL) @patch_dict_second_dict_is_patched(scope=asynctest.LIMITED) @asyncio.coroutine def a_coroutine(future): return (yield from tester_coroutine(future)) before, between, after = run_test(a_coroutine) self.assertEqual((True, True), before) self.assertEqual((True, False), between) self.assertEqual((True, True), after) with self.subTest("old style coroutine - Outer: LIMITED, inner: GLOBAL"): @patch_dict_is_patched(scope=asynctest.LIMITED) @patch_dict_second_dict_is_patched(scope=asynctest.GLOBAL) @asyncio.coroutine def a_coroutine(future): return (yield from tester_coroutine(future)) before, between, after = run_test(a_coroutine) self.assertEqual((True, True), before) self.assertEqual((False, True), between) self.assertEqual((True, True), after) async def tester_native_coroutine(future): before = tester() await future after = tester() return before, after with self.subTest("new style coroutine - Outer: GLOBAL, inner: LIMITED"): @patch_dict_is_patched(scope=asynctest.GLOBAL) @patch_dict_second_dict_is_patched(scope=asynctest.LIMITED) async def a_coroutine(future): return await tester_native_coroutine(future) before, between, after = run_test(a_coroutine) self.assertEqual((True, True), before) self.assertEqual((True, False), between) self.assertEqual((True, True), after) with self.subTest("old style coroutine - Outer: LIMITED, inner: GLOBAL"): @patch_dict_is_patched(scope=asynctest.LIMITED) @patch_dict_second_dict_is_patched(scope=asynctest.GLOBAL) async def a_coroutine(future): return await tester_native_coroutine(future) before, between, after = run_test(a_coroutine) self.assertEqual((True, True), before) self.assertEqual((False, True), between) self.assertEqual((True, True), after) class Test_patch_and_patch_dict_scope(unittest.TestCase): def test_both_patch_and_patch_dict_with_scope_global(self): def test_result(): import test.test_mock instance = test.test_mock.Test() return (instance.is_patched(), instance.a_dict['is_patched']) with self.subTest("patch and patch.dict"): @patch_dict_is_patched(scope=asynctest.GLOBAL) @patch_is_patched(scope=asynctest.GLOBAL) @asyncio.coroutine def a_coroutine(): return test_result() self.assertEqual((True, True), run_coroutine(a_coroutine())) with self.subTest("patch.dict and patch"): @patch_is_patched(scope=asynctest.GLOBAL) @patch_dict_is_patched(scope=asynctest.GLOBAL) @asyncio.coroutine def a_coroutine(): return test_result() self.assertEqual((True, True), run_coroutine(a_coroutine())) def test_both_patch_and_patch_dict_with_scope_limited(self): import test.test_mock instance = test.test_mock.Test() def test_result(instance): yield (instance.is_patched(), instance.a_dict['is_patched']) yield (instance.is_patched(), instance.a_dict['is_patched']) with self.subTest("patch and patch.dict"): @patch_dict_is_patched(scope=asynctest.LIMITED) @patch_is_patched(scope=asynctest.LIMITED) def a_generator(instance): yield from test_result(instance) gen = a_generator(instance) self.assertEqual((True, True), next(gen)) self.assertEqual((False, False), (instance.is_patched(), instance.a_dict['is_patched'])) self.assertEqual((True, True), next(gen)) with self.subTest("patch.dict and patch"): @patch_is_patched(scope=asynctest.LIMITED) @patch_dict_is_patched(scope=asynctest.LIMITED) def a_generator(instance): yield from test_result(instance) gen = a_generator(instance) self.assertEqual((True, True), next(gen)) self.assertEqual((False, False), (instance.is_patched(), instance.a_dict['is_patched'])) self.assertEqual((True, True), next(gen)) class Test_patch_decorator_coroutine_or_generator_scope(patch_scope_TestCase): # Tests of patch() related to the use of scope=*, with several scopes used def test_default_scope_is_global(self): @patch_is_patched() def a_generator(): yield self.is_patched() yield self.is_patched() gen = a_generator() self.addCleanup(gen.close) self.assertTrue(next(gen)) self.assertTrue(self.is_patched()) self.assertTrue(next(gen)) def test_patch_generator_with_multiple_scopes(self): def a_generator(): yield (self.is_patched(), self.second_is_patched()) yield (self.is_patched(), self.second_is_patched()) with self.subTest("Outer: GLOBAL, inner: LIMITED"): @patch_is_patched(scope=asynctest.GLOBAL) @patch_second_is_patched(scope=asynctest.LIMITED) def patched(): yield from a_generator() gen = patched() try: self.assertEqual((True, True), next(gen)) self.assertTrue(self.is_patched()) self.assertFalse(self.second_is_patched()) self.assertEqual((True, True), next(gen)) finally: gen.close() with self.subTest("Outer: LIMITED, inner: GLOBAL"): @patch_second_is_patched(scope=asynctest.LIMITED) @patch_is_patched(scope=asynctest.GLOBAL) def patched(): yield from a_generator() gen = patched() try: self.assertEqual((True, True), next(gen)) self.assertTrue(self.is_patched()) self.assertFalse(self.second_is_patched()) self.assertEqual((True, True), next(gen)) finally: gen.close() def test_patch_coroutine_with_multiple_scopes(self): def set_fut_result(fut): fut.set_result((self.is_patched(), self.second_is_patched())) @asyncio.coroutine def tester(coro_function): loop = asyncio.get_event_loop() fut = asyncio.Future(loop=loop) loop.call_soon(set_fut_result, fut) before, after = yield from coro_function(fut) self.assertEqual((True, True), before) self.assertEqual((True, False), fut.result()) self.assertEqual((True, True), after) self.assertFalse(self.is_patched()) self.assertFalse(self.second_is_patched()) with self.subTest("old style coroutine - Outer: GLOBAL, inner: LIMITED"): @patch_is_patched(scope=asynctest.GLOBAL) @patch_second_is_patched(scope=asynctest.LIMITED) def a_coroutine(fut): before = (self.is_patched(), self.second_is_patched()) yield from fut after = (self.is_patched(), self.second_is_patched()) return before, after run_coroutine(tester(a_coroutine)) with self.subTest("old style coroutine - Outer: LIMITED, inner: GLOBAL"): @patch_second_is_patched(scope=asynctest.LIMITED) @patch_is_patched(scope=asynctest.GLOBAL) def a_coroutine(fut): before = (self.is_patched(), self.second_is_patched()) yield from fut after = (self.is_patched(), self.second_is_patched()) return before, after run_coroutine(tester(a_coroutine)) with self.subTest("new style coroutine - Outer: GLOBAL, inner: LIMITED"): @patch_is_patched(scope=asynctest.GLOBAL) @patch_second_is_patched(scope=asynctest.LIMITED) async def a_native_coroutine(fut): before = (self.is_patched(), self.second_is_patched()) await fut after = (self.is_patched(), self.second_is_patched()) return before, after run_coroutine(tester(a_native_coroutine)) with self.subTest("new style coroutine - Outer: LIMITED, inner: GLOBAL"): @patch_second_is_patched(scope=asynctest.LIMITED) @patch_is_patched(scope=asynctest.GLOBAL) async def a_native_coroutine(fut): before = (self.is_patched(), self.second_is_patched()) await fut after = (self.is_patched(), self.second_is_patched()) return before, after run_coroutine(tester(a_native_coroutine)) class Test_patch_decorator_coroutine_or_generator_scope_GLOBAL(patch_scope_TestCase): # Tests of patch() using scope=GLOBAL def test_deactivate_patch_when_generator_init_fails(self): self._test_deactivate_patch_when_generator_init_fails(asynctest.GLOBAL) def test_deactivate_patch_when_generator_exec_fails(self): self._test_deactivate_patch_when_generator_exec_fails(asynctest.GLOBAL) def test_patch_generator_during_its_lifetime(self): @patch_is_patched(scope=asynctest.GLOBAL) def a_generator(): yield self.is_patched() yield self.is_patched() gen = a_generator() self.assertTrue(next(gen)) self.assertTrue(self.is_patched()) self.assertTrue(next(gen)) # exhaust the generator try: next(gen) self.fail("Coroutine must be stopped") except StopIteration: pass self.assertFalse(self.is_patched()) def test_patch_generator_during_its_close(self): when_generator_closes = (False, False) @patch_second_is_patched(scope=asynctest.LIMITED) @patch_is_patched(scope=asynctest.GLOBAL) def a_generator(): try: while True: yield (self.is_patched(), self.second_is_patched()) except GeneratorExit: nonlocal when_generator_closes when_generator_closes = (self.is_patched(), self.second_is_patched()) raise gen = a_generator() self.assertEqual((True, True), next(gen)) gen.close() with self.assertRaises(StopIteration): next(gen) self.assertEqual((True, True), when_generator_closes) self.assertFalse(self.is_patched()) def test_patch_coroutine_during_its_lifetime(self): def set_fut_result(fut): fut.set_result(self.is_patched()) @asyncio.coroutine def tester(coro_function): loop = asyncio.get_event_loop() fut = asyncio.Future(loop=loop) loop.call_soon(set_fut_result, fut) before, after = yield from coro_function(fut) self.assertTrue(before) self.assertTrue(fut.result()) self.assertTrue(after) self.assertFalse(self.is_patched()) with self.subTest("old style coroutine"): @patch_is_patched(scope=asynctest.GLOBAL) def a_coroutine(fut): before = self.is_patched() yield from fut after = self.is_patched() return before, after run_coroutine(tester(a_coroutine)) with self.subTest("new style coroutine"): @patch_is_patched(scope=asynctest.GLOBAL) async def a_native_coroutine(fut): before = self.is_patched() await fut after = self.is_patched() return before, after run_coroutine(tester(a_native_coroutine)) # It's really hard to test this behavior for a coroutine, but I assume it's # fine as long as the implementation is shared with a generator. Also, it's # really hard to fall in a case like this one with a coroutine. @unittest.skipIf(platform.python_implementation() != "CPython", "Test relying on how __del__ is called by implementation") def test_patch_stopped_when_generator_is_collected(self): @patch_is_patched(scope=asynctest.GLOBAL) def a_generator(): yield self.is_patched() gen = a_generator() self.assertTrue(next(gen)) self.assertTrue(self.is_patched()) del gen self.assertFalse(self.is_patched()) def test_patch_stopped_when_generator_is_closed(self): @patch_is_patched(scope=asynctest.GLOBAL) def a_generator(): yield self.is_patched() gen = a_generator() self.assertTrue(next(gen)) self.assertTrue(self.is_patched()) gen.close() self.assertFalse(self.is_patched()) def test_multiple_patches_on_generator(self): @patch_second_is_patched(scope=asynctest.GLOBAL) @patch_is_patched(scope=asynctest.GLOBAL) def a_generator(): yield self.is_patched() and self.second_is_patched() yield self.is_patched() and self.second_is_patched() gen = a_generator() self.assertTrue(next(gen)) self.assertTrue(self.is_patched()) self.assertTrue(self.second_is_patched()) self.assertTrue(next(gen)) # exhaust the generator try: next(gen) self.fail("Coroutine must be stopped") except StopIteration: pass self.assertFalse(self.is_patched()) self.assertFalse(self.second_is_patched()) def test_multiple_patches_on_coroutine(self): def set_fut_result(fut): fut.set_result((self.is_patched(), self.second_is_patched())) @asyncio.coroutine def tester(coro_function): loop = asyncio.get_event_loop() fut = asyncio.Future(loop=loop) loop.call_soon(set_fut_result, fut) before, after = yield from coro_function(fut) self.assertEqual((True, True), before) self.assertEqual((True, True), fut.result()) self.assertEqual((True, True), after) self.assertFalse(self.is_patched()) self.assertFalse(self.second_is_patched()) with self.subTest("old style coroutine"): @patch_second_is_patched(scope=asynctest.GLOBAL) @patch_is_patched(scope=asynctest.GLOBAL) def a_coroutine(fut): before = (self.is_patched(), self.second_is_patched()) yield from fut after = (self.is_patched(), self.second_is_patched()) return before, after run_coroutine(tester(a_coroutine)) with self.subTest("new style coroutine"): @patch_second_is_patched(scope=asynctest.GLOBAL) @patch_is_patched(scope=asynctest.GLOBAL) async def a_native_coroutine(fut): before = (self.is_patched(), self.second_is_patched()) await fut after = (self.is_patched(), self.second_is_patched()) return before, after run_coroutine(tester(a_native_coroutine)) class Test_patch_decorator_coroutine_or_generator_scope_LIMITED(patch_scope_TestCase): # Tests of patch() using scope=LIMITED def test_deactivate_patch_when_generator_init_fails(self): self._test_deactivate_patch_when_generator_init_fails(asynctest.LIMITED) def test_deactivate_patch_when_generator_exec_fails(self): self._test_deactivate_patch_when_generator_exec_fails(asynctest.LIMITED) def test_patch_generator_only_when_running(self): @patch_is_patched(scope=asynctest.LIMITED) def a_generator(): yield self.is_patched() yield self.is_patched() gen = a_generator() self.assertTrue(next(gen)) self.assertFalse(self.is_patched()) self.assertTrue(next(gen)) def test_patch_coroutine_only_when_running(self): def set_fut_result(fut): fut.set_result(self.is_patched()) @asyncio.coroutine def tester(coro_function): loop = asyncio.get_event_loop() fut = asyncio.Future(loop=loop) loop.call_soon(set_fut_result, fut) before, after = yield from coro_function(fut) self.assertTrue(before) self.assertFalse(fut.result()) self.assertTrue(after) with self.subTest("old style coroutine"): @patch_is_patched(scope=asynctest.LIMITED) def a_coroutine(fut): before = self.is_patched() yield from fut after = self.is_patched() return before, after run_coroutine(tester(a_coroutine)) with self.subTest("new style coroutine"): @patch_is_patched(scope=asynctest.LIMITED) async def a_native_coroutine(fut): before = self.is_patched() await fut after = self.is_patched() return before, after run_coroutine(tester(a_native_coroutine)) def test_patched_coroutine_with_mock_args(self): @asynctest.mock.patch('test.test_mock.Test', side_effect=lambda: None, scope=asynctest.LIMITED) @asyncio.coroutine def a_coroutine(mock): loop = asyncio.get_event_loop() self.assertIs(mock, Test) yield from asyncio.sleep(0, loop=loop) self.assertIs(mock, Test) yield from asyncio.sleep(0, loop=loop) self.assertIs(mock, Test) run_coroutine(a_coroutine()) def test_multiple_patches_on_coroutine(self): def set_fut_result(fut): fut.set_result((self.is_patched(), self.second_is_patched())) @asyncio.coroutine def tester(coro_function): loop = asyncio.get_event_loop() fut = asyncio.Future(loop=loop) loop.call_soon(set_fut_result, fut) before, after = yield from coro_function(fut) self.assertEqual((True, True), before) self.assertEqual((False, False), fut.result()) self.assertEqual((True, True), after) self.assertFalse(self.is_patched()) self.assertFalse(self.second_is_patched()) with self.subTest("old style coroutine"): @patch_second_is_patched(scope=asynctest.LIMITED) @patch_is_patched(scope=asynctest.LIMITED) def a_coroutine(fut): before = (self.is_patched(), self.second_is_patched()) yield from fut after = (self.is_patched(), self.second_is_patched()) return before, after run_coroutine(tester(a_coroutine)) with self.subTest("new style coroutine"): @patch_second_is_patched(scope=asynctest.LIMITED) @patch_is_patched(scope=asynctest.LIMITED) async def a_native_coroutine(fut): before = (self.is_patched(), self.second_is_patched()) await fut after = (self.is_patched(), self.second_is_patched()) return before, after run_coroutine(tester(a_native_coroutine)) class Test_return_once(unittest.TestCase): def test_default_value(self): iterator = asynctest.mock.return_once("ProbeValue") self.assertEqual("ProbeValue", next(iterator)) for _ in range(3): self.assertIsNone(next(iterator)) def test_then(self): iterator = asynctest.mock.return_once("ProbeValue", "ThenValue") self.assertEqual("ProbeValue", next(iterator)) for _ in range(2): self.assertEqual("ThenValue", next(iterator)) iterator = asynctest.mock.return_once("ProbeValue", then="ThenValue") self.assertEqual("ProbeValue", next(iterator)) self.assertEqual("ThenValue", next(iterator)) def test_with_side_effect_default(self): mock = asynctest.Mock(side_effect=asynctest.mock.return_once("ProbeValue")) self.assertEqual("ProbeValue", mock()) for _ in range(3): self.assertIsNone(mock()) def test_with_side_effect_then(self): side_effect = asynctest.mock.return_once("ProbeValue", "ThenValue") mock = asynctest.Mock(side_effect=side_effect) self.assertEqual("ProbeValue", mock()) for _ in range(2): self.assertEqual("ThenValue", mock()) def test_with_side_effect_raises(self): mock = asynctest.mock.Mock(side_effect=asynctest.mock.return_once(Exception)) self.assertRaises(Exception, mock) self.assertIsNone(mock()) def test_with_side_effect_raises_then(self): side_effect = asynctest.mock.return_once("ProbeValue", BlockingIOError) mock = asynctest.mock.Mock(side_effect=side_effect) self.assertEqual("ProbeValue", mock()) for _ in range(2): self.assertRaises(BlockingIOError, mock) def test_with_side_effect_raises_all(self): side_effect = asynctest.mock.return_once(Exception, BlockingIOError) mock = asynctest.mock.Mock(side_effect=side_effect) self.assertRaises(Exception, mock) for _ in range(2): self.assertRaises(BlockingIOError, mock) class Test_create_autospec(unittest.TestCase): def test_generator_and_coroutine_is_instance_of_FunctionType(self): # this test is somewhat a forward compatibility test: if ever # unittest.mock.FunctionTypes doesn't detect generators and coroutines # as instance of these types, we need to fix it in asynctest. def gen(): yield from range(10) self.assertIsInstance(gen, unittest.mock.FunctionTypes) self.assertIsInstance(Test.a_coroutine, unittest.mock.FunctionTypes) self.assertIsInstance(Test.an_async_coroutine, unittest.mock.FunctionTypes) # create_autospec: # * Ensure we check the signature of the coroutine function (and/or # generator) # * Ensure a coroutine function attribute of a mock create with # create_autospec is a CoroutineMock # * Ensure an instance of a class mocked from create_autospec will be an # asynctest mock, and its coroutine attribute will be mocked by # a CoroutineMock # * Ensure all expected create_autospec tests still run fine # # Also test cases where create_autospec is used (_patch, etc) def test_autospec_returns_asynctest_mocks(self): def a_generator(): yield from range(10) cases = { "non callable": "Constant value", "callable class": Test, "non callable instance": Test(), "callable function": Test.a_function, "callable generator": a_generator, } for name, value in cases.items(): with self.subTest(name): mock = asynctest.mock.create_autospec(value) unittest_mock = unittest.mock.create_autospec(value) try: expected_type = getattr(asynctest.mock, type(unittest_mock).__name__) except AttributeError: # The type of returned object is not readable as a mock # This happens with mocks updated by _set_signature expected_type = type(unittest_mock) self.assertIsInstance(mock, expected_type) self.assertNotIsInstance(mock, asynctest.mock.CoroutineMock) def test_autospec_of_coroutine_function_is_coroutinefunction(self): mock = asynctest.mock.create_autospec(Test.a_function) self.assertFalse(asyncio.iscoroutinefunction(mock)) mock = asynctest.mock.create_autospec(Test.a_coroutine) self.assertTrue(asyncio.iscoroutinefunction(mock)) mock = asynctest.mock.create_autospec(Test.a_classmethod_coroutine) self.assertTrue(asyncio.iscoroutinefunction(mock)) mock = asynctest.mock.create_autospec(Test.a_staticmethod_coroutine) self.assertTrue(asyncio.iscoroutinefunction(mock)) mock = asynctest.mock.create_autospec(Test.an_async_coroutine) self.assertTrue(asyncio.iscoroutinefunction(mock)) mock = asynctest.mock.create_autospec(Test.an_async_classmethod_coroutine) self.assertTrue(asyncio.iscoroutinefunction(mock)) mock = asynctest.mock.create_autospec(Test.an_async_staticmethod_coroutine) self.assertTrue(asyncio.iscoroutinefunction(mock)) def test_autospec_attributes_being_coroutine_functions(self): mock = asynctest.mock.create_autospec(Test) self.assertFalse(asyncio.iscoroutinefunction(mock)) self.assertFalse(asyncio.iscoroutinefunction(mock.a_function)) self.assertTrue(asyncio.iscoroutinefunction(mock.a_coroutine)) self.assertTrue(asyncio.iscoroutinefunction(mock.a_classmethod_coroutine)) self.assertTrue(asyncio.iscoroutinefunction(mock.a_staticmethod_coroutine)) self.assertTrue(asyncio.iscoroutinefunction(mock.an_async_coroutine)) self.assertTrue(asyncio.iscoroutinefunction(mock.an_async_classmethod_coroutine)) self.assertTrue(asyncio.iscoroutinefunction(mock.an_async_staticmethod_coroutine)) def test_create_autospec_on_coroutine_with_return_value(self): mock = asynctest.mock.create_autospec(Test.a_coroutine, return_value="PROBE") self.assertEqual("PROBE", run_coroutine(mock(None))) mock = asynctest.mock.create_autospec(Test.an_async_coroutine, return_value="PROBE") self.assertEqual("PROBE", run_coroutine(mock(None))) def test_create_autospec_on_coroutine_with_iterable_side_effect(self): coroutines = [Test.a_coroutine, Test.an_async_coroutine] for a_coroutine in coroutines: mock = asynctest.mock.create_autospec( a_coroutine, side_effect=("PROBE1", "PROBE2")) self.assertEqual("PROBE1", run_coroutine(mock(None))) self.assertEqual("PROBE2", run_coroutine(mock(None))) def test_create_autospec_on_coroutine_with_exception_side_effect(self): coroutines = [Test.a_coroutine, Test.an_async_coroutine] for a_coroutine in coroutines: mock = asynctest.mock.create_autospec(a_coroutine, side_effect=ProbeException) with self.assertRaises(ProbeException): run_coroutine(mock(None)) def test_create_autospec_on_coroutine_with_coroutine_side_effect(self): coroutines = [Test.a_coroutine, Test.an_async_coroutine] for a_coroutine in coroutines: mock = asynctest.mock.create_autospec( a_coroutine, side_effect=asyncio.coroutine(lambda r: r)) self.assertEqual("PROBE", run_coroutine(mock("PROBE"))) def test_create_autospec_on_coroutine_with_instance_raises_RuntimeError(self): with self.assertRaises(RuntimeError): asynctest.mock.create_autospec(Test.a_coroutine, instance=True) def test_mock_add_spec_on_mock_created_with_autospec(self): # See bug #107 mock = asynctest.mock.create_autospec(Test()) self.assertFalse(hasattr(mock, "added_attribute")) mock.mock_add_spec(["added_attribute"]) self.assertIsInstance(mock.added_attribute, asynctest.Mock) self.assertFalse(hasattr(mock, "__aenter__")) mock.mock_add_spec(["__aenter__"]) self.assertTrue(hasattr(mock, "__aenter__")) def test_mock_add_spec_on_mock_with_magics(self): instance = _Test_Mock_Of_Async_Magic_Methods.WithAsyncContextManager() mock = asynctest.mock.create_autospec(instance) self.assertFalse(hasattr(mock, "added_attribute")) mock.mock_add_spec(["added_attribute"]) self.assertIsInstance(mock.added_attribute, asynctest.Mock) if __name__ == "__main__": unittest.main() asynctest-0.13.0/test/test_selector.py000066400000000000000000000301541346656454500200450ustar00rootroot00000000000000# coding: utf-8 import asyncio import copy import selectors import functools import os import socket try: import ssl except ImportError: ssl = None import sys import unittest import asynctest class Selector_TestCase(unittest.TestCase): def setUp(self): asynctest.selector.FileDescriptor.next_fd = 0 class Test_FileDescriptor(Selector_TestCase): def test_is_an_int(self): self.assertIsInstance(asynctest.selector.FileDescriptor(), int) def test_init_increments_value(self): self.assertEqual(0, asynctest.selector.FileDescriptor()) self.assertEqual(1, asynctest.selector.FileDescriptor()) self.assertNotEqual(asynctest.selector.FileDescriptor(), asynctest.selector.FileDescriptor()) def test_init_increments_value_with_fixed_value(self): self.assertEqual(5, asynctest.selector.FileDescriptor(5)) self.assertEqual(6, asynctest.selector.FileDescriptor()) class Test_FileMock(Selector_TestCase): def test_fileno_returns_FileDescriptor(self): self.assertIsInstance(asynctest.selector.FileMock().fileno(), asynctest.selector.FileDescriptor) class Test_SocketMock(Selector_TestCase): def test_is_socket(self): self.assertIsInstance(asynctest.selector.SocketMock(), socket.socket) if ssl: class Test_SSLSocketMock(Selector_TestCase): def test_is_ssl_socket(self): self.assertIsInstance(asynctest.selector.SSLSocketMock(), ssl.SSLSocket) def selector_subtest(method): @functools.wraps(method) def wrapper(self): with self.subTest(test='without_selector'): method(self, asynctest.selector.TestSelector(), None) with self.subTest(test='with_selector'): mock = unittest.mock.Mock(selectors.BaseSelector) method(self, asynctest.selector.TestSelector(mock), mock) return wrapper class Test_TestSelector(Selector_TestCase): @selector_subtest def test_register_mock(self, selector, selector_mock): mock = asynctest.selector.FileMock() key = selector.register(mock, selectors.EVENT_READ, "data") self.assertEqual(key, selector.get_map()[mock]) if selector_mock: self.assertFalse(selector_mock.register.called) @selector_subtest def test_register_fileno(self, selector, selector_mock): with open(os.devnull, 'r') as devnull: if selector_mock: selector_mock.register.return_value = selectors.SelectorKey( devnull, devnull.fileno(), selectors.EVENT_READ, "data" ) key = selector.register(devnull, selectors.EVENT_READ, "data") self.assertEqual(key, selector.get_map()[devnull]) if selector_mock: selector_mock.register.assert_called_with(devnull, selectors.EVENT_READ, "data") @selector_subtest def test_unregister_mock(self, selector, selector_mock): mock = asynctest.selector.FileMock() selector.register(mock, selectors.EVENT_READ, "data") selector.unregister(mock) self.assertNotIn(mock, selector.get_map()) self.assertNotIn(mock.fileno(), selector.get_map()) if selector_mock: self.assertFalse(selector_mock.unregister.called) @selector_subtest def test_unregister_fileno(self, selector, selector_mock): with open(os.devnull, 'r') as devnull: if selector_mock: key = selectors.SelectorKey(devnull, devnull.fileno(), selectors.EVENT_READ, "data") selector_mock.register.return_value = key selector_mock.unregister.return_value = key selector.register(devnull, selectors.EVENT_READ, "data") selector.unregister(devnull) self.assertNotIn(devnull, selector.get_map()) self.assertNotIn(devnull.fileno(), selector.get_map()) @selector_subtest def test_modify_mock(self, selector, selector_mock): mock = asynctest.selector.FileMock() original_key = selector.register(mock, selectors.EVENT_READ, "data") # modify may update the original key, keep a copy original_key = copy.copy(original_key) RW = selectors.EVENT_READ | selectors.EVENT_WRITE key = selector.modify(mock, RW, "data") self.assertNotEqual(original_key, key) self.assertEqual(key, selector.get_map()[mock]) @selector_subtest def test_modify_fileno(self, selector, selector_mock): with open(os.devnull, 'r') as devnull: if selector_mock: selector_mock.modify.return_value = selectors.SelectorKey( devnull, devnull.fileno(), selectors.EVENT_READ, "data2" ) original_key = selector.register(devnull, selectors.EVENT_READ, "data") # modify may update the original key, keep a copy original_key = copy.copy(original_key) key = selector.modify(devnull, selectors.EVENT_READ, "data2") self.assertNotEqual(original_key, key) self.assertEqual(key, selector.get_map()[devnull]) if selector_mock: selector_mock.modify.assert_called_with(devnull, selectors.EVENT_READ, "data2") @selector_subtest def test_modify_fd(self, selector, selector_mock): fd = 1 if selector_mock: selector_mock.modify.return_value = selectors.SelectorKey( fd, fd, selectors.EVENT_READ, "data2" ) original_key = selector.register(fd, selectors.EVENT_READ, "data") original_key = copy.copy(original_key) key = selector.modify(fd, selectors.EVENT_READ, "data2") self.assertNotEqual(original_key, key) self.assertEqual(key, selector.get_map()[fd]) if selector_mock: selector_mock.modify.assert_called_with(fd, selectors.EVENT_READ, "data2") @selector_subtest def test_modify_but_selector_raises(self, selector, selector_mock): if not selector_mock: return exception = RuntimeError() selector_mock.modify.side_effect = exception with open(os.devnull, 'r') as devnull: selector.register(devnull, selectors.EVENT_READ, "data") with self.assertRaises(type(exception)) as ctx: selector.modify(devnull, selectors.EVENT_READ, "data2") self.assertIs(exception, ctx.exception) self.assertNotIn(devnull, selector.get_map()) @selector_subtest def test_select(self, selector, selector_mock): if selector_mock: selector_mock.select.return_value = ["ProbeValue"] self.assertEqual(["ProbeValue"], selector.select(5)) selector_mock.select.assert_called_with(5) else: self.assertEqual([], selector.select()) @selector_subtest def test_close(self, selector, selector_mock): if not selector_mock: return selector.close() selector_mock.close.assert_called_with() class Test_set_read_write_ready(Selector_TestCase): def setUp(self): super().setUp() self.loop = asyncio.new_event_loop() self.loop._selector = asynctest.selector.TestSelector(self.loop._selector) self.addCleanup(self.loop.close) self.mock = asynctest.selector.FileMock() # Older versions of asyncio may complain with PYTHONASYNCIODEBUG=1 if sys.version_info < (3, 5): asyncio.set_event_loop(self.loop) def test_nothing_scheduled(self): # nothing will happen (no exception) for mode in ('read', 'write'): with self.subTest(mode=mode): getattr(asynctest.selector, 'set_{}_ready'.format(mode))(self.mock, self.loop) self.loop.run_until_complete(asyncio.sleep(0, loop=self.loop)) def test_callback_scheduled(self): for mode in ('read', 'write'): with self.subTest(mode=mode): future = asyncio.Future(loop=self.loop) callback_mock = unittest.mock.Mock() # We need at least two iterations of the loop self.loop.call_soon(self.loop.call_soon, future.set_result, None) getattr(self.loop, 'add_{}er'.format(mode.strip('e')))(self.mock, callback_mock) getattr(asynctest.selector, 'set_{}_ready'.format(mode))(self.mock, self.loop) self.loop.run_until_complete(future) callback_mock.assert_called_with() def test_callback_scheduled_during_current_iteration(self): for mode in ('read', 'write'): with self.subTest(mode=mode): future = asyncio.Future(loop=self.loop) callback_mock = unittest.mock.Mock() # We need at least two iterations of the loop self.loop.call_soon(self.loop.call_soon, future.set_result, None) self.loop.call_soon(getattr(self.loop, 'add_{}er'.format(mode.strip('e'))), self.mock, callback_mock) getattr(asynctest.selector, 'set_{}_ready'.format(mode))(self.mock, self.loop) self.loop.run_until_complete(future) callback_mock.assert_called_with() @unittest.mock.patch.dict('asynctest._fail_on.DEFAULTS', clear=True, active_selector_callbacks=True) class Test_fail_on_active_selector_callbacks(Selector_TestCase): def test_passes_without_callbacks_set(self): class TestCase(asynctest.TestCase): def runTest(self): pass TestCase().debug() def test_passes_when_no_callbacks_left(self): class TestCase(asynctest.TestCase): def runTest(self): mock = asynctest.selector.FileMock() self.loop.add_reader(mock, lambda: None) self.loop.remove_reader(mock) TestCase().debug() def test_events_watched_outside_test_are_ignored(self): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: mock = asynctest.selector.FileMock() loop.add_reader(mock, lambda: None) self.addCleanup(loop.remove_reader, mock) class TestCase(asynctest.TestCase): use_default_loop = False def runTest(self): pass TestCase().debug() finally: loop.close() asyncio.set_event_loop(None) def test_fail_on_active_selector_callbacks_on_mock_files(self): class TestCase(asynctest.TestCase): def runTest(self): mock = asynctest.selector.FileMock() self.loop.add_reader(mock, lambda: None) # it's too late to check that during cleanup self.addCleanup(self.loop.remove_reader, mock) with self.assertRaisesRegex(AssertionError, "some events watched " "during the tests were not removed"): TestCase().debug() def test_fail_on_original_selector_callback(self): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: with unittest.mock.patch.object(loop, "_selector") as mock: class TestCase(asynctest.TestCase): use_default_loop = True def runTest(self): # add a dummy event handle = asyncio.Handle(lambda: None, (), self.loop) key = selectors.SelectorKey(1, 1, selectors.EVENT_READ, (handle, None)) mock.get_map.return_value = {1: key} with self.assertRaisesRegex(AssertionError, "some events watched during the " "tests were not removed"): TestCase().debug() finally: loop.close() asyncio.set_event_loop(None) asynctest-0.13.0/test/utils.py000066400000000000000000000003411346656454500163210ustar00rootroot00000000000000# coding: utf-8 import asyncio def run_coroutine(coroutine): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: return loop.run_until_complete(coroutine) finally: loop.close()