Skip to content

Commit ef37cef

Browse files
committed
Added option --error-on-failed-ordering
- makes tests error that cannot be ordered - closes #140
1 parent c2e0ff4 commit ef37cef

File tree

6 files changed

+139
-8
lines changed

6 files changed

+139
-8
lines changed

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 `--error-on-failed-ordering` to make tests that cannot be ordered fail
7+
(see [#140](https://github.com/pytest-dev/pytest-order/issues/140))
8+
59
## Infrastructure
610
- added missing documentation examples, structured examples according to documentation structure
711

docs/source/configuration.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,49 @@ tests first from the start, then from the end if there are negative numbers,
603603
and the rest will be in between (e.g. between positive and negative numbers),
604604
as it is without this option.
605605

606+
``--error-on-failed-ordering``
607+
------------------------------
608+
Relative ordering of tests my fail under some circumstances. Mostly this happens if the related marker
609+
is not found, or if the tests have a cyclic dependency.
610+
The default behavior in this case is not to order the test in question, issue a warning during test
611+
collection and execute the test as usual. If you want to make sure that your relative markers work
612+
without checking all warning messages, you can also make the tests that cannot be ordered fail, so that
613+
they show up as errored in the report:
614+
615+
.. code:: python
616+
617+
import pytest
618+
619+
620+
def test_one():
621+
assert True
622+
623+
624+
@pytest.mark.order(before="test_three")
625+
def test_two():
626+
assert True
627+
628+
629+
In this example, the test "test_three" on which "test_two" depends, does not exist.
630+
If you use the option `--error-on-failed-ordering`, "test_two" will now error:
631+
632+
$ pytest tests -vv --error-on-failed-ordering
633+
============================= test session starts ==============================
634+
...
635+
WARNING: cannot execute 'test_two' relative to others: 'test_three'
636+
637+
test_failed_ordering.py::test_one PASSED
638+
test_failed_ordering.py::test_two ERROR
639+
640+
=================================== ERRORS ====================================
641+
__________________________ ERROR at setup of test_two__________________________
642+
...
643+
=========================== short test summary info ===========================
644+
ERROR test_failed_ordering.py::test_two - Failed: The test could not be ordered
645+
========================= 1 passed, 1 error in 0.75s ==========================
646+
647+
648+
606649
.. _`pytest-dependency`: https://pypi.org/project/pytest-dependency/
607650
.. _`dynamic compilation of marked parameters`: https://pytest-dependency.readthedocs.io/en/stable/advanced.html#dynamic-compilation-of-marked-parameters
608651
.. _`add dependencies at runtime`: https://pytest-dependency.readthedocs.io/en/stable/usage.html#marking-dependencies-at-runtime

pytest_order/item.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,20 @@ def sort_numbered_items(self) -> List[Item]:
102102
return sorted_list
103103

104104
def print_unhandled_items(self) -> None:
105-
msg = " ".join(
106-
[mark.item.node_id for mark in self.rel_marks]
107-
+ [mark.item.node_id for mark in self.dep_marks]
108-
)
109-
if msg:
110-
sys.stdout.write("\nWARNING: cannot execute test relative to others: ")
111-
sys.stdout.write(msg)
105+
failed_items = [mark.item for mark in self.rel_marks] + [
106+
mark.item for mark in self.dep_marks
107+
]
108+
msg = " ".join([item.node_id for item in failed_items])
109+
sys.stdout.write("\nWARNING: cannot execute test relative to others: ")
110+
sys.stdout.write(msg)
111+
if self.settings.error_on_failed_ordering:
112112
sys.stdout.write(" - ignoring the marker.\n")
113-
sys.stdout.flush()
113+
else:
114+
sys.stdout.write(".\n")
115+
sys.stdout.flush()
116+
if self.settings.error_on_failed_ordering:
117+
for item in failed_items:
118+
item.item.fixturenames.insert(0, "fail_after_cannot_order")
114119

115120
def number_of_rel_groups(self) -> int:
116121
return len(self.rel_marks) + len(self.dep_marks)

pytest_order/plugin.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ def pytest_addoption(parser: Parser) -> None:
110110
"are handled like order markers with an index."
111111
),
112112
)
113+
group.addoption(
114+
"--error-on-failed-ordering",
115+
action="store_true",
116+
dest="error_on_failed_ordering",
117+
help=(
118+
"If set, tests with relative markers that could not be ordered "
119+
"will error instead of generating only a warning."
120+
),
121+
)
113122

