Skip to content

Commit be11e29

Browse files
authored
Merge pull request scipy#21334 from lucascolley/xfail-xp
TST: add `xfail_xp_backends`
2 parents cf3d28f + acbcb72 commit be11e29

File tree

3 files changed

+83
-64
lines changed

3 files changed

+83
-64
lines changed

scipy/conftest.py

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ def pytest_configure(config):
4545
"skip_xp_backends(*backends, reasons=None, np_only=False, cpu_only=False, "
4646
"exceptions=None): "
4747
"mark the desired skip configuration for the `skip_xp_backends` fixture.")
48+
config.addinivalue_line("markers",
49+
"xfail_xp_backends(*backends, reasons=None, np_only=False, cpu_only=False, "
50+
"exceptions=None): "
51+
"mark the desired xfail configuration for the `xfail_xp_backends` fixture.")
4852

4953

5054
def pytest_runtest_setup(item):
@@ -178,8 +182,26 @@ def check_fpu_mode(request):
178182

179183
@pytest.fixture
180184
def skip_xp_backends(xp, request):
185+
"""See the `skip_or_xfail_xp_backends` docstring."""
186+
if "skip_xp_backends" not in request.keywords:
187+
return
188+
backends = request.keywords["skip_xp_backends"].args
189+
kwargs = request.keywords["skip_xp_backends"].kwargs
190+
skip_or_xfail_xp_backends(xp, backends, kwargs, skip_or_xfail='skip')
191+
192+
@pytest.fixture
193+
def xfail_xp_backends(xp, request):
194+
"""See the `skip_or_xfail_xp_backends` docstring."""
195+
if "xfail_xp_backends" not in request.keywords:
196+
return
197+
backends = request.keywords["xfail_xp_backends"].args
198+
kwargs = request.keywords["xfail_xp_backends"].kwargs
199+
skip_or_xfail_xp_backends(xp, backends, kwargs, skip_or_xfail='xfail')
200+
201+
202+
def skip_or_xfail_xp_backends(xp, backends, kwargs, skip_or_xfail='skip'):
181203
"""
182-
Skip based on the ``skip_xp_backends`` marker.
204+
Skip based on the ``skip_xp_backends`` or ``xfail_xp_backends`` marker.
183205
184206
See the "Support for the array API standard" docs page for usage examples.
185207
@@ -201,51 +223,50 @@ def skip_xp_backends(xp, request):
201223
any ``backends`` in this case. To specify a reason, pass a
202224
singleton list to ``reasons``. Default: ``False``.
203225
cpu_only : bool, optional
204-
When ``True``, the test is skipped on non-CPU devices.
226+
When ``True``, the test is skipped/x-failed on non-CPU devices.
205227
There is no need to provide any ``backends`` in this case,
206228
but any ``backends`` will also be skipped on the CPU.
207229
Default: ``False``.
208230
exceptions : list, optional
209-
A list of exceptions for use with `cpu_only`. This should be provided
210-
when delegation is implemented for some, but not all, non-CPU backends.
231+
A list of exceptions for use with `cpu_only` or `np_only`.
232+
This should be provided when delegation is implemented for some,
233+
but not all, non-CPU/non-NumPy backends.
211234
"""
212-
if "skip_xp_backends" not in request.keywords:
213-
return
214-
backends = request.keywords["skip_xp_backends"].args
215-
kwargs = request.keywords["skip_xp_backends"].kwargs
235+
skip_or_xfail = getattr(pytest, skip_or_xfail)
236+
216237
np_only = kwargs.get("np_only", False)
217238
cpu_only = kwargs.get("cpu_only", False)
218-
exceptions = kwargs.get("exceptions", None)
239+
exceptions = kwargs.get("exceptions", [])
219240

220241
# input validation
221242
if np_only and cpu_only:
222243
raise ValueError("at most one of `np_only` and `cpu_only` should be provided")
223-
if exceptions and not cpu_only:
224-
raise ValueError("`exceptions` is only valid alongside `cpu_only`")
244+
if exceptions and not (cpu_only or np_only):
245+
raise ValueError("`exceptions` is only valid alongside `cpu_only` or `np_only`")
225246

