Skip to content

Commit 7c53ded

Browse files
authored
Merge pull request #29 from honno/skip-extensions
Skip extension tests if the respective extension is not found or is passed via `--disable-extensions`
2 parents 07b4081 + 6e452fe commit 7c53ded

File tree

9 files changed

+173
-67
lines changed

9 files changed

+173
-67
lines changed

.github/workflows/numpy.yml

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,41 +22,26 @@ jobs:
2222
python -m pip install git+https://github.com/numpy/numpy
2323
python -m pip install -r requirements.txt
2424
- name: Run the test suite
25+
env:
26+
ARRAY_API_TESTS_MODULE: numpy.array_api
2527
run: |
2628
# Mark some known issues as XFAIL
27-
cat << EOF >> conftest.py
29+
cat << EOF >> xfails.txt
30+
31+
# https://github.com/numpy/numpy/issues/18881
32+
array_api_tests/test_creation_functions.py::test_linspace
33+
# einsum is not yet completed in the spec
34+
array_api_tests/test_signatures.py::test_has_names[einsum]
35+
# dlpack support is not yet implemented in NumPy
36+
# See https://github.com/numpy/numpy/pull/19083
37+
array_api_tests/test_signatures.py::test_function_positional_args[__dlpack__]
38+
array_api_tests/test_signatures.py::test_function_positional_args[__dlpack_device__]
39+
array_api_tests/test_signatures.py::test_function_positional_args[from_dlpack]
40+
array_api_tests/test_signatures.py::test_function_positional_args[to_device]
41+
array_api_tests/test_signatures.py::test_function_keyword_only_args[__dlpack__]
42+
# floor_divide has an issue related to https://github.com/data-apis/array-api/issues/264
43+
array_api_tests/test_elementwise_functions.py::test_floor_divide
2844
29-
names_to_be_xfailed = (
30-
# https://github.com/numpy/numpy/issues/18881
31-
"array_api_tests/test_creation_functions.py::test_linspace",
32-
# einsum is not yet completed in the spec
33-
"array_api_tests/test_signatures.py::test_has_names[einsum]",
34-
# The linalg extension is not yet implemented in NumPy
35-
"array_api_tests/test_signatures.py::test_has_names[linalg]",
36-
# dlpack support is not yet implemented in NumPy. https://github.com/numpy/numpy/pull/19083
37-
"array_api_tests/test_signatures.py::test_function_positional_args[__dlpack__]",
38-
"array_api_tests/test_signatures.py::test_function_positional_args[__dlpack_device__]",
39-
"array_api_tests/test_signatures.py::test_function_positional_args[from_dlpack]",
40-
"array_api_tests/test_signatures.py::test_function_keyword_only_args[__dlpack__]",
41-
42-
# Updates to the spec since the last change to numpy.array_api.
43-
# These will fail until NumPy is updated.
44-
"array_api_tests/test_signatures.py::test_has_names[__index__]",
45-
"array_api_tests/test_signatures.py::test_has_names[to_device]",
46-
"array_api_tests/test_signatures.py::test_has_names[mT]",
47-
"array_api_tests/test_signatures.py::test_has_names[tril]",
48-
"array_api_tests/test_signatures.py::test_has_names[triu]",
49-
"array_api_tests/test_signatures.py::test_has_names[matrix_transpose]",
50-
"array_api_tests/test_signatures.py::test_has_names[permute_dims]",
51-
"array_api_tests/test_signatures.py::test_function_positional_args[__index__]",
52-
"array_api_tests/test_signatures.py::test_function_keyword_only_args[prod]",
53-
"array_api_tests/test_signatures.py::test_function_keyword_only_args[sum]",
54-
)
55-
56-
def pytest_collection_modifyitems(config, items):
57-
for item in items:
58-
if item.nodeid in names_to_be_xfailed:
59-
item.add_marker("xfail")
6045
EOF
6146
62-
ARRAY_API_TESTS_MODULE=numpy.array_api pytest -v -rxXfE
47+
pytest -v -rxXfE

README.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,23 @@ specification that are not yet tested here.
1010

1111
## Running the tests
1212

