diff --git a/CHANGELOG.md b/CHANGELOG.md index 889ac04..f290ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # pytest-order Release Notes +## Unreleased + +### Fixes +* `--order-marker-prefix` uses the complete marker suffix as the order index, + so multi-digit and negative indices work; negative indices can be written + as `neg` (for example `m_neg1` for -1) + ## [Version 1.5.0](https://pypi.org/project/pytest-order/1.5.0/) (2026-06-13) Adds option to abort tests after a failed ordering. diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index a1ac89c..541651d 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -494,6 +494,29 @@ gives you now:: test_module.py:18: test_c PASSED test_module.py:6: test_a PASSED +The complete suffix after the prefix is used as the integer order index. For +example, ``m11`` has index 11. A marker without an integer suffix is ignored. +An explicit ``order`` marker takes precedence. + +A minus sign cannot be written in ``@pytest.mark.``, so for a negative +index write ``neg`` in its place, optionally preceded by an underscore: +``m_neg1`` (or ``mneg1``) has index -1. You can also use the minus sign itself +by looking the marker up by name: + +.. code:: python + + import pytest + + + @pytest.mark.m_neg1 + def test_last(): + assert True + + + @getattr(pytest.mark, "m-2") + def test_second_to_last(): + assert True + .. note:: As usually, you are responsible for registering your own markers, either in the code or in the ``pytest.ini`` file. If you forget this, pytest will give you warnings about unknown markers. diff --git a/src/pytest_order/sorter.py b/src/pytest_order/sorter.py index eca7b57..b169887 100644 --- a/src/pytest_order/sorter.py +++ b/src/pytest_order/sorter.py @@ -113,8 +113,10 @@ def mark_binning( if not has_order and self.settings.marker_prefix: for key in keys: if key.startswith(self.settings.marker_prefix): + suffix = key[len(self.settings.marker_prefix) :] + negative = re.fullmatch(r"_?neg(\d+)", suffix) try: - index = int(key[len(self.settings.marker_prefix)]) + index = -int(negative[1]) if negative else int(suffix) item.order = index except ValueError: pass diff --git a/tests/test_marker_prefix.py b/tests/test_marker_prefix.py index c57b3fb..8255522 100644 --- a/tests/test_marker_prefix.py +++ b/tests/test_marker_prefix.py @@ -161,3 +161,158 @@ def test_c(): "test_marker.py::test_a PASSED", ] ) + + +@pytest.mark.parametrize( + "suffix, expected", + [ + ("11", ["test_b", "test_a"]), + ("12", ["test_b", "test_a"]), + ("", ["test_b", "test_a"]), + ("1special", ["test_b", "test_a"]), + ("other", ["test_b", "test_a"]), + ("0", ["test_a", "test_b"]), + ], +) +def test_marker_prefix_uses_complete_numeric_suffix(test_path, suffix, expected): + test_path.makeini( + f""" + [pytest] + console_output_style = classic + markers = + my{suffix}: custom order + my2: custom order + """ + ) + test_path.makepyfile( + test_marker=f""" + import pytest + + @getattr(pytest.mark, "my{suffix}") + def test_a(): + pass + + @pytest.mark.my2 + def test_b(): + pass + """ + ) + result = test_path.runpytest("-v", "--order-marker-prefix=my") + result.assert_outcomes(passed=2) + result.stdout.fnmatch_lines([f"test_marker.py::{name} PASSED" for name in expected]) + + +def test_marker_prefix_supports_negative_indices(test_path): + test_path.makeini( + """ + [pytest] + console_output_style = classic + markers = + my-1: custom order + my-2: custom order + my2: custom order + """ + ) + test_path.makepyfile( + test_marker=""" + import pytest + + @getattr(pytest.mark, "my-1") + def test_a(): + pass + + @getattr(pytest.mark, "my-2") + def test_b(): + pass + + def test_c(): + pass + + @pytest.mark.my2 + def test_d(): + pass + """ + ) + result = test_path.runpytest("-v", "--order-marker-prefix=my") + result.assert_outcomes(passed=4) + result.stdout.fnmatch_lines( + [ + "test_marker.py::test_d PASSED", + "test_marker.py::test_c PASSED", + "test_marker.py::test_b PASSED", + "test_marker.py::test_a PASSED", + ] + ) + + +@pytest.mark.parametrize( + "suffix, expected", + [ + ("_neg1", ["test_c", "test_b", "test_a"]), + ("neg1", ["test_c", "test_b", "test_a"]), + ("_neg", ["test_c", "test_a", "test_b"]), + ("neg-1", ["test_c", "test_a", "test_b"]), + ("_negative1", ["test_c", "test_a", "test_b"]), + ], +) +def test_marker_prefix_supports_neg_placeholder(test_path, suffix, expected): + test_path.makeini( + f""" + [pytest] + console_output_style = classic + markers = + my{suffix}: custom order + my2: custom order + """ + ) + test_path.makepyfile( + test_marker=f""" + import pytest + + @getattr(pytest.mark, "my{suffix}") + def test_a(): + pass + + def test_b(): + pass + + @pytest.mark.my2 + def test_c(): + pass + """ + ) + result = test_path.runpytest("-v", "--order-marker-prefix=my") + result.assert_outcomes(passed=3) + result.stdout.fnmatch_lines([f"test_marker.py::{name} PASSED" for name in expected]) + + +@pytest.mark.parametrize("suffix", ["", "0", "11", "1special", "-1", "_neg1"]) +def test_explicit_order_overrides_marker_prefix(test_path, suffix): + test_path.makeini( + f""" + [pytest] + console_output_style = classic + markers = + my{suffix}: custom order + my2: custom order + """ + ) + test_path.makepyfile( + test_marker=f""" + import pytest + + @pytest.mark.order(3) + @getattr(pytest.mark, "my{suffix}") + def test_a(): + pass + + @pytest.mark.my2 + def test_b(): + pass + """ + ) + result = test_path.runpytest("-v", "--order-marker-prefix=my") + result.assert_outcomes(passed=2) + result.stdout.fnmatch_lines( + ["test_marker.py::test_b PASSED", "test_marker.py::test_a PASSED"] + )