pytest-arraydiff 0.5.0__tar.gz → 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. pytest-arraydiff-0.6.1/.github/workflows/ci_workflows.yml +40 -0
  2. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/CHANGES.md +15 -0
  3. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/PKG-INFO +21 -10
  4. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/README.rst +10 -4
  5. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pytest_arraydiff/plugin.py +156 -78
  6. pytest-arraydiff-0.6.1/pytest_arraydiff/version.py +16 -0
  7. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pytest_arraydiff.egg-info/PKG-INFO +21 -10
  8. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pytest_arraydiff.egg-info/SOURCES.txt +5 -3
  9. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pytest_arraydiff.egg-info/entry_points.txt +0 -1
  10. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pytest_arraydiff.egg-info/requires.txt +2 -0
  11. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/setup.cfg +9 -1
  12. pytest-arraydiff-0.6.1/tests/baseline/test_relative_tolerance.fits +0 -0
  13. pytest-arraydiff-0.6.1/tests/baseline/test_single_reference.fits +0 -0
  14. pytest-arraydiff-0.6.1/tests/baseline/test_succeeds_func_pdhdf.h5 +0 -0
  15. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/tests/test_pytest_arraydiff.py +47 -3
  16. pytest-arraydiff-0.6.1/tox.ini +47 -0
  17. pytest-arraydiff-0.5.0/.github/workflows/ci_workflows.yml +0 -53
  18. pytest-arraydiff-0.5.0/.github/workflows/publish.yml +0 -48
  19. pytest-arraydiff-0.5.0/pytest_arraydiff/version.py +0 -5
  20. pytest-arraydiff-0.5.0/tox.ini +0 -33
  21. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/.gitignore +0 -0
  22. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/LICENSE +0 -0
  23. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/MANIFEST.in +0 -0
  24. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pyproject.toml +0 -0
  25. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pytest_arraydiff/__init__.py +0 -0
  26. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pytest_arraydiff.egg-info/dependency_links.txt +0 -0
  27. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pytest_arraydiff.egg-info/not-zip-safe +0 -0
  28. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/pytest_arraydiff.egg-info/top_level.txt +0 -0
  29. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/setup.py +0 -0
  30. /pytest-arraydiff-0.5.0/tests/baseline/test_tolerance.fits → /pytest-arraydiff-0.6.1/tests/baseline/test_absolute_tolerance.fits +0 -0
  31. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/tests/baseline/test_succeeds_class.fits +0 -0
  32. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/tests/baseline/test_succeeds_func_default.txt +0 -0
  33. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/tests/baseline/test_succeeds_func_fits.fits +0 -0
  34. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/tests/baseline/test_succeeds_func_fits_hdu.fits +0 -0
  35. {pytest-arraydiff-0.5.0 → pytest-arraydiff-0.6.1}/tests/baseline/test_succeeds_func_text.txt +0 -0
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ schedule:
7
+ # Run every Sunday at 06:53 UTC
8
+ - cron: 53 6 * * 0
9
+
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ tests:
16
+ uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1
17
+ with:
18
+ envs: |
19
+ - linux: codestyle
20
+ - linux: py37-test-pytestoldest
21
+ - macos: py37-test-pytest50
22
+ - windows: py38-test-pytest52
23
+ - linux: py38-test-pytest53
24
+ - macos: py39-test-pytest60
25
+ - windows: py39-test-pytest61
26
+ - linux: py310-test-pytest62
27
+ - macos: py310-test-pytest70
28
+ - windows: py310-test-pytest71
29
+ - linux: py311-test-pytest72
30
+ - macos: py311-test-pytest73
31
+ - windows: py312-test-pytest74
32
+ - linux: py312-test-devdeps
33
+ publish:
34
+ needs: tests
35
+ uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@v1
36
+ with:
37
+ test_extras: test
38
+ test_command: pytest $GITHUB_WORKSPACE/tests; pytest --arraydiff $GITHUB_WORKSPACE/tests
39
+ secrets:
40
+ pypi_token: ${{ secrets.pypi_password }}
@@ -1,3 +1,18 @@
1
+ 0.6.1 (2023-11-27)
2
+ ------------------
3
+
4
+ - Fix broken ``single_reference=True`` usage. [#43]
5
+
6
+ 0.6 (2023-11-15)
7
+ ----------------
8
+
9
+ - Add ability to compare to Pandas DataFrames and store them as HDF5 files [#23]
10
+
11
+ - Fix ``array_compare`` so that the ``atol`` parameter is correctly used with
12
+ FITS files. [#33]
13
+
14
+ - Test inside ``pytest_runtest_call`` hook. [#36]
15
+
1
16
  0.5 (2022-01-12)
2
17
  ----------------
3
18
 
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytest-arraydiff
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: pytest plugin to help with comparing array output from tests
5
5
  Home-page: https://github.com/astropy/pytest-arraydiff
6
6
  Author: The Astropy Developers
7
7
  Author-email: astropy.team@gmail.com
8
8
  License: BSD
9
- Platform: UNKNOWN
10
9
  Classifier: Development Status :: 4 - Beta
11
10
  Classifier: Framework :: Pytest
12
11
  Classifier: Intended Audience :: Developers
@@ -15,16 +14,24 @@ Classifier: Operating System :: OS Independent
15
14
  Classifier: Programming Language :: Python
16
15
  Classifier: Programming Language :: Python :: 3
17
16
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.6
19
17
  Classifier: Programming Language :: Python :: 3.7
20
18
  Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
21
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
22
24
  Classifier: Topic :: Software Development :: Testing
23
25
  Classifier: Topic :: Utilities
24
26
  Requires-Python: >=3.7
25
27
  Description-Content-Type: text/x-rst
26
- Provides-Extra: test
27
28
  License-File: LICENSE
29
+ Requires-Dist: pytest>=4.6
30
+ Requires-Dist: numpy
31
+ Provides-Extra: test
32
+ Requires-Dist: astropy; extra == "test"
33
+ Requires-Dist: pandas; extra == "test"
34
+ Requires-Dist: tables; extra == "test"
28
35
 
29
36
  .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.5811772.svg
30
37
  :target: https://doi.org/10.5281/zenodo.5811772
@@ -34,6 +41,10 @@ License-File: LICENSE
34
41
  :target: https://github.com/astropy/pytest-arraydiff/actions
35
42
  :alt: CI Status
36
43
 
44
+ .. image:: https://img.shields.io/pypi/v/pytest-arraydiff.svg
45
+ :target: https://pypi.org/project/pytest-arraydiff
46
+ :alt: PyPI Status
47
+
37
48
  About
38
49
  -----
39
50
 
@@ -43,7 +54,8 @@ in cases where the arrays are too large to conveniently hard-code them
43
54
  in the tests (e.g. ``np.testing.assert_allclose(x, [1, 2, 3])``).
44
55
 
45
56
  The basic idea is that you can write a test that generates a Numpy array (or
46
- other related objects depending on the format). You can then either run the
57
+ other related objects depending on the format, e.g. pandas DataFrame).
58
+ You can then either run the
47
59
  tests in a mode to **generate** reference files from the arrays, or you can run
48
60
  the tests in **comparison** mode, which will compare the results of the tests to
49
61
  the reference ones within some tolerance.
@@ -53,6 +65,7 @@ At the moment, the supported file formats for the reference files are:
53
65
  - A plain text-based format (based on Numpy ``loadtxt`` output)
54
66
  - The FITS format (requires `astropy <http://www.astropy.org>`__). With this
55
67
  format, tests can return either a Numpy array for a FITS HDU object.
68
+ - A pandas HDF5 format using the pandas HDFStore
56
69
 
57
70
  For more information on how to write tests to do this, see the **Using**
58
71
  section below.
@@ -137,12 +150,12 @@ The supported formats at this time are ``text`` and ``fits``, and
137
150
  contributions for other formats are welcome. The default format is
138
151
  ``text``.
139
152
 
140
- Another argument is the relative tolerance for floating point values
141
- (which defaults to 1e-7):
153
+ Additional arguments are the relative and absolute tolerances for floating
154
+ point values (which default to 1e-7 and 0, respectively):
142
155
 
143
156
  .. code:: python
144
157
 
145
- @pytest.mark.array_compare(rtol=20)
158
+ @pytest.mark.array_compare(rtol=20, atol=0.1)
146
159
  def test_array():
147
160
  ...
148
161
 
@@ -220,5 +233,3 @@ install the latest version of the plugin then do::
220
233
 
221
234
  The reason for having to install the plugin first is to ensure that the
222
235
  plugin is correctly loaded as part of the test suite.
223
-
224
-
@@ -6,6 +6,10 @@
6
6
  :target: https://github.com/astropy/pytest-arraydiff/actions
7
7
  :alt: CI Status
8
8
 
9
+ .. image:: https://img.shields.io/pypi/v/pytest-arraydiff.svg
10
+ :target: https://pypi.org/project/pytest-arraydiff
11
+ :alt: PyPI Status
12
+
9
13
  About
10
14
  -----
11
15
 
@@ -15,7 +19,8 @@ in cases where the arrays are too large to conveniently hard-code them
15
19
  in the tests (e.g. ``np.testing.assert_allclose(x, [1, 2, 3])``).
16
20
 
17
21
  The basic idea is that you can write a test that generates a Numpy array (or
18
- other related objects depending on the format). You can then either run the
22
+ other related objects depending on the format, e.g. pandas DataFrame).
23
+ You can then either run the
19
24
  tests in a mode to **generate** reference files from the arrays, or you can run
20
25
  the tests in **comparison** mode, which will compare the results of the tests to
21
26
  the reference ones within some tolerance.
@@ -25,6 +30,7 @@ At the moment, the supported file formats for the reference files are:
25
30
  - A plain text-based format (based on Numpy ``loadtxt`` output)
26
31
  - The FITS format (requires `astropy <http://www.astropy.org>`__). With this
27
32
  format, tests can return either a Numpy array for a FITS HDU object.
33
+ - A pandas HDF5 format using the pandas HDFStore
28
34
 
29
35
  For more information on how to write tests to do this, see the **Using**
30
36
  section below.
@@ -109,12 +115,12 @@ The supported formats at this time are ``text`` and ``fits``, and
109
115
  contributions for other formats are welcome. The default format is
110
116
  ``text``.
111
117
 
112
- Another argument is the relative tolerance for floating point values
113
- (which defaults to 1e-7):
118
+ Additional arguments are the relative and absolute tolerances for floating
119
+ point values (which default to 1e-7 and 0, respectively):
114
120
 
115
121
  .. code:: python
116
122
 
117
- @pytest.mark.array_compare(rtol=20)
123
+ @pytest.mark.array_compare(rtol=20, atol=0.1)
118
124
  def test_array():
119
125
  ...
120
126
 
@@ -28,9 +28,6 @@
28
28
  #
29
29
  # https://github.com/astrofrog/pytest-mpl
30
30
 
31
-
32
- from functools import wraps
33
-
34
31
  import os
35
32
  import abc
36
33
  import shutil
@@ -116,8 +113,9 @@ class FITSDiff(BaseDiff):
116
113
  from astropy.io.fits.diff import FITSDiff
117
114
  from astropy.utils.introspection import minversion
118
115
  if minversion(astropy, '2.0'):
119
- diff = FITSDiff(reference_file, test_file, rtol=rtol)
116
+ diff = FITSDiff(reference_file, test_file, rtol=rtol, atol=atol)
120
117
  else:
118
+ # `atol` is not supported prior to Astropy 2.0
121
119
  diff = FITSDiff(reference_file, test_file, tolerance=rtol)
122
120
  return diff.identical, diff.report()
123
121
 
@@ -137,9 +135,43 @@ class TextDiff(SimpleArrayDiff):
137
135
  return np.savetxt(filename, data, **kwargs)
138
136
 
139
137
 
138
+ class PDHDFDiff(BaseDiff):
139
+
140
+ extension = 'h5'
141
+
142
+ @staticmethod
143
+ def read(filename):
144
+ import pandas as pd
145
+ return pd.read_hdf(filename)
146
+
147
+ @staticmethod
148
+ def write(filename, data, **kwargs):
149
+ import pandas as pd # noqa: F401
150
+ key = os.path.basename(filename).replace('.h5', '')
151
+ return data.to_hdf(filename, key=key, **kwargs)
152
+
153
+ @classmethod
154
+ def compare(cls, reference_file, test_file, atol=None, rtol=None):
155
+ import pandas.testing as pdt
156
+ import pandas as pd
157
+
158
+ ref_data = pd.read_hdf(reference_file)
159
+ test_data = pd.read_hdf(test_file)
160
+ try:
161
+ pdt.assert_frame_equal(ref_data, test_data)
162
+ except AssertionError as exc:
163
+ message = "\n\na: {0}".format(test_file) + '\n'
164
+ message += "b: {0}".format(reference_file) + '\n'
165
+ message += exc.args[0]
166
+ return False, message
167
+ else:
168
+ return True, ""
169
+
170
+
140
171
  FORMATS = {}
141
172
  FORMATS['fits'] = FITSDiff
142
173
  FORMATS['text'] = TextDiff
174
+ FORMATS['pd_hdf'] = PDHDFDiff
143
175
 
144
176
 
145
177
  def _download_file(url):
@@ -166,7 +198,7 @@ def pytest_addoption(parser):
166
198
  def pytest_configure(config):
167
199
  config.getini('markers').append(
168
200
  'array_compare: for functions using array comparison')
169
-
201
+
170
202
  if config.getoption("--arraydiff") or config.getoption("--arraydiff-generate-path") is not None:
171
203
 
172
204
  reference_dir = config.getoption("--arraydiff-reference-path")
@@ -186,6 +218,37 @@ def pytest_configure(config):
186
218
  reference_dir=reference_dir,
187
219
  generate_dir=generate_dir,
188
220
  default_format=default_format))
