Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to selectively not shuffle tests by using markers #575

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.rst
Expand Up @@ -160,6 +160,15 @@ You can disable behaviours you don't like with the following flags:
the start of every test
* ``--randomly-dont-reorganize`` - turn off the shuffling of the order of tests

Or you can selectively disable the shuffling of the test order with the
``randomly_dont_reorganize`` marker:

* ``pytestmark = pytest.mark.randomly_dont_reorganize`` - disable shuffling for
all tests in a module
* ``@pytest.mark.randomly_dont_reorganize`` - disable shuffling for a class or function
Note: if using the marker on individual tests, all functions inside a module must be marked,
due to how the plugin shuffles test execution order

The plugin appears to Pytest with the name 'randomly'. To disable it
altogether, you can use the ``-p`` argument, for example:

Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Expand Up @@ -12,6 +12,9 @@ addopts = """\
--strict-config
--strict-markers
"""
markers = [
"randomly_dont_reorganize: Do not shuffle marked class or module",
]

[tool.mypy]
mypy_path = "src/"
Expand Down
21 changes: 14 additions & 7 deletions src/pytest_randomly/__init__.py
Expand Up @@ -229,12 +229,14 @@ def pytest_collection_modifyitems(config: Config, items: list[Item]) -> None:

modules_items: list[tuple[ModuleType | None, list[Item]]] = []
for module, group in groupby(items, _get_module):
modules_items.append(
(
module,
_shuffle_by_class(list(group), seed),
)
)
module_items = list(group)

for item in module_items:
if not item.get_closest_marker("randomly_dont_reorganize"):
module_items = _shuffle_by_class(module_items, seed)
break

modules_items.append((module, module_items))

def _module_key(module_item: tuple[ModuleType | None, list[Item]]) -> bytes:
module, _items = module_item
Expand Down Expand Up @@ -262,7 +264,12 @@ def _item_key(item: Item) -> bytes:

for klass, group in groupby(items, _get_cls):
klass_items = list(group)
klass_items.sort(key=_item_key)

for item in klass_items:
if not item.get_closest_marker("randomly_dont_reorganize"):
klass_items.sort(key=_item_key)
break

klasses_items.append((klass, klass_items))

def _cls_key(klass_items: tuple[type[Any] | None, list[Item]]) -> bytes:
Expand Down
248 changes: 248 additions & 0 deletions tests/test_pytest_randomly.py
Expand Up @@ -350,6 +350,107 @@ def test_d(self):
]


def test_class_test_methods_not_reordered_if_marked(ourtester):
ourtester.makepyfile(
test_one="""
import pytest

from unittest import TestCase

@pytest.mark.randomly_dont_reorganize
class T(TestCase):
def test_a(self):
pass

def test_b(self):
pass

def test_c(self):
pass

def test_d(self):
pass
"""
)
args = ["-v", "--randomly-seed=15"]

out = ourtester.runpytest(*args)

out.assert_outcomes(passed=4, failed=0)
assert out.outlines[9:13] == [
"test_one.py::T::test_a PASSED",
"test_one.py::T::test_b PASSED",
"test_one.py::T::test_c PASSED",
"test_one.py::T::test_d PASSED",
]


def test_classes_reordered_unless_marked(ourtester):
ourtester.makepyfile(
test_one="""
import pytest

from unittest import TestCase


class A(TestCase):
def test_aa(self):
pass
def test_ab(self):
pass
def test_ac(self):
pass


class B(TestCase):
def test_ba(self):
pass
def test_bb(self):
pass
def test_bc(self):
pass


@pytest.mark.randomly_dont_reorganize
class C(TestCase):
def test_ca(self):
pass
def test_cb(self):
pass
def test_cc(self):
pass


class D(TestCase):
def test_da(self):
pass
def test_db(self):
pass
def test_dc(self):
pass
"""
)
args = ["-v", "--randomly-seed=1"]

out = ourtester.runpytest(*args)

