Skip to content

Commit 43e0769

Browse files
Implement --fail-all-on-failed-ordering (#246)
1 parent c411fc8 commit 43e0769

7 files changed

Lines changed: 110 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### New features
6+
* added option `--fail-all-on-failed-ordering` to abort the whole test run
7+
without executing any tests if some tests could not be ordered
8+
59
### Fixes
610
- transitive relative chains are resolved as a single globally consistent order
711

docs/source/configuration.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,36 @@ If you use the option `--error-on-failed-ordering`, "test_two" will now error:
653653
ERROR test_failed_ordering.py::test_two - Failed: The test could not be ordered
654654
========================= 1 passed, 1 error in 0.75s ==========================
655655

656+
``--fail-all-on-failed-ordering``
657+
---------------------------------
658+
This option is analogous to ``--error-on-failed-ordering``, but instead of failing only
659+
the tests that could not be ordered, it aborts the whole test run immediately, without
660+
executing any tests. This is useful if running the tests in a wrong order has
661+
unwanted effects.
662+
663+
Using the example shown above::
664+
665+
$ pytest tests -vv --fail-all-on-failed-ordering
666+
============================= test session starts ==============================
667+
...
668+
collected 2 items
669+
670+
============================ no tests ran in 0.02s =============================
671+
ERROR: pytest-order: cannot execute 'test_two' relative to others: 'test_three'
672+
673+
The test run exits with the usage error exit code (4).
674+
675+
The option also works with ``--collect-only``, as the ordering happens during test
676+
collection. This allows validating the ordering, e.g. in CI, without executing any
677+
tests::
678+
679+
$ pytest tests --collect-only --fail-all-on-failed-ordering
680+
ERROR: pytest-order: cannot execute 'test_two' relative to others: 'test_three'
681+
============================= test session starts ==============================
682+
...
683+
========================== 2 tests collected in 0.01s ==========================
684+
685+
The exit code is 4 in this case as well, and 0 if all tests could be ordered.
656686

657687

658688
.. _`pytest-dependency`: https://pypi.org/project/pytest-dependency/

src/pytest_order/item.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Optional, Generic, TypeVar
33
from collections import defaultdict
44

5-
from _pytest.python import Function
5+
from pytest import Function, UsageError
66

77
from .settings import Scope, Settings
88

@@ -176,6 +176,10 @@ def print_unhandled_items(self) -> None:
176176
msg = " ".join([item.node_id for item in failed_items])
177177
sys.stdout.write("\nWARNING: cannot execute test relative to others: ")
178178
sys.stdout.write(msg)
179+
if self.settings.fail_all_on_failed_ordering:
180+
raise UsageError(
181+
f"pytest-order: cannot execute test relative to others: {msg}"
182+
)
179183
if self.settings.error_on_failed_ordering:
180184
sys.stdout.write(" - ignoring the marker.\n")
181185
else:

src/pytest_order/plugin.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from collections.abc import Generator, Callable
22

33
import pytest
4+
from pytest import Function
45
from _pytest.config import Config
56
from _pytest.config.argparsing import Parser
67
from _pytest.main import Session
78
from _pytest.mark import Mark
8-
from _pytest.python import Function
99

1010
from .sorter import Sorter
1111

@@ -126,6 +126,15 @@ def pytest_addoption(parser: Parser) -> None:
126126
"will error instead of generating only a warning."
127127
),
128128
)
129+
group.addoption(
130+
"--fail-all-on-failed-ordering",
131+
action="store_true",
132+
dest="fail_all_on_failed_ordering",
133+
help=(
134+
"If set, the whole test run fails immediately without running "
135+
"any tests if some tests with relative markers could not be ordered."
136+
),
137+
)
129138
group.addoption(
130139
"--order-after-ff",
131140
action="store_true",

src/pytest_order/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ def __init__(self, config: Config) -> None:
2626
self.error_on_failed_ordering: str = config.getoption(
2727
"error_on_failed_ordering"
2828
)
29+
self.fail_all_on_failed_ordering: bool = config.getoption(
30+
"fail_all_on_failed_ordering"
31+
)
2932
scope: str = config.getoption("order_scope")
3033
if scope in self.valid_scopes:
3134
self.scope: Scope = self.valid_scopes[scope]

src/pytest_order/sorter.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from _pytest.config import Config
99
from _pytest.mark import Mark
10-
from _pytest.python import Function
10+
from pytest import Function, UsageError
1111

1212
from .item import Item, ItemList, ItemGroup, filter_marks, move_item, RelativeMark
1313
from .settings import Settings, Scope
@@ -270,15 +270,15 @@ def handle_relative_marks(self, item: Item, mark: Mark) -> bool:
270270
return has_relative_marks
271271

272272
def warn_about_unknown_test(self, item: Item, rel_mark: str) -> None:
273+
msg = f"cannot execute '{item.item.name}' relative to others: '{rel_mark}'"
274+
if self.settings.fail_all_on_failed_ordering:
275+
raise UsageError(f"pytest-order: {msg}")
273276
if self.settings.error_on_failed_ordering:
274277
item.item.fixturenames.insert(0, "fail_after_cannot_order")
275278
ignore_msg = ""
276279
else:
277280
ignore_msg = " - ignoring the marker"
278-
sys.stdout.write(
279-
f"\nWARNING: cannot execute '{item.item.name}' relative to others: "
280-
f"'{rel_mark}'{ignore_msg}."
281-
)
281+
sys.stdout.write(f"\nWARNING: {msg}{ignore_msg}.")
282282

283283
def collect_markers(self) -> None:
284284
aliases: dict[str, list[Item]] = {}

tests/test_relative_ordering.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,30 @@ def test_3():
474474
)
475475