221
+ else:
222
+ config.pluginmanager.register(ArrayInterceptor(config))
223
+
224
+
225
+ def generate_test_name(item):
226
+ """
227
+ Generate a unique name for this test.
228
+ """
229
+ if item.cls is not None:
230
+ name = f"{item.module.__name__}.{item.cls.__name__}.{item.name}"
231
+ else:
232
+ name = f"{item.module.__name__}.{item.name}"
233
+ return name
234
+
235
+
236
+ def wrap_array_interceptor(plugin, item):
237
+ """
238
+ Intercept and store arrays returned by test functions.
239
+ """
240
+ # Only intercept array on marked array tests
241
+ if item.get_closest_marker('array_compare') is not None:
242
+
243
+ # Use the full test name as a key to ensure correct array is being retrieved
244
+ test_name = generate_test_name(item)
245
+
246
+ def array_interceptor(store, obj):
247
+ def wrapper(*args, **kwargs):
248
+ store.return_value[test_name] = obj(*args, **kwargs)
249
+ return wrapper
250
+
251
+ item.obj = array_interceptor(plugin, item.obj)
189
252
 
190
253
 
191
254
  class ArrayComparison(object):
@@ -195,12 +258,15 @@ class ArrayComparison(object):
195
258
  self.reference_dir = reference_dir
