CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
conftest.py376 linesDownload Raw Back to sklearn
1# Authors: The scikit-learn developers
2# SPDX-License-Identifier: BSD-3-Clause
3
4import builtins
5import faulthandler
6import platform
7import sys
8from contextlib import suppress
9from functools import wraps
10from os import environ
11from unittest import SkipTest
12
13import joblib
14import numpy as np
15import pytest
16from _pytest.doctest import DoctestItem
17from threadpoolctl import threadpool_limits
18
19from sklearn import set_config
20from sklearn._min_dependencies import PYTEST_MIN_VERSION
21from sklearn.datasets import (
22    fetch_20newsgroups,
23    fetch_20newsgroups_vectorized,
24    fetch_california_housing,
25    fetch_covtype,
26    fetch_kddcup99,
27    fetch_lfw_pairs,
28    fetch_lfw_people,
29    fetch_olivetti_faces,
30    fetch_rcv1,
31    fetch_species_distributions,
32)
33from sklearn.utils._testing import get_pytest_filterwarning_lines
34from sklearn.utils.fixes import (
35    _IS_32BIT,
36    np_base_version,
37    parse_version,
38    sp_version,
39)
40
41try:
42    from scipy_doctest.conftest import dt_config
43except ModuleNotFoundError:
44    dt_config = None
45
46if parse_version(pytest.__version__) < parse_version(PYTEST_MIN_VERSION):
47    raise ImportError(
48        f"Your version of pytest is too old. Got version {pytest.__version__}, you"
49        f" should have pytest >= {PYTEST_MIN_VERSION} installed."
50    )
51
52scipy_datasets_require_network = sp_version >= parse_version("1.10")
53
54
55def raccoon_face_or_skip():
56    # SciPy >= 1.10 requires network to access to get data
57    if scipy_datasets_require_network:
58        run_network_tests = environ.get("SKLEARN_SKIP_NETWORK_TESTS", "1") == "0"
59        if not run_network_tests:
60            raise SkipTest("test is enabled when SKLEARN_SKIP_NETWORK_TESTS=0")
61
62        try:
63            import pooch  # noqa: F401
64        except ImportError:
65            raise SkipTest("test requires pooch to be installed")
66
67        from scipy.datasets import face
68    else:
69        from scipy.misc import face
70
71    return face(gray=True)
72
73
74dataset_fetchers = {
75    "fetch_20newsgroups_fxt": fetch_20newsgroups,
76    "fetch_20newsgroups_vectorized_fxt": fetch_20newsgroups_vectorized,
77    "fetch_california_housing_fxt": fetch_california_housing,
78    "fetch_covtype_fxt": fetch_covtype,
79    "fetch_kddcup99_fxt": fetch_kddcup99,
80    "fetch_lfw_pairs_fxt": fetch_lfw_pairs,
81    "fetch_lfw_people_fxt": fetch_lfw_people,
82    "fetch_olivetti_faces_fxt": fetch_olivetti_faces,
83    "fetch_rcv1_fxt": fetch_rcv1,
84    "fetch_species_distributions_fxt": fetch_species_distributions,
85}
86
87if scipy_datasets_require_network:
88    dataset_fetchers["raccoon_face_fxt"] = raccoon_face_or_skip
89
90_SKIP32_MARK = pytest.mark.skipif(
91    environ.get("SKLEARN_RUN_FLOAT32_TESTS", "0") != "1",
92    reason="Set SKLEARN_RUN_FLOAT32_TESTS=1 to run float32 dtype tests",
93)
94
95
96# Global fixtures
97@pytest.fixture(params=[pytest.param(np.float32, marks=_SKIP32_MARK), np.float64])
98def global_dtype(request):
99    yield request.param
100
101
102def _fetch_fixture(f):
103    """Fetch dataset (download if missing and requested by environment)."""
104    download_if_missing = environ.get("SKLEARN_SKIP_NETWORK_TESTS", "1") == "0"
105
106    @wraps(f)
107    def wrapped(*args, **kwargs):
108        kwargs["download_if_missing"] = download_if_missing
109        try:
110            return f(*args, **kwargs)
111        except OSError as e:
112            if str(e) != "Data not found and `download_if_missing` is False":
113                raise
114            pytest.skip("test is enabled when SKLEARN_SKIP_NETWORK_TESTS=0")
115
116    return pytest.fixture(lambda: wrapped)
117
118
119# Adds fixtures for fetching data
120fetch_20newsgroups_fxt = _fetch_fixture(fetch_20newsgroups)
121fetch_20newsgroups_vectorized_fxt = _fetch_fixture(fetch_20newsgroups_vectorized)
122fetch_california_housing_fxt = _fetch_fixture(fetch_california_housing)
123fetch_covtype_fxt = _fetch_fixture(fetch_covtype)
124fetch_kddcup99_fxt = _fetch_fixture(fetch_kddcup99)
125fetch_lfw_pairs_fxt = _fetch_fixture(fetch_lfw_pairs)
126fetch_lfw_people_fxt = _fetch_fixture(fetch_lfw_people)
127fetch_olivetti_faces_fxt = _fetch_fixture(fetch_olivetti_faces)
128fetch_rcv1_fxt = _fetch_fixture(fetch_rcv1)
129fetch_species_distributions_fxt = _fetch_fixture(fetch_species_distributions)
130raccoon_face_fxt = pytest.fixture(raccoon_face_or_skip)
131
132
133def pytest_collection_modifyitems(config, items):
134    """Called after collect is completed.
135
136    Parameters
137    ----------
138    config : pytest config
139    items : list of collected items
140    """
141    run_network_tests = environ.get("SKLEARN_SKIP_NETWORK_TESTS", "1") == "0"
142    skip_network = pytest.mark.skip(
143        reason="test is enabled when SKLEARN_SKIP_NETWORK_TESTS=0"
144    )
145
146    # download datasets during collection to avoid thread unsafe behavior
147    # when running pytest in parallel with pytest-xdist
148    dataset_features_set = set(dataset_fetchers)
149    datasets_to_download = set()
150
151    for item in items:
152        if isinstance(item, DoctestItem) and "fetch_" in item.name:
153            fetcher_function_name = item.name.split(".")[-1]
154            dataset_fetchers_key = f"{fetcher_function_name}_fxt"
155            dataset_to_fetch = set([dataset_fetchers_key]) & dataset_features_set
156        elif not hasattr(item, "fixturenames"):
157            continue
158        else:
159            item_fixtures = set(item.fixturenames)
160            dataset_to_fetch = item_fixtures & dataset_features_set
161
162        if not dataset_to_fetch:
163            continue
164
165        if run_network_tests:
166            datasets_to_download |= dataset_to_fetch
167        else:
168            # network tests are skipped
169            item.add_marker(skip_network)
170
171    # Only download datasets on the first worker spawned by pytest-xdist
172    # to avoid thread unsafe behavior. If pytest-xdist is not used, we still
173    # download before tests run.
174    worker_id = environ.get("PYTEST_XDIST_WORKER", "gw0")
175    if worker_id == "gw0" and run_network_tests:
176        for name in datasets_to_download:
177            with suppress(SkipTest):
178                dataset_fetchers[name]()
179
180    for item in items:
181        # Known failure on with GradientBoostingClassifier on ARM64
182        if (
183            item.name.endswith("GradientBoostingClassifier")
184            and platform.machine() == "aarch64"
185        ):
186            marker = pytest.mark.xfail(
187                reason=(
188                    "know failure. See "
189                    "https://github.com/scikit-learn/scikit-learn/issues/17797"
190                )
191            )
192            item.add_marker(marker)
193
194    skip_doctests = False
195    try:
196        import matplotlib  # noqa: F401
197    except ImportError:
198        skip_doctests = True
199        reason = "matplotlib is required to run the doctests"
200
201    if _IS_32BIT:
202        reason = "doctest are only run when the default numpy int is 64 bits."
203        skip_doctests = True
204    elif sys.platform.startswith("win32"):
205        reason = (
206            "doctests are not run for Windows because numpy arrays "
207            "repr is inconsistent across platforms."
208        )
209        skip_doctests = True
210
211    if np_base_version < parse_version("2"):
212        # TODO: configure numpy to output scalar arrays as regular Python scalars
213        # once possible to improve readability of the tests docstrings.
214        # https://numpy.org/neps/nep-0051-scalar-representation.html#implementation
215        reason = "Due to NEP 51 numpy scalar repr has changed in numpy 2"
216        skip_doctests = True
217
218    if sp_version < parse_version("1.14"):
219        reason = "Scipy sparse matrix repr has changed in scipy 1.14"
220        skip_doctests = True
221
222    # Normally doctest has the entire module's scope. Here we set globs to an empty dict
223    # to remove the module's scope:
224    # https://docs.python.org/3/library/doctest.html#what-s-the-execution-context
225    for item in items:
226        if isinstance(item, DoctestItem):
227            item.dtest.globs = {}
228
229    if skip_doctests:
230        skip_marker = pytest.mark.skip(reason=reason)
231
232        for item in items:
233            if isinstance(item, DoctestItem):
234                # work-around an internal error with pytest if adding a skip
235                # mark to a doctest in a contextmanager, see
236                # https://github.com/pytest-dev/pytest/issues/8796 for more
237                # details.
238                if item.name != "sklearn._config.config_context":
239                    item.add_marker(skip_marker)
240    try:
241        import PIL  # noqa: F401
242
243        pillow_installed = True
244    except ImportError:
245        pillow_installed = False
246
247    if not pillow_installed:
248        skip_marker = pytest.mark.skip(reason="pillow (or PIL) not installed!")
249        for item in items:
250            if item.name in [
251                "sklearn.feature_extraction.image.PatchExtractor",
252                "sklearn.feature_extraction.image.extract_patches_2d",
253            ]:
254                item.add_marker(skip_marker)
255
256
257@pytest.fixture(scope="function")
258def pyplot():
259    """Setup and teardown fixture for matplotlib.
260
261    This fixture checks if we can import matplotlib. If not, the tests will be
262    skipped. Otherwise, we close the figures before and after running the
263    functions.
264
265    Returns
266    -------
267    pyplot : module
268        The ``matplotlib.pyplot`` module.
269    """
270    pyplot = pytest.importorskip("matplotlib.pyplot")
271    pyplot.close("all")
272    yield pyplot
273    pyplot.close("all")
274
275
276def pytest_generate_tests(metafunc):
277    """Parametrization of global_random_seed fixture
278
279    based on the SKLEARN_TESTS_GLOBAL_RANDOM_SEED environment variable.
280
281    The goal of this fixture is to prevent tests that use it to be sensitive
282    to a specific seed value while still being deterministic by default.
283
284    See the documentation for the SKLEARN_TESTS_GLOBAL_RANDOM_SEED
285    variable for instructions on how to use this fixture.
286
287    https://scikit-learn.org/dev/computing/parallelism.html#sklearn-tests-global-random-seed
288
289    """
290    # When using pytest-xdist this function is called in the xdist workers.
291    # We rely on SKLEARN_TESTS_GLOBAL_RANDOM_SEED environment variable which is
292    # set in before running pytest and is available in xdist workers since they
293    # are subprocesses.
294    RANDOM_SEED_RANGE = list(range(100))  # All seeds in [0, 99] should be valid.
295    random_seed_var = environ.get("SKLEARN_TESTS_GLOBAL_RANDOM_SEED")
296
297    default_random_seeds = [42]
298
299    if random_seed_var is None:
300        random_seeds = default_random_seeds
301    elif random_seed_var == "all":
302        random_seeds = RANDOM_SEED_RANGE
303    else:
304        if "-" in random_seed_var:
305            start, stop = random_seed_var.split("-")
306            random_seeds = list(range(int(start), int(stop) + 1))
307        else:
308            random_seeds = [int(random_seed_var)]
309
310        if min(random_seeds) < 0 or max(random_seeds) > 99:
311            raise ValueError(
312                "The value(s) of the environment variable "
313                "SKLEARN_TESTS_GLOBAL_RANDOM_SEED must be in the range [0, 99] "
314                f"(or 'all'), got: {random_seed_var}"
315            )
316
317    if "global_random_seed" in metafunc.fixturenames:
318        metafunc.parametrize("global_random_seed", random_seeds)
319
320
321def pytest_configure(config):
322    # Use matplotlib agg backend during the tests including doctests
323    try:
324        import matplotlib
325
326        matplotlib.use("agg")
327    except ImportError:
328        pass
329
330    allowed_parallelism = joblib.cpu_count(only_physical_cores=True)
331    xdist_worker_count = environ.get("PYTEST_XDIST_WORKER_COUNT")
332    if xdist_worker_count is not None:
333        # Set the number of OpenMP and BLAS threads based on the number of workers
334        # xdist is using to prevent oversubscription.
335        allowed_parallelism = max(allowed_parallelism // int(xdist_worker_count), 1)
336    threadpool_limits(allowed_parallelism)
337
338    if environ.get("SKLEARN_WARNINGS_AS_ERRORS", "0") != "0":
339        # This seems like the only way to programmatically change the config
340        # filterwarnings. This was suggested in
341        # https://github.com/pytest-dev/pytest/issues/3311#issuecomment-373177592
342        for line in get_pytest_filterwarning_lines():
343            config.addinivalue_line("filterwarnings", line)
344
345    faulthandler_timeout = int(environ.get("SKLEARN_FAULTHANDLER_TIMEOUT", "0"))
346    if faulthandler_timeout > 0:
347        faulthandler.enable()
348        faulthandler.dump_traceback_later(faulthandler_timeout, exit=True)
349
350
351@pytest.fixture
352def hide_available_pandas(monkeypatch):
353    """Pretend pandas was not installed."""
354    import_orig = builtins.__import__
355
356    def mocked_import(name, *args, **kwargs):
357        if name == "pandas":
358            raise ImportError()
359        return import_orig(name, *args, **kwargs)
360
361    monkeypatch.setattr(builtins, "__import__", mocked_import)
362
363
364@pytest.fixture
365def print_changed_only_false():
366    """Set `print_changed_only` to False for the duration of the test."""
367    set_config(print_changed_only=False)
368    yield
369    set_config(print_changed_only=True)  # reset to default
370
371
372if dt_config is not None:
373    # Strict mode to differentiate between 3.14 and np.float64(3.14)
374    dt_config.strict_check = True
375    # dt_config.rtol = 0.01
376 
Aluode/PerceptionLabPortable · CoolFace