114123

115124
def _get_mark_description(mark: Mark):
@@ -146,6 +155,11 @@ def pytest_generate_tests(metafunc):
146155
metafunc.parametrize("order", args)
147156

148157

158+
@pytest.fixture
159+
def fail_after_cannot_order():
160+
pytest.fail("The test could not be ordered")
161+
162+
149163
class OrderingPlugin:
150164
"""
151165
Plugin implementation.

pytest_order/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ def __init__(self, config: Config) -> None:
2323
self.sparse_ordering: bool = config.getoption("sparse_ordering")
2424
self.order_dependencies: bool = config.getoption("order_dependencies")
2525
self.marker_prefix: str = config.getoption("order_marker_prefix")
26+
self.error_on_failed_ordering: str = config.getoption(
27+
"error_on_failed_ordering"
28+
)
2629
scope: str = config.getoption("order_scope")
2730
if scope in self.valid_scopes:
2831
self.scope: Scope = self.valid_scopes[scope]

tests/test_relative_ordering.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,36 @@ def test_3():
419419
assert warning in out
420420

421421

422+
def test_failing_dependency_fails_test(test_path):
423+
test_path.makepyfile(
424+
test_failed_ordering="""
425+
import pytest
426+
427+
def test_1():
428+
pass
429+
430+
@pytest.mark.order(before="test_4")
431+
def test_2():
432+
pass
433+
434+
def test_3():
435+
pass
436+
"""
437+
)
438+
result = test_path.runpytest("-v", "--error-on-failed-ordering")
439+
if int(pytest.__version__.split(".")[0]) < 6:
440+
result.assert_outcomes(passed=2, error=1)
441+
else:
442+
result.assert_outcomes(passed=2, errors=1)
443+
result.stdout.fnmatch_lines(
444+
[
445+
"test_failed_ordering.py::test_1 PASSED",
446+
"test_failed_ordering.py::test_2 ERROR",
447+
"test_failed_ordering.py::test_3 PASSED",
448+
]
449+
)
450+
451+
422452
def test_dependency_in_class_before_unknown_test(item_names_for, capsys):
423453
test_content = """
424454
import pytest
@@ -470,6 +500,38 @@ def test_3():
470500
assert warning in out
471501

472502

503+
def test_failed_tests_after_dependency_loop(test_path):
504+
test_path.makepyfile(
505+
test_failed_ordering="""
506+
import pytest
507+
508+
@pytest.mark.order(after="test_3")
509+
def test_1():
510+
pass
511+
512+
@pytest.mark.order(1)
513+
def test_2():
514+
pass
515+
516+
@pytest.mark.order(before="test_1")
517+
def test_3():
518+
pass
519+
"""
520+
)
521+
result = test_path.runpytest("-v", "--error-on-failed-ordering")
522+
if int(pytest.__version__.split(".")[0]) < 6:
523+
result.assert_outcomes(passed=1, error=2)
524+
else:
525+
result.assert_outcomes(passed=1, errors=2)
526+
result.stdout.fnmatch_lines(
527+
[
528+
"test_failed_ordering.py::test_2 PASSED",
529+
"test_failed_ordering.py::test_1 ERROR",
530+
"test_failed_ordering.py::test_3 ERROR",
531+
]
532+
)
533+
534+
473535
def test_dependency_on_parametrized_test(item_names_for):
474536
test_content = """
475537
import pytest

0 commit comments

Comments
 (0)