196
259
  self.generate_dir = generate_dir
197
260
  self.default_format = default_format
261
+ self.return_value = {}
198
262
 
199
- def pytest_runtest_setup(self, item):
263
+ @pytest.hookimpl(hookwrapper=True)
264
+ def pytest_runtest_call(self, item):
200
265
 
201
266
  compare = item.get_closest_marker('array_compare')
202
267
 
203
268
  if compare is None:
269
+ yield
204
270
  return
205
271
 
206
272
  file_format = compare.kwargs.get('file_format', self.default_format)
@@ -220,85 +286,97 @@ class ArrayComparison(object):
220
286
 
221
287
  write_kwargs = compare.kwargs.get('write_kwargs', {})
222
288
 
223
- original = item.function
289
+ reference_dir = compare.kwargs.get('reference_dir', None)
290
+ if reference_dir is None:
291
+ if self.reference_dir is None:
292
+ reference_dir = os.path.join(os.path.dirname(item.fspath.strpath), 'reference')
293
+ else:
294
+ reference_dir = self.reference_dir
295
+ else:
296
+ if not reference_dir.startswith(('http://', 'https://')):
297
+ reference_dir = os.path.join(os.path.dirname(item.fspath.strpath), reference_dir)
298
+
299
+ baseline_remote = reference_dir.startswith('http')
224
300
 