13+
### Setup
14+
1315
To run the tests, first install the testing dependencies
1416

15-
pip install pytest hypothesis numpy
17+
pip install pytest hypothesis
1618

1719
or
1820

19-
conda install pytest hypothesis numpy
21+
conda install pytest hypothesis
22+
23+
as well as the array libraries that you want to test.
24+
25+
### Specifying the array module
2026

21-
as well as the array libraries that you want to test. (Note, in the future,
22-
NumPy will be removed as a dependency on the test suite). To run the tests,
23-
you need to set the array library that is to be tested. There are two ways to
24-
do this. One way is to set the `ARRAY_API_TESTS_MODULE` environment variable.
25-
For example
27+
To run the tests, you need to set the array library that is to be tested. There
28+
are two ways to do this. One way is to set the `ARRAY_API_TESTS_MODULE`
29+
environment variable. For example you can set it when running `pytest`
2630

2731
ARRAY_API_TESTS_MODULE=numpy pytest
2832

@@ -35,12 +39,21 @@ array_module = None
3539

3640
to
3741

38-
```
42+
```py
3943
import numpy as array_module
4044
```
4145

4246
(replacing `numpy` with the array module namespace to be tested).
4347

48+
### Specifying test cases
49+
50+
The test suite tries to logically organise its tests so you can find specific
51+
test cases whilst developing something in particular. So to avoid running the
52+
rather slow complete suite, you can specify particular test cases like any other
53+
test suite.
54+
55+
pytest array_api_tests/test_creation_functions.py::test_zeros
56+
4457
## Notes on Interpreting Errors
4558

4659
- Some tests cannot be run unless other tests pass first. This is because very
@@ -66,6 +79,12 @@ import numpy as array_module
6679

6780
## Configuring Tests
6881

82+
By default, tests for the optional Array API extensions such as
83+
[`linalg`](https://data-apis.org/array-api/latest/extensions/linear_algebra_functions.html)
84+
will be skipped if not present in the specified array module. You can purposely
85+
skip testing extension(s) via the `--disable-extension` option, and likewise
86+
purposely test them via the `--enable-extension` option.
87+
6988
The tests make heavy use of the
7089
[Hypothesis](https://hypothesis.readthedocs.io/en/latest/) testing library.
7190
Hypothesis generates random input values for the tests. You can configure how

array_api_tests/meta/test_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from ..test_signatures import extension_module
2+
3+
4+
def test_extension_module_is_extension():
5+
assert extension_module('linalg')
6+
7+
8+
def test_extension_func_is_not_extension():
9+
assert not extension_module('linalg.cross')

array_api_tests/test_linalg.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
1414
"""
1515

16+
import pytest
1617
from hypothesis import assume, given
1718
from hypothesis.strategies import (booleans, composite, none, tuples, integers,
1819
shared, sampled_from)
@@ -33,6 +34,7 @@
3334
from . import _array_module
3435
from ._array_module import linalg
3536

37+
3638
# Standin strategy for not yet implemented tests
3739
todo = none()
3840

@@ -74,6 +76,7 @@ def _test_namedtuple(res, fields, func_name):
7476
assert hasattr(res, field), f"{func_name}() result namedtuple doesn't have the '{field}' field"
7577
assert res[i] is getattr(res, field), f"{func_name}() result namedtuple '{field}' field is not in position {i}"
7678

79+
@pytest.mark.xp_extension('linalg')
7780
@given(
7881
x=positive_definite_matrices(),
7982
kw=kwargs(upper=booleans())
@@ -121,6 +124,7 @@ def cross_args(draw, dtype_objects=dh.numeric_dtypes):
121124
)
122125
return draw(arrays1), draw(arrays2), kw
123126

127+
@pytest.mark.xp_extension('linalg')
124128
@given(
125129
cross_args()
126130
)
@@ -159,6 +163,7 @@ def test_cross(x1_x2_kw):
159163
], dtype=res.dtype)
160164
assert_exactly_equal(res_stack, exact_cross)
161165

166+
@pytest.mark.xp_extension('linalg')
162167
@given(
163168
x=xps.arrays(dtype=xps.floating_dtypes(), shape=square_matrix_shapes),
164169
)
@@ -172,6 +177,7 @@ def test_det(x):
172177

173178
# TODO: Test that res actually corresponds to the determinant of x
174179

180+
@pytest.mark.xp_extension('linalg')
175181
@given(
176182
x=xps.arrays(dtype=dtypes, shape=matrix_shapes),
177183
# offset may produce an overflow if it is too large. Supporting offsets
@@ -206,6 +212,7 @@ def true_diag(x_stack):
206212

207213
_test_stacks(linalg.diagonal, x, **kw, res=res, dims=1, true_val=true_diag)
208214

215+
@pytest.mark.xp_extension('linalg')
209216
@given(x=symmetric_matrices(finite=True))
210217
def test_eigh(x):
211218
res = linalg.eigh(x)
@@ -229,6 +236,7 @@ def test_eigh(x):
229236
# TODO: Test that res actually corresponds to the eigenvalues and
230237
# eigenvectors of x
231238

239+
@pytest.mark.xp_extension('linalg')
232240
@given(x=symmetric_matrices(finite=True))
233241
def test_eigvalsh(x):
234242
res = linalg.eigvalsh(x)
@@ -242,6 +250,7 @@ def test_eigvalsh(x):
242250

243251
# TODO: Test that res actually corresponds to the eigenvalues of x
244252

253+
@pytest.mark.xp_extension('linalg')
245254
@given(x=invertible_matrices())
246255
def test_inv(x):
247256
res = linalg.inv(x)
@@ -286,6 +295,7 @@ def test_matmul(x1, x2):
286295
assert res.shape == stack_shape + (x1.shape[-2], x2.shape[-1])
287296
_test_stacks(_array_module.matmul, x1, x2, res=res)
288297

298+
@pytest.mark.xp_extension('linalg')
289299
@given(
290300
x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes),
291301
kw=kwargs(axis=todo, keepdims=todo, ord=todo)
@@ -295,6 +305,7 @@ def test_matrix_norm(x, kw):
295305
pass
296306

297307
matrix_power_n = shared(integers(-1000, 1000), key='matrix_power n')
308+
@pytest.mark.xp_extension('linalg')
298309
@given(
299310
# Generate any square matrix if n >= 0 but only invertible matrices if n < 0
300311
x=matrix_power_n.flatmap(lambda n: invertible_matrices() if n < 0 else
@@ -316,6 +327,7 @@ def test_matrix_power(x, n):
316327
func = lambda x: linalg.matrix_power(x, n)
317328
_test_stacks(func, x, res=res, true_val=true_val)
318329

330+
@pytest.mark.xp_extension('linalg')
319331
@given(
320332
x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes),
321333
kw=kwargs(rtol=todo)
@@ -341,6 +353,7 @@ def test_matrix_transpose(x):
341353

342354
_test_stacks(_array_module.matrix_transpose, x, res=res, true_val=true_val)
343355

356+
@pytest.mark.xp_extension('linalg')
344357
@given(
345358
*two_mutual_arrays(dtype_objs=dh.numeric_dtypes,
346359
two_shapes=tuples(one_d_shapes, one_d_shapes))
@@ -364,6 +377,7 @@ def test_outer(x1, x2):
364377

365378
assert_exactly_equal(res, true_res)
366379

380+
@pytest.mark.xp_extension('linalg')
367381
@given(
368382
x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes),
369383
kw=kwargs(rtol=todo)
@@ -372,6 +386,7 @@ def test_pinv(x, kw):
372386
# res = linalg.pinv(x, **kw)
373387
pass
374388

389+
@pytest.mark.xp_extension('linalg')
375390
@given(
376391
x=xps.arrays(dtype=xps.floating_dtypes(), shape=matrix_shapes),
377392
kw=kwargs(mode=sampled_from(['reduced', 'complete']))
@@ -407,6 +422,7 @@ def test_qr(x, kw):
407422
# Check that r is upper-triangular.
408423
assert_exactly_equal(r, _array_module.triu(r))
409424

425+
@pytest.mark.xp_extension('linalg')
410426
@given(
411427
x=xps.arrays(dtype=xps.floating_dtypes(), shape=square_matrix_shapes),
412428
)
@@ -464,6 +480,7 @@ def x2_shapes(draw):
464480
x2 = xps.arrays(dtype=xps.floating_dtypes(), shape=x2_shapes())
465481
return x1, x2
466482

483+
@pytest.mark.xp_extension('linalg')
467484
@given(*solve_args())
468485
def test_solve(x1, x2):
469486
# TODO: solve() is currently ambiguous, in that some inputs can be
@@ -476,6 +493,7 @@ def test_solve(x1, x2):
476493
# res = linalg.solve(x1, x2)
477494
pass
478495

496+
@pytest.mark.xp_extension('linalg')
479497
@given(
480498
x=finite_matrices,
481499
kw=kwargs(full_matrices=booleans())
@@ -503,6 +521,7 @@ def test_svd(x, kw):
503521
assert u.shape == (*stack, M, K)
504522
assert vh.shape == (*stack, K, N)
505523

524+
@pytest.mark.xp_extension('linalg')
506525
@given(
507526
x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes),
508527
)
@@ -519,6 +538,7 @@ def test_tensordot(x1, x2, kw):
519538
# res = _array_module.tensordot(x1, x2, **kw)
520539
pass
521540

541+
@pytest.mark.xp_extension('linalg')
522542
@given(
523543
x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes),
524544
kw=kwargs(offset=todo)
@@ -536,6 +556,7 @@ def test_vecdot(x1, x2, kw):
536556
# res = _array_module.vecdot(x1, x2, **kw)
537557
pass
538558

559+
@pytest.mark.xp_extension('linalg')
539560
@given(
540561
x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes),
541562
kw=kwargs(axis=todo, keepdims=todo, ord=todo)

array_api_tests/test_signatures.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,18 @@ def extension_module(name):
2626
if extension_module(n):
2727
extension_module_names.extend([f'{n}.{i}' for i in getattr(function_stubs, n).__all__])
2828

29-
all_names = function_stubs.__all__ + extension_module_names
29+
30+
params = []
31+
for name in function_stubs.__all__:
32+
marks = []
33+
if extension_module(name):
34+
marks.append(pytest.mark.xp_extension(name))
35+
params.append(pytest.param(name, marks=marks))
36+
for name in extension_module_names:
37+
ext = name.split('.')[0]
38+
mark = pytest.mark.xp_extension(ext)
39+
params.append(pytest.param(name, marks=[mark]))
40+
3041

3142
def array_method(name):
3243
return stub_module(name) == 'array_object'
@@ -130,7 +141,7 @@ def example_argument(arg, func_name, dtype):
130141
else:
131142
raise RuntimeError(f"Don't know how to test argument {arg}. Please update test_signatures.py")
132143

133-
@pytest.mark.parametrize('name', all_names)
144+
@pytest.mark.parametrize('name', params)
134145
def test_has_names(name):
135146
if extension_module(name):
136147
assert hasattr(mod, name), f'{mod_name} is missing the {name} extension'
@@ -146,7 +157,7 @@ def test_has_names(name):
146157
else:
147158
assert hasattr(mod, name), f"{mod_name} is missing the {function_category(name)} function {name}()"
148159

149-
@pytest.mark.parametrize('name', all_names)
160+
@pytest.mark.parametrize('name', params)
150161
def test_function_positional_args(name):
151162
# Note: We can't actually test that positional arguments are
152163
# positional-only, as that would require knowing the argument name and
@@ -224,7 +235,7 @@ def test_function_positional_args(name):
224235
# NumPy ufuncs raise ValueError instead of TypeError
225236
raises((TypeError, ValueError), lambda: mod_func(*args[:n]), f"{name}() should not accept {n} positional arguments")
226237

227-
@pytest.mark.parametrize('name', all_names)
238+
@pytest.mark.parametrize('name', params)
228239
def test_function_keyword_only_args(name):
229240
if extension_module(name):
230241
return

0 commit comments

Comments
 (0)