-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Expand file tree
/
Copy pathdecorators.py
More file actions
152 lines (118 loc) · 4.73 KB
/
decorators.py
File metadata and controls
152 lines (118 loc) · 4.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import os
import shutil
import sys
import tempfile
from importlib import import_module
import pytest
# Expose the unittest-driven decorators
from .ipunittest import ipdoctest, ipdocstring
from _pytest.mark.structures import MarkDecorator
from typing import Optional
def skipif(skip_condition: bool, msg: Optional[str]=None) -> MarkDecorator:
"""Make function raise SkipTest exception if skip_condition is true
Parameters
----------
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.
msg : string
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.
"""
if msg is None:
msg = "Test skipped due to test condition."
assert isinstance(skip_condition, bool)
return pytest.mark.skipif(skip_condition, reason=msg)
# A version with the condition set to true, common case just to attach a message
# to a skip decorator
def skip(msg: Optional[str]=None) -> MarkDecorator:
"""Decorator factory - mark a test function for skipping from test suite.
Parameters
----------
msg : string
Optional message to be added.
Returns
-------
decorator : function
Decorator, which, when applied to a function, causes SkipTest
to be raised, with the optional message added.
"""
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)
def onlyif(condition: bool, msg: str) -> MarkDecorator:
"""The reverse from skipif, see skipif for details."""
return skipif(not condition, msg)
# -----------------------------------------------------------------------------
# Utility functions for decorators
def module_not_available(module: str) -> bool:
"""Can module be imported? Returns true if module does NOT import.
This is used to make a decorator to skip tests that require module to be
available, but delay the 'import numpy' to test execution time.
"""
try:
mod = import_module(module)
mod_not_avail = False
except Exception:
# Catch all exceptions, not just ImportError, since modules can fail
# to import for various reasons (e.g., compatibility issues with newer
# Python versions causing TypeError, AttributeError, etc.)
mod_not_avail = True
return mod_not_avail
# -----------------------------------------------------------------------------
# Decorators for public use
# Decorators to skip certain tests on specific platforms.
skip_win32 = skipif(sys.platform == "win32", "This test does not run under Windows")
# Decorators to skip tests if not on specific platforms.
skip_if_not_win32 = skipif(sys.platform != "win32", "This test only runs under Windows")
skip_if_not_osx = skipif(
not sys.platform.startswith("darwin"), "This test only runs under macOS"
)
_x11_skip_cond = (
sys.platform not in ("darwin", "win32") and os.environ.get("DISPLAY", "") == ""
)
_x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
# Other skip decorators
# generic skip without module
skip_without = lambda mod: skipif(
module_not_available(mod), "This test requires %s" % mod
)
skipif_not_numpy = skip_without("numpy")
skipif_not_matplotlib = skip_without("matplotlib")
# 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
# 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:
f = tempfile.NamedTemporaryFile(prefix="tmp€")
except UnicodeEncodeError:
unicode_paths = False
# TODO: should this be finnally ?
else:
unicode_paths = True
f.close()
onlyif_unicode_paths = onlyif(
unicode_paths,
("This test is only applicable where we can use unicode in filenames."),
)
def onlyif_cmds_exist(*commands):
"""
Decorator to skip test when at least one of `commands` is not found.
"""
for cmd in commands:
reason = f"This test runs only if command '{cmd}' is installed"
if not shutil.which(cmd):
return pytest.mark.skip(reason=reason)
return null_deco