225
- @wraps(item.function)
226
- def item_function_wrapper(*args, **kwargs):
301
+ # Run test and get array object
302
+ wrap_array_interceptor(self, item)
303
+ yield
304
+ test_name = generate_test_name(item)
305
+ if test_name not in self.return_value:
306
+ # Test function did not complete successfully
307
+ return
308
+ array = self.return_value[test_name]
227
309
 
228
- reference_dir = compare.kwargs.get('reference_dir', None)
229
- if reference_dir is None:
230
- if self.reference_dir is None:
231
- reference_dir = os.path.join(os.path.dirname(item.fspath.strpath), 'reference')
232
- else:
233
- reference_dir = self.reference_dir
310
+ # Find test name to use as plot name
311
+ filename = compare.kwargs.get('filename', None)
312
+ if filename is None:
313
+ if single_reference:
314
+ filename = item.originalname + '.' + extension
234
315
  else:
235
- if not reference_dir.startswith(('http://', 'https://')):
236
- reference_dir = os.path.join(os.path.dirname(item.fspath.strpath), reference_dir)
237
-
238
- baseline_remote = reference_dir.startswith('http')
239
-
240
- # Run test and get figure object
241
- import inspect
242
- if inspect.ismethod(original): # method
243
- array = original(*args[1:], **kwargs)
244
- else: # function
245
- array = original(*args, **kwargs)
246
-
247
- # Find test name to use as plot name
248
- filename = compare.kwargs.get('filename', None)
249
- if filename is None:
250
- if single_reference:
251
- filename = original.__name__ + '.' + extension
252
- else:
253
- filename = item.name + '.' + extension
254
- filename = filename.replace('[', '_').replace(']', '_')
255
- filename = filename.replace('_.' + extension, '.' + extension)
256
-
257
- # What we do now depends on whether we are generating the reference
258
- # files or simply running the test.
259
- if self.generate_dir is None:
260
-
261
- # Save the figure
262
- result_dir = tempfile.mkdtemp()
263
- test_array = os.path.abspath(os.path.join(result_dir, filename))
264
-
265
- FORMATS[file_format].write(test_array, array, **write_kwargs)
266
-
267
- # Find path to baseline array
268
- if baseline_remote:
269
- baseline_file_ref = _download_file(reference_dir + filename)
270
- else:
271
- baseline_file_ref = os.path.abspath(os.path.join(os.path.dirname(item.fspath.strpath), reference_dir, filename))
272
-
273
- if not os.path.exists(baseline_file_ref):
274
- raise Exception("""File not found for comparison test
275
- Generated file:
276
- \t{test}
277
- This is expected for new tests.""".format(
278
- test=test_array))
279
-
280
- # setuptools may put the baseline arrays in non-accessible places,
281
- # copy to our tmpdir to be sure to keep them in case of failure
282
- baseline_file = os.path.abspath(os.path.join(result_dir, 'reference-' + filename))
283
- shutil.copyfile(baseline_file_ref, baseline_file)
284
-
285
- identical, msg = FORMATS[file_format].compare(baseline_file, test_array, atol=atol, rtol=rtol)
286
-
287
- if identical:
288
- shutil.rmtree(result_dir)
289
- else:
290
- raise Exception(msg)
316
+ filename = item.name + '.' + extension
317
+ filename = filename.replace('[', '_').replace(']', '_')
318
+ filename = filename.replace('_.' + extension, '.' + extension)
319
+
320
+ # What we do now depends on whether we are generating the reference
321
+ # files or simply running the test.
322
+ if self.generate_dir is None:
323
+
324
+ # Save the figure
325
+ result_dir = tempfile.mkdtemp()
326
+ test_array = os.path.abspath(os.path.join(result_dir, filename))
327
+
328
+ FORMATS[file_format].write(test_array, array, **write_kwargs)
291
329
 