226247
if np_only:
227248
reasons = kwargs.get("reasons", ["do not run with non-NumPy backends."])
228249
if len(reasons) > 1:
229250
raise ValueError("please provide a singleton list to `reasons` "
230251
"when using `np_only`")
231252
reason = reasons[0]
232-
if xp.__name__ != 'numpy':
233-
pytest.skip(reason=reason)
253+
if xp.__name__ != 'numpy' and xp.__name__ not in exceptions:
254+
skip_or_xfail(reason=reason)
234255
return
235256
if cpu_only:
236257
reason = ("no array-agnostic implementation or delegation available "
237258
"for this backend and device")
238259
exceptions = [] if exceptions is None else exceptions
239260
if SCIPY_ARRAY_API and SCIPY_DEVICE != 'cpu':
240261
if xp.__name__ == 'cupy' and 'cupy' not in exceptions:
241-
pytest.skip(reason=reason)
262+
skip_or_xfail(reason=reason)
242263
elif xp.__name__ == 'torch' and 'torch' not in exceptions:
243264
if 'cpu' not in xp.empty(0).device.type:
244-
pytest.skip(reason=reason)
265+
skip_or_xfail(reason=reason)
245266
elif xp.__name__ == 'jax.numpy' and 'jax.numpy' not in exceptions:
246267
for d in xp.empty(0).devices():
247268
if 'cpu' not in d.device_kind:
248-
pytest.skip(reason=reason)
269+
skip_or_xfail(reason=reason)
249270

250271
if backends is not None:
251272
reasons = kwargs.get("reasons", False)
@@ -255,7 +276,7 @@ def skip_xp_backends(xp, request):
255276
reason = f"do not run with array API backend: {backend}"
256277
else:
257278
reason = reasons[i]
258-
pytest.skip(reason=reason)
279+
skip_or_xfail(reason=reason)
259280

260281

261282
# Following the approach of NumPy's conftest.py...

scipy/ndimage/tests/test_filters.py

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
from scipy.conftest import array_api_compatible
2323
skip_xp_backends = pytest.mark.skip_xp_backends
24+
xfail_xp_backends = pytest.mark.xfail_xp_backends
2425
pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends"),
26+
pytest.mark.usefixtures("xfail_xp_backends"),
2527
skip_xp_backends(cpu_only=True, exceptions=['cupy', 'jax.numpy']),]
2628

2729

@@ -179,12 +181,10 @@ def test_correlate01(self, xp):
179181
output = ndimage.convolve1d(array, weights)
180182
assert_array_almost_equal(output, expected)
181183

184+
@xfail_xp_backends('cupy', reason="Differs by a factor of two?")
182185
@skip_xp_backends("jax.numpy", reasons=["output array is read-only."],
183186
cpu_only=True, exceptions=['cupy', 'jax.numpy'])
184187
def test_correlate01_overlap(self, xp):
185-
if is_cupy(xp):
186-
pytest.xfail("Differs by a factor of two?")
187-
188188
array = xp.reshape(xp.arange(256), (16, 16))
189189
weights = xp.asarray([2])
190190
expected = 2 * array
@@ -334,12 +334,12 @@ def test_correlate12(self, xp):
334334
output = ndimage.convolve(array, kernel)
335335
assert_array_almost_equal(xp.asarray([[6, 8, 9], [9, 11, 12]]), output)
336336

337+
@xfail_xp_backends(np_only=True,
338+
reasons=["output=dtype is numpy-specific"],
339+
exceptions=['cupy'],)
337340
@pytest.mark.parametrize('dtype_array', types)
338341
@pytest.mark.parametrize('dtype_kernel', types)
339342
def test_correlate13(self, dtype_array, dtype_kernel, xp):
340-
if not (is_numpy(xp) or is_cupy(xp)):
341-
pytest.xfail("output=dtype is numpy-specific")
342-
343343
dtype_array = getattr(xp, dtype_array)
344344
dtype_kernel = getattr(xp, dtype_kernel)
345345

@@ -356,12 +356,12 @@ def test_correlate13(self, dtype_array, dtype_kernel, xp):
356356
assert_array_almost_equal(xp.asarray([[6, 8, 9], [9, 11, 12]]), output)
357357
assert output.dtype.type == dtype_kernel
358358

