pytest-imply 0.1.0__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.
- pytest_imply-0.1.0/.gitignore +7 -0
- pytest_imply-0.1.0/LICENSE +21 -0
- pytest_imply-0.1.0/MANIFEST.in +3 -0
- pytest_imply-0.1.0/PKG-INFO +223 -0
- pytest_imply-0.1.0/README.md +194 -0
- pytest_imply-0.1.0/pyproject.toml +47 -0
- pytest_imply-0.1.0/setup.cfg +4 -0
- pytest_imply-0.1.0/src/pytest_imply/__init__.py +12 -0
- pytest_imply-0.1.0/src/pytest_imply/plugin.py +571 -0
- pytest_imply-0.1.0/src/pytest_imply.egg-info/PKG-INFO +223 -0
- pytest_imply-0.1.0/src/pytest_imply.egg-info/SOURCES.txt +19 -0
- pytest_imply-0.1.0/src/pytest_imply.egg-info/dependency_links.txt +1 -0
- pytest_imply-0.1.0/src/pytest_imply.egg-info/entry_points.txt +2 -0
- pytest_imply-0.1.0/src/pytest_imply.egg-info/requires.txt +1 -0
- pytest_imply-0.1.0/src/pytest_imply.egg-info/top_level.txt +1 -0
- pytest_imply-0.1.0/tests/conftest.py +1 -0
- pytest_imply-0.1.0/tests/test_edge_cases.py +465 -0
- pytest_imply-0.1.0/tests/test_implies.py +170 -0
- pytest_imply-0.1.0/tests/test_monotonic.py +119 -0
- pytest_imply-0.1.0/tests/test_ordering.py +118 -0
- pytest_imply-0.1.0/tests/test_validation.py +163 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dimitri Staessens <dimitri@ouroboros.rocks>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-imply
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pytest plugin for test implication — skip tests implied by stronger ones
|
|
5
|
+
Author-email: Dimitri Staessens <dimitri@ouroboros.rocks>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://codeberg.org/o7s/pytest-imply
|
|
8
|
+
Project-URL: Repository, https://codeberg.org/o7s/pytest-imply
|
|
9
|
+
Project-URL: Issues, https://codeberg.org/o7s/pytest-imply/issues
|
|
10
|
+
Keywords: pytest,testing,implication,monotonic,optimization
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Framework :: Pytest
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: pytest>=7.0
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# pytest-imply
|
|
31
|
+
|
|
32
|
+
A pytest plugin for **test implication** — skip tests that are implied
|
|
33
|
+
by stronger ones.
|
|
34
|
+
|
|
35
|
+
## The problem
|
|
36
|
+
|
|
37
|
+
Parametrized test suites often have an ordered severity axis:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
test_count[c100] PASSED 2.58s
|
|
41
|
+
test_count[c500] PASSED 4.62s
|
|
42
|
+
test_count[c1000] PASSED 7.13s
|
|
43
|
+
test_count[c5000] PASSED 27.45s
|
|
44
|
+
test_count[c10000] PASSED 52.90s
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If `c10000` passes, the smaller counts will almost certainly pass
|
|
48
|
+
too — running them all wastes CI time.
|
|
49
|
+
|
|
50
|
+
## The solution
|
|
51
|
+
|
|
52
|
+
**pytest-imply** lets the developer declare implication relationships
|
|
53
|
+
between tests. When a stronger test passes, weaker tests are
|
|
54
|
+
short-circuited with a synthetic PASSED result:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
test_count[c10000] PASSED 52.90s
|
|
58
|
+
test_count[c5000] IMPLIED (higher count passed)
|
|
59
|
+
test_count[c1000] IMPLIED (higher count passed)
|
|
60
|
+
test_count[c500] IMPLIED (higher count passed)
|
|
61
|
+
test_count[c100] IMPLIED (higher count passed)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If the strongest test fails, the plugin works downward to find the
|
|
65
|
+
threshold — no time wasted on tests below the passing level.
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
pip install pytest-imply
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Markers
|
|
74
|
+
|
|
75
|
+
### `monotonic` — parametrized implication
|
|
76
|
+
|
|
77
|
+
For tests parametrized along an ordered axis:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
@pytest.mark.monotonic("count")
|
|
81
|
+
@pytest.mark.parametrize("count", [100, 500, 1000, 5000, 10000])
|
|
82
|
+
def test_stress(count):
|
|
83
|
+
run_workload(count)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The highest value runs first. If it passes, all lower values are
|
|
87
|
+
implied. If it fails, the next-highest runs, and so on.
|
|
88
|
+
|
|
89
|
+
Use `direction="asc"` to run the smallest first:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
@pytest.mark.monotonic("threshold", direction="asc")
|
|
93
|
+
@pytest.mark.parametrize("threshold", [0.01, 0.1, 0.5, 1.0])
|
|
94
|
+
def test_precision(threshold):
|
|
95
|
+
assert error < threshold
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `implies` / `implied_by` / `implied_by_any` / `implied_by_all` — named tokens
|
|
99
|
+
|
|
100
|
+
For arbitrary implication relationships between tests:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
@pytest.mark.implies("full_stack_ok")
|
|
104
|
+
def test_integration():
|
|
105
|
+
"""Full stack test — if this passes, unit tests are implied."""
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
@pytest.mark.implied_by("full_stack_ok")
|
|
109
|
+
def test_unit():
|
|
110
|
+
"""Implied by passing integration test — no need to run."""
|
|
111
|
+
...
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use `implied_by_any` for OR semantics (implied if **any** token was
|
|
115
|
+
recorded):
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
@pytest.mark.implied_by_any("tcp_ok", "udp_ok")
|
|
119
|
+
def test_loopback():
|
|
120
|
+
"""Implied if either transport passed."""
|
|
121
|
+
...
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Use `implied_by_all` for AND semantics (implied only if **all** tokens
|
|
125
|
+
were recorded):
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
@pytest.mark.implied_by_all("tcp_ok", "udp_ok")
|
|
129
|
+
def test_both_transports():
|
|
130
|
+
"""Implied only if both transports passed."""
|
|
131
|
+
...
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Ordering
|
|
135
|
+
|
|
136
|
+
The plugin builds a dependency graph from all implication
|
|
137
|
+
relationships and topologically sorts the test items using Kahn's
|
|
138
|
+
algorithm. This guarantees that implying tests always run before
|
|
139
|
+
their implied dependents. Tests without implication markers keep
|
|
140
|
+
their original collection order.
|
|
141
|
+
|
|
142
|
+
## Caveats
|
|
143
|
+
|
|
144
|
+
- **Transitive propagation**: when a test is implied (short-circuited),
|
|
145
|
+
its `implies` tokens are still propagated, so downstream tests see
|
|
146
|
+
them. This means chains like A→B→C work as expected.
|
|
147
|
+
|
|
148
|
+
- **Stacked markers**: multiple markers of the same type on one test
|
|
149
|
+
are all honoured. For example, stacking two `@pytest.mark.implies`
|
|
150
|
+
decorators records both sets of tokens.
|
|
151
|
+
|
|
152
|
+
- **Token namespacing**: tokens live in a single flat namespace.
|
|
153
|
+
In large projects, use a prefix convention (e.g.,
|
|
154
|
+
`"mymodule::token"`) to avoid accidental collisions between
|
|
155
|
+
independently-authored test modules.
|
|
156
|
+
|
|
157
|
+
- **Fixtures**: when a test is implied, its fixtures **do not run**.
|
|
158
|
+
If an implied test has a session- or module-scoped fixture whose
|
|
159
|
+
side effects other tests depend on, those tests may break. Only
|
|
160
|
+
mark tests as implied when their fixtures are not needed by other
|
|
161
|
+
tests.
|
|
162
|
+
|
|
163
|
+
- **pytest-xdist**: the plugin does not support parallel workers.
|
|
164
|
+
Implication state is per-process. When xdist is active with
|
|
165
|
+
workers, implication logic is **automatically disabled** and a
|
|
166
|
+
warning is emitted. Use `-p no:imply` to suppress the warning.
|
|
167
|
+
|
|
168
|
+
- **Plugin interoperability**: when a test is implied, the plugin
|
|
169
|
+
returns `True` from `pytest_runtest_protocol`, which tells pytest
|
|
170
|
+
the item is fully handled. Other plugins that wrap the test
|
|
171
|
+
protocol (e.g., `pytest-cov`, `pytest-timeout`) will not see
|
|
172
|
+
implied tests. This is inherent to the short-circuit design.
|
|
173
|
+
|
|
174
|
+
- **Orphan tokens**: if an `implied_by` (or variant) references a
|
|
175
|
+
token that no test provides via `implies`, a warning is emitted
|
|
176
|
+
at collection time.
|
|
177
|
+
|
|
178
|
+
- **Dependency cycles**: if implication markers form a cycle, the
|
|
179
|
+
plugin falls back to original collection order for the affected
|
|
180
|
+
tests and emits a warning.
|
|
181
|
+
|
|
182
|
+
- **xfail interaction**: a test marked `@pytest.mark.xfail` with
|
|
183
|
+
`strict=False` that passes (xpass) **does** record its `implies`
|
|
184
|
+
tokens. With `strict=True`, an xpass is treated as a failure and
|
|
185
|
+
tokens are **not** recorded.
|
|
186
|
+
|
|
187
|
+
## Disabling implication
|
|
188
|
+
|
|
189
|
+
### `--no-imply`
|
|
190
|
+
|
|
191
|
+
Disable all implication logic and run every test:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
pytest --no-imply
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Use this for exhaustive nightly or release-gate runs to verify that
|
|
198
|
+
the developer's implication assumptions still hold.
|
|
199
|
+
|
|
200
|
+
### `imply_enabled` ini option
|
|
201
|
+
|
|
202
|
+
Disable implication via configuration instead of a CLI flag:
|
|
203
|
+
|
|
204
|
+
```toml
|
|
205
|
+
[tool.pytest.ini_options]
|
|
206
|
+
imply_enabled = false
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## How it works
|
|
210
|
+
|
|
211
|
+
1. **Collection** — `pytest_collection_modifyitems` builds the
|
|
212
|
+
dependency graph and topologically sorts items.
|
|
213
|
+
2. **Execution** — `pytest_runtest_protocol` checks whether a test is
|
|
214
|
+
implied; if so, it emits synthetic `TestReport` objects and
|
|
215
|
+
returns `True`.
|
|
216
|
+
3. **Recording** — `pytest_runtest_makereport` records tokens and
|
|
217
|
+
monotonic passes after a test succeeds.
|
|
218
|
+
4. **Reporting** — `pytest_report_teststatus` renders implied tests as
|
|
219
|
+
`IMPLIED (reason)` with a cyan `i` marker.
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# pytest-imply
|
|
2
|
+
|
|
3
|
+
A pytest plugin for **test implication** — skip tests that are implied
|
|
4
|
+
by stronger ones.
|
|
5
|
+
|
|
6
|
+
## The problem
|
|
7
|
+
|
|
8
|
+
Parametrized test suites often have an ordered severity axis:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
test_count[c100] PASSED 2.58s
|
|
12
|
+
test_count[c500] PASSED 4.62s
|
|
13
|
+
test_count[c1000] PASSED 7.13s
|
|
14
|
+
test_count[c5000] PASSED 27.45s
|
|
15
|
+
test_count[c10000] PASSED 52.90s
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If `c10000` passes, the smaller counts will almost certainly pass
|
|
19
|
+
too — running them all wastes CI time.
|
|
20
|
+
|
|
21
|
+
## The solution
|
|
22
|
+
|
|
23
|
+
**pytest-imply** lets the developer declare implication relationships
|
|
24
|
+
between tests. When a stronger test passes, weaker tests are
|
|
25
|
+
short-circuited with a synthetic PASSED result:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
test_count[c10000] PASSED 52.90s
|
|
29
|
+
test_count[c5000] IMPLIED (higher count passed)
|
|
30
|
+
test_count[c1000] IMPLIED (higher count passed)
|
|
31
|
+
test_count[c500] IMPLIED (higher count passed)
|
|
32
|
+
test_count[c100] IMPLIED (higher count passed)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
If the strongest test fails, the plugin works downward to find the
|
|
36
|
+
threshold — no time wasted on tests below the passing level.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
pip install pytest-imply
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Markers
|
|
45
|
+
|
|
46
|
+
### `monotonic` — parametrized implication
|
|
47
|
+
|
|
48
|
+
For tests parametrized along an ordered axis:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
@pytest.mark.monotonic("count")
|
|
52
|
+
@pytest.mark.parametrize("count", [100, 500, 1000, 5000, 10000])
|
|
53
|
+
def test_stress(count):
|
|
54
|
+
run_workload(count)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The highest value runs first. If it passes, all lower values are
|
|
58
|
+
implied. If it fails, the next-highest runs, and so on.
|
|
59
|
+
|
|
60
|
+
Use `direction="asc"` to run the smallest first:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
@pytest.mark.monotonic("threshold", direction="asc")
|
|
64
|
+
@pytest.mark.parametrize("threshold", [0.01, 0.1, 0.5, 1.0])
|
|
65
|
+
def test_precision(threshold):
|
|
66
|
+
assert error < threshold
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `implies` / `implied_by` / `implied_by_any` / `implied_by_all` — named tokens
|
|
70
|
+
|
|
71
|
+
For arbitrary implication relationships between tests:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
@pytest.mark.implies("full_stack_ok")
|
|
75
|
+
def test_integration():
|
|
76
|
+
"""Full stack test — if this passes, unit tests are implied."""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
@pytest.mark.implied_by("full_stack_ok")
|
|
80
|
+
def test_unit():
|
|
81
|
+
"""Implied by passing integration test — no need to run."""
|
|
82
|
+
...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use `implied_by_any` for OR semantics (implied if **any** token was
|
|
86
|
+
recorded):
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
@pytest.mark.implied_by_any("tcp_ok", "udp_ok")
|
|
90
|
+
def test_loopback():
|
|
91
|
+
"""Implied if either transport passed."""
|
|
92
|
+
...
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use `implied_by_all` for AND semantics (implied only if **all** tokens
|
|
96
|
+
were recorded):
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
@pytest.mark.implied_by_all("tcp_ok", "udp_ok")
|
|
100
|
+
def test_both_transports():
|
|
101
|
+
"""Implied only if both transports passed."""
|
|
102
|
+
...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Ordering
|
|
106
|
+
|
|
107
|
+
The plugin builds a dependency graph from all implication
|
|
108
|
+
relationships and topologically sorts the test items using Kahn's
|
|
109
|
+
algorithm. This guarantees that implying tests always run before
|
|
110
|
+
their implied dependents. Tests without implication markers keep
|
|
111
|
+
their original collection order.
|
|
112
|
+
|
|
113
|
+
## Caveats
|
|
114
|
+
|
|
115
|
+
- **Transitive propagation**: when a test is implied (short-circuited),
|
|
116
|
+
its `implies` tokens are still propagated, so downstream tests see
|
|
117
|
+
them. This means chains like A→B→C work as expected.
|
|
118
|
+
|
|
119
|
+
- **Stacked markers**: multiple markers of the same type on one test
|
|
120
|
+
are all honoured. For example, stacking two `@pytest.mark.implies`
|
|
121
|
+
decorators records both sets of tokens.
|
|
122
|
+
|
|
123
|
+
- **Token namespacing**: tokens live in a single flat namespace.
|
|
124
|
+
In large projects, use a prefix convention (e.g.,
|
|
125
|
+
`"mymodule::token"`) to avoid accidental collisions between
|
|
126
|
+
independently-authored test modules.
|
|
127
|
+
|
|
128
|
+
- **Fixtures**: when a test is implied, its fixtures **do not run**.
|
|
129
|
+
If an implied test has a session- or module-scoped fixture whose
|
|
130
|
+
side effects other tests depend on, those tests may break. Only
|
|
131
|
+
mark tests as implied when their fixtures are not needed by other
|
|
132
|
+
tests.
|
|
133
|
+
|
|
134
|
+
- **pytest-xdist**: the plugin does not support parallel workers.
|
|
135
|
+
Implication state is per-process. When xdist is active with
|
|
136
|
+
workers, implication logic is **automatically disabled** and a
|
|
137
|
+
warning is emitted. Use `-p no:imply` to suppress the warning.
|
|
138
|
+
|
|
139
|
+
- **Plugin interoperability**: when a test is implied, the plugin
|
|
140
|
+
returns `True` from `pytest_runtest_protocol`, which tells pytest
|
|
141
|
+
the item is fully handled. Other plugins that wrap the test
|
|
142
|
+
protocol (e.g., `pytest-cov`, `pytest-timeout`) will not see
|
|
143
|
+
implied tests. This is inherent to the short-circuit design.
|
|
144
|
+
|
|
145
|
+
- **Orphan tokens**: if an `implied_by` (or variant) references a
|
|
146
|
+
token that no test provides via `implies`, a warning is emitted
|
|
147
|
+
at collection time.
|
|
148
|
+
|
|
149
|
+
- **Dependency cycles**: if implication markers form a cycle, the
|
|
150
|
+
plugin falls back to original collection order for the affected
|
|
151
|
+
tests and emits a warning.
|
|
152
|
+
|
|
153
|
+
- **xfail interaction**: a test marked `@pytest.mark.xfail` with
|
|
154
|
+
`strict=False` that passes (xpass) **does** record its `implies`
|
|
155
|
+
tokens. With `strict=True`, an xpass is treated as a failure and
|
|
156
|
+
tokens are **not** recorded.
|
|
157
|
+
|
|
158
|
+
## Disabling implication
|
|
159
|
+
|
|
160
|
+
### `--no-imply`
|
|
161
|
+
|
|
162
|
+
Disable all implication logic and run every test:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
pytest --no-imply
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Use this for exhaustive nightly or release-gate runs to verify that
|
|
169
|
+
the developer's implication assumptions still hold.
|
|
170
|
+
|
|
171
|
+
### `imply_enabled` ini option
|
|
172
|
+
|
|
173
|
+
Disable implication via configuration instead of a CLI flag:
|
|
174
|
+
|
|
175
|
+
```toml
|
|
176
|
+
[tool.pytest.ini_options]
|
|
177
|
+
imply_enabled = false
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## How it works
|
|
181
|
+
|
|
182
|
+
1. **Collection** — `pytest_collection_modifyitems` builds the
|
|
183
|
+
dependency graph and topologically sorts items.
|
|
184
|
+
2. **Execution** — `pytest_runtest_protocol` checks whether a test is
|
|
185
|
+
implied; if so, it emits synthetic `TestReport` objects and
|
|
186
|
+
returns `True`.
|
|
187
|
+
3. **Recording** — `pytest_runtest_makereport` records tokens and
|
|
188
|
+
monotonic passes after a test succeeds.
|
|
189
|
+
4. **Reporting** — `pytest_report_teststatus` renders implied tests as
|
|
190
|
+
`IMPLIED (reason)` with a cyan `i` marker.
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pytest-imply"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Pytest plugin for test implication — skip tests implied by stronger ones"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Dimitri Staessens", email = "dimitri@ouroboros.rocks" },
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = ["pytest>=7.0"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Framework :: Pytest",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.8",
|
|
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
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Topic :: Software Development :: Testing",
|
|
26
|
+
]
|
|
27
|
+
keywords = ["pytest", "testing", "implication", "monotonic", "optimization"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://codeberg.org/o7s/pytest-imply"
|
|
31
|
+
Repository = "https://codeberg.org/o7s/pytest-imply"
|
|
32
|
+
Issues = "https://codeberg.org/o7s/pytest-imply/issues"
|
|
33
|
+
|
|
34
|
+
[project.entry-points.pytest11]
|
|
35
|
+
imply = "pytest_imply.plugin"
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["setuptools>=64", "setuptools-scm>=8"]
|
|
39
|
+
build-backend = "setuptools.build_meta"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools_scm]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
where = ["src"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""pytest-imply — skip tests implied by stronger ones."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
4
|
+
|
|
5
|
+
from pytest_imply.plugin import PytestImplyWarning
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("pytest-imply")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
__version__ = "unknown"
|
|
11
|
+
|
|
12
|
+
__all__ = ["PytestImplyWarning", "__version__"]
|