330
+ # Find path to baseline array
331
+ if baseline_remote:
332
+ baseline_file_ref = _download_file(reference_dir + filename)
292
333
  else:
334
+ baseline_file_ref = os.path.abspath(os.path.join(os.path.dirname(item.fspath.strpath), reference_dir, filename))
293
335
 
294
- if not os.path.exists(self.generate_dir):
295
- os.makedirs(self.generate_dir)
336
+ if not os.path.exists(baseline_file_ref):
337
+ raise Exception("""File not found for comparison test
338
+ Generated file:
339
+ \t{test}
340
+ This is expected for new tests.""".format(
341
+ test=test_array))
296
342
 
297
- FORMATS[file_format].write(os.path.abspath(os.path.join(self.generate_dir, filename)), array, **write_kwargs)
343
+ # setuptools may put the baseline arrays in non-accessible places,
344
+ # copy to our tmpdir to be sure to keep them in case of failure
345
+ baseline_file = os.path.abspath(os.path.join(result_dir, 'reference-' + filename))
346
+ shutil.copyfile(baseline_file_ref, baseline_file)
298
347
 
299
- pytest.skip("Skipping test, since generating data")
348
+ identical, msg = FORMATS[file_format].compare(baseline_file, test_array, atol=atol, rtol=rtol)
349
+
350
+ if identical:
351
+ shutil.rmtree(result_dir)
352
+ else:
353
+ raise Exception(msg)
300
354
 
301
- if item.cls is not None:
302
- setattr(item.cls, item.function.__name__, item_function_wrapper)
303
355
  else:
304
- item.obj = item_function_wrapper
356
+
357
+ if not os.path.exists(self.generate_dir):
358
+ os.makedirs(self.generate_dir)
359
+
360
+ FORMATS[file_format].write(os.path.abspath(os.path.join(self.generate_dir, filename)), array, **write_kwargs)
361
+
362
+ pytest.skip("Skipping test, since generating data")
363
+
364
+
365
+ class ArrayInterceptor:
366
+ """
367
+ This is used in place of ArrayComparison when the array comparison option is not used,
368
+ to make sure that we still intercept arrays returned by tests.
369
+ """
370
+
371
+ def __init__(self, config):
372
+ self.config = config
373
+ self.return_value = {}
374
+
375
+ @pytest.hookimpl(hookwrapper=True)
376
+ def pytest_runtest_call(self, item):
377
+
378
+ if item.get_closest_marker('array_compare') is not None:
379
+ wrap_array_interceptor(self, item)
380
+
381
+ yield
382
+ return
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.6.1'
16
+ __version_tuple__ = version_tuple = (0, 6, 1)
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytest-arraydiff
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: pytest plugin to help with comparing array output from tests
5
5
  Home-page: https://github.com/astropy/pytest-arraydiff
6
6
  Author: The Astropy Developers
7
7
  Author-email: astropy.team@gmail.com
8
8
  License: BSD
9
- Platform: UNKNOWN
10
9
  Classifier: Development Status :: 4 - Beta
11
10
  Classifier: Framework :: Pytest
12
11
  Classifier: Intended Audience :: Developers
@@ -15,16 +14,24 @@ Classifier: Operating System :: OS Independent
15
14
  Classifier: Programming Language :: Python
16
15
  Classifier: Programming Language :: Python :: 3
17
16
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.6
19
17
  Classifier: Programming Language :: Python :: 3.7
20
18
  Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
21
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
22
24
  Classifier: Topic :: Software Development :: Testing
23
25
  Classifier: Topic :: Utilities
24
26
  Requires-Python: >=3.7
25
27
  Description-Content-Type: text/x-rst
26
- Provides-Extra: test
27
28
  License-File: LICENSE
29
+ Requires-Dist: pytest>=4.6
30
+ Requires-Dist: numpy
31
+ Provides-Extra: test
32
+ Requires-Dist: astropy; extra == "test"
33
+ Requires-Dist: pandas; extra == "test"
34
+ Requires-Dist: tables; extra == "test"
28
35
 
29
36
  .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.5811772.svg
30
37
  :target: https://doi.org/10.5281/zenodo.5811772
@@ -34,6 +41,10 @@ License-File: LICENSE
34
41
  :target: https://github.com/astropy/pytest-arraydiff/actions
35
42
  :alt: CI Status
36
43
 
44
+ .. image:: https://img.shields.io/pypi/v/pytest-arraydiff.svg
45
+ :target: https://pypi.org/project/pytest-arraydiff
46
+ :alt: PyPI Status
47
+
37
48
  About
38
49
  -----
39
50
 
