CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
test_testing.py1144 linesDownload Raw Back to tests
1import atexit
2import os
3import warnings
4
5import numpy as np
6import pytest
7from scipy import sparse
8
9from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
10from sklearn.tree import DecisionTreeClassifier
11from sklearn.utils._testing import (
12    TempMemmap,
13    _convert_container,
14    _delete_folder,
15    _get_warnings_filters_info_list,
16    assert_allclose,
17    assert_allclose_dense_sparse,
18    assert_docstring_consistency,
19    assert_run_python_script_without_output,
20    check_docstring_parameters,
21    create_memmap_backed_data,
22    ignore_warnings,
23    raises,
24    set_random_state,
25    skip_if_no_numpydoc,
26    turn_warnings_into_errors,
27)
28from sklearn.utils.deprecation import deprecated
29from sklearn.utils.fixes import (
30    _IS_WASM,
31    CSC_CONTAINERS,
32    CSR_CONTAINERS,
33)
34from sklearn.utils.metaestimators import available_if
35
36
37def test_set_random_state():
38    lda = LinearDiscriminantAnalysis()
39    tree = DecisionTreeClassifier()
40    # Linear Discriminant Analysis doesn't have random state: smoke test
41    set_random_state(lda, 3)
42    set_random_state(tree, 3)
43    assert tree.random_state == 3
44
45
46@pytest.mark.parametrize("csr_container", CSC_CONTAINERS)
47def test_assert_allclose_dense_sparse(csr_container):
48    x = np.arange(9).reshape(3, 3)
49    msg = "Not equal to tolerance "
50    y = csr_container(x)
51    for X in [x, y]:
52        # basic compare
53        with pytest.raises(AssertionError, match=msg):
54            assert_allclose_dense_sparse(X, X * 2)
55        assert_allclose_dense_sparse(X, X)
56
57    with pytest.raises(ValueError, match="Can only compare two sparse"):
58        assert_allclose_dense_sparse(x, y)
59
60    A = sparse.diags(np.ones(5), offsets=0).tocsr()
61    B = csr_container(np.ones((1, 5)))
62    with pytest.raises(AssertionError, match="Arrays are not equal"):
63        assert_allclose_dense_sparse(B, A)
64
65
66def test_ignore_warning():
67    # This check that ignore_warning decorator and context manager are working
68    # as expected
69    def _warning_function():
70        warnings.warn("deprecation warning", DeprecationWarning)
71
72    def _multiple_warning_function():
73        warnings.warn("deprecation warning", DeprecationWarning)
74        warnings.warn("deprecation warning")
75
76    # Check the function directly
77    with warnings.catch_warnings():
78        warnings.simplefilter("error")
79
80        ignore_warnings(_warning_function)
81        ignore_warnings(_warning_function, category=DeprecationWarning)
82
83    with pytest.warns(DeprecationWarning):
84        ignore_warnings(_warning_function, category=UserWarning)()
85
86    with pytest.warns() as record:
87        ignore_warnings(_multiple_warning_function, category=FutureWarning)()
88    assert len(record) == 2
89    assert isinstance(record[0].message, DeprecationWarning)
90    assert isinstance(record[1].message, UserWarning)
91
92    with pytest.warns() as record:
93        ignore_warnings(_multiple_warning_function, category=UserWarning)()
94    assert len(record) == 1
95    assert isinstance(record[0].message, DeprecationWarning)
96
97    with warnings.catch_warnings():
98        warnings.simplefilter("error")
99
100        ignore_warnings(_warning_function, category=(DeprecationWarning, UserWarning))
101
102    # Check the decorator
103    @ignore_warnings
104    def decorator_no_warning():
105        _warning_function()
106        _multiple_warning_function()
107
108    @ignore_warnings(category=(DeprecationWarning, UserWarning))
109    def decorator_no_warning_multiple():
110        _multiple_warning_function()
111
112    @ignore_warnings(category=DeprecationWarning)
113    def decorator_no_deprecation_warning():
114        _warning_function()
115
116    @ignore_warnings(category=UserWarning)
117    def decorator_no_user_warning():
118        _warning_function()
119
120    @ignore_warnings(category=DeprecationWarning)
121    def decorator_no_deprecation_multiple_warning():
122        _multiple_warning_function()
123
124    @ignore_warnings(category=UserWarning)
125    def decorator_no_user_multiple_warning():
126        _multiple_warning_function()
127
128    with warnings.catch_warnings():
129        warnings.simplefilter("error")
130
131        decorator_no_warning()
132        decorator_no_warning_multiple()
133        decorator_no_deprecation_warning()
134
135    with pytest.warns(DeprecationWarning):
136        decorator_no_user_warning()
137    with pytest.warns(UserWarning):
138        decorator_no_deprecation_multiple_warning()
139    with pytest.warns(DeprecationWarning):
140        decorator_no_user_multiple_warning()
141
142    # Check the context manager
143    def context_manager_no_warning():
144        with ignore_warnings():
145            _warning_function()
146
147    def context_manager_no_warning_multiple():
148        with ignore_warnings(category=(DeprecationWarning, UserWarning)):
149            _multiple_warning_function()
150
151    def context_manager_no_deprecation_warning():
152        with ignore_warnings(category=DeprecationWarning):
153            _warning_function()
154
155    def context_manager_no_user_warning():
156        with ignore_warnings(category=UserWarning):
157            _warning_function()
158
159    def context_manager_no_deprecation_multiple_warning():
160        with ignore_warnings(category=DeprecationWarning):
161            _multiple_warning_function()
162
163    def context_manager_no_user_multiple_warning():
164        with ignore_warnings(category=UserWarning):
165            _multiple_warning_function()
166
167    with warnings.catch_warnings():
168        warnings.simplefilter("error")
169
170        context_manager_no_warning()
171        context_manager_no_warning_multiple()
172        context_manager_no_deprecation_warning()
173
174    with pytest.warns(DeprecationWarning):
175        context_manager_no_user_warning()
176    with pytest.warns(UserWarning):
177        context_manager_no_deprecation_multiple_warning()
178    with pytest.warns(DeprecationWarning):
179        context_manager_no_user_multiple_warning()
180
181    # Check that passing warning class as first positional argument
182    warning_class = UserWarning
183    match = "'obj' should be a callable.+you should use 'category=UserWarning'"
184
185    with pytest.raises(ValueError, match=match):
186        silence_warnings_func = ignore_warnings(warning_class)(_warning_function)
187        silence_warnings_func()
188
189    with pytest.raises(ValueError, match=match):
190
191        @ignore_warnings(warning_class)
192        def test():
193            pass
194
195
196# Tests for docstrings:
197
198
199def f_ok(a, b):
200    """Function f
201
202    Parameters
203    ----------
204    a : int
205        Parameter a
206    b : float
207        Parameter b
208
209    Returns
210    -------
211    c : list
212        Parameter c
213    """
214    c = a + b
215    return c
216
217
218def f_bad_sections(a, b):
219    """Function f
220
221    Parameters
222    ----------
223    a : int
224        Parameter a
225    b : float
226        Parameter b
227
228    Results
229    -------
230    c : list
231        Parameter c
232    """
233    c = a + b
234    return c
235
236
237def f_bad_order(b, a):
238    """Function f
239
240    Parameters
241    ----------
242    a : int
243        Parameter a
244    b : float
245        Parameter b
246
247    Returns
248    -------
249    c : list
250        Parameter c
251    """
252    c = a + b
253    return c
254
255
256def f_too_many_param_docstring(a, b):
257    """Function f
258
259    Parameters
260    ----------
261    a : int
262        Parameter a
263    b : int
264        Parameter b
265    c : int
266        Parameter c
267
268    Returns
269    -------
270    d : list
271        Parameter c
272    """
273    d = a + b
274    return d
275
276
277def f_missing(a, b):
278    """Function f
279
280    Parameters
281    ----------
282    a : int
283        Parameter a
284
285    Returns
286    -------
287    c : list
288        Parameter c
289    """
290    c = a + b
291    return c
292
293
294def f_check_param_definition(a, b, c, d, e):
295    """Function f
296
297    Parameters
298    ----------
299    a: int
300        Parameter a
301    b:
302        Parameter b
303    c :
304        This is parsed correctly in numpydoc 1.2
305    d:int
306        Parameter d
307    e
308        No typespec is allowed without colon
309    """
310    return a + b + c + d
311
312
313class Klass:
314    def f_missing(self, X, y):
315        pass
316
317    def f_bad_sections(self, X, y):
318        """Function f
319
320        Parameter
321        ---------
322        a : int
323            Parameter a
324        b : float
325            Parameter b
326
327        Results
328        -------
329        c : list
330            Parameter c
331        """
332        pass
333
334
335class MockEst:
336    def __init__(self):
337        """MockEstimator"""
338
339    def fit(self, X, y):
340        return X
341
342    def predict(self, X):
343        return X
344
345    def predict_proba(self, X):
346        return X
347
348    def score(self, X):
349        return 1.0
350
351
352class MockMetaEstimator:
353    def __init__(self, delegate):
354        """MetaEstimator to check if doctest on delegated methods work.
355
356        Parameters
357        ---------
358        delegate : estimator
359            Delegated estimator.
360        """
361        self.delegate = delegate
362
363    @available_if(lambda self: hasattr(self.delegate, "predict"))
364    def predict(self, X):
365        """This is available only if delegate has predict.
366
367        Parameters
368        ----------
369        y : ndarray
370            Parameter y
371        """
372        return self.delegate.predict(X)
373
374    @available_if(lambda self: hasattr(self.delegate, "score"))
375    @deprecated("Testing a deprecated delegated method")
376    def score(self, X):
377        """This is available only if delegate has score.
378
379        Parameters
380        ---------
381        y : ndarray
382            Parameter y
383        """
384
385    @available_if(lambda self: hasattr(self.delegate, "predict_proba"))
386    def predict_proba(self, X):
387        """This is available only if delegate has predict_proba.
388
389        Parameters
390        ---------
391        X : ndarray
392            Parameter X
393        """
394        return X
395
396    @deprecated("Testing deprecated function with wrong params")
397    def fit(self, X, y):
398        """Incorrect docstring but should not be tested"""
399
400
401@skip_if_no_numpydoc
402def test_check_docstring_parameters():
403    incorrect = check_docstring_parameters(f_ok)
404    assert incorrect == []
405    incorrect = check_docstring_parameters(f_ok, ignore=["b"])
406    assert incorrect == []
407    incorrect = check_docstring_parameters(f_missing, ignore=["b"])
408    assert incorrect == []
409    with pytest.raises(RuntimeError, match="Unknown section Results"):
410        check_docstring_parameters(f_bad_sections)
411    with pytest.raises(RuntimeError, match="Unknown section Parameter"):
412        check_docstring_parameters(Klass.f_bad_sections)
413
414    incorrect = check_docstring_parameters(f_check_param_definition)
415    mock_meta = MockMetaEstimator(delegate=MockEst())
416    mock_meta_name = mock_meta.__class__.__name__
417    assert incorrect == [
418        (
419            "sklearn.utils.tests.test_testing.f_check_param_definition There "
420            "was no space between the param name and colon ('a: int')"
421        ),
422        (
423            "sklearn.utils.tests.test_testing.f_check_param_definition There "
424            "was no space between the param name and colon ('b:')"
425        ),
426        (
427            "sklearn.utils.tests.test_testing.f_check_param_definition There "
428            "was no space between the param name and colon ('d:int')"
429        ),
430    ]
431
432    messages = [
433        [
434            "In function: sklearn.utils.tests.test_testing.f_bad_order",
435            (
436                "There's a parameter name mismatch in function docstring w.r.t."
437                " function signature, at index 0 diff: 'b' != 'a'"
438            ),
439            "Full diff:",
440            "- ['b', 'a']",
441            "+ ['a', 'b']",
442        ],
443        [
444            "In function: sklearn.utils.tests.test_testing.f_too_many_param_docstring",
445            (
446                "Parameters in function docstring have more items w.r.t. function"
447                " signature, first extra item: c"
448            ),
449            "Full diff:",
450            "- ['a', 'b']",
451            "+ ['a', 'b', 'c']",
452            "?          +++++",
453        ],
454        [
455            "In function: sklearn.utils.tests.test_testing.f_missing",
456            (
457                "Parameters in function docstring have less items w.r.t. function"
458                " signature, first missing item: b"
459            ),
460            "Full diff:",
461            "- ['a', 'b']",
462            "+ ['a']",
463        ],
464        [
465            "In function: sklearn.utils.tests.test_testing.Klass.f_missing",
466            (
467                "Parameters in function docstring have less items w.r.t. function"
468                " signature, first missing item: X"
469            ),
470            "Full diff:",
471            "- ['X', 'y']",
472            "+ []",
473        ],
474        [
475            f"In function: sklearn.utils.tests.test_testing.{mock_meta_name}.predict",
476            (
477                "There's a parameter name mismatch in function docstring w.r.t."
478                " function signature, at index 0 diff: 'X' != 'y'"
479            ),
480            "Full diff:",
481            "- ['X']",
482            "?   ^",
483            "+ ['y']",
484            "?   ^",
485        ],
486        [
487            "In function: "
488            f"sklearn.utils.tests.test_testing.{mock_meta_name}."
489            "predict_proba",
490            "potentially wrong underline length... ",
491            "Parameters ",
492            "--------- in ",
493        ],
494        [
495            f"In function: sklearn.utils.tests.test_testing.{mock_meta_name}.score",
496            "potentially wrong underline length... ",
497            "Parameters ",
498            "--------- in ",
499        ],
500        [
501            f"In function: sklearn.utils.tests.test_testing.{mock_meta_name}.fit",
502            (
503                "Parameters in function docstring have less items w.r.t. function"
504                " signature, first missing item: X"
505            ),
506            "Full diff:",
507            "- ['X', 'y']",
508            "+ []",
509        ],
510    ]
511
512    for msg, f in zip(
513        messages,
514        [
515            f_bad_order,
516            f_too_many_param_docstring,
517            f_missing,
518            Klass.f_missing,
519            mock_meta.predict,
520            mock_meta.predict_proba,
521            mock_meta.score,
522            mock_meta.fit,
523        ],
524    ):
525        incorrect = check_docstring_parameters(f)
526        assert msg == incorrect, '\n"%s"\n not in \n"%s"' % (msg, incorrect)
527
528
529def f_one(a, b):  # pragma: no cover
530    """Function one.
531
532    Parameters
533    ----------
534    a : int,   float
535        Parameter a.
536        Second    line.
537
538    b : str
539        Parameter b.
540
541    Returns
542    -------
543    c : int
544       Returning
545
546    d : int
547       Returning
548    """
549    pass
550
551
552def f_two(a, b):  # pragma: no cover
553    """Function two.
554
555    Parameters
556    ----------
557    a :   int, float
558        Parameter a.
559          Second line.
560
561    b : str
562        Parameter bb.
563
564    e : int
565        Extra parameter.
566
567    Returns
568    -------
569    c : int
570       Returning
571
572    d : int
573       Returning
574    """
575    pass
576
577
578def f_three(a, b):  # pragma: no cover
579    """Function two.
580
581    Parameters
582    ----------
583    a :   int, float
584        Parameter a.
585
586    b : str
587        Parameter B!
588
589    e :
590        Extra parameter.
591
592    Returns
593    -------
594    c : int
595       Returning.
596
597    d : int
598       Returning
599    """
600    pass
601
602
603@skip_if_no_numpydoc
604def test_assert_docstring_consistency_object_type():
605    """Check error raised when `objects` incorrect type."""
606    with pytest.raises(TypeError, match="All 'objects' must be one of"):
607        assert_docstring_consistency(["string", f_one])
608
609
610@skip_if_no_numpydoc
611@pytest.mark.parametrize(
612    "objects, kwargs, error",
613    [
614        (
615            [f_one, f_two],
616            {"include_params": ["a"], "exclude_params": ["b"]},
617            "The 'exclude_params' argument",
618        ),
619        (
620            [f_one, f_two],
621            {"include_returns": False, "exclude_returns": ["c"]},
622            "The 'exclude_returns' argument",
623        ),
624    ],
625)
626def test_assert_docstring_consistency_arg_checks(objects, kwargs, error):
627    """Check `assert_docstring_consistency` argument checking correct."""
628    with pytest.raises(TypeError, match=error):
629        assert_docstring_consistency(objects, **kwargs)
630
631
632@skip_if_no_numpydoc
633@pytest.mark.parametrize(
634    "objects, kwargs, error, warn",
635    [
636        pytest.param(
637            [f_one, f_two], {"include_params": ["a"]}, "", "", id="whitespace"
638        ),
639        pytest.param([f_one, f_two], {"include_returns": True}, "", "", id="incl_all"),
640        pytest.param(
641            [f_one, f_two, f_three],
642            {"include_params": ["a"]},
643            (
644                r"The description of Parameter 'a' is inconsistent between "
645                r"\['f_one',\n'f_two'\]"
646            ),
647            "",
648            id="2-1 group",
649        ),
650        pytest.param(
651            [f_one, f_two, f_three],
652            {"include_params": ["b"]},
653            (
654                r"The description of Parameter 'b' is inconsistent between "
655                r"\['f_one'\] and\n\['f_two'\] and"
656            ),
657            "",
658            id="1-1-1 group",
659        ),
660        pytest.param(
661            [f_two, f_three],
662            {"include_params": ["e"]},
663            (
664                r"The type specification of Parameter 'e' is inconsistent between\n"
665                r"\['f_two'\] and"
666            ),
667            "",
668            id="empty type",
669        ),
670        pytest.param(
671            [f_one, f_two],
672            {"include_params": True, "exclude_params": ["b"]},
673            "",
674            r"Checking was skipped for Parameters: \['e'\]",
675            id="skip warn",
676        ),
677    ],
678)
679def test_assert_docstring_consistency(objects, kwargs, error, warn):
680    """Check `assert_docstring_consistency` gives correct results."""
681    if error:
682        with pytest.raises(AssertionError, match=error):
683            assert_docstring_consistency(objects, **kwargs)
684    elif warn:
685        with pytest.warns(UserWarning, match=warn):
686            assert_docstring_consistency(objects, **kwargs)
687    else:
688        assert_docstring_consistency(objects, **kwargs)
689
690
691def f_four(labels):  # pragma: no cover
692    """Function four.
693
694    Parameters
695    ----------
696
697    labels : array-like, default=None
698        The set of labels to include when `average != 'binary'`, and their
699        order if `average is None`. Labels present in the data can be excluded.
700    """
701    pass
702
703
704def f_five(labels):  # pragma: no cover
705    """Function five.
706
707    Parameters
708    ----------
709
710    labels : array-like, default=None
711        The set of labels to include when `average != 'binary'`, and their
712        order if `average is None`. This is an extra line. Labels present in the
713        data can be excluded.
714    """
715    pass
716
717
718def f_six(labels):  # pragma: no cover
719    """Function six.
720
721    Parameters
722    ----------
723
724    labels : array-like, default=None
725        The group of labels to add when `average != 'binary'`, and the
726        order if `average is None`. Labels present on them datas can be excluded.
727    """
728    pass
729
730
731@skip_if_no_numpydoc
732def test_assert_docstring_consistency_error_msg():
733    """Check `assert_docstring_consistency` difference message."""
734    msg = r"""The description of Parameter 'labels' is inconsistent between
735\['f_four'\] and \['f_five'\] and \['f_six'\]:
736
737\*\*\* \['f_four'\]
738--- \['f_five'\]
739\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
740
741\*\*\* 10,25 \*\*\*\*
742
743--- 10,30 ----
744
745  'binary'`, and their order if `average is None`.
746\+ This is an extra line.
747  Labels present in the data can be excluded.
748
749\*\*\* \['f_four'\]
750--- \['f_six'\]
751\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
752
753\*\*\* 1,25 \*\*\*\*
754
755  The
756! set
757  of labels to
758! include
759  when `average != 'binary'`, and
760! their
761  order if `average is None`. Labels present
762! in the data
763  can be excluded.
764--- 1,25 ----
765
766  The
767! group
768  of labels to
769! add
770  when `average != 'binary'`, and
771! the
772  order if `average is None`. Labels present
773! on them datas
774  can be excluded."""
775
776    with pytest.raises(AssertionError, match=msg):
777        assert_docstring_consistency([f_four, f_five, f_six], include_params=True)
778
779
780@skip_if_no_numpydoc
781def test_assert_docstring_consistency_descr_regex_pattern():
782    """Check `assert_docstring_consistency` `descr_regex_pattern` works."""
783    # Check regex that matches full parameter descriptions
784    regex_full = (
785        r"The (set|group) "  # match 'set' or 'group'
786        r"of labels to (include|add) "  # match 'include' or 'add'
787        r"when `average \!\= 'binary'`, and (their|the) "  #  match 'their' or 'the'
788        r"order if `average is None`\."
789        r"[\s\w]*\.* "  # optionally match additional sentence
790        r"Labels present (on|in) "  # match 'on' or 'in'
791        r"(them|the) "  # match 'them' or 'the'
792        r"datas? can be excluded\."  # match 'data' or 'datas'
793    )
794
795    assert_docstring_consistency(
796        [f_four, f_five, f_six],
797        include_params=True,
798        descr_regex_pattern=" ".join(regex_full.split()),
799    )
800    # Check we can just match a few alternate words
801    regex_words = r"(labels|average|binary)"  # match any of these 3 words
802    assert_docstring_consistency(
803        [f_four, f_five, f_six],
804        include_params=True,
805        descr_regex_pattern=" ".join(regex_words.split()),
806    )
807    # Check error raised when regex doesn't match
808    regex_error = r"The set of labels to include when.+"
809    msg = r"The description of Parameter 'labels' in \['f_six'\] does not match"
810    with pytest.raises(AssertionError, match=msg):
811        assert_docstring_consistency(
812            [f_four, f_five, f_six],
813            include_params=True,
814            descr_regex_pattern=" ".join(regex_error.split()),
815        )
816
817
818class RegistrationCounter:
819    def __init__(self):
820        self.nb_calls = 0
821
822    def __call__(self, to_register_func):
823        self.nb_calls += 1
824        assert to_register_func.func is _delete_folder
825
826
827def check_memmap(input_array, mmap_data, mmap_mode="r"):
828    assert isinstance(mmap_data, np.memmap)
829    writeable = mmap_mode != "r"
830    assert mmap_data.flags.writeable is writeable
831    np.testing.assert_array_equal(input_array, mmap_data)
832
833
834def test_tempmemmap(monkeypatch):
835    registration_counter = RegistrationCounter()
836    monkeypatch.setattr(atexit, "register", registration_counter)
837
838    input_array = np.ones(3)
839    with TempMemmap(input_array) as data:
840        check_memmap(input_array, data)
841        temp_folder = os.path.dirname(data.filename)
842    if os.name != "nt":
843        assert not os.path.exists(temp_folder)
844    assert registration_counter.nb_calls == 1
845
846    mmap_mode = "r+"
847    with TempMemmap(input_array, mmap_mode=mmap_mode) as data:
848        check_memmap(input_array, data, mmap_mode=mmap_mode)
849        temp_folder = os.path.dirname(data.filename)
850    if os.name != "nt":
851        assert not os.path.exists(temp_folder)
852    assert registration_counter.nb_calls == 2
853
854
855def test_create_memmap_backed_data(monkeypatch):
856    registration_counter = RegistrationCounter()
857    monkeypatch.setattr(atexit, "register", registration_counter)
858
859    input_array = np.ones(3)
860    data = create_memmap_backed_data(input_array)
861    check_memmap(input_array, data)
862    assert registration_counter.nb_calls == 1
863
864    data, folder = create_memmap_backed_data(input_array, return_folder=True)
865    check_memmap(input_array, data)
866    assert folder == os.path.dirname(data.filename)
867    assert registration_counter.nb_calls == 2
868
869    mmap_mode = "r+"
870    data = create_memmap_backed_data(input_array, mmap_mode=mmap_mode)
871    check_memmap(input_array, data, mmap_mode)
872    assert registration_counter.nb_calls == 3
873
874    input_list = [input_array, input_array + 1, input_array + 2]
875    mmap_data_list = create_memmap_backed_data(input_list)
876    for input_array, data in zip(input_list, mmap_data_list):
877        check_memmap(input_array, data)
878    assert registration_counter.nb_calls == 4
879
880    output_data, other = create_memmap_backed_data([input_array, "not-an-array"])
881    check_memmap(input_array, output_data)
882    assert other == "not-an-array"
883
884
885@pytest.mark.parametrize(
886    "constructor_name, container_type",
887    [
888        ("list", list),
889        ("tuple", tuple),
890        ("array", np.ndarray),
891        ("sparse", sparse.csr_matrix),
892        # using `zip` will only keep the available sparse containers
893        # depending of the installed SciPy version
894        *zip(["sparse_csr", "sparse_csr_array"], CSR_CONTAINERS),
895        *zip(["sparse_csc", "sparse_csc_array"], CSC_CONTAINERS),
896        ("dataframe", lambda: pytest.importorskip("pandas").DataFrame),
897        ("series", lambda: pytest.importorskip("pandas").Series),
898        ("index", lambda: pytest.importorskip("pandas").Index),
899        ("pyarrow", lambda: pytest.importorskip("pyarrow").Table),
900        ("pyarrow_array", lambda: pytest.importorskip("pyarrow").Array),
901        ("polars", lambda: pytest.importorskip("polars").DataFrame),
902        ("polars_series", lambda: pytest.importorskip("polars").Series),
903        ("slice", slice),
904    ],
905)
906@pytest.mark.parametrize(
907    "dtype, superdtype",
908    [
909        (np.int32, np.integer),
910        (np.int64, np.integer),
911        (np.float32, np.floating),
912        (np.float64, np.floating),
913    ],
914)
915def test_convert_container(
916    constructor_name,
917    container_type,
918    dtype,
919    superdtype,
920):
921    """Check that we convert the container to the right type of array with the
922    right data type."""
923    if constructor_name in (
924        "dataframe",
925        "index",
926        "polars",
927        "polars_series",
928        "pyarrow",
929        "pyarrow_array",
930        "series",
931    ):
932        # delay the import of pandas/polars within the function to only skip this test
933        # instead of the whole file
934        container_type = container_type()
935    container = [0, 1]
936
937    container_converted = _convert_container(
938        container,
939        constructor_name,
940        dtype=dtype,
941    )
942    assert isinstance(container_converted, container_type)
943
944    if constructor_name in ("list", "tuple", "index"):
945        # list and tuple will use Python class dtype: int, float
946        # pandas index will always use high precision: np.int64 and np.float64
947        assert np.issubdtype(type(container_converted[0]), superdtype)
948    elif constructor_name in ("polars", "polars_series", "pyarrow", "pyarrow_array"):
949        return
950    elif hasattr(container_converted, "dtype"):
951        assert container_converted.dtype == dtype
952    elif hasattr(container_converted, "dtypes"):
953        assert container_converted.dtypes[0] == dtype
954
955
956def test_convert_container_categories_pandas():
957    pytest.importorskip("pandas")
958    df = _convert_container(
959        [["x"]], "dataframe", ["A"], categorical_feature_names=["A"]
960    )
961    assert df.dtypes.iloc[0] == "category"
962
963
964def test_convert_container_categories_polars():
965    pl = pytest.importorskip("polars")
966    df = _convert_container([["x"]], "polars", ["A"], categorical_feature_names=["A"])
967    assert df.schema["A"] == pl.Categorical()
968
969
970def test_convert_container_categories_pyarrow():
971    pa = pytest.importorskip("pyarrow")
972    df = _convert_container([["x"]], "pyarrow", ["A"], categorical_feature_names=["A"])
973    assert type(df.schema[0].type) is pa.DictionaryType
974
975
976def test_raises():
977    # Tests for the raises context manager
978
979    # Proper type, no match
980    with raises(TypeError):
981        raise TypeError()
982
983    # Proper type, proper match
984    with raises(TypeError, match="how are you") as cm:
985        raise TypeError("hello how are you")
986    assert cm.raised_and_matched
987
988    # Proper type, proper match with multiple patterns
989    with raises(TypeError, match=["not this one", "how are you"]) as cm:
990        raise TypeError("hello how are you")
991    assert cm.raised_and_matched
992
993    # bad type, no match
994    with pytest.raises(ValueError, match="this will be raised"):
995        with raises(TypeError) as cm:
996            raise ValueError("this will be raised")
997    assert not cm.raised_and_matched
998
999    # Bad type, no match, with a err_msg
1000    with pytest.raises(AssertionError, match="the failure message"):
1001        with raises(TypeError, err_msg="the failure message") as cm:
1002            raise ValueError()
1003    assert not cm.raised_and_matched
1004
1005    # bad type, with match (is ignored anyway)
1006    with pytest.raises(ValueError, match="this will be raised"):
1007        with raises(TypeError, match="this is ignored") as cm:
1008            raise ValueError("this will be raised")
1009    assert not cm.raised_and_matched
1010
1011    # proper type but bad match
1012    with pytest.raises(
1013        AssertionError, match="should contain one of the following patterns"
1014    ):
1015        with raises(TypeError, match="hello") as cm:
1016            raise TypeError("Bad message")
1017    assert not cm.raised_and_matched
1018
1019    # proper type but bad match, with err_msg
1020    with pytest.raises(AssertionError, match="the failure message"):
1021        with raises(TypeError, match="hello", err_msg="the failure message") as cm:
1022            raise TypeError("Bad message")
1023    assert not cm.raised_and_matched
1024
1025    # no raise with default may_pass=False
1026    with pytest.raises(AssertionError, match="Did not raise"):
1027        with raises(TypeError) as cm:
1028            pass
1029    assert not cm.raised_and_matched
1030
1031    # no raise with may_pass=True
1032    with raises(TypeError, match="hello", may_pass=True) as cm:
1033        pass  # still OK
1034    assert not cm.raised_and_matched
1035
1036    # Multiple exception types:
1037    with raises((TypeError, ValueError)):
1038        raise TypeError()
1039    with raises((TypeError, ValueError)):
1040        raise ValueError()
1041    with pytest.raises(AssertionError):
1042        with raises((TypeError, ValueError)):
1043            pass
1044
1045
1046def test_float32_aware_assert_allclose():
1047    # The relative tolerance for float32 inputs is 1e-4
1048    assert_allclose(np.array([1.0 + 2e-5], dtype=np.float32), 1.0)
1049    with pytest.raises(AssertionError):
1050        assert_allclose(np.array([1.0 + 2e-4], dtype=np.float32), 1.0)
1051
1052    # The relative tolerance for other inputs is left to 1e-7 as in
1053    # the original numpy version.
1054    assert_allclose(np.array([1.0 + 2e-8], dtype=np.float64), 1.0)
1055    with pytest.raises(AssertionError):
1056        assert_allclose(np.array([1.0 + 2e-7], dtype=np.float64), 1.0)
1057
1058    # atol is left to 0.0 by default, even for float32
1059    with pytest.raises(AssertionError):
1060        assert_allclose(np.array([1e-5], dtype=np.float32), 0.0)
1061    assert_allclose(np.array([1e-5], dtype=np.float32), 0.0, atol=2e-5)
1062
1063
1064@pytest.mark.xfail(_IS_WASM, reason="cannot start subprocess")
1065def test_assert_run_python_script_without_output():
1066    code = "x = 1"
1067    assert_run_python_script_without_output(code)
1068
1069    code = "print('something to stdout')"
1070    with pytest.raises(AssertionError, match="Expected no output"):
1071        assert_run_python_script_without_output(code)
1072
1073    code = "print('something to stdout')"
1074    with pytest.raises(
1075        AssertionError,
1076        match="output was not supposed to match.+got.+something to stdout",
1077    ):
1078        assert_run_python_script_without_output(code, pattern="to.+stdout")
1079
1080    code = "\n".join(["import sys", "print('something to stderr', file=sys.stderr)"])
1081    with pytest.raises(
1082        AssertionError,
1083        match="output was not supposed to match.+got.+something to stderr",
1084    ):
1085        assert_run_python_script_without_output(code, pattern="to.+stderr")
1086
1087
1088@pytest.mark.parametrize(
1089    "constructor_name",
1090    [
1091        "sparse_csr",
1092        "sparse_csc",
1093        pytest.param(
1094            "sparse_csr_array",
1095        ),
1096        pytest.param(
1097            "sparse_csc_array",
1098        ),
1099    ],
1100)
1101def test_convert_container_sparse_to_sparse(constructor_name):
1102    """Non-regression test to check that we can still convert a sparse container
1103    from a given format to another format.
1104    """
1105    X_sparse = sparse.random(10, 10, density=0.1, format="csr")
1106    _convert_container(X_sparse, constructor_name)
1107
1108
1109def check_warnings_as_errors(warning_info, warnings_as_errors):
1110    if warning_info.action == "error" and warnings_as_errors:
1111        with pytest.raises(warning_info.category, match=warning_info.message):
1112            warnings.warn(
1113                message=warning_info.message,
1114                category=warning_info.category,
1115            )
1116    if warning_info.action == "ignore":
1117        with warnings.catch_warnings(record=True) as record:
1118            message = warning_info.message
1119            # Special treatment when regex is used
1120            if "Pyarrow" in message:
1121                message = "\nPyarrow will become a required dependency"
1122
1123            warnings.warn(
1124                message=message,
1125                category=warning_info.category,
1126            )
1127            assert len(record) == 0 if warnings_as_errors else 1
1128            if record:
1129                assert str(record[0].message) == message
1130                assert record[0].category == warning_info.category
1131
1132
1133@pytest.mark.parametrize("warning_info", _get_warnings_filters_info_list())
1134def test_sklearn_warnings_as_errors(warning_info):
1135    warnings_as_errors = os.environ.get("SKLEARN_WARNINGS_AS_ERRORS", "0") != "0"
1136    check_warnings_as_errors(warning_info, warnings_as_errors=warnings_as_errors)
1137
1138
1139@pytest.mark.parametrize("warning_info", _get_warnings_filters_info_list())
1140def test_turn_warnings_into_errors(warning_info):
1141    with warnings.catch_warnings():
1142        turn_warnings_into_errors()
1143        check_warnings_as_errors(warning_info, warnings_as_errors=True)
1144 
Aluode/PerceptionLabPortable · CoolFace