Skip to content

Commit 7680cba

Browse files
committed
Added option --order-after-ff
- allows to run pytest-order after built-in hooks like the --failed-first option
1 parent 3c5f6f9 commit 7680cba

7 files changed

Lines changed: 175 additions & 58 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
### Changes
66
* removed official support for Python 3.7-3.9 (EOL), added Python 3.13 and 3.14
77

8+
### New features
9+
* added option `--order-after-ff`, that allows to run `pytest-order` after built-in hooks
10+
like the `--failed-first` option (see [#234](https://github.com/pytest-dev/pytest-order/issues/234))
11+
812
### Infrastructure
913
* use trusted publisher for release (see https://docs.pypi.org/trusted-publishers/)
1014
* use `pyproject.toml` for project setup
1115

1216
### Documentation
1317
* use a theme for documentation supporting dark mode
14-
15-
### Documentation
1618
* added use case for ordering test modules (see [#51](https://github.com/pytest-dev/pytest-order/issues/51))
19+
* fixed documentation for `--indulgent-ordering` option
1720

1821
## [Version 1.3.0](https://pypi.org/project/pytest-order/1.3.0/) (2024-08-22)
1922
Allows to fail tests that cannot be ordered.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ yields the output:
8484

8585
$ pytest test_foo.py -vv
8686
============================= test session starts ==============================
87-
platform darwin -- Python 3.7.1, pytest-5.4.3, py-1.8.1, pluggy-0.13.1 -- env/bin/python
87+
platform linux -- Python 3.11.11, pytest-9.0.3, pluggy-1.6.0 -- env/bin/python
8888
plugins: order
8989
collected 2 items
9090

docs/source/configuration.rst

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -503,26 +503,35 @@ gives you now::
503503
``--indulgent-ordering``
504504
------------------------
505505
You may sometimes find that you want to suggest an ordering of tests, while
506-
allowing it to be overridden for good reason. For example, if you run your test
507-
suite in parallel and have a number of tests which are particularly slow, it
508-
might be desirable to start those tests running first, in order to optimize
509-
your completion time. You can use the ``pytest-order`` plugin to inform pytest
510-
of this.
511-
512-
Now suppose you also want to prioritize tests which failed during the
513-
previous run, by using the ``--failed-first`` option. By default,
514-
pytest-order will override the ``--failed-first`` order, but by adding the
515-
``--indulgent-ordering`` option, you can ask pytest to run the sort from
516-
pytest-order *before* the sort from ``--failed-first``, allowing the failed
517-
tests to be sorted to the front (note that in pytest versions from 6.0 on,
518-
this seems not to be needed anymore, at least in this specific case).
506+
allowing it to be overridden for good reason. If you have another plugin installed
507+
that also affects the test order, and you want these changes made after ``pytest-order``
508+
has ordered the tests, this may not work: ``pytest-order`` always tries to run last.
509+
Using this option, ``pytest-order`` will try to run first instead, allowing other plugins
510+
to change the test order afterwards.
511+
512+
.. note::
513+
This option was originally added to allow to run ``pytest-order`` before the
514+
``--failed-first`` option hook. This is no longer needed with current ``pytest``
515+
version (see also the next option)
516+
517+
.. _order_after_ff:
518+
519+
``--order-after-ff``
520+
--------------------
521+
Using the ``pytest`` option ``--failed-first`` runs failing tests first in the next test run.
522+
Normally, this is done regardless of the ``order`` markers, because the hook implementing
523+
this option is executed after ``pytest-order``. Usually, this is what you want. However,
524+
if the failed test depends on the ordering, this may break the test, so it will continue
525+
to fail for that reason. For such cases, you can instruct ``pytest-order`` to run after
526+
that hook using the ``--order-after-ff`` option. This will also affect similar hooks
527+
(like ``failed-last``) or other ordering plugins that try to run last.
519528

520529
.. _sparse-ordering:
521530

522531
``--sparse-ordering``
523532
---------------------
524533
Ordering tests by ordinals where some numbers are missing by default behaves
525-
the same as if the the ordinals are consecutive. For example, these tests:
534+
the same as if the ordinals were consecutive. For example, these tests:
526535

527536
.. code:: python
528537
@@ -605,11 +614,11 @@ as it is without this option.
605614

606615
``--error-on-failed-ordering``
607616
------------------------------
608-
Relative ordering of tests my fail under some circumstances. Mostly this happens if the related marker
617+
Relative ordering of tests may fail under some circumstances. Mostly this happens if the related marker
609618
is not found, or if the tests have a cyclic dependency.
610619
The default behavior in this case is not to order the test in question, issue a warning during test
611620
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
621+
without having to check all warning messages, you can make the tests that cannot be ordered fail, so that
613622
they show up as errored in the report:
614623

615624
.. code:: python

src/pytest_order/plugin.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from collections.abc import Generator, Callable
2+
13
import pytest
24
from _pytest.config import Config
35
from _pytest.config.argparsing import Parser
@@ -27,12 +29,21 @@ def pytest_configure(config: Config) -> None:
2729
# only when the CLI option is present should the decorator be added.
2830
# Thus, we manually run the decorator on the class function and
2931
# manually replace it.
32+
hook_function: Callable[
33+
[Session, Config, list[Function]], None | Generator[None]
34+
] = modify_items
3035
if config.getoption("indulgent_ordering"):
36+
# try to run before other plugins
3137
wrapper = pytest.hookimpl(tryfirst=True)
38+
elif config.getoption("order_after_ff"):
39+
# run after the LFPlugin plugin, handling --failed-first and --failed-last
40+
wrapper = pytest.hookimpl(hookwrapper=True, tryfirst=True)
41+
hook_function = modify_items_gen
3242
else:
43+
# try to run after other plugins
3344
wrapper = pytest.hookimpl(trylast=True)
3445
OrderingPlugin.pytest_collection_modifyitems = wrapper( # type:ignore[attr-defined]
35-
modify_items
46+
hook_function
3647
)
3748
config.pluginmanager.register(OrderingPlugin(), "orderingplugin")
3849

@@ -115,6 +126,12 @@ def pytest_addoption(parser: Parser) -> None:
115126
"will error instead of generating only a warning."
116127
),
117128
)
129+
group.addoption(
130+
"--order-after-ff",
131+
action="store_true",
132+
dest="order_after_ff",
133+
help="If set, the plugin will run after the --failed-first and similar option hooks.",
134+
)
118135

119136

120137
def _get_mark_description(mark: Mark):
@@ -168,3 +185,11 @@ class OrderingPlugin:
168185
def modify_items(session: Session, config: Config, items: list[Function]) -> None:
169186
sorter = Sorter(config, items)
170187
items[:] = sorter.sort_items()
188+
189+
190+
def modify_items_gen(
191+
session: Session, config: Config, items: list[Function]
192+
) -> Generator[None]:
193+
yield
194+
sorter = Sorter(config, items)
195+
items[:] = sorter.sort_items()

tests/plugin_reverse.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# simple plugin that reverts the test order for testing indulgent ordering
2+
def pytest_collection_modifyitems(config, items):
3+
items[:] = items[::-1]

tests/test_indulgent_ordering.py

Lines changed: 0 additions & 38 deletions
This file was deleted.

tests/test_ordering_options.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
def test_order_after_ff(test_path):
2+
test_path.makepyfile(
3+
test_failing=(
4+
"""
5+
import pytest
6+
7+
@pytest.mark.order("second")
8+
def test_me_second():
9+
assert True
10+
11+
def test_that_fails():
12+
assert False
13+
14+
def test_after_failed():
15+
assert True
16+
17+
@pytest.mark.order("first")
18+
def test_me_first():
19+
assert True
20+
"""
21+
)
22+
)
23+
result = test_path.runpytest("-v")
24+
result.assert_outcomes(passed=3, failed=1)
25+
result.stdout.fnmatch_lines(
26+
[
27+
"test_failing.py::test_me_first PASSED",
28+
"test_failing.py::test_me_second PASSED",
29+
"test_failing.py::test_that_fails FAILED",
30+
"test_failing.py::test_after_failed PASSED",
31+
]
32+
)
33+
34+
result = test_path.runpytest("-v", "--ff")
35+
result.assert_outcomes(passed=3, failed=1)
36+
result.stdout.fnmatch_lines(
37+
[
38+
"test_failing.py::test_that_fails FAILED",
39+
"test_failing.py::test_me_first PASSED",
40+
"test_failing.py::test_me_second PASSED",
41+
"test_failing.py::test_after_failed PASSED",
42+
]
43+
)
44+
45+
result = test_path.runpytest("-v", "--ff", "--order-after-ff")
46+
result.assert_outcomes(passed=3, failed=1)
47+
result.stdout.fnmatch_lines(
48+
[
49+
"test_failing.py::test_me_first PASSED",
50+
"test_failing.py::test_me_second PASSED",
51+
"test_failing.py::test_that_fails FAILED",
52+
"test_failing.py::test_after_failed PASSED",
53+
]
54+
)
55+
56+
57+
def test_indulgent_ordering(test_path):
58+
test_path.makepyfile(
59+
test_failing=(
60+
"""
61+
import pytest
62+
63+
@pytest.mark.order("second")
64+
def test_me_second():
65+
assert True
66+
67+
def test_me_third():
68+
assert True
69+
70+
@pytest.mark.order("first")
71+
def test_me_first():
72+
assert True
73+
74+
def test_me_fourth():
75+
assert True
76+
"""
77+
)
78+
)
79+
result = test_path.runpytest("-v")
80+
result.assert_outcomes(passed=4)
81+
result.stdout.fnmatch_lines(
82+
[
83+
"test_failing.py::test_me_first PASSED",
84+
"test_failing.py::test_me_second PASSED",
85+
"test_failing.py::test_me_third PASSED",
86+
"test_failing.py::test_me_fourth PASSED",
87+
]
88+
)
89+
90+
result = test_path.runpytest("-v", "-p", "tests.plugin_reverse")
91+
result.assert_outcomes(passed=4)
92+
result.stdout.fnmatch_lines(
93+
[
94+
"test_failing.py::test_me_first PASSED",
95+
"test_failing.py::test_me_second PASSED",
96+
"test_failing.py::test_me_fourth PASSED",
97+
"test_failing.py::test_me_third PASSED",
98+
]
99+
)
100+
101+
result = test_path.runpytest(
102+
"-v",
103+
"-p",
104+
"tests.plugin_reverse",
105+
"--indulgent-ordering",
106+
)
107+
result.assert_outcomes(passed=4)
108+
result.stdout.fnmatch_lines(
109+
[
110+
"test_failing.py::test_me_fourth PASSED",
111+
"test_failing.py::test_me_third PASSED",
112+
"test_failing.py::test_me_second PASSED",
113+
"test_failing.py::test_me_first PASSED",
114+
]
115+
)

0 commit comments

Comments
 (0)