359+
@xfail_xp_backends(np_only=True,
360+
reasons=["output=dtype is numpy-specific"],
361+
exceptions=['cupy'],)
359362
@pytest.mark.parametrize('dtype_array', types)
360363
@pytest.mark.parametrize('dtype_output', types)
361364
def test_correlate14(self, dtype_array, dtype_output, xp):
362-
if not (is_numpy(xp) or is_cupy(xp)):
363-
pytest.xfail("output=dtype is numpy-specific")
364-
365365
dtype_array = getattr(xp, dtype_array)
366366
dtype_output = getattr(xp, dtype_output)
367367

@@ -378,11 +378,11 @@ def test_correlate14(self, dtype_array, dtype_output, xp):
378378
assert_array_almost_equal(xp.asarray([[6, 8, 9], [9, 11, 12]]), output)
379379
assert output.dtype.type == dtype_output
380380

381+
@xfail_xp_backends(np_only=True,
382+
reasons=["output=dtype is numpy-specific"],
383+
exceptions=['cupy'],)
381384
@pytest.mark.parametrize('dtype_array', types)
382385
def test_correlate15(self, dtype_array, xp):
383-
if not (is_numpy(xp) or is_cupy(xp)):
384-
pytest.xfail("output=dtype is numpy-specific")
385-
386386
dtype_array = getattr(xp, dtype_array)
387387