out.assert_outcomes(passed=12, failed=0)
assert out.outlines[9:21] == [
"test_one.py::A::test_ab PASSED",
"test_one.py::A::test_aa PASSED",
"test_one.py::A::test_ac PASSED",
"test_one.py::C::test_ca PASSED",
"test_one.py::C::test_cb PASSED",
"test_one.py::C::test_cc PASSED",
"test_one.py::B::test_bb PASSED",
"test_one.py::B::test_ba PASSED",
"test_one.py::B::test_bc PASSED",
"test_one.py::D::test_da PASSED",
"test_one.py::D::test_dc PASSED",
"test_one.py::D::test_db PASSED",
]


def test_test_functions_reordered(ourtester):
ourtester.makepyfile(
test_one="""
Expand Down Expand Up @@ -379,6 +480,153 @@ def test_d():
]


def test_test_functions_not_reordered_if_marked(ourtester):
ourtester.makepyfile(
test_one="""
import pytest

pytestmark = pytest.mark.randomly_dont_reorganize


def test_a():
pass

def test_b():
pass

def test_c():
pass

def test_d():
pass
"""
)
args = ["-v", "--randomly-seed=15"]

out = ourtester.runpytest(*args)

out.assert_outcomes(passed=4, failed=0)
assert out.outlines[9:13] == [
"test_one.py::test_a PASSED",
"test_one.py::test_b PASSED",
"test_one.py::test_c PASSED",
"test_one.py::test_d PASSED",
]


def test_test_functions_reordered_if_individual_function_is_marked(ourtester):
ourtester.makepyfile(
test_one="""
import pytest


def test_a():
pass

@pytest.mark.randomly_dont_reorganize
def test_b():
pass

def test_c():
pass

def test_d():
pass
"""
)
args = ["-v", "--randomly-seed=15"]

out = ourtester.runpytest(*args)

out.assert_outcomes(passed=4, failed=0)
assert out.outlines[9:13] == [
"test_one.py::test_c PASSED",
"test_one.py::test_a PASSED",
"test_one.py::test_b PASSED",
"test_one.py::test_d PASSED",
]


def test_test_functions_not_reordered_if_all_functions_are_marked(ourtester):
ourtester.makepyfile(
test_one="""
import pytest


@pytest.mark.randomly_dont_reorganize
def test_a():
pass

@pytest.mark.randomly_dont_reorganize
def test_b():
pass

@pytest.mark.randomly_dont_reorganize
def test_c():
pass

@pytest.mark.randomly_dont_reorganize
def test_d():
pass
"""
)
args = ["-v", "--randomly-seed=15"]

out = ourtester.runpytest(*args)

out.assert_outcomes(passed=4, failed=0)
assert out.outlines[9:13] == [
"test_one.py::test_a PASSED",
"test_one.py::test_b PASSED",
"test_one.py::test_c PASSED",
"test_one.py::test_d PASSED",
]


def test_test_files_reordered_unless_marked(ourtester):
code = """
def test_1():
pass

def test_2():
pass

def test_3():
pass
"""
ourtester.makepyfile(
test_a=code,
test_b=code,
test_c=f"""
import pytest

pytestmark = pytest.mark.randomly_dont_reorganize

{code}
""",
test_d=code,
)
args = ["-v", "--randomly-seed=15"]

out = ourtester.runpytest(*args)

out.assert_outcomes(passed=12, failed=0)
assert out.outlines[9:21] == [
"test_b.py::test_3 PASSED",
"test_b.py::test_1 PASSED",
"test_b.py::test_2 PASSED",
"test_a.py::test_3 PASSED",
"test_a.py::test_2 PASSED",
"test_a.py::test_1 PASSED",
"test_d.py::test_3 PASSED",
"test_d.py::test_1 PASSED",
"test_d.py::test_2 PASSED",
"test_c.py::test_1 PASSED",
"test_c.py::test_2 PASSED",
"test_c.py::test_3 PASSED",
]


def test_test_functions_reordered_when_randomness_in_module(ourtester):
ourtester.makepyfile(
test_one="""
Expand Down