@@ -43,7 +54,8 @@ in cases where the arrays are too large to conveniently hard-code them
43
54
  in the tests (e.g. ``np.testing.assert_allclose(x, [1, 2, 3])``).
44
55
 
45
56
  The basic idea is that you can write a test that generates a Numpy array (or
46
- other related objects depending on the format). You can then either run the
57
+ other related objects depending on the format, e.g. pandas DataFrame).
58
+ You can then either run the
47
59
  tests in a mode to **generate** reference files from the arrays, or you can run
48
60
  the tests in **comparison** mode, which will compare the results of the tests to
49
61
  the reference ones within some tolerance.
@@ -53,6 +65,7 @@ At the moment, the supported file formats for the reference files are:
53
65
  - A plain text-based format (based on Numpy ``loadtxt`` output)
54
66
  - The FITS format (requires `astropy <http://www.astropy.org>`__). With this
55
67
  format, tests can return either a Numpy array for a FITS HDU object.
68
+ - A pandas HDF5 format using the pandas HDFStore
56
69
 
57
70
  For more information on how to write tests to do this, see the **Using**
58
71
  section below.
@@ -137,12 +150,12 @@ The supported formats at this time are ``text`` and ``fits``, and
137
150
  contributions for other formats are welcome. The default format is
138
151
  ``text``.
139
152
 
140
- Another argument is the relative tolerance for floating point values
141
- (which defaults to 1e-7):
153
+ Additional arguments are the relative and absolute tolerances for floating
154
+ point values (which default to 1e-7 and 0, respectively):
142
155
 
143
156
  .. code:: python
144
157
 
145
- @pytest.mark.array_compare(rtol=20)
158
+ @pytest.mark.array_compare(rtol=20, atol=0.1)
146
159
  def test_array():
147
160
  ...
148
161
 
@@ -220,5 +233,3 @@ install the latest version of the plugin then do::
220
233
 
221
234
  The reason for having to install the plugin first is to ensure that the
222
235
  plugin is correctly loaded as part of the test suite.
223
-
224
-
@@ -8,7 +8,6 @@ setup.cfg
8
8
  setup.py
9
9
  tox.ini
10
10
  .github/workflows/ci_workflows.yml
11
- .github/workflows/publish.yml
12
11
  pytest_arraydiff/__init__.py
13
12
  pytest_arraydiff/plugin.py
14
13
  pytest_arraydiff/version.py
@@ -20,9 +19,12 @@ pytest_arraydiff.egg-info/not-zip-safe
20
19
  pytest_arraydiff.egg-info/requires.txt
21
20
  pytest_arraydiff.egg-info/top_level.txt
22
21
  tests/test_pytest_arraydiff.py
22
+ tests/baseline/test_absolute_tolerance.fits
23
+ tests/baseline/test_relative_tolerance.fits
24
+ tests/baseline/test_single_reference.fits
23
25
  tests/baseline/test_succeeds_class.fits
24
26
  tests/baseline/test_succeeds_func_default.txt
25
27
  tests/baseline/test_succeeds_func_fits.fits
26
28
  tests/baseline/test_succeeds_func_fits_hdu.fits
27
- tests/baseline/test_succeeds_func_text.txt
28
- tests/baseline/test_tolerance.fits
29
+ tests/baseline/test_succeeds_func_pdhdf.h5
30
+ tests/baseline/test_succeeds_func_text.txt
@@ -1,3 +1,2 @@
1
1
  [pytest11]
2
2
  pytest_arraydiff = pytest_arraydiff.plugin
3
-
@@ -3,3 +3,5 @@ numpy
3
3
 
4
4
  [test]
5
5
  astropy
6
+ pandas
7
+ tables
@@ -12,9 +12,12 @@ classifiers =
12
12
  Programming Language :: Python
13
13
  Programming Language :: Python :: 3
14
14
  Programming Language :: Python :: 3 :: Only
15
- Programming Language :: Python :: 3.6
16
15
  Programming Language :: Python :: 3.7
17
16
  Programming Language :: Python :: 3.8
17
+ Programming Language :: Python :: 3.9
18
+ Programming Language :: Python :: 3.10
19
+ Programming Language :: Python :: 3.11
20
+ Programming Language :: Python :: 3.12
18
21
  Programming Language :: Python :: Implementation :: CPython
19
22
  Topic :: Software Development :: Testing
20
23
  Topic :: Utilities
@@ -36,6 +39,8 @@ install_requires =
36
39
  [options.extras_require]
37
40
  test =
38
41
  astropy
42
+ pandas
43
+ tables
39
44
 
40
45
  [options.entry_points]
41
46
  pytest11 =
@@ -47,6 +52,9 @@ testpaths = tests
47
52
  xfail_strict = true
48
53
  markers =
49
54
  array_compare: for functions using array comparison
55
+ filterwarnings =
56
+ error
57
+ ignore:distutils Version classes are deprecated
50
58
 
51
59
  [flake8]
52
60
  max-line-length = 150
@@ -4,6 +4,9 @@ import tempfile
4
4
 
5
5
  import pytest
6
6
  import numpy as np
