Skip to content

Commit 4e01d45

Browse files
committed
Update handling of cross-class/module relative markers
- change class delimiter to "::" to be distinguishable from modules - fix handling of markers relative to other classes/modules - add more usage documentation - split off tests for relative ordering
1 parent fd679a2 commit 4e01d45

File tree

6 files changed

+499
-314
lines changed

6 files changed

+499
-314
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
## Unreleased
44

5+
### Changes
6+
- changed definition of classes in before/after markers (now uses `::` as
7+
delimiter)
8+
59
### Fixes
10+
- fixed handling of before/after markers in different classes and modules
611
- fixed handling of names in dependencies - did not match the actual
712
behavior of `pytest-dependency`
813

docs/source/index.rst

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,17 @@ The above is a trivial example, but ordering is respected across test files.
122122
regardless of the module and class they reside in. This can be changed
123123
by using the :ref:`order-scope` option.
124124

125-
There are currently three possibilities to define the order:
125+
Ordering is done either absolutely, by using ordinal numbers that define the
126+
order, or relative to other tests, using the ``before`` and ``after``
127+
attributes of the marker.
128+
129+
Ordering by numbers
130+
-------------------
131+
The order can be defined by ordinal numbers, or by ordinal strings.
126132

127133
Order by index
128-
--------------
129-
As already shown above, the order can be defined using the ordinal numbers.
134+
~~~~~~~~~~~~~~
135+
As already shown above, the order can be defined using ordinal numbers.
130136
There is a long form that uses the keyword ``index``, and a short form, that
131137
uses just the ordinal number--both are shown in the example below. The long
132138
form may be better readable if you want to combine it with a dependency marker
@@ -172,7 +178,7 @@ are used in Python lists, e.g. to count from the end:
172178
There is no limit for the numbers that can be used in this way.
173179

174180
Order using ordinals
175-
--------------------
181+
~~~~~~~~~~~~~~~~~~~~
176182

177183
Instead of the numbers, you can use ordinal names such as "first", "second",
178184
"last", and "second_to_last". These are convenience notations, and have the
@@ -233,8 +239,19 @@ Here is the complete list with the corresponding numbers:
233239
- 'seventh_to_last': -7
234240
- 'eighth_to_last': -8
235241

242+
Handling of unordered tests
243+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
244+
By default, tests with no ``order`` mark are executed after all tests with
245+
positive ordinal numbers (or the respective names), and before tests with
246+
negative ordinal numbers. The order of these tests in relationship to each
247+
other is not changed. This behavior may slightly change if the option
248+
:ref:`sparse-ordering` is used and the ordinals are not contiguous (see
249+
below).
250+
251+
236252
Order relative to other tests
237253
-----------------------------
254+
238255
The test order can be defined relative to other tests, which are referenced
239256
by their name:
240257

@@ -249,7 +266,7 @@ by their name:
249266
def test_second():
250267
assert True
251268
252-
@pytest.mark.run(before='test_second')
269+
@pytest.mark.order(before='test_second')
253270
def test_first():
254271
assert True
255272
@@ -267,20 +284,67 @@ by their name:
267284

268285
=========================== 4 passed in 0.02 seconds ===========================
269286

287+
Referencing of tests in other classes or modules
288+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
270289
If a test is referenced using the unqualified test name as shown in the
271-
example, the test is assumed to be in the current module. For tests in other
272-
modules the qualified test name (e.g. ``my_module.test_something``) has to
273-
be used.
290+
example above, the test is assumed to be in the current module and the current
291+
class, if any. For tests in other classes in the same module the class name
292+
with a ``::`` suffix has to be prepended to the test name:
293+
294+
.. code:: python
295+
296+
import pytest
297+
298+
class TestA:
299+
@pytest.mark.order(after='TestB::test_c')
300+
def test_a():
301+
assert True
302+
303+
def test_b():
304+
assert True
305+
306+
class TestB:
307+
def test_c():
308+
assert True
309+
310+
If the referenced test lives in another module, the test name has to be
311+
prepended by the module path to be uniquely identifiable
312+
(e.g. ``mod_test.test_something`` or ``mod_test.TestA.test_a``).
274313

275314
If an unknown test is referenced, a warning is issued and the test in
276315
question is ordered behind all other tests.
277316

278-
.. note::
279-
The `pytest-dependency <https://pypi.org/project/pytest-dependency/>`__
280-
plugin also manages dependencies between tests (skips tests that depend
281-
on skipped or failed tests), but doesn't do any ordering. You can combine
282-
both plugins if you need both options--see :ref:`order-dependencies`
283-
below for more information.
317+
Combination of absolute and relative ordering
318+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
319+
If you combine absolute and relative order markers, the ordering is first done
320+
for the absolute markers (e.g. the ordinals), and afterwards for the relative
321+
ones. This means that relative ordering always takes preference:
322+
323+
.. code:: python
324+
325+
import pytest
326+
327+
@pytest.mark.order(index=0, after='test_second')
328+
def test_first():
329+
assert True
330+
331+
@pytest.mark.order(1)
332+
def test_second():
333+
assert True
334+
335+
In this case, ``test_second`` will be executed before ``test_first``,
336+
regardless of the ordinal markers.
337+
338+
Relationship with pytest-dependency
339+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
340+
The `pytest-dependency <https://pypi.org/project/pytest-dependency/>`__
341+
plugin also manages dependencies between tests (skips tests that depend
342+
on skipped or failed tests), but currently doesn't do any ordering. If you
343+
want to execute the tests in a specific order to each other, you can use
344+
``pytest-ordering``. If you want to skip or xfail tests dependent on other
345+
tests you can use ``pytest-dependency``. If you want to have both behaviors
346+
combined, you can use both plugins together with the
347+
option :ref:`order-dependencies`--see below for more information.
284348

