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

Loadgroup: Don't modify test nodeids any longer. #1119

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/xdist/dsession.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,10 @@ def pytest_terminal_summary(self, terminalreporter: Any) -> None:
terminalreporter.write_sep("=", f"xdist: {self._summary_report}")

def worker_collectionfinish(
self, node: WorkerController, ids: Sequence[str]
self,
node: WorkerController,
ids: Sequence[str],
group_markers: dict[str, str],
) -> None:
"""Worker has finished test collection.

Expand All @@ -290,6 +293,7 @@ def worker_collectionfinish(
assert self._session is not None
self._session.testscollected = len(ids)
assert self.sched is not None
self.sched.set_group_markers(group_markers)
self.sched.add_node_collection(node, ids)
if self.terminal:
self.trdist.setstatus(
Expand Down
32 changes: 13 additions & 19 deletions src/xdist/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,30 +201,25 @@ def run_one_test(self) -> None:
"runtest_protocol_complete", item_index=self.item_index, duration=duration
)

def pytest_collection_modifyitems(
self,
config: pytest.Config,
items: list[pytest.Item],
) -> None:
# add the group name to nodeid as suffix if --dist=loadgroup
if config.getvalue("loadgroup"):
for item in items:
mark = item.get_closest_marker("xdist_group")
if not mark:
continue
gname = (
mark.args[0]
if len(mark.args) > 0
else mark.kwargs.get("name", "default")
)
item._nodeid = f"{item.nodeid}@{gname}"

@pytest.hookimpl
def pytest_collection_finish(self, session: pytest.Session) -> None:
# collect the scope for each node for --dist=loadgroup
group_markers = {}
for item in session.items:
mark = item.get_closest_marker("xdist_group")
if not mark:
continue
gname = (
mark.args[0]
if len(mark.args) > 0
else mark.kwargs.get("name", "default")
)
group_markers[item.nodeid] = gname
self.sendevent(
"collectionfinish",
topdir=str(self.config.rootpath),
ids=[item.nodeid for item in session.items],
group_markers=group_markers,
)

@pytest.hookimpl
Expand Down Expand Up @@ -356,7 +351,6 @@ def getinfodict() -> WorkerInfo:


def setup_config(config: pytest.Config, basetemp: str | None) -> None:
config.option.loadgroup = config.getvalue("dist") == "loadgroup"
config.option.looponfail = False
config.option.usepdb = False
config.option.dist = "no"
Expand Down
5 changes: 5 additions & 0 deletions src/xdist/scheduler/each.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,17 @@ def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.node2pending: dict[WorkerController, list[int]] = {}
self._started: list[WorkerController] = []
self._removed2pending: dict[WorkerController, list[int]] = {}
self.group_markers: dict[str, str] = {}

if log is None:
self.log = Producer("eachsched")
else:
self.log = log.eachsched
self.collection_is_completed = False

def set_group_markers(self, group_markers: dict[str, str]) -> None:
self.group_markers = group_markers

@property
def nodes(self) -> list[WorkerController]:
"""A list of all nodes in the scheduler."""
Expand Down
4 changes: 4 additions & 0 deletions src/xdist/scheduler/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,17 @@ def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.node2pending: dict[WorkerController, list[int]] = {}
self.pending: list[int] = []
self.collection: list[str] | None = None
self.group_markers: dict[str, str] = {}
if log is None:
self.log = Producer("loadsched")
else:
self.log = log.loadsched
self.config = config
self.maxschedchunk = self.config.getoption("maxschedchunk")

def set_group_markers(self, group_markers: dict[str, str]) -> None:
self.group_markers = group_markers

@property
def nodes(self) -> list[WorkerController]:
"""A list of all nodes in the scheduler."""
Expand Down
33 changes: 3 additions & 30 deletions src/xdist/scheduler/loadgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,9 @@ def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
def _split_scope(self, nodeid: str) -> str:
"""Determine the scope (grouping) of a nodeid.

There are usually 3 cases for a nodeid::

example/loadsuite/test/test_beta.py::test_beta0
example/loadsuite/test/test_delta.py::Delta1::test_delta0
example/loadsuite/epsilon/__init__.py::epsilon.epsilon

#. Function in a test module.
#. Method of a class in a test module.
#. Doctest in a function in a package.

With loadgroup, two cases are added::

example/loadsuite/test/test_beta.py::test_beta0
example/loadsuite/test/test_delta.py::Delta1::test_delta0
example/loadsuite/epsilon/__init__.py::epsilon.epsilon
example/loadsuite/test/test_gamma.py::test_beta0@gname
example/loadsuite/test/test_delta.py::Gamma1::test_gamma0@gname

This function will group tests with the scope determined by splitting the first ``@``
from the right. That is, test will be grouped in a single work unit when they have
same group name. In the above example, scopes will be::

example/loadsuite/test/test_beta.py::test_beta0
example/loadsuite/test/test_delta.py::Delta1::test_delta0
example/loadsuite/epsilon/__init__.py::epsilon.epsilon
gname
gname
Either we get a scope from a `xdist_group` mark (and then return that), or we don't do any grouping.
"""
if nodeid.rfind("@") > nodeid.rfind("]"):
# check the index of ']' to avoid the case: parametrize mark value has '@'
return nodeid.split("@")[-1]
if nodeid in self.group_markers:
return self.group_markers[nodeid]
else:
return nodeid
4 changes: 4 additions & 0 deletions src/xdist/scheduler/loadscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.workqueue: OrderedDict[str, dict[str, bool]] = OrderedDict()
self.assigned_work: dict[WorkerController, dict[str, dict[str, bool]]] = {}
self.registered_collections: dict[WorkerController, list[str]] = {}
self.group_markers: dict[str, str] = {}

if log is None:
self.log = Producer("loadscopesched")
Expand All @@ -105,6 +106,9 @@ def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:

self.config = config

def set_group_markers(self, group_markers: dict[str, str]) -> None:
self.group_markers = group_markers

@property
def nodes(self) -> list[WorkerController]:
"""A list of all active nodes in the scheduler."""
Expand Down
2 changes: 2 additions & 0 deletions src/xdist/scheduler/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def add_node_collection(
collection: Sequence[str],
) -> None: ...

def set_group_markers(self, group_markers: dict[str, str]) -> None: ...

def mark_test_complete(
self,
node: WorkerController,
Expand Down
4 changes: 4 additions & 0 deletions src/xdist/scheduler/worksteal.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,17 @@ def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.node2pending: dict[WorkerController, list[int]] = {}
self.pending: list[int] = []
self.collection: list[str] | None = None
self.group_markers: dict[str, str] = {}
if log is None:
self.log = Producer("workstealsched")
else:
self.log = log.workstealsched
self.config = config
self.steal_requested_from_node: WorkerController | None = None

def set_group_markers(self, group_markers: dict[str, str]) -> None:
self.group_markers = group_markers

@property
def nodes(self) -> list[WorkerController]:
"""A list of all nodes in the scheduler."""
Expand Down
7 changes: 6 additions & 1 deletion src/xdist/workermanage.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,12 @@ def process_from_remote(
rep.item_index = item_index
self.notify_inproc(eventname, node=self, rep=rep)
elif eventname == "collectionfinish":
self.notify_inproc(eventname, node=self, ids=kwargs["ids"])
self.notify_inproc(
eventname,
node=self,
ids=kwargs["ids"],
group_markers=kwargs["group_markers"],
)
elif eventname == "runtest_protocol_complete":
self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "unscheduled":
Expand Down