Pytest plugin for test implication — skip tests implied by stronger ones
Project description
pytest-imply
A pytest plugin for test implication — skip tests that are implied by stronger ones.
The problem
Parametrized test suites often have an ordered severity axis:
test_count[c100] PASSED 2.58s
test_count[c500] PASSED 4.62s
test_count[c1000] PASSED 7.13s
test_count[c5000] PASSED 27.45s
test_count[c10000] PASSED 52.90s
If c10000 passes, the smaller counts will almost certainly pass
too — running them all wastes CI time.
The solution
pytest-imply lets the developer declare implication relationships between tests. When a stronger test passes, weaker tests are short-circuited with a synthetic PASSED result:
test_count[c10000] PASSED 52.90s
test_count[c5000] IMPLIED
test_count[c1000] IMPLIED
test_count[c500] IMPLIED
test_count[c100] IMPLIED
For named-token implication, reports now display the provider(s):
test_integration PASSED
test_unit IMPLIED by [test_integration]
implied_by: reports exactly one providerimplied_by_any: reports the first matched providerimplied_by_all: reports all providers
If the strongest test fails, the plugin works downward to find the threshold — no time wasted on tests below the passing level.
Installation
pip install pytest-imply
Markers
monotonic — parametrized implication
For tests parametrized along an ordered axis:
@pytest.mark.monotonic("count")
@pytest.mark.parametrize("count", [100, 500, 1000, 5000, 10000])
def test_stress(count):
run_workload(count)
The highest value runs first. If it passes, all lower values are implied. If it fails, the next-highest runs, and so on.
Use direction="asc" to run the smallest first:
@pytest.mark.monotonic("threshold", direction="asc")
@pytest.mark.parametrize("threshold", [0.01, 0.1, 0.5, 1.0])
def test_precision(threshold):
assert error < threshold
implies / implied_by / implied_by_any / implied_by_all — named tokens
For arbitrary implication relationships between tests:
@pytest.mark.implies("full_stack_ok")
def test_integration():
"""Full stack test — if this passes, unit tests are implied."""
...
@pytest.mark.implied_by("full_stack_ok")
def test_unit():
"""Implied by passing integration test — no need to run."""
...
Use implied_by_any for OR semantics (implied if any token was
recorded):
@pytest.mark.implied_by_any("tcp_ok", "udp_ok")
def test_loopback():
"""Implied if either transport passed."""
...
Use implied_by_all for AND semantics (implied only if all tokens
were recorded):
@pytest.mark.implied_by_all("tcp_ok", "udp_ok")
def test_both_transports():
"""Implied only if both transports passed."""
...
Ordering
The plugin builds a dependency graph from all implication relationships and topologically sorts the test items using Kahn's algorithm. This guarantees that implying tests always run before their implied dependents. Tests without implication markers keep their original collection order.
Caveats
-
Transitive propagation: when a test is implied (short-circuited), its
impliestokens are still propagated, so downstream tests see them. This means chains like A→B→C work as expected. -
Stacked markers: multiple markers of the same type on one test are all honoured. For example, stacking two
@pytest.mark.impliesdecorators records both sets of tokens. -
Token namespacing: tokens live in a single flat namespace. In large projects, use a prefix convention (e.g.,
"mymodule::token") to avoid accidental collisions between independently-authored test modules. -
Fixtures: when a test is implied, its fixtures do not run. If an implied test has a session- or module-scoped fixture whose side effects other tests depend on, those tests may break. Only mark tests as implied when their fixtures are not needed by other tests. The plugin warns about non-function-scoped fixtures on implied tests. See Suppressing fixture warnings for ways to silence these when the fixtures are known to be safe.
-
pytest-xdist: the plugin does not support parallel workers. Implication state is per-process. When xdist is active with workers, implication logic is automatically disabled and a warning is emitted. Use
-p no:implyto suppress the warning. -
Plugin interoperability: when a test is implied, the plugin returns
Truefrompytest_runtest_protocol, which tells pytest the item is fully handled. Other plugins that wrap the test protocol (e.g.,pytest-cov,pytest-timeout) will not see implied tests. This is inherent to the short-circuit design. -
Ordering plugins (e.g.,
pytest-randomly,pytest-ordering): pytest-imply topologically sorts test items to satisfy dependency constraints. If another plugin reorders items after the sort, pytest-imply detects the change at the start of execution and emits aPytestImplyWarning. Implication stays active: tests whose providers still happen to run first are implied as usual, while others simply run normally. Correctness is never affected. -
Orphan tokens: if an
implied_by(or variant) references a token that no test provides viaimplies, a warning is emitted at collection time. -
Dependency cycles: if implication markers form a cycle, the plugin falls back to original collection order for the affected tests and emits a warning.
-
xfail interaction: a test marked
@pytest.mark.xfailwithstrict=Falsethat passes (xpass) does record itsimpliestokens. Withstrict=True, an xpass is treated as a failure and tokens are not recorded.
Disabling implication
--no-imply
Disable all implication logic and run every test:
pytest --no-imply
Use this for exhaustive nightly or release-gate runs to verify that the developer's implication assumptions still hold.
imply_enabled ini option
Disable implication via configuration instead of a CLI flag:
[tool.pytest.ini_options]
imply_enabled = false
Suppressing fixture warnings
The plugin warns when an implied test uses a non-function-scoped
fixture, since that fixture's side effects will be skipped. For
fixtures that are safe to skip (e.g. session-scoped autouse
directory creation), suppress the warning in two ways:
imply_ignore_fixtures ini option (global)
List fixture names that should never trigger the warning:
[tool.pytest.ini_options]
imply_ignore_fixtures = ["_prepare_log_dir", "db_schema"]
@pytest.mark.imply_suppress (per-test)
Suppress the warning for a specific test or test class:
@pytest.mark.imply_suppress
@pytest.mark.monotonic("count")
@pytest.mark.parametrize("count", [100, 1000, 10000])
def test_stress(count, session_fixture):
...
Apply it at the class level to suppress for all tests in the class:
@pytest.mark.imply_suppress
class TestSuite:
@pytest.mark.monotonic("count")
@pytest.mark.parametrize("count", [100, 1000])
def test_a(self, count, session_fixture):
...
How it works
- Collection —
pytest_collection_modifyitemsbuilds the dependency graph and topologically sorts items. - Execution —
pytest_runtest_protocolchecks whether a test is implied; if so, it emits syntheticTestReportobjects and returnsTrue. - Recording —
pytest_runtest_makereportrecords tokens and monotonic passes after a test succeeds. - Reporting —
pytest_report_teststatusrenders implied tests asIMPLIED (reason)with a cyanimarker.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_imply-0.1.4.tar.gz.
File metadata
- Download URL: pytest_imply-0.1.4.tar.gz
- Upload date:
- Size: 22.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b54a107e4a6efbcf63406681276732b3d3f9da6e1315f7b6e9ce854c3505f0a
|
|
| MD5 |
45d730abd4da1314ffffe7896dbc4c0a
|
|
| BLAKE2b-256 |
f7bbe2a2933353724234447a6f597d80742937fc95d97bfd479ab0c75ce317e1
|
File details
Details for the file pytest_imply-0.1.4-py3-none-any.whl.
File metadata
- Download URL: pytest_imply-0.1.4-py3-none-any.whl
- Upload date:
- Size: 14.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f68b742e673f018709acba4b424f1848b9a9c700c957f302e6b748d4369e52b5
|
|
| MD5 |
a5852b50e0f2fe3cbabe5025b84844bd
|
|
| BLAKE2b-256 |
e1896f9b31c9dbacaea16aaa3892ceadbf2d7bc1873756a0a028bdb29de1ae8a
|