285349
Configuration
286350
=============
@@ -325,6 +389,8 @@ separate test functions, these test functions are handled separately from the
325389
test classes. If a module has no test classes, the effect is the same as
326390
if using ``--order-scope=module``.
327391

392+
.. _sparse-ordering:
393+
328394
``--sparse-ordering``
329395
---------------------
330396
Ordering tests by ordinals where some numbers are missing by default behaves
@@ -430,9 +496,8 @@ Note that ``pytest-order`` does not replace ``pytest-dependency``--it just
430496
adds ordering to the existing functionality if needed.
431497

432498
.. note::
433-
This feature is considered experimental. It will not handle all cases of
434-
defined dependencies. If there is sufficient demand (reflected in issues),
435-
this may be expanded.
499+
This feature is considered experimental. It may not handle all cases of
500+
defined dependencies. Please write an issue if you need further support.
436501

437502
Miscellaneous
438503
=============

pytest_order/__init__.py

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,13 @@ def initialize(cls, config):
113113

114114
def full_name(item, name=None):
115115
if name and "." in name:
116-
# assume it is already qualified
116+
# assumed to be sufficiently qualified
117117
return name
118-
path = item.location[0]
119-
if os.sep in path:
120-
path = item.location[0].rsplit(os.sep, 1)[1]
121-
path = path[:-3] + "."
118+
path = item.location[0].replace(os.sep, ".")[:-3] + "."
122119
if name is None:
123-
return path + item.location[2]
124-
if "." in item.location[2]:
125-
path += item.location[2].rsplit(".", 1)[0] + "."
120+
return path + item.location[2].replace(".", "::")
121+
if "." in item.location[2] and "::" not in name:
122+
path += item.location[2].rsplit(".", 1)[0] + "::"
126123
return path + name
127124

128125

@@ -182,37 +179,38 @@ def mark_binning(item, keys, start, end, before, after, dep, unordered, alias):
182179

183180

184181
def insert_before(name, items, sort):
185-
for pos, item in enumerate(sort):
186-
if name == full_name(item):
187-
for item_to_insert in items:
188-
if item_to_insert in sort:
189-
index = sort.index(item_to_insert)
190-
if index > pos:
191-
del sort[index]
192-
sort.insert(pos, item_to_insert)
193-
else:
194-
if pos == 0:
195-
sort[:] = items + sort
182+
if name:
183+
for pos, item in enumerate(sort):
184+
if full_name(item).endswith(name):
185+
for item_to_insert in items:
186+
if item_to_insert in sort:
187+
index = sort.index(item_to_insert)
188+
if index > pos:
189+
del sort[index]
190+
sort.insert(pos, item_to_insert)
196191
else:
197-
sort[pos:1] = items
198-
return True
192+
if pos == 0:
193+
sort[:] = items + sort
194+
else:
195+
sort[pos:1] = items
196+
return True
199197
return False
200198

201199

202200
def insert_after(name, items, sort):
203-
for pos, item in reversed(list(enumerate(sort))):
204-
item_name = full_name(item)
205-
if item_name == name:
206-
for item_to_insert in items:
207-
if item_to_insert in sort:
208-
index = sort.index(item_to_insert)
209-
if index < pos + 1:
210-
del sort[index]
211-
pos -= 1
212-
sort.insert(pos + 1, item_to_insert)
213-
else:
214-
sort[pos + 1:1] = items
215-
return True
201+
if name:
202+
for pos, item in reversed(list(enumerate(sort))):
203+
if full_name(item).endswith(name):
204+
for item_to_insert in items:
205+
if item_to_insert in sort:
206+
index = sort.index(item_to_insert)
207+
if index < pos + 1:
208+
del sort[index]
209+
pos -= 1
210+
sort.insert(pos + 1, item_to_insert)
211+
else:
212+
sort[pos + 1:1] = items
213+
return True
216214
return False
217215

218216

tests/test_order_scope.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
import pytest
66

77
import pytest_order
8-
from utils import assert_test_order
8+
try:
9+
from tests.utils import write_test, assert_test_order
10+
except ImportError:
11+
from utils import write_test, assert_test_order
912

1013

1114
@pytest.fixture(scope="module")
@@ -41,8 +44,7 @@ def test_two():
4144
def test_one():
4245
assert True
4346
"""
44-
with open(testname, "w") as fi:
45-
fi.write(test_class_contents)
47+
write_test(testname, test_class_contents)
4648

4749
test_function_contents = """
4850
import pytest
@@ -56,9 +58,8 @@ def test1_one():
5658
assert True
5759
"""
5860
testname = os.path.join(fixture_path, "test_functions1.py")
59-
with open(testname, "w") as fi:
60-
fi.write(test_function_contents)
61-
test_function_contents = """
61+
write_test(testname, test_function_contents)
62+
test_function_contents = """
6263
import pytest
6364
6465
@pytest.mark.order("last")
@@ -70,8 +71,7 @@ def test2_one():
7071
assert True
7172
"""
7273
testname = os.path.join(fixture_path, "test_functions2.py")
73-
with open(testname, "w") as fi:
74-
fi.write(test_function_contents)
74+
write_test(testname, test_function_contents)
7575
yield fixture_path
7676
shutil.rmtree(fixture_path, ignore_errors=True)
7777

0 commit comments

Comments
 (0)