1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
from typing import List
import pytest
from _pytest.config import Config
from _pytest.config.argparsing import Parser
from _pytest.main import Session
from _pytest.mark import Mark
from _pytest.python import Function
from .sorter import Sorter
def pytest_configure(config: Config) -> None:
"""
Register the "order" marker and configure the plugin,
depending on the CLI options.
"""
provided_by_pytest_order = (
"Provided by pytest-order. " "See also: https://pytest-order.readthedocs.io/"
)
config_line = (
"order: specify ordering information for when tests should run "
"in relation to one another. " + provided_by_pytest_order
)
config.addinivalue_line("markers", config_line)
# We need to dynamically add this `tryfirst` decorator to the plugin:
# only when the CLI option is present should the decorator be added.
# Thus, we manually run the decorator on the class function and
# manually replace it.
if config.getoption("indulgent_ordering"):
wrapper = pytest.hookimpl(tryfirst=True)
else:
wrapper = pytest.hookimpl(trylast=True)
OrderingPlugin.pytest_collection_modifyitems = wrapper( # type:ignore[attr-defined]
modify_items
)
config.pluginmanager.register(OrderingPlugin(), "orderingplugin")
def pytest_addoption(parser: Parser) -> None:
"""Set up CLI option for pytest"""
group = parser.getgroup("order")
group.addoption(
"--indulgent-ordering",
action="store_true",
dest="indulgent_ordering",
help=(
"Request that the sort order provided by pytest-order be applied "
"before other sorting, allowing the other sorting to have priority"
),
)
group.addoption(
"--order-scope",
action="store",
dest="order_scope",
help=(
"Defines the scope used for ordering. Possible values are: "
"'session' (default), 'module', and 'class'. "
"Ordering is only done inside a scope."
),
)
group.addoption(
"--order-scope-level",
action="store",
type=int,
dest="order_scope_level",
help=(
"Defines that the given directory level is used as order scope. "
"Cannot be used with --order-scope. The value is a number "
"that defines the hierarchical index of the directories used as "
"order scope, starting with 0 at session scope."
),
)
group.addoption(
"--order-group-scope",
action="store",
dest="order_group_scope",
help=(
"Defines the scope used for order groups. Possible values are: "
" 'session' (default), 'module', and 'class'. "
"Ordering is first done inside a group, then between groups."
),
)
group.addoption(
"--sparse-ordering",
action="store_true",
dest="sparse_ordering",
help=(
"If there are gaps between ordinals, they are filled "
"with unordered tests."
),
)
group.addoption(
"--order-dependencies",
action="store_true",
dest="order_dependencies",
help=(
"If set, dependencies added by pytest-dependency will be ordered "
"if needed."
),
)
group.addoption(
"--order-marker-prefix",
action="store",
dest="order_marker_prefix",
help=(
"If set, markers starting with the given prefix followed by a number "
"are handled like order markers with an index."
),
)
group.addoption(
"--error-on-failed-ordering",
action="store_true",
dest="error_on_failed_ordering",
help=(
"If set, tests with relative markers that could not be ordered "
"will error instead of generating only a warning."
),
)
def _get_mark_description(mark: Mark):
if mark.kwargs:
return ", ".join([f"{k}={v}" for k, v in mark.kwargs.items()])
elif mark.args:
return f"index={mark.args[0]}"
return mark
def pytest_generate_tests(metafunc):
"""
Handle multiple pytest.mark.order decorators.
Make parametrized tests with corresponding order marks.
"""
if getattr(metafunc, "function", False):
if getattr(metafunc.function, "pytestmark", False):
# Get list of order marks
marks = metafunc.function.pytestmark
order_marks = [mark for mark in marks if mark.name == "order"]
if len(order_marks) > 1:
# Remove all order marks
metafunc.function.pytestmark = [
mark for mark in marks if mark.name != "order"
]
# Prepare arguments for parametrization with order marks
args = [
pytest.param(_get_mark_description(mark), marks=[mark])
for mark in order_marks
]
if "order" not in metafunc.fixturenames:
metafunc.fixturenames.append("order")
metafunc.parametrize("order", args)
@pytest.fixture
def fail_after_cannot_order():
pytest.fail("The test could not be ordered")
class OrderingPlugin:
"""
Plugin implementation.
By putting this in a class, we are able to dynamically register it after
the CLI is parsed.
"""
def modify_items(session: Session, config: Config, items: List[Function]) -> None:
sorter = Sorter(config, items)
items[:] = sorter.sort_items()
|