Skip to content

Commit 3948344

Browse files
committed
Add support for --indulgent-ordering option
- Author: Andrew Gilbert <[email protected]> This requests that the plugin be run first, allowing other plugins to override its ordering. This is useful if the order requirements provided by this plugin are intended to be priorities, rather than absolute requirements. For example, if some test cases run slower than others in your test suite, you can mark them as desirable to run first when running tests in parallel, allowing a more efficient use of a multi-core processor. By adding the `--indulgent-ordering` option, this ordering can still be overridden by, for example, the `--failed-first` option, which prioritizes tests which failed during the last run. Without `--indulgent-ordering`, the `--failed-first` option will be overridden by pytest-ordering, reducing its usefulness by increasing the total time until the results are available.
1 parent e4daa4d commit 3948344

File tree

6 files changed

+102
-4
lines changed

6 files changed

+102
-4
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ Ben Greene <[email protected]>
44
Adam Talsma <[email protected]>
55
Brian Maissy <[email protected]>
66
Jonas Zinn <[email protected]>
7+
Andrew Gilbert <[email protected]>

CHANGELOG

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Unreleased
2+
---
3+
### Added
4+
- #50 Add ``--indulgent-ordering`` to request that the sort from
5+
pytest-ordering be run before other plugins. This allows the built-in
6+
``--failed-first`` implementation to override the ordering.
17

28
0.6
39
---

docs/source/index.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,21 @@ You can also use markers such as "first", "second", "last", and "second_to_last"
120120

121121
=========================== 4 passed in 0.02 seconds ===========================
122122

123+
``--indulgent-ordering`` and overriding ordering
124+
-------------
125+
126+
You may sometimes find that you want to suggest an ordering of tests, while
127+
allowing it to be overridden for good reson. For example, if you run your test
128+
suite in parallel and have a number of tests which are particularly slow, it
129+
might be desirable to start those tests running first, in order to optimize
130+
your completion time. You can use the pytest-ordering plugin to inform pytest
131+
of this. Now suppose you also want to prioritize tests which failed during the
132+
previous run, by using the ``--failed-first`` option. By default,
133+
pytest-ordering will override the ``--failed-first`` order, but by adding the
134+
``--indulgent-ordering`` option, you can ask pytest to run the sort from
135+
pytest-ordering *before* the sort from ``--failed-first``, allowing the failed
136+
tests to be sorted to the front.
137+
123138

124139
Aspirational
125140
============

pytest_ordering/__init__.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import re
44
import sys
55

6+
import pytest
7+
68
from ._version import __version__
79

810
orders_map = {
@@ -26,7 +28,8 @@
2628

2729

2830
def pytest_configure(config):
29-
"""Register the "run" marker."""
31+
"""Register the "run" marker and configure the plugin depending on the CLI
32+
options"""
3033

3134
provided_by_pytest_ordering = (
3235
'Provided by pytest-ordering. '
@@ -45,6 +48,41 @@ def pytest_configure(config):
4548
provided_by_pytest_ordering)
4649
config.addinivalue_line('markers', config_line)
4750

51+
if config.getoption('indulgent-ordering'):
52+
# We need to dynamically add this `tryfirst` decorator to the plugin:
53+
# only when the CLI option is present should the decorator be added.
54+
# Thus, we manually run the decorator on the class function and
55+
# manually replace it.
56+
# Python 2.7 didn't allow arbitrary attributes on methods, so we have
57+
# to keep the function as a function and then add it to the class as a
58+
# pseudomethod. Since the class is purely for structuring and `self`
59+
# is never referenced, this seems reasonable.
60+
OrderingPlugin.pytest_collection_modifyitems = pytest.hookimpl(
61+
function=modify_items, tryfirst=True)
62+
else:
63+
OrderingPlugin.pytest_collection_modifyitems = pytest.hookimpl(
64+
function=modify_items, trylast=True)
65+
config.pluginmanager.register(OrderingPlugin(), 'orderingplugin')
66+
67+
68+
def pytest_addoption(parser):
69+
"""Set up CLI option for pytest"""
70+
group = parser.getgroup('ordering')
71+
group.addoption('--indulgent-ordering', action='store_true',
72+
dest='indulgent-ordering', help=
73+
'''Request that the sort \
74+
order provided by pytest-ordering be applied before other sorting, \
75+
allowing the other sorting to have priority''')
76+
77+
78+
class OrderingPlugin(object):
79+
"""
80+
Plugin implementation
81+
82+
By putting this in a class, we are able to dynamically register it after
83+
the CLI is parsed.
84+
"""
85+
4886

4987
def get_filename(item):
5088
name = item.location[0]
@@ -105,7 +143,6 @@ def mark_binning(item, keys, start, end, before, after, unordered):
105143

106144

107145
def insert(items, sort):
108-
list_items = []
109146
if isinstance(items, tuple):
110147
list_items = items[1]
111148
else:
@@ -139,7 +176,7 @@ def insert_after(name, items, sort):
139176
return False
140177

141178

142-
def pytest_collection_modifyitems(session, config, items):
179+
def modify_items(session, config, items):
143180
before_item = {}
144181
after_item = {}
145182
start_item = {}

tests/test_indulgent_ordering.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# -*- coding: utf-8 -*-
2+
import re
3+
4+
import pytest
5+
import pytest_ordering
6+
7+
pytest_plugins = ["pytester"]
8+
9+
10+
def test_run_marker_registered(capsys, tmpdir):
11+
testname = str(tmpdir.join("failing.py"))
12+
with open(testname, "w") as fi:
13+
fi.write(
14+
"""
15+
import pytest
16+
17+
@pytest.mark.second
18+
def test_me_second():
19+
assert True
20+
21+
def test_that_fails():
22+
assert False
23+
24+
@pytest.mark.first
25+
def test_me_first():
26+
assert True
27+
"""
28+
)
29+
args = ["--quiet", "--color=no", testname]
30+
pytest.main(args, [pytest_ordering])
31+
out, err = capsys.readouterr()
32+
assert "..F" in out
33+
args.insert(0, "--ff")
34+
pytest.main(args, [pytest_ordering])
35+
out, err = capsys.readouterr()
36+
assert "..F" in out
37+
args.insert(0, "--indulgent-ordering")
38+
pytest.main(args, [pytest_ordering])
39+
out, err = capsys.readouterr()
40+
assert "F.." in out

tests/test_ordering.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# -*- coding: utf-8 -*-
2-
import re
32

43
import pytest
54

0 commit comments

Comments
 (0)