pytest-regtest 2.1.0__tar.gz → 2.3.2__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.
@@ -0,0 +1,27 @@
1
+ /.*
2
+ !.gitlab-ci.yml
3
+ !.pre-commit-config.yaml
4
+ !.readthedocs.yaml
5
+
6
+ .ropeproject
7
+
8
+
9
+ *.egg-info/
10
+ *.pyc
11
+ .*.sw?
12
+ venv*
13
+ coverage.xml
14
+
15
+ tests_dev/_regtest_outputs/
16
+
17
+ /dist
18
+ /build
19
+ /site
20
+ /html
21
+ /sandbox
22
+
23
+ package.json
24
+ package-lock.json
25
+ uv.lock
26
+ devbox.lock
27
+ node_modules/
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.3
2
+ Name: pytest-regtest
3
+ Version: 2.3.2
4
+ Summary: pytest plugin for snapshot regression testing
5
+ Project-URL: Source, https://gitlab.com/uweschmitt/pytest-regtest
6
+ Project-URL: Documentation, https://pytest-regtest.readthedocs.org
7
+ Author-email: Uwe Schmitt <uwe.schmitt@id.ethz.ch>
8
+ License: MIT License
9
+ License-File: LICENSE.txt
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Dist: pytest>7.2
17
+ Description-Content-Type: text/markdown
18
+
19
+ ![](https://gitlab.com/uweschmitt/pytest-regtest/badges/main/pipeline.svg)
20
+ ![](https://gitlab.com/uweschmitt/pytest-regtest/badges/main/coverage.svg?job=coverage)
21
+
22
+
23
+ The full documentation for this package are available at
24
+ https://pytest-regtest.readthedocs.org
25
+
26
+ # About
27
+
28
+ ## Introduction
29
+
30
+ `pytest-regtest` is a plugin for [pytest](https://pytest.org) to implement
31
+ **regression testing**.
32
+
33
+ Unlike [functional testing](https://en.wikipedia.org/wiki/Functional_testing),
34
+ [regression testing](https://en.wikipedia.org/wiki/Regression_testing)
35
+ does not test whether the software produces the correct
36
+ results, but whether it behaves as it did before changes were introduced.
37
+
38
+ More specifically, `pytest-regtest` provides **snapshot testing**, which
39
+ implements regression testing by recording data within a test function
40
+ and comparing this recorded output to a previously recorded reference
41
+ output.
42
+
43
+
44
+ ## Installation
45
+
46
+ To install and activate this plugin execute:
47
+
48
+ $ pip install pytest-regtest
49
+
50
+
51
+ !!! note
52
+
53
+ `pytest-regtest` provides some functionality specific to `NumPy`,
54
+ `pandas`, and `polars`. These dependencies are not installed when
55
+ you install `pytest-regtest`. For example, if you are using NumPy
56
+ snapshots, we assume that your production code (the code under
57
+ test) uses NumPy and therefore should be part of your project's
58
+ setup.
59
+
60
+
61
+ ## Use case 1: Changing code with no or little testing setup yet
62
+ If you're working with code that has little or no unit testing, you
63
+ can use regression testing to ensure that your changes don't break or
64
+ alter previous results.
65
+
66
+ **Example**:
67
+ This can be useful when working with data analysis scripts, which often
68
+ start as one long script and then are restructured into different
69
+ functions as they evolve.
70
+
71
+
72
+ ## Use case 2: Testing complex data
73
+ If a unit tests contains many `assert` statements to check a complex
74
+ data structure you can use regression tests instead.
75
+
76
+ **Example**: To test code which ingests data into a database one can
77
+ use regression tests on textual database dumps.
78
+
79
+ ## Use case 3: Testing NumPy arrays or pandas data frames
80
+
81
+ If your code generates numerical results, such as `NumPy` arrays,
82
+ `pandas` or `polars` data frames, you can use `pytest-regtest` to simply record such
83
+ results and test them later, taking into account relative and absolute
84
+ tolerances.
85
+
86
+
87
+ **Example**:
88
+ A function creates a 10 x 10 matrix. Either you have to write 100
89
+ assert statements or you use summary statistics to test your result.
90
+ In both cases, you may get little debugging information if a test
91
+ fails.
@@ -0,0 +1,73 @@
1
+ ![](https://gitlab.com/uweschmitt/pytest-regtest/badges/main/pipeline.svg)
2
+ ![](https://gitlab.com/uweschmitt/pytest-regtest/badges/main/coverage.svg?job=coverage)
3
+
4
+
5
+ The full documentation for this package are available at
6
+ https://pytest-regtest.readthedocs.org
7
+
8
+ # About
9
+
10
+ ## Introduction
11
+
12
+ `pytest-regtest` is a plugin for [pytest](https://pytest.org) to implement
13
+ **regression testing**.
14
+
15
+ Unlike [functional testing](https://en.wikipedia.org/wiki/Functional_testing),
16
+ [regression testing](https://en.wikipedia.org/wiki/Regression_testing)
17
+ does not test whether the software produces the correct
18
+ results, but whether it behaves as it did before changes were introduced.
19
+
20
+ More specifically, `pytest-regtest` provides **snapshot testing**, which
21
+ implements regression testing by recording data within a test function
22
+ and comparing this recorded output to a previously recorded reference
23
+ output.
24
+
25
+
26
+ ## Installation
27
+
28
+ To install and activate this plugin execute:
29
+
30
+ $ pip install pytest-regtest
31
+
32
+
33
+ !!! note
34
+
35
+ `pytest-regtest` provides some functionality specific to `NumPy`,
36
+ `pandas`, and `polars`. These dependencies are not installed when
37
+ you install `pytest-regtest`. For example, if you are using NumPy
38
+ snapshots, we assume that your production code (the code under
39
+ test) uses NumPy and therefore should be part of your project's
40
+ setup.
41
+
42
+
43
+ ## Use case 1: Changing code with no or little testing setup yet
44
+ If you're working with code that has little or no unit testing, you
45
+ can use regression testing to ensure that your changes don't break or
46
+ alter previous results.
47
+
48
+ **Example**:
49
+ This can be useful when working with data analysis scripts, which often
50
+ start as one long script and then are restructured into different
51
+ functions as they evolve.
52
+
53
+
54
+ ## Use case 2: Testing complex data
55
+ If a unit tests contains many `assert` statements to check a complex
56
+ data structure you can use regression tests instead.
57
+
58
+ **Example**: To test code which ingests data into a database one can
59
+ use regression tests on textual database dumps.
60
+
61
+ ## Use case 3: Testing NumPy arrays or pandas data frames
62
+
63
+ If your code generates numerical results, such as `NumPy` arrays,
64
+ `pandas` or `polars` data frames, you can use `pytest-regtest` to simply record such
65
+ results and test them later, taking into account relative and absolute
66
+ tolerances.
67
+
68
+
69
+ **Example**:
70
+ A function creates a 10 x 10 matrix. Either you have to write 100
71
+ assert statements or you use summary statistics to test your result.
72
+ In both cases, you may get little debugging information if a test
73
+ fails.
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+
6
+ [project]
7
+ name = "pytest-regtest"
8
+ version = "2.3.2"
9
+ description = "pytest plugin for snapshot regression testing"
10
+ readme = "README.md"
11
+ authors = [
12
+ {name = "Uwe Schmitt", email = "uwe.schmitt@id.ethz.ch"}
13
+ ]
14
+
15
+ license = {text = "MIT License"}
16
+
17
+ classifiers = [
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "License :: OSI Approved :: MIT License"
24
+ ]
25
+
26
+ dependencies = [
27
+ "pytest>7.2",
28
+ ]
29
+
30
+ [project.urls]
31
+ Source = "https://gitlab.com/uweschmitt/pytest-regtest"
32
+ Documentation = "https://pytest-regtest.readthedocs.org"
33
+
34
+
35
+ [project.entry-points.pytest11]
36
+ regtest = "pytest_regtest"
37
+
38
+ [tool.hatch.build.targets.sdist]
39
+ only-include = ["pytest_regtest", "tests/conftest.py", "tests/test_plugin.py"]
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/pytest_regtest"]
43
+
44
+ [tool.ruff]
45
+ line-length = 88
46
+ exclude = ["_regtest_output"]
47
+
48
+ [tool.ruff.lint]
49
+ ignore = ["E731", "E203"]
50
+
51
+ [tool.uv]
52
+ dev-dependencies = [
53
+ "twine", "build", "hatchling", "wheel", "pre-commit", "ruff", "black",
54
+ "pytest-cov", "numpy", "pandas", "mkdocs", "mkdocs-material", "mistletoe",
55
+ "mkdocs-awesome-pages-plugin", "jinja2-cli", "mkdocstrings[python]",
56
+ "numpy>=2", "pandas>=2", "polars>=1.9",
57
+ "md-transformer>=0.0.3"
58
+ ]
@@ -0,0 +1,28 @@
1
+ import sys
2
+
3
+ import numpy
4
+ import pytest
5
+
6
+ # hack to disable reload numpy in pytester (current code only prevent reload of
7
+ # zope, so we trick pytester:
8
+ # (see also _pytest/pytester.py __take_sys_modules_snapshot method)
9
+ sys.modules["zope"] = numpy
10
+
11
+ pytest_plugins = ["pytester"]
12
+
13
+ # for whatever reason pytester loaded the old bytcode in one test and caused it
14
+ # to fail:
15
+ sys.dont_write_bytecode = True
16
+
17
+
18
+ @pytest.fixture
19
+ def assert_outcomes():
20
+ def check(result, **kw):
21
+ try:
22
+ result.assert_outcomes(**kw)
23
+ except AssertionError:
24
+ outcome = result.parseoutcomes()
25
+ message = f"expected: {kw}\ngot: {outcome}\n\n" + result.stdout.str()
26
+ raise AssertionError(message)
27
+
28
+ yield check
@@ -1 +0,0 @@
1
- include LICENSE.txt
@@ -1,350 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: pytest-regtest
3
- Version: 2.1.0
4
- Summary: "pytest plugin for snapshot regression testing"
5
- Author: Uwe Schmitt
6
- Author-email: uwe.schmitt@id.ethz.ch
7
- License: MIT
8
- Project-URL: Source, https://gitlab.com/uweschmitt/pytest-regtest
9
- Project-URL: Documentation, https://pytest-regtest.readthedocs.org
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.9
14
- Classifier: Programming Language :: Python :: 3.8
15
- Requires-Python: >=3.9
16
- Description-Content-Type: text/markdown
17
- License-File: LICENSE.txt
18
- Requires-Dist: pytest>7.2
19
- Provides-Extra: dev
20
- Requires-Dist: twine; extra == "dev"
21
- Requires-Dist: build; extra == "dev"
22
- Requires-Dist: wheel; extra == "dev"
23
- Requires-Dist: pytest; extra == "dev"
24
- Requires-Dist: pre-commit; extra == "dev"
25
-
26
-
27
- # Home
28
-
29
- ## About
30
-
31
- `pytest-regtest` is a plugin for [pytest](https://pytest.org) to implement
32
- **regression testing**.
33
-
34
- Unlike [functional testing](https://en.wikipedia.org/wiki/Functional_testing),
35
- [regression testing](https://en.wikipedia.org/wiki/Regression_testing)
36
- testing does not test whether the software produces the correct
37
- results, but whether it behaves as it did before changes were introduced.
38
-
39
- More specifically, `pytest-regtest` provides **snapshot testing**, which
40
- implements regression testing by recording the textual output of a test
41
- function and comparing this recorded output to a reference output.
42
-
43
- **Regression testing** is a common technique to implement basic testing
44
- before refactoring legacy code that lacks a test suite.
45
-
46
- Snapshot testing can also be used to implement tests for complex outcomes, such
47
- as recording textual database dumps or the results of a scientific analysis
48
- routine.
49
-
50
-
51
- ## Installation
52
-
53
- To install and activate this plugin execute:
54
-
55
- $ pip install pytest-regtest
56
-
57
- ## Basic Usage
58
-
59
-
60
- ### Write a test
61
-
62
- The `pytest-regtest` plugin provides multiple fixtures.
63
- To record output, use the fixture `regtest` that works like a file handle:
64
-
65
- ```py
66
- def test_squares(regtest):
67
-
68
- result = [i*i for i in range(10)]
69
-
70
- # one way to record output:
71
- print(result, file=regtest)
72
-
73
- # alternative method to record output:
74
- regtest.write("done")
75
- ```
76
-
77
- You can also use the `regtest_all` fixture. This enables all output to stdout to be
78
- recorded in a test function.
79
-
80
-
81
- ### Run the test
82
-
83
- If you run this test script with *pytest* the first time there is no
84
- recorded output for this test function so far and thus the test will
85
- fail with a message including a diff:
86
-
87
- ```
88
- $ pytest -v test_squares.py
89
- ============================= test session starts ==============================
90
- platform darwin -- Python 3.11.4, pytest-7.4.3, pluggy-1.3.0 -- ...
91
- cachedir: .pytest_cache
92
- rootdir: ...
93
- plugins: regtest-2.0.3
94
- collecting ... collected 1 item
95
-
96
- test_squares.py::test_squares FAILED [100%]
97
-
98
- =================================== FAILURES ===================================
99
- _________________________________ test_squares _________________________________
100
-
101
- regression test output not recorded yet for test_squares.py::test_squares:
102
-
103
- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
104
- done
105
- ---------------------------- pytest-regtest report -----------------------------
106
- total number of failed regression tests: 1
107
- =========================== short test summary info ============================
108
- FAILED test_squares.py::test_squares
109
- ============================== 1 failed in 0.02s ===============================
110
- ```
111
-
112
- This is a diff of the current output `is` to a previously recorded output
113
- `tobe`. Since we did not record output yet, the diff contains no lines marked
114
- `+`.
115
-
116
-
117
- ### Reset the test
118
-
119
- To record the current output, we run *pytest* with the *--reset-regtest*
120
- flag:
121
-
122
- ```
123
- $ pytest -v --regtest-reset test_squares.py
124
- ============================= test session starts ==============================
125
- platform darwin -- Python 3.11.4, pytest-7.4.3, pluggy-1.3.0 -- ...
126
- cachedir: .pytest_cache
127
- rootdir: ...
128
- plugins: regtest-2.0.3
129
- collecting ... collected 1 item
130
-
131
- test_squares.py::test_squares RESET [100%]
132
-
133
- ---------------------------- pytest-regtest report -----------------------------
134
- total number of failed regression tests: 0
135
- the following output files have been reset:
136
- _regtest_outputs/test_squares.test_squares.out
137
- ============================== 1 passed in 0.00s ===============================
138
- ```
139
-
140
- You can also see from the output that the recorded output is in the
141
- `_regtest_outputs` folder which in the same folder as the test script.
142
- Don't forget to commit this folder to your version control system!
143
-
144
- ### Run the test again
145
-
146
- When we run the test again, it succeeds:
147
-
148
- ```
149
- $ pytest -v test_squares.py
150
- ============================= test session starts ==============================
151
- platform darwin -- Python 3.11.4, pytest-7.4.3, pluggy-1.3.0 -- ...
152
- cachedir: .pytest_cache
153
- rootdir: ...
154
- plugins: regtest-2.0.3
155
- collecting ... collected 1 item
156
-
157
- test_squares.py::test_squares PASSED [100%]
158
-
159
- ---------------------------- pytest-regtest report -----------------------------
160
- total number of failed regression tests: 0
161
- ============================== 1 passed in 0.00s ===============================
162
- ```
163
-
164
- ### Break the test
165
-
166
- Let us break the test by changing the test function to compute
167
- 11 instead of 10 square numbers:
168
-
169
- ```py
170
- def test_squares(regtest):
171
-
172
- result = [i*i for i in range(11)]
173
-
174
- # one way to record output:
175
- print(result, file=regtest)
176
-
177
- # alternative method to record output:
178
- regtest.write("done")
179
- ```
180
-
181
- The next run of pytest delivers a nice diff of the current and expected output
182
- from this test function:
183
-
184
- ```
185
- $ pytest -v test_squares.py
186
- ============================= test session starts ==============================
187
- platform darwin -- Python 3.11.4, pytest-7.4.3, pluggy-1.3.0 -- ...
188
- cachedir: .pytest_cache
189
- rootdir: ...
190
- plugins: regtest-2.0.3
191
- collecting ... collected 1 item
192
-
193
- test_squares.py::test_squares FAILED [100%]
194
-
195
- =================================== FAILURES ===================================
196
- _________________________________ test_squares _________________________________
197
-
198
- regression test output differences for test_squares.py::test_squares:
199
- (recorded output from _regtest_outputs/test_squares.test_squares.out)
200
-
201
- > --- current
202
- > +++ expected
203
- > @@ -1,2 +1,2 @@
204
- > -[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
205
- > +[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
206
- > done
207
-
208
- ---------------------------- pytest-regtest report -----------------------------
209
- total number of failed regression tests: 1
210
- =========================== short test summary info ============================
211
- FAILED test_squares.py::test_squares
212
- ============================== 1 failed in 0.02s ===============================
213
- ```
214
-
215
-
216
- ## Other features
217
-
218
- ### Using the `regtest` fixture as context manager
219
-
220
- The `regtest` fixture also works as a context manager to capture
221
- all output from the wrapped code block:
222
-
223
- ```py
224
- def test_squares(regtest):
225
-
226
- result = [i*i for i in range(10)]
227
-
228
- with regtest:
229
- print(result)
230
- ```
231
-
232
- ### The `regtest_all` fixture
233
-
234
- The `regtest_all` fixture leads to recording of all output to `stdout` in a
235
- test function.
236
-
237
- ```py
238
- def test_all(regtest_all):
239
- print("this line will be recorded.")
240
- print("and this line also.")
241
- ```
242
-
243
- ### Reset individual tests
244
-
245
- You can reset recorded output of files and functions individually as:
246
-
247
- ```sh
248
- $ py.test --regtest-reset test_demo.py
249
- $ py.test --regtest-reset test_demo.py::test_squares
250
- ```
251
-
252
- ### Suppress diff for failed tests
253
-
254
- To hide the diff and just show the number of lines changed, use:
255
-
256
- ```sh
257
- $ py.test --regtest-nodiff ...
258
- ```
259
-
260
-
261
- ### Show all recorded output
262
-
263
-
264
- For complex diffs it helps to see the full recorded output also.
265
- To enable this use:
266
-
267
- ```sh
268
- $ py.test --regtest-tee...
269
- ```
270
-
271
-
272
- ### Line endings
273
-
274
- Per default `pytest-regtest` ignores different line endings in the output.
275
- In case you want to disable this feature, use the `-regtest-consider-line-endings`
276
- flag.
277
-
278
-
279
- ## Clean indeterministic output before recording
280
-
281
- Output can contain data which is changing from test run to test
282
- run, e.g. paths created with the `tmpdir` fixture, hexadecimal object ids or
283
- timestamps.
284
-
285
- Per default the plugin helps to make output more deterministic by:
286
-
287
- - replacing all temporary folder in the output with `<tmpdir...>` or similar markers,
288
- depending on the origin of the temporary folder (`tempfile` module, `tmpdir` fixture,
289
- ...)
290
- - replacing hexadecimal numbers ` 0x...` of arbitary length by the fixed string `0x?????????`.
291
-
292
- You can also implement your own cleanup routines as described below.
293
-
294
- ### Register own cleanup functions
295
-
296
- You can register own converters in `conftest.py`:
297
-
298
- ```py
299
- import re
300
- import pytest_regtest
301
-
302
- @pytest_regtest.register_converter_pre
303
- def remove_password_lines(txt):
304
- '''modify recorded output BEFORE the default fixes
305
- like temp folders or hex object ids are applied'''
306
-
307
- # remove lines with passwords:
308
- lines = txt.splitlines(keepends=True)
309
- lines = [l for l in lines if "password is" not in l]
310
- return "".join(lines)
311
-
312
- @pytest_regtest.register_converter_post
313
- def fix_time_measurements(txt):
314
- '''modify recorded output AFTER the default fixes
315
- like temp folders or hex object ids are applied'''
316
-
317
- # fix time measurements:
318
- return re.sub(
319
- "\d+(\.\d+)? seconds",
320
- "<SECONDS> seconds",
321
- txt
322
- )
323
- ```
324
-
325
- If you register multiple converters they will be applied in the order of
326
- registration.
327
-
328
- In case your routines replace, improve or conflict with the standard cleanup converters,
329
- you can use the flag `--regtest-disable-stdconv` to disable the default cleanup
330
- procedure.
331
-
332
- ## Command line options summary
333
-
334
- These are all supported command line options:
335
-
336
- ```
337
- $ pytest --help
338
- ...
339
-
340
- regression test plugin:
341
- --regtest-reset do not run regtest but record current output
342
- --regtest-tee print recorded results to console too
343
- --regtest-consider-line-endings
344
- do not strip whitespaces at end of recorded lines
345
- --regtest-nodiff do not show diff output for failed regresson tests
346
- --regtest-disable-stdconv
347
- do not apply standard output converters to clean up
348
- indeterministic output
349
- ...
350
- ```