7
+ from packaging.version import Version
8
+
9
+ NUMPY_LT_2_0 = Version(np.__version__) < Version("2.0.dev")
7
10
 
8
11
  reference_dir = 'baseline'
9
12
 
@@ -18,6 +21,14 @@ def test_succeeds_func_text():
18
21
  return np.arange(3 * 5).reshape((3, 5))
19
22
 
20
23
 
24
+ @pytest.mark.skipif(not NUMPY_LT_2_0, reason="AttributeError: `np.unicode_` was removed in the NumPy 2.0 release. Use `np.str_` instead.")
25
+ @pytest.mark.array_compare(file_format='pd_hdf', reference_dir=reference_dir)
26
+ def test_succeeds_func_pdhdf():
27
+ pd = pytest.importorskip('pandas')
28
+ return pd.DataFrame(data=np.arange(20, dtype='int64'),
29
+ columns=['test_data'])
30
+
31
+
21
32
  @pytest.mark.array_compare(file_format='fits', reference_dir=reference_dir)
22
33
  def test_succeeds_func_fits():
23
34
  return np.arange(3 * 5).reshape((3, 5)).astype(np.int64)
@@ -125,9 +136,42 @@ def test_default_format(file_format):
125
136
  assert os.path.exists(os.path.join(gen_dir, 'test_default.' + ('fits' if file_format == 'fits' else 'txt')))
126
137
 
127
138
 
128
- @pytest.mark.array_compare(reference_dir=reference_dir, rtol=0.5, file_format='fits')
129
- def test_tolerance():
130
- return np.ones((3, 4)) * 1.6
139
+ @pytest.mark.array_compare(reference_dir=reference_dir, rtol=0.5,
140
+ file_format='fits')
141
+ def test_relative_tolerance():
142
+ # Scale up the output values by 1.5 to ensure the large `rtol` value is
143
+ # needed. (The comparison file contains all 1.6.)
144
+ return np.ones((3, 4)) * 1.6 * 1.5
145
+
146
+
147
+ @pytest.mark.array_compare(reference_dir=reference_dir, atol=1.5,
148
+ file_format='fits')
149
+ def test_absolute_tolerance():
150
+ # Increase the output values by 1.4 to ensure the large `atol` value is
151
+ # needed. (The comparison file contains all 1.6.)
152
+ return np.ones((3, 4)) * 1.6 + 1.4
153
+
154
+
155
+ @pytest.mark.array_compare(
156
+ reference_dir=reference_dir,
157
+ atol=1.5,
158
+ file_format='fits',
159
+ single_reference=True)
160
+ @pytest.mark.parametrize('spam', ('egg', 'bacon'))
161
+ def test_single_reference(spam):
162
+ return np.ones((3, 4)) * 1.6 + 1.4
163
+
164
+
165
+ class TestSingleReferenceClass:
166
+
167
+ @pytest.mark.array_compare(
168
+ reference_dir=reference_dir,
169
+ atol=1.5,
170
+ file_format='fits',
171
+ single_reference=True)
172
+ @pytest.mark.parametrize('spam', ('egg', 'bacon'))
173
+ def test_single_reference(self, spam):
174
+ return np.ones((3, 4)) * 1.6 + 1.4
131
175
 
132
176
 
133
177
  def test_nofile():
