decorators.py
204 lines
| 6.7 KiB
| text/x-python
|
PythonLexer
Thomas Kluyver
|
r3904 | # -*- coding: utf-8 -*- | ||
Fernando Perez
|
r1420 | """Decorators for labeling test objects. | ||
Fernando Perez
|
r1848 | Decorators that merely return a modified version of the original function | ||
object are straightforward. Decorators that return a new function object need | ||||
to use nose.tools.make_decorator(original_function)(decorator) in returning the | ||||
decorator, in order to preserve metadata such as function name, setup and | ||||
teardown functions and so on - see nose.tools for more information. | ||||
Fernando Perez
|
r1420 | |||
Fernando Perez
|
r1721 | This module provides a set of useful decorators meant to be ready to use in | ||
your own tests. See the bottom of the file for the ready-made ones, and if you | ||||
find yourself writing a new one that may be of generic use, add it here. | ||||
Fernando Perez
|
r2368 | Included decorators: | ||
Lightweight testing that remains unittest-compatible. | ||||
- An @as_unittest decorator can be used to tag any normal parameter-less | ||||
function as a unittest TestCase. Then, both nose and normal unittest will | ||||
recognize it as such. This will make it easier to migrate away from Nose if | ||||
we ever need/want to while maintaining very lightweight tests. | ||||
Paul Ivanov
|
r3511 | NOTE: This file contains IPython-specific decorators. Using the machinery in | ||
IPython.external.decorators, we import either numpy.testing.decorators if numpy is | ||||
available, OR use equivalent code in IPython.external._decorators, which | ||||
we've copied verbatim from numpy. | ||||
Fernando Perez
|
r2368 | |||
Fernando Perez
|
r1420 | """ | ||
Min RK
|
r21122 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Fernando Perez
|
r2368 | |||
Paul Ivanov
|
r11970 | import os | ||
Hugo
|
r24010 | import shutil | ||
import sys | ||||
Thomas Kluyver
|
r3903 | import tempfile | ||
Fernando Perez
|
r2368 | import unittest | ||
Diego Garcia
|
r22954 | from importlib import import_module | ||
Fernando Perez
|
r1420 | |||
MinRK
|
r20813 | from decorator import decorator | ||
Fernando Perez
|
r1420 | |||
Fernando Perez
|
r2414 | # Expose the unittest-driven decorators | ||
Thomas Kluyver
|
r13347 | from .ipunittest import ipdoctest, ipdocstring | ||
Fernando Perez
|
r2414 | |||
Fernando Perez
|
r2368 | #----------------------------------------------------------------------------- | ||
# Classes and functions | ||||
#----------------------------------------------------------------------------- | ||||
Fernando Perez
|
r1420 | |||
Fernando Perez
|
r2368 | # Simple example of the basic idea | ||
def as_unittest(func): | ||||
"""Decorator to make a simple function into a normal test via unittest.""" | ||||
class Tester(unittest.TestCase): | ||||
def test(self): | ||||
func() | ||||
Tester.__name__ = func.__name__ | ||||
return Tester | ||||
Fernando Perez
|
r1420 | |||
# Utility functions | ||||
Fernando Perez
|
r1848 | def skipif(skip_condition, msg=None): | ||
Nikita Kniazev
|
r27042 | """Make function raise SkipTest exception if skip_condition is true | ||
Fernando Perez
|
r1848 | |||
Parameters | ||||
Fernando Perez
|
r1850 | ---------- | ||
Thomas Kluyver
|
r13595 | |||
skip_condition : bool or callable | ||||
Flag to determine whether to skip test. If the condition is a | ||||
callable, it is used at runtime to dynamically make the decision. This | ||||
is useful for tests that may require costly imports, to delay the cost | ||||
until the test suite is actually executed. | ||||
Fernando Perez
|
r1848 | msg : string | ||
Thomas Kluyver
|
r13595 | Message to give on raising a SkipTest exception. | ||
Returns | ||||
------- | ||||
decorator : function | ||||
Decorator, which, when applied to a function, causes SkipTest | ||||
to be raised when the skip_condition was True, and the function | ||||
to be called normally otherwise. | ||||
Nikita Kniazev
|
r27042 | """ | ||
if msg is None: | ||||
msg = "Test skipped due to test condition." | ||||
import pytest | ||||
assert isinstance(skip_condition, bool) | ||||
return pytest.mark.skipif(skip_condition, reason=msg) | ||||
Fernando Perez
|
r1848 | |||
Jason Grout
|
r6177 | # A version with the condition set to true, common case just to attach a message | ||
Fernando Perez
|
r1848 | # to a skip decorator | ||
def skip(msg=None): | ||||
"""Decorator factory - mark a test function for skipping from test suite. | ||||
Fernando Perez
|
r1721 | |||
Fernando Perez
|
r2368 | Parameters | ||
---------- | ||||
Fernando Perez
|
r1560 | msg : string | ||
Optional message to be added. | ||||
Fernando Perez
|
r1848 | |||
Fernando Perez
|
r2368 | Returns | ||
------- | ||||
Fernando Perez
|
r1848 | decorator : function | ||
Decorator, which, when applied to a function, causes SkipTest | ||||
to be raised, with the optional message added. | ||||
Fernando Perez
|
r1560 | """ | ||
Matthias Bussonnier
|
r25097 | if msg and not isinstance(msg, str): | ||
raise ValueError('invalid object passed to `@skip` decorator, did you ' | ||||
'meant `@skip()` with brackets ?') | ||||
return skipif(True, msg) | ||||
Gael Varoquaux
|
r1505 | |||
Fernando Perez
|
r1577 | |||
Fernando Perez
|
r2452 | def onlyif(condition, msg): | ||
"""The reverse from skipif, see skipif for details.""" | ||||
Nikita Kniazev
|
r27042 | return skipif(not condition, msg) | ||
Fernando Perez
|
r2452 | |||
Fernando Perez
|
r1848 | #----------------------------------------------------------------------------- | ||
# Utility functions for decorators | ||||
Paul Ivanov
|
r3504 | def module_not_available(module): | ||
"""Can module be imported? Returns true if module does NOT import. | ||||
Fernando Perez
|
r1577 | |||
Paul Ivanov
|
r3504 | This is used to make a decorator to skip tests that require module to be | ||
Fernando Perez
|
r1848 | available, but delay the 'import numpy' to test execution time. | ||
""" | ||||
try: | ||||
Diego Garcia
|
r22954 | mod = import_module(module) | ||
Paul Ivanov
|
r3504 | mod_not_avail = False | ||
Fernando Perez
|
r1848 | except ImportError: | ||
Paul Ivanov
|
r3504 | mod_not_avail = True | ||
Fernando Perez
|
r1848 | |||
Paul Ivanov
|
r3505 | return mod_not_avail | ||
Fernando Perez
|
r1848 | |||
Paul Ivanov
|
r11970 | |||
Fernando Perez
|
r1848 | #----------------------------------------------------------------------------- | ||
# Decorators for public use | ||||
Fernando Perez
|
r1721 | # Decorators to skip certain tests on specific platforms. | ||
Fernando Perez
|
r1872 | skip_win32 = skipif(sys.platform == 'win32', | ||
Fernando Perez
|
r1848 | "This test does not run under Windows") | ||
MinRK
|
r4138 | skip_linux = skipif(sys.platform.startswith('linux'), | ||
Fernando Perez
|
r1872 | "This test does not run under Linux") | ||
skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X") | ||||
Fernando Perez
|
r1848 | |||
Jorgen Stenarson
|
r1803 | # Decorators to skip tests if not on specific platforms. | ||
Ian Thomas
|
r28785 | skip_if_not_win32 = skipif(sys.platform != "win32", "This test only runs under Windows") | ||
skip_if_not_linux = skipif( | ||||
not sys.platform.startswith("linux"), "This test only runs under Linux" | ||||
) | ||||
skip_if_not_osx = skipif( | ||||
not sys.platform.startswith("darwin"), "This test only runs under macOS" | ||||
) | ||||
Paul Ivanov
|
r11970 | |||
_x11_skip_cond = (sys.platform not in ('darwin', 'win32') and | ||||
Paul Ivanov
|
r12135 | os.environ.get('DISPLAY', '') == '') | ||
Paul Ivanov
|
r11970 | _x11_skip_msg = "Skipped under *nix when X11/XOrg not available" | ||
skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg) | ||||
Fernando Perez
|
r1872 | # Other skip decorators | ||
Fernando Perez
|
r1848 | |||
MinRK
|
r5146 | # generic skip without module | ||
skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod) | ||||
Jens Hedegaard Nielsen
|
r4788 | |||
MinRK
|
r5146 | skipif_not_numpy = skip_without('numpy') | ||
skipif_not_matplotlib = skip_without('matplotlib') | ||||
Fernando Perez
|
r2461 | # A null 'decorator', useful to make more readable code that needs to pick | ||
# between different decorators based on OS or other conditions | ||||
null_deco = lambda f: f | ||||
Thomas Kluyver
|
r3903 | |||
# Some tests only run where we can use unicode paths. Note that we can't just | ||||
# check os.path.supports_unicode_filenames, which is always False on Linux. | ||||
try: | ||||
Thomas Kluyver
|
r3904 | f = tempfile.NamedTemporaryFile(prefix=u"tmp€") | ||
Thomas Kluyver
|
r3903 | except UnicodeEncodeError: | ||
unicode_paths = False | ||||
else: | ||||
unicode_paths = True | ||||
Thomas Kluyver
|
r3904 | f.close() | ||
Thomas Kluyver
|
r3903 | |||
onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable " | ||||
"where we can use unicode in filenames.")) | ||||
Takafumi Arakaki
|
r7858 | |||
def onlyif_cmds_exist(*commands): | ||||
""" | ||||
Decorator to skip test when at least one of `commands` is not found. | ||||
""" | ||||
Matthias Bussonnier
|
r27358 | assert ( | ||
os.environ.get("IPTEST_WORKING_DIR", None) is None | ||||
), "iptest deprecated since IPython 8.0" | ||||
Takafumi Arakaki
|
r7858 | for cmd in commands: | ||
Nikita Kniazev
|
r26967 | reason = f"This test runs only if command '{cmd}' is installed" | ||
Hugo
|
r24010 | if not shutil.which(cmd): | ||
Matthias Bussonnier
|
r27358 | import pytest | ||
Matthias Bussonnier
|
r26185 | |||
Matthias Bussonnier
|
r27358 | return pytest.mark.skip(reason=reason) | ||
Takafumi Arakaki
|
r7858 | return null_deco | ||