Aluode/PerceptionLabPortable
0
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 