@@ -0,0 +1,47 @@
1
+ [tox]
2
+ envlist =
3
+ py{37,38,39,310,311,312}-test{,-pytestoldest,-pytest50,-pytest52,-pytest53,-pytest60,-pytest61,-pytest62,-pytest70,-pytest71,-pytest72,-pytest73,-pytest74,-devdeps}
4
+ codestyle
5
+ requires =
6
+ setuptools >= 30.3.0
7
+ pip >= 19.3.1
8
+ isolated_build = true
9
+
10
+ [testenv]
11
+ changedir = .tmp/{envname}
12
+ setenv =
13
+ devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/liberfa/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
14
+ description = run tests
15
+ deps =
16
+ pytestoldest: pytest==4.6.0
17
+ pytest50: pytest==5.0.*
18
+ pytest52: pytest==5.2.*
19
+ pytest53: pytest==5.3.*
20
+ pytest60: pytest==6.0.*
21
+ pytest61: pytest==6.1.*
22
+ pytest62: pytest==6.2.*
23
+ pytest70: pytest==7.0.*
24
+ pytest71: pytest==7.1.*
25
+ pytest72: pytest==7.2.*
26
+ pytest73: pytest==7.3.*
27
+ pytest74: pytest==7.4.*
28
+ devdeps: git+https://github.com/pytest-dev/pytest#egg=pytest
29
+ devdeps: numpy>=0.0.dev0
30
+ devdeps: pandas>=0.0.dev0
31
+ devdeps: pyerfa>=0.0.dev0
32
+ devdeps: astropy>=0.0.dev0
33
+ extras =
34
+ test
35
+ commands =
36
+ # Force numpy-dev after something in the stack downgrades it
37
+ devdeps: python -m pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy
38
+ pip freeze
39
+ pytest {toxinidir}/tests {posargs}
40
+ pytest {toxinidir}/tests --arraydiff {posargs}
41
+
42
+ [testenv:codestyle]
43
+ skip_install = true
44
+ changedir = {toxinidir}
45
+ description = check code style, e.g. with flake8
46
+ deps = flake8
47
+ commands = flake8 pytest_arraydiff --count
@@ -1,53 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- pull_request:
5
- push:
6
- schedule:
7
- # Run every Sunday at 06:53 UTC
8
- - cron: 53 6 * * 0
9
-
10
- jobs:
11
- tests:
12
- runs-on: ${{ matrix.os }}
13
- strategy:
14
- fail-fast: false
15
- matrix:
16
- include:
17
- - os: ubuntu-latest
18
- python-version: 3.7
19
- toxenv: py37-test-pytest46
20
- - os: windows-latest
21
- python-version: 3.7
22
- toxenv: py37-test-pytest50
23
- - os: macos-latest
24
- python-version: 3.8
25
- toxenv: py38-test-pytest52
26
- - os: ubuntu-latest
27
- python-version: 3.8
28
- toxenv: py38-test-pytest53
29
- - os: windows-latest
30
- python-version: 3.9
31
- toxenv: py39-test-pytest60
32
- - os: macos-latest
33
- python-version: 3.9
34
- toxenv: py39-test-pytest61
35
- - os: ubuntu-latest
36
- python-version: '3.10'
37
- toxenv: py310-test-pytest62
38
- - os: ubuntu-latest
39
- python-version: '3.10'
40
- toxenv: py310-test-pytestdev
41
-
42
- steps:
43
- - uses: actions/checkout@v2
44
- with:
45
- fetch-depth: 0
46
- - name: Set up Python ${{ matrix.python-version }}
47
- uses: actions/setup-python@v2
48
- with:
49
- python-version: ${{ matrix.python-version }}
50
- - name: Install tox
51
- run: python -m pip install tox
52
- - name: Run tox
53
- run: tox -v -e ${{ matrix.toxenv }}
@@ -1,48 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- pull_request:
5
- push:
6
- tags:
7
- - '*'
8
-
9
- jobs:
10
- build-n-publish:
11
- name: Build and publish Python 🐍 distributions 📦 to PyPI
12
- runs-on: ubuntu-latest
13
- if: ((github.event_name == 'push' && startsWith(github.ref, 'refs/tags')) || contains(github.event.pull_request.labels.*.name, 'Build wheels'))
14
-
15
- steps:
16
- - uses: actions/checkout@v2
17
- with:
18
- fetch-depth: 0
19
- - uses: actions/setup-python@v2
20
- with:
21
- python-version: 3.8
22
-
23
- - name: Install python-build and twine
24
- run: python -m pip install pip build "twine>=3.3" -U
25
-
26
- - name: Build package
27
- run: python -m build --sdist --wheel .
28
-
29
- - name: List result
30
- run: ls -l dist
31
-
32
- - name: Check long_description
33
- run: python -m twine check --strict dist/*
34
-
35
- # FIXME: pytest not found
36
- #- name: Test package
37
- # run: |
38
- # cd ..
39
- # python -m venv testenv
40
- # testenv/bin/pip install pytest pytest-arraydiff/dist/*.whl
41
- # testenv/bin/pytest pytest-arraydiff/tests --arraydiff
42
-
43
- - name: Publish distribution 📦 to PyPI
44
- if: startsWith(github.ref, 'refs/tags')
45
- uses: pypa/gh-action-pypi-publish@release/v1
46
- with:
47
- user: __token__
48
- password: ${{ secrets.pypi_password }}
@@ -1,5 +0,0 @@
1
- # coding: utf-8
2
- # file generated by setuptools_scm
3
- # don't change, don't track in version control
4
- version = '0.5.0'
5
- version_tuple = (0, 5, 0)
@@ -1,33 +0,0 @@
1
- [tox]
2
- envlist =
3
- py{37,38,39,310}-test{,-devdeps}
4
- codestyle
5
- requires =
6
- setuptools >= 30.3.0
7
- pip >= 19.3.1
8
- isolated_build = true
9
-
10
- [testenv]
11
- changedir = .tmp/{envname}
12
- description = run tests
13
- deps =
14
- pytest46: pytest==4.6.*
15
- pytest50: pytest==5.0.*
16
- pytest52: pytest==5.2.*
17
- pytest53: pytest==5.3.*
18
- pytest60: pytest==6.0.*
19
- pytest61: pytest==6.1.*
20
- pytest62: pytest==6.2.*
21
- pytestdev: git+https://github.com/pytest-dev/pytest#egg=pytest
22
- extras =
23
- test
24
- commands =
25
- pip freeze
26
- pytest {toxinidir}/tests {posargs}
27
- pytest {toxinidir}/tests --arraydiff {posargs}
28
-
29
- [testenv:codestyle]
30
- skip_install = true
31
- description = check code style, e.g. with flake8
32
- deps = flake8
33
- commands = flake8 pytest_arraydiff --count