476476

477+
def test_failing_dependency_fails_run(test_path):
478+
test_path.makepyfile(
479+
test_failed_ordering="""
480+
import pytest
481+
482+
def test_1():
483+
pass
484+
485+
@pytest.mark.order(before="test_4")
486+
def test_2():
487+
pass
488+
489+
def test_3():
490+
pass
491+
"""
492+
)
493+
result = test_path.runpytest("-v", "--fail-all-on-failed-ordering")
494+
assert result.ret == pytest.ExitCode.USAGE_ERROR
495+
result.assert_outcomes(passed=0, failed=0)
496+
result.stderr.fnmatch_lines(
497+
["ERROR: pytest-order: cannot execute 'test_2' relative to others: 'test_4'"]
498+
)
499+
500+
477501
def test_dependency_in_class_before_unknown_test(item_names_for, capsys):
478502
test_content = """
479503
import pytest
@@ -565,6 +589,35 @@ def test_4():
565589
)
566590

567591

592+
def test_failed_run_after_dependency_loop(test_path):
593+
test_path.makepyfile(
594+
test_failed_ordering="""
595+
import pytest
596+
597+
@pytest.mark.order(after="test_3")
598+
def test_1():
599+
pass
600+
601+
@pytest.mark.order(1)
602+
def test_2():
603+
pass
604+
605+
@pytest.mark.order(after="test_1")
606+
def test_3():
607+
pass
608+
"""
609+
)
610+
result = test_path.runpytest("-v", "--fail-all-on-failed-ordering")
611+
assert result.ret == pytest.ExitCode.USAGE_ERROR
612+
result.assert_outcomes(passed=0, failed=0)
613+
result.stderr.fnmatch_lines(
614+
[
615+
"ERROR: pytest-order: cannot execute test relative to others: "
616+
"test_failed_ordering.py::test_* test_failed_ordering.py::test_*"
617+
]
618+
)
619+
620+
568621
def test_dependency_on_parametrized_test(item_names_for):
569622
test_content = """
570623
import pytest

0 commit comments

Comments
 (0)