frappe/env/lib/python3.12/site-packages/pytest_order/plugin.py

175 lines
5.5 KiB
Python
Raw Permalink Normal View History

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()