|
1 |
| -# Array API Standard Test Suite |
| 1 | +# Test Suite for Array API Compliance |
2 | 2 |
|
3 |
| -This is the test suite for the Python array API standard. |
| 3 | +This is the test suite for array libraries adopting the [Python Array API |
| 4 | +standard](https://data-apis.org/array-api/). |
4 | 5 |
|
5 |
| -**NOTE: This test suite is still a work in progress.** |
| 6 | +Note the suite is still a **work in progress**. Feedback and contributions are |
| 7 | +welcome! |
6 | 8 |
|
7 |
| -Feedback and contributions are welcome, but be aware that this suite is not |
8 |
| -yet completed. In particular, there are still many parts of the array API |
9 |
| -specification that are not yet tested here. |
10 |
| - |
11 |
| -## Running the tests |
| 9 | +## Quickstart |
12 | 10 |
|
13 | 11 | ### Setup
|
14 | 12 |
|
15 |
| -To run the tests, first install the testing dependencies |
| 13 | +To run the tests, install the testing dependencies. |
| 14 | + |
| 15 | +```bash |
| 16 | +$ pip install -r requirements |
| 17 | +``` |
16 | 18 |
|
17 |
| - pip install pytest hypothesis |
| 19 | +Ensure you have the array library that you want to test installed. |
18 | 20 |
|
19 |
| -or |
| 21 | +### Specifying the array module |
20 | 22 |
|
21 |
| - conda install pytest hypothesis |
| 23 | +You need to specify the array library to test. It can be specified via the |
| 24 | +`XPTESTS_MODULE` environment variable, e.g. |
22 | 25 |
|
23 |
| -as well as the array libraries that you want to test. |
| 26 | +```bash |
| 27 | +$ export XPTESTS_MODULE=numpy.array_api |
| 28 | +``` |
24 | 29 |
|
25 |
| -### Specifying the array module |
| 30 | +Alternately, change the `array_module` variable in `xptests/_array_module.py` |
| 31 | +line, e.g. |
26 | 32 |
|
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` |
| 33 | +```diff |
| 34 | +- array_module = None |
| 35 | ++ import numpy.array_api as array_module |
| 36 | +``` |
30 | 37 |
|
31 |
| - ARRAY_API_TESTS_MODULE=numpy pytest |
| 38 | +### Run the suite |
32 | 39 |
|
33 |
| -Alternately, edit the `array_api_tests/_array_module.py` file and change the |
34 |
| -line |
| 40 | +Simply run `pytest` against the `xptests/` folder to run the full suite. |
35 | 41 |
|
36 |
| -```py |
37 |
| -array_module = None |
| 42 | +```bash |
| 43 | +$ pytest xptests/ |
38 | 44 | ```
|
39 | 45 |
|
40 |
| -to |
| 46 | +The suite tries to logically organise its tests. `pytest` allows you to only run |
| 47 | +a specific test case, which is useful when developing functions. |
41 | 48 |
|
42 |
| -```py |
43 |
| -import numpy as array_module |
| 49 | +```bash |
| 50 | +$ pytest xptests/test_creation_functions.py::test_zeros |
44 | 51 | ```
|
45 | 52 |
|
46 |
| -(replacing `numpy` with the array module namespace to be tested). |
| 53 | +## What the test suite covers |
| 54 | + |
| 55 | +We are interested in array libraries conforming to the |
| 56 | +[spec](https://data-apis.org/array-api/latest/API_specification/index.html). |
| 57 | +Ideally this means that if a library has fully adopted the Array API, the test |
| 58 | +suite passes. We take great care to _not_ test things which are out-of-scope, so |
| 59 | +as to not unexpectedly fail the suite. |
| 60 | + |
| 61 | +### Primary tests |
| 62 | + |
| 63 | +Every function—including array object methods—has a respective test method. We |
| 64 | +use [Hypothesis](https://hypothesis.readthedocs.io/en/latest/) to generate a |
| 65 | +diverse set of valid inputs. This means array inputs will cover different dtypes |
| 66 | +and shapes, as well as contain interesting elements. These examples generate |
| 67 | +with interesting arrangements of non-array positional arguments and keyword |
| 68 | +arguments. |
| 69 | + |
| 70 | +Each test case will cover the following areas if relevant: |
| 71 | + |
| 72 | +* **Smoking**: We pass our generated examples to all functions. As these |
| 73 | + examples solely consist of *valid* inputs, we are testing that functions can |
| 74 | + be called using their documented inputs without raising errors. |
47 | 75 |
|
48 |
| -### Specifying test cases |
| 76 | +* **Data type**: For functions returning/modifying arrays, we assert that output |
| 77 | + arrays have the correct data types. Most functions |
| 78 | + [type-promote](https://data-apis.org/array-api/latest/API_specification/type_promotion.html) |
| 79 | + input arrays and some functions have bespoke rules—in both cases we simulate |
| 80 | + the correct behaviour to find the expected data types. |
49 | 81 |
|
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. |
| 82 | +* **Shape**: For functions returning/modifying arrays, we assert that output |
| 83 | + arrays have the correct shape. Most functions |
| 84 | + [broadcast](https://data-apis.org/array-api/latest/API_specification/broadcasting.html) |
| 85 | + input arrays and some functions have bespoke rules—in both cases we simulate |
| 86 | + the correct behaviour to find the expected shapes. |
54 | 87 |
|
55 |
| - pytest array_api_tests/test_creation_functions.py::test_zeros |
| 88 | +* **Values**: We assert output values (including the elements of |
| 89 | + returned/modified arrays) are as expected. Except for manipulation functions |
| 90 | + or special cases, the spec allows floating-point inputs to have inexact |
| 91 | + outputs, so with such examples we only assert values are roughly as expected. |
56 | 92 |
|
57 |
| -## Notes on Interpreting Errors |
| 93 | +### Additional tests |
58 | 94 |
|
59 |
| -- Some tests cannot be run unless other tests pass first. This is because very |
60 |
| - basic APIs such as certain array creation APIs are required for a large |
61 |
| - fraction of the tests to run. TODO: Write which tests are required to pass |
62 |
| - first here. |
| 95 | +In addition to having one test case for each function, we test other properties |
| 96 | +of the functions and some miscellaneous things. |
63 | 97 |
|
64 |
| -- If an error message involves `_UndefinedStub`, it means some name that is |
65 |
| - required for the test to run is not defined in the array library. |
| 98 | +* **Special cases**: For functions with special case behaviour, we assert that |
| 99 | + these functions return the correct values. |
66 | 100 |
|
67 |
| -- Due to the nature of the array api spec, virtually every array library will |
68 |
| - produce a large number of errors from nonconformance. It is still a work in |
69 |
| - progress to enable reporting the errors in a way that makes them easy to |
70 |
| - understand, even if there are a large number of them. |
| 101 | +* **Signatures**: We assert functions have the correct signatures. |
71 | 102 |
|
72 |
| -- The spec documents are the ground source of truth. If the test suite appears |
73 |
| - to be testing something that is different from the spec, or something that |
74 |
| - isn't actually mentioned in the spec, this is a bug. [Please report |
75 |
| - it](https://github.com/data-apis/array-api-tests/issues/new). Furthermore, |
76 |
| - be aware that some aspects of the spec are either impossible or extremely |
77 |
| - difficult to actually test, so they are not covered in the test suite (TODO: |
78 |
| - list what these are). |
| 103 | +* **Constants**: We assert that |
| 104 | + [constants](https://data-apis.org/array-api/latest/API_specification/constants.html) |
| 105 | + behave expectedly, are roughly the expected value, and that any related |
| 106 | + functions interact with them correctly. |
| 107 | + |
| 108 | +Be aware that some aspects of the spec are impractical or impossible to actually |
| 109 | +test, so they are not covered in the suite <!-- TODO: note what these are --> |
79 | 110 |
|
80 |
| -## Configuring Tests |
| 111 | +## Interpreting errors |
| 112 | + |
| 113 | +First and foremost, note that most tests have to assume that certain aspects of |
| 114 | +the Array API have been correctly adopted, as fundamental APIs such as array |
| 115 | +creation and equalities are hard requirements for many assertions. This means a |
| 116 | +test case for one function might fail because another function has bugs or even |
| 117 | +no implementation. |
| 118 | + |
| 119 | +This means adopting libraries at first will result in a vast number of errors |
| 120 | +due to cascading errors. Generally the nature of the spec means many granular |
| 121 | +details such as type promotion is likely going to also fail nearly-conforming |
| 122 | +functions. |
| 123 | + |
| 124 | +We hope to improve user experience in regards to "noisy" errors in |
| 125 | +[#51](https://github.com/data-apis/array-api-tests/issues/51). For now, if an |
| 126 | +error message involves `_UndefinedStub`, it means an attribute of the array |
| 127 | +library (including functions) and it's objects (e.g. the array) is missing. |
| 128 | + |
| 129 | +The spec is the suite's source of truth. If the suite appears to assume |
| 130 | +behaviour different from the spec, or test something that is not documented, |
| 131 | +this is a bug—please [report such |
| 132 | +issues](https://github.com/data-apis/array-api-tests/issues/) to us. |
| 133 | + |
| 134 | +## Configuration |
81 | 135 |
|
82 | 136 | By default, tests for the optional Array API extensions such as
|
83 | 137 | [`linalg`](https://data-apis.org/array-api/latest/extensions/linear_algebra_functions.html)
|
84 | 138 | will be skipped if not present in the specified array module. You can purposely
|
85 | 139 | skip testing extension(s) via the `--disable-extension` option, and likewise
|
86 | 140 | purposely test them via the `--enable-extension` option.
|
87 | 141 |
|
88 |
| -The tests make heavy use of the |
89 |
| -[Hypothesis](https://hypothesis.readthedocs.io/en/latest/) testing library. |
90 |
| -Hypothesis generates random input values for the tests. You can configure how |
91 |
| -many values are generated and run using the `--max-examples` flag. The default |
92 |
| -`--max-examples` is 100. For example, `--max-examples 50` will only generate |
93 |
| -half as many examples and as a result, the test suite will run in about half |
94 |
| -the time. Setting `--max-examples` to a lower value can be useful when you |
95 |
| -want to have a faster test run. It can also be useful to set `--max-examples` |
96 |
| -to a large value to do a longer, more rigorous run of the tests. For example, |
97 |
| -`--max-examples 10000` will do a very rigorous check of the tests, but may |
98 |
| -take a few hours to run. |
99 |
| - |
100 |
| -## Contributing |
101 |
| - |
102 |
| -### Adding Tests |
| 142 | +The tests make heavy use |
| 143 | +[Hypothesis](https://hypothesis.readthedocs.io/en/latest/). You can configure |
| 144 | +how many examples are generated using the `--max-examples` flag, which defaults |
| 145 | +to 100. Lower values can be useful for quick checks, and larger values should |
| 146 | +result in more rigorous runs. For example, `--max-examples 10000` may find bugs |
| 147 | +where default runs don't, but will take a much longer time. |
103 | 148 |
|
104 |
| -It is important that every test in the test suite only uses APIs that are part |
105 |
| -of the standard. This means that, for instance, when creating test arrays, you |
106 |
| -should only use array creation functions that are part of the spec, such as |
107 |
| -`ones` or `full`. It also means that many array testing functions that are |
108 |
| -built-in to libraries like numpy are reimplemented in the test suite (see |
109 |
| -`array_api_tests/pytest_helpers.py`, `array_api_tests/array_helpers.py`, and |
110 |
| -`array_api_tests/hypothesis_helpers.py`). |
| 149 | +<!-- TODO: howto on CI --> |
111 | 150 |
|
112 |
| -In order to enforce this, the `array_api_tests._array_module` should be used |
113 |
| -everywhere in place of the actual array module that is being tested. |
| 151 | +## Contributing |
114 | 152 |
|
115 |
| -### Hypothesis |
| 153 | +### Remain in-scope |
116 | 154 |
|
117 |
| -The test suite uses [Hypothesis](https://hypothesis.readthedocs.io/en/latest/) |
118 |
| -to generate random input data. Any test that should be applied over all |
119 |
| -possible array inputs should use hypothesis tests. Custom Hypothesis |
120 |
| -strategies are in the `array_api_tests/hypothesis_helpers.py` file. |
| 155 | +It is important that every test only uses APIs that are part of the standard. |
| 156 | +For instance, when creating input arrays you should only use the [array creation |
| 157 | +functions](https://data-apis.org/array-api/latest/API_specification/creation_functions.html) |
| 158 | +that are documented in the spec. The same goes for testing arrays—you'll find |
| 159 | +many utilities that parralel NumPy's own test utils in the `*_helpers.py` files. |
121 | 160 |
|
122 |
| -### Parameterization |
| 161 | +### Tools |
123 | 162 |
|
124 |
| -Any test that applies over all functions in a module should use |
125 |
| -`pytest.mark.parametrize` to parameterize over them. For example, |
| 163 | +Hypothesis should always be used for the primary tests, and can be useful |
| 164 | +elsewhere. Effort should be made so drawn arguments are labeled with their |
| 165 | +respective names. For |
| 166 | +[`st.data()`](https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.data), |
| 167 | +draws should be accompanied with the `label` kwarg i.e. `data.draw(<strategy>, |
| 168 | +label=<label>)`. |
126 | 169 |
|
127 |
| -```py |
128 |
| -from . import function_stubs |
| 170 | +[`pytest.mark.parametrize`](https://docs.pytest.org/en/latest/how-to/parametrize.html) |
| 171 | +should be used to run tests over multiple arguments. Parameterization should be |
| 172 | +preferred over using Hypothesis when there are a small number of possible |
| 173 | +inputs, as this allows better failure reporting. Note using both parametrize and |
| 174 | +Hypothesis for a single test method is possible and can be quite useful. |
129 | 175 |
|
130 |
| -@pytest.mark.parametrize('name', function_stubs.__all__) |
131 |
| -def test_whatever(name): |
132 |
| - ... |
133 |
| -``` |
| 176 | +### Error messages |
134 | 177 |
|
135 |
| -will parameterize `test_whatever` over all the functions stubs generated from |
136 |
| -the spec. Parameterization should be preferred over using Hypothesis whenever |
137 |
| -there are a finite number of input possibilities, as this will cause pytest to |
138 |
| -report failures for all input values separately, as opposed to Hypothesis |
139 |
| -which will only report one failure. |
| 178 | +Any assertion should be accompanied with a descriptive error message, including |
| 179 | +the relevant values. Error messages should be self-explanatory as to why a given |
| 180 | +test fails, as one should not need prior knowledge of how the test is |
| 181 | +implemented. |
140 | 182 |
|
141 |
| -### Error Strings |
| 183 | +### Generated files |
142 | 184 |
|
143 |
| -Any assertion or exception should be accompanied with a useful error message. |
144 |
| -The test suite is designed to be ran by people who are not familiar with the |
145 |
| -test suite code, so the error messages should be self explanatory as to why |
146 |
| -the module fails a given test. |
| 185 | +Some files in the suite are automatically generated from the spec, and should |
| 186 | +not be edited directly. To regenerate these files, run the script |
147 | 187 |
|
148 |
| -### Meta-errors |
| 188 | + ./generate_stubs.py path/to/array-api |
149 | 189 |
|
150 |
| -Any error that indicates a bug in the test suite itself, rather than in the |
151 |
| -array module not following the spec, should use `RuntimeError` whenever |
152 |
| -possible. |
| 190 | +where `path/to/array-api` is the path to a local clone of the [`array-api` |
| 191 | +repo](https://github.com/data-apis/array-api/). Edit `generate_stubs.py` to make |
| 192 | +changes to the generated files. |
153 | 193 |
|
154 |
| -(TODO: Update this policy to something better. See [#5](https://github.com/data-apis/array-api-tests/issues/5).) |
| 194 | +## Future plans |
155 | 195 |
|
156 |
| -### Automatically Generated Files |
| 196 | +Keeping full coverage of the spec is an on-going priority as the Array API |
| 197 | +evolves. |
157 | 198 |
|
158 |
| -Some files in the test suite are automatically generated from the API spec |
159 |
| -files. These files should not be edited directly. To regenerate these files, |
160 |
| -run the script |
| 199 | +Additionally, we have features and general improvements planned. Work on such |
| 200 | +functionality is guided primarily by the concerete needs of developers |
| 201 | +implementing and using the Array API—be sure to [let us |
| 202 | +know](https://github.com/data-apis/array-api-tests/issues) any limitations you |
| 203 | +come across. |
161 | 204 |
|
162 |
| - ./generate_stubs.py path/to/array-api |
| 205 | +* A dependency graph for every test case, which could be used to modify pytest's |
| 206 | + collection so that low-dependency tests are run first, and tests with faulty |
| 207 | + dependencies would skip/xfail. |
163 | 208 |
|
164 |
| -where `path/to/array-api` is the path to the local clone of the `array-api` |
165 |
| -repo. To modify the automatically generated files, edit the code that |
166 |
| -generates them in the `generate_stubs.py` script. |
| 209 | +* In some tests we've found it difficult to find appropaite assertion parameters |
| 210 | + for output values (particularly epsilons for floating-point outputs), so we |
| 211 | + need to review these and either implement assertions or properly note the lack |
| 212 | + thereof. |
0 commit comments