388388
kernel = xp.asarray([[1, 0],
@@ -397,11 +397,11 @@ def test_correlate15(self, dtype_array, xp):
397397
assert_array_almost_equal(xp.asarray([[6, 8, 9], [9, 11, 12]]), output)
398398
assert output.dtype.type == xp.float32
399399

400+
@xfail_xp_backends(np_only=True,
401+
reasons=["output=dtype is numpy-specific"],
402+
exceptions=['cupy'],)
400403
@pytest.mark.parametrize('dtype_array', types)
401404
def test_correlate16(self, dtype_array, xp):
402-
if not (is_numpy(xp) or is_cupy(xp)):
403-
pytest.xfail("output=dtype is numpy-specific")
404-
405405
dtype_array = getattr(xp, dtype_array)
406406

407407
kernel = xp.asarray([[0.5, 0],
@@ -429,11 +429,11 @@ def test_correlate17(self, xp):
429429
output = ndimage.convolve1d(array, kernel, origin=-1)
430430
assert_array_almost_equal(tcov, output)
431431

432+
@xfail_xp_backends(np_only=True,
433+
reasons=["output=dtype is numpy-specific"],
434+
exceptions=['cupy'],)
432435
@pytest.mark.parametrize('dtype_array', types)
433436
def test_correlate18(self, dtype_array, xp):
434-
if not (is_numpy(xp) or is_cupy(xp)):
435-
pytest.xfail("output=dtype is numpy-specific")
436-
437437
dtype_array = getattr(xp, dtype_array)
438438

439439
kernel = xp.asarray([[1, 0],
@@ -460,11 +460,11 @@ def test_correlate_mode_sequence(self, xp):
460460
with assert_raises(RuntimeError):
461461
ndimage.convolve(array, kernel, mode=['nearest', 'reflect'])
462462

463+
@xfail_xp_backends(np_only=True,
464+
reasons=["output=dtype is numpy-specific"],
465+
exceptions=['cupy'],)
463466
@pytest.mark.parametrize('dtype_array', types)
464467
def test_correlate19(self, dtype_array, xp):
465-
if not (is_numpy(xp) or is_cupy(xp)):
466-
pytest.xfail("output=dtype is numpy-specific")
467-
468468
dtype_array = getattr(xp, dtype_array)
469469

470470
kernel = xp.asarray([[1, 0],
@@ -483,12 +483,12 @@ def test_correlate19(self, dtype_array, xp):
483483
assert_array_almost_equal(xp.asarray([[3, 5, 6], [6, 8, 9]]), output)
484484
assert output.dtype.type == xp.float32
485485

486+
@xfail_xp_backends(np_only=True,
487+
reasons=["output=dtype is numpy-specific"],
488+
exceptions=['cupy'],)
486489
@pytest.mark.parametrize('dtype_array', types)
487490
@pytest.mark.parametrize('dtype_output', types)
488491
def test_correlate20(self, dtype_array, dtype_output, xp):
489-
if not (is_numpy(xp) or is_cupy(xp)):
490-
pytest.xfail("output=dtype is numpy-specific")
491-
492492
dtype_array = getattr(xp, dtype_array)
493493
dtype_output = getattr(xp, dtype_output)
494494

@@ -512,12 +512,12 @@ def test_correlate21(self, xp):
512512
output = ndimage.convolve1d(array, weights, axis=0)
513513
assert_array_almost_equal(output, expected)
514514

515+
@xfail_xp_backends(np_only=True,
516+
reasons=["output=dtype is numpy-specific"],
517+
exceptions=['cupy'],)
515518
@pytest.mark.parametrize('dtype_array', types)
516519
@pytest.mark.parametrize('dtype_output', types)
517520
def test_correlate22(self, dtype_array, dtype_output, xp):
518-
if not (is_numpy(xp) or is_cupy(xp)):
519-
pytest.xfail("output=dtype is numpy-specific")
520-
521521
dtype_array = getattr(xp, dtype_array)
522522
dtype_output = getattr(xp, dtype_output)
523523

@@ -603,15 +603,14 @@ def test_correlate26(self, xp):
603603
y = ndimage.correlate1d(xp.ones(1), xp.ones(5), mode='mirror')
604604
xp_assert_equal(y, xp.asarray([5.]))
605605

606+
@xfail_xp_backends(np_only=True,
607+
reasons=["output=dtype is numpy-specific"],
608+
exceptions=['cupy'],)
606609
@pytest.mark.parametrize('dtype_kernel', complex_types)
607610
@pytest.mark.parametrize('dtype_input', types)
608611
@pytest.mark.parametrize('dtype_output', complex_types)
609612
def test_correlate_complex_kernel(self, dtype_input, dtype_kernel,
610613
dtype_output, xp):
611-
if not (is_numpy(xp) or is_cupy(xp)):
612-
# XXX: the issue is in _validate_complex, _correlate_complex
613-
pytest.xfail("output=dtype are numpy-specific")
614-
615614
dtype_input = getattr(xp, dtype_input)
616615
dtype_kernel = getattr(xp, dtype_kernel)
617616
dtype_output = getattr(xp, dtype_output)
@@ -622,6 +621,9 @@ def test_correlate_complex_kernel(self, dtype_input, dtype_kernel,
622621
[4, 5, 6]], dtype=dtype_input)
623622
self._validate_complex(xp, array, kernel, dtype_output)
624623

624+
@xfail_xp_backends(np_only=True,
625+
reasons=["output=dtype is numpy-specific"],
626+
exceptions=['cupy'],)
625627
@pytest.mark.parametrize('dtype_kernel', complex_types)
626628
@pytest.mark.parametrize('dtype_input', types)
627629
@pytest.mark.parametrize('dtype_output', complex_types)
@@ -634,8 +636,6 @@ def test_correlate_complex_kernel_cval(self, dtype_input, dtype_kernel,
634636

635637
if is_cupy(xp) and mode == 'grid-constant':
636638
pytest.xfail('https://github.com/cupy/cupy/issues/8404')
637-
elif not is_numpy(xp):
638-
pytest.xfail("output= arrays are numpy-specific")
639639

640640
# test use of non-zero cval with complex inputs
641641
# also verifies that mode 'grid-constant' does not segfault
@@ -646,16 +646,14 @@ def test_correlate_complex_kernel_cval(self, dtype_input, dtype_kernel,
646646
self._validate_complex(xp, array, kernel, dtype_output, mode=mode,
647647
cval=5.0)
648648

649+
@xfail_xp_backends('cupy', reasons=["cupy/cupy#8405"])
649650
@pytest.mark.parametrize('dtype_kernel', complex_types)
650651
@pytest.mark.parametrize('dtype_input', types)
651652
def test_correlate_complex_kernel_invalid_cval(self, dtype_input,
652653
dtype_kernel, xp):
653654
dtype_input = getattr(xp, dtype_input)
654655
dtype_kernel = getattr(xp, dtype_kernel)
655656

656-
if is_cupy(xp):
657-
pytest.xfail("https://github.com/cupy/cupy/issues/8405")
658-
659657
# cannot give complex cval with a real image
660658
kernel = xp.asarray([[1, 0],
661659
[0, 1 + 1j]], dtype=dtype_kernel)
@@ -726,7 +724,10 @@ def test_correlate1d_complex_input(self, dtype_input, dtype_kernel,
726724
array = xp.asarray([1, 2j, 3, 1 + 4j, 5, 6j], dtype=dtype_input)
727725
self._validate_complex(xp, array, kernel, dtype_output)
728726

729-
@skip_xp_backends(np_only=True, reasons=['output=dtype is numpy-specific'])
727+
@xfail_xp_backends('cupy', reasons=["cupy/cupy#8405"])
728+
@skip_xp_backends(np_only=True,
729+
reasons=['output=dtype is numpy-specific'],
730+
exceptions=['cupy'])
730731
@pytest.mark.parametrize('dtype_kernel', types)
731732
@pytest.mark.parametrize('dtype_input', complex_types)
732733
@pytest.mark.parametrize('dtype_output', complex_types)
@@ -736,9 +737,6 @@ def test_correlate1d_complex_input_cval(self, dtype_input, dtype_kernel,
736737
dtype_kernel = getattr(xp, dtype_kernel)
737738
dtype_output = getattr(xp, dtype_output)
738739

739-
if is_cupy(xp):
740-
pytest.xfail("https://github.com/cupy/cupy/issues/8405")
741-
742740
kernel = xp.asarray([1, 0, 1], dtype=dtype_kernel)
743741
array = xp.asarray([1, 2j, 3, 1 + 4j, 5, 6j], dtype=dtype_input)
744742
self._validate_complex(xp, array, kernel, dtype_output, mode='constant',
@@ -757,15 +755,14 @@ def test_correlate_complex_input_and_kernel(self, dtype, dtype_output, xp):
757755
[1 + 4j, 5, 6j]], dtype=dtype)
758756
self._validate_complex(xp, array, kernel, dtype_output)
759757

758+
@xfail_xp_backends('cupy', reasons=["cupy/cupy#8405"])
759+
@skip_xp_backends(np_only=True,
760+
reasons=["output=dtype is numpy-specific"],
761+
exceptions=['cupy'],)
760762
@pytest.mark.parametrize('dtype', complex_types)
761763
@pytest.mark.parametrize('dtype_output', complex_types)
762764
def test_correlate_complex_input_and_kernel_cval(self, dtype,
763765
dtype_output, xp):
764-
if not is_numpy(xp):
765-
pytest.xfail("output=dtype is numpy-specific")
766-
elif is_cupy(xp):
767-
pytest.xfail("https://github.com/cupy/cupy/issues/8405")
768-
769766
dtype = getattr(xp, dtype)
770767
dtype_output = getattr(xp, dtype_output)
771768

scipy/ndimage/tests/test_morphology.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
from scipy.conftest import array_api_compatible
1414
skip_xp_backends = pytest.mark.skip_xp_backends
15+
xfail_xp_backends = pytest.mark.xfail_xp_backends
1516
pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends"),
17+
pytest.mark.usefixtures("xfail_xp_backends"),
1618
skip_xp_backends(cpu_only=True, exceptions=['cupy', 'jax.numpy'],)]
1719

1820

@@ -2560,9 +2562,8 @@ def test_black_tophat02(self, xp):
25602562
structure=structure)
25612563
assert_array_almost_equal(expected, output)
25622564

2565+
@xfail_xp_backends('cupy', reasons=["cupy/cupy#8399"])
25632566
def test_black_tophat03(self, xp):
2564-
if is_cupy(xp):
2565-
pytest.xfail("https://github.com/cupy/cupy/issues/8399")
25662567

25672568
array = np.asarray([[1, 0, 0, 0, 0, 0, 0],
25682569
[0, 1, 1, 1, 1, 1, 0],

0 commit comments

Comments
 (0)