codekingpro/portable-devtools
115k
1"""
2Pytest configuration and fixtures for the Numpy test suite.
3"""
4import os
5import sys
6import tempfile
7import warnings
8from contextlib import contextmanager
9from pathlib import Path
10
11import hypothesis
12import pytest
13
14import numpy
15from numpy._core._multiarray_tests import get_fpu_mode
16from numpy.testing._private.utils import NOGIL_BUILD
17
18try:
19 from scipy_doctest.conftest import dt_config
20 HAVE_SCPDT = True
21except ModuleNotFoundError:
22 HAVE_SCPDT = False
23
24try:
25 import pytest_run_parallel # noqa: F401
26 PARALLEL_RUN_AVALIABLE = True
27except ModuleNotFoundError:
28 PARALLEL_RUN_AVALIABLE = False
29
30_old_fpu_mode = None
31_collect_results = {}
32
33# Use a known and persistent tmpdir for hypothesis' caches, which
34# can be automatically cleared by the OS or user.
35hypothesis.configuration.set_hypothesis_home_dir(
36 os.path.join(tempfile.gettempdir(), ".hypothesis")
37)
38
39# We register two custom profiles for Numpy - for details see
40# https://hypothesis.readthedocs.io/en/latest/settings.html
41# The first is designed for our own CI runs; the latter also
42# forces determinism and is designed for use via np.test()
43hypothesis.settings.register_profile(
44 name="numpy-profile", deadline=None, print_blob=True,
45)
46hypothesis.settings.register_profile(
47 name="np.test() profile",
48 deadline=None, print_blob=True, database=None, derandomize=True,
49 suppress_health_check=list(hypothesis.HealthCheck),
50)
51# Note that the default profile is chosen based on the presence
52# of pytest.ini, but can be overridden by passing the
53# --hypothesis-profile=NAME argument to pytest.
54_pytest_ini = os.path.join(os.path.dirname(__file__), "..", "pytest.ini")
55hypothesis.settings.load_profile(
56 "numpy-profile" if os.path.isfile(_pytest_ini) else "np.test() profile"
57)
58
59# The experimentalAPI is used in _umath_tests
60os.environ["NUMPY_EXPERIMENTAL_DTYPE_API"] = "1"
61
62def pytest_configure(config):
63 config.addinivalue_line("markers",
64 "valgrind_error: Tests that are known to error under valgrind.")
65 config.addinivalue_line("markers",
66 "leaks_references: Tests that are known to leak references.")
67 config.addinivalue_line("markers",
68 "slow: Tests that are very slow.")
69 config.addinivalue_line("markers",
70 "slow_pypy: Tests that are very slow on pypy.")
71 if not PARALLEL_RUN_AVALIABLE:
72 config.addinivalue_line("markers",
73 "parallel_threads(n): run the given test function in parallel "
74 "using `n` threads.",
75 )
76 config.addinivalue_line("markers",
77 "iterations(n): run the given test function `n` times in each thread",
78 )
79 config.addinivalue_line("markers",
80 "thread_unsafe: mark the test function as single-threaded",
81 )
82
83
84def pytest_addoption(parser):
85 parser.addoption("--available-memory", action="store", default=None,
86 help=("Set amount of memory available for running the "
87 "test suite. This can result to tests requiring "
88 "especially large amounts of memory to be skipped. "
89 "Equivalent to setting environment variable "
90 "NPY_AVAILABLE_MEM. Default: determined"
91 "automatically."))
92
93
94gil_enabled_at_start = True
95if NOGIL_BUILD:
96 gil_enabled_at_start = sys._is_gil_enabled()
97
98
99def pytest_sessionstart(session):
100 available_mem = session.config.getoption('available_memory')
101 if available_mem is not None:
102 os.environ['NPY_AVAILABLE_MEM'] = available_mem
103
104
105def pytest_terminal_summary(terminalreporter, exitstatus, config):
106 if NOGIL_BUILD and not gil_enabled_at_start and sys._is_gil_enabled():
107 tr = terminalreporter
108 tr.ensure_newline()
109 tr.section("GIL re-enabled", sep="=", red=True, bold=True)
110 tr.line("The GIL was re-enabled at runtime during the tests.")
111 tr.line("This can happen with no test failures if the RuntimeWarning")
112 tr.line("raised by Python when this happens is filtered by a test.")
113 tr.line("")
114 tr.line("Please ensure all new C modules declare support for running")
115 tr.line("without the GIL. Any new tests that intentionally imports ")
116 tr.line("code that re-enables the GIL should do so in a subprocess.")
117 pytest.exit("GIL re-enabled during tests", returncode=1)
118
119# FIXME when yield tests are gone.
120@pytest.hookimpl(tryfirst=True)
121def pytest_itemcollected(item):
122 """
123 Check FPU precision mode was not changed during test collection.
124
125 The clumsy way we do it here is mainly necessary because numpy
126 still uses yield tests, which can execute code at test collection
127 time.
128 """
129 global _old_fpu_mode
130
131 mode = get_fpu_mode()
132
133 if _old_fpu_mode is None:
134 _old_fpu_mode = mode
135 elif mode != _old_fpu_mode:
136 _collect_results[item] = (_old_fpu_mode, mode)
137 _old_fpu_mode = mode
138
139 # mark f2py tests as thread unsafe
140 if Path(item.fspath).parent == Path(__file__).parent / 'f2py' / 'tests':
141 item.add_marker(pytest.mark.thread_unsafe(
142 reason="f2py tests are thread-unsafe"))
143
144
145@pytest.fixture(scope="function", autouse=True)
146def check_fpu_mode(request):
147 """
148 Check FPU precision mode was not changed during the test.
149 """
150 old_mode = get_fpu_mode()
151 yield
152 new_mode = get_fpu_mode()
153
154 if old_mode != new_mode:
155 raise AssertionError(f"FPU precision mode changed from {old_mode:#x} to "
156 f"{new_mode:#x} during the test")
157
158 collect_result = _collect_results.get(request.node)
159 if collect_result is not None:
160 old_mode, new_mode = collect_result
161 raise AssertionError(f"FPU precision mode changed from {old_mode:#x} to "
162 f"{new_mode:#x} when collecting the test")
163
164
165@pytest.fixture(autouse=True)
166def add_np(doctest_namespace):
167 doctest_namespace['np'] = numpy
168
169
170if HAVE_SCPDT:
171
172 @contextmanager
173 def warnings_errors_and_rng(test=None):
174 """Filter out the wall of DeprecationWarnings.
175 """
176 msgs = ["The numpy.linalg.linalg",
177 "The numpy.fft.helper",
178 "dep_util",
179 "pkg_resources",
180 "numpy.core.umath",
181 "msvccompiler",
182 "Deprecated call",
183 "numpy.core",
184 "Importing from numpy.matlib",
185 "This function is deprecated.", # random_integers
186 "Data type alias 'a'", # numpy.rec.fromfile
187 "Arrays of 2-dimensional vectors", # matlib.cross
188 "NumPy warning suppression and assertion utilities are deprecated."
189 ]
190 msg = "|".join(msgs)
191
192 msgs_r = [
193 "invalid value encountered",
194 "divide by zero encountered"
195 ]
196 msg_r = "|".join(msgs_r)
197
198 with warnings.catch_warnings():
199 warnings.filterwarnings(
200 'ignore', category=DeprecationWarning, message=msg
201 )
202 warnings.filterwarnings(
203 'ignore', category=RuntimeWarning, message=msg_r
204 )
205 yield
206
207 # find and check doctests under this context manager
208 dt_config.user_context_mgr = warnings_errors_and_rng
209
210 # numpy specific tweaks from refguide-check
211 dt_config.rndm_markers.add('#uninitialized')
212 dt_config.rndm_markers.add('# uninitialized')
213
214 # make the checker pick on mismatched dtypes
215 dt_config.strict_check = True
216
217 import doctest
218 dt_config.optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
219
220 # recognize the StringDType repr
221 dt_config.check_namespace['StringDType'] = numpy.dtypes.StringDType
222
223 # temporary skips
224 dt_config.skiplist = {
225 'numpy.savez', # unclosed file
226 'numpy.matlib.savez',
227 'numpy.__array_namespace_info__',
228 'numpy.matlib.__array_namespace_info__',
229 }
230
231 # xfail problematic tutorials
232 dt_config.pytest_extra_xfail = {
233 'how-to-verify-bug.rst': '',
234 'c-info.ufunc-tutorial.rst': '',
235 'basics.interoperability.rst': 'needs pandas',
236 'basics.dispatch.rst': 'errors out in /testing/overrides.py',
237 'basics.subclassing.rst': '.. testcode:: admonitions not understood',
238 'misc.rst': 'manipulates warnings',
239 }
240
241 # ignores are for things fail doctest collection (optionals etc)
242 dt_config.pytest_extra_ignore = [
243 'numpy/distutils',
244 'numpy/_core/cversions.py',
245 'numpy/_pyinstaller',
246 'numpy/random/_examples',
247 'numpy/f2py/_backends/_distutils.py',
248 ]
249 