Skip to content

Commit 179d99a

Browse files
committed
Integrate isoscope scheduling and distributed sccope isolation into xdist. Not tested yet.
1 parent 7c5a664 commit 179d99a

File tree

9 files changed

+159
-105
lines changed

9 files changed

+159
-105
lines changed

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
pytest-xdist 3.7.zzz (2024-zz-zz)
2+
===============================
3+
4+
Features
5+
--------
6+
- ZZZ Describe addition of isoscope scheduler
7+
18
pytest-xdist 3.6.1 (2024-04-28)
29
===============================
310

docs/distribution.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ The test distribution algorithm is configured with the ``--dist`` command-line o
4949

5050
.. _distribution modes:
5151

52+
* ``--dist isoscope``: Scope Isolation Scheduler. Tests are grouped by module for
53+
test functions and by class for test methods. Tests are executed one group at a
54+
time, distributed across available workers. This groupwise isolation guarantees
55+
that all tests in one group complete execution before running another group of
56+
tests. This can be useful when module-level or class-level fixtures of one group
57+
could create undesirable side-effects for tests in other test groups, while
58+
taking advantage of distributed execution of tests within each group. Grouping
59+
by class takes priority over grouping by module. NOTE: the use of this scheduler
60+
requires distributed coordination for setup and teardown such as provided by
61+
the ``iso_scheduling`` fixture or an alternate implementation of distributed
62+
coordination - see the ``iso_scheduling.coordinate_setup_teardown`` usage example
63+
in iso_scheduling_plugin.py.
64+
5265
* ``--dist load`` **(default)**: Sends pending tests to any worker that is
5366
available, without any guaranteed order. Scheduling can be fine-tuned with
5467
the `--maxschedchunk` option, see output of `pytest --help`.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ classifiers = [
3434
requires-python = ">=3.8"
3535
dependencies = [
3636
"execnet>=2.1",
37+
"filelock>=3.13.1",
3738
"pytest>=7.0.0",
3839
]
3940
dynamic = ["version"]
@@ -47,6 +48,7 @@ Tracker = "https://github.com/pytest-dev/pytest-xdist/issues"
4748

4849
[project.entry-points.pytest11]
4950
xdist = "xdist.plugin"
51+
"xdist.iso_scheduling_plugin" = "xdist.iso_scheduling_plugin"
5052
"xdist.looponfail" = "xdist.looponfail"
5153

5254
[project.optional-dependencies]

src/xdist/dsession.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from xdist.remote import Producer
1616
from xdist.remote import WorkerInfo
1717
from xdist.scheduler import EachScheduling
18+
from xdist.scheduler import IsoScopeScheduling
1819
from xdist.scheduler import LoadFileScheduling
1920
from xdist.scheduler import LoadGroupScheduling
2021
from xdist.scheduler import LoadScheduling
@@ -113,6 +114,8 @@ def pytest_xdist_make_scheduler(
113114
dist = config.getvalue("dist")
114115
if dist == "each":
115116
return EachScheduling(config, log)
117+
if dist == "isoscope":
118+
return IsoScopeScheduling(config, log)
116119
if dist == "load":
117120
return LoadScheduling(config, log)
118121
if dist == "loadscope":

src/xdist/iso_scheduling_plugin.py

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2323
# SOFTWARE.
2424

25-
"""Pytest Fixtures for supporting the PARALLEL_MONO_SCOPE Test Distribution Mode.
25+
"""Pytest Fixtures for supporting users of isoscope scheduling.
2626
27-
NOTE: These fixtures are NOT compatible with any other Test Distribution Modes.
27+
NOTE: These fixtures are NOT compatible with any other xdist schedulers.
2828
2929
NOTE: DO NOT IMPORT this module. It needs to be loaded via pytest's
3030
`conftest.pytest_plugins` mechanism. Pytest doc discourages importing fixtures
@@ -46,8 +46,8 @@
4646
import filelock
4747
import pytest
4848

49-
from utils.common.parallel_mono_scope_utils import (
50-
ParallelMonoScopeFixture,
49+
from xdist.iso_scheduling_utils import (
50+
IsoSchedulingFixture,
5151
DistributedSetupCoordinator,
5252
DistributedSetupContext,
5353
DistributedTeardownContext,
@@ -63,14 +63,13 @@
6363

6464

6565
@pytest.fixture(scope='session')
66-
def parallel_mono_scope(
66+
def iso_scheduling(
6767
tmp_path_factory: pytest.TempPathFactory,
6868
testrun_uid: str,
6969
worker_id: str
70-
) -> ParallelMonoScopeFixture:
70+
) -> IsoSchedulingFixture:
7171
"""A session-scoped pytest fixture for coordinating setup/teardown of test
72-
scope/class which is executing in the parallel_mono_scope Test Distribution
73-
Mode.
72+
scope/class which is executing under isoscope scheduling.
7473
7574
NOTE: Each XDist remote worker is running its own Pytest Session.
7675
@@ -82,24 +81,23 @@ def parallel_mono_scope(
8281
import pytest
8382
8483
if TYPE_CHECKING:
85-
from utils.common.parallel_mono_scope_utils import (
86-
ParallelMonoScopeFixture,
84+
from xdist.iso_scheduling_utils import (
85+
IsoSchedulingFixture,
8786
DistributedSetupContext,
8887
DistributedTeardownContext
8988
)
9089
91-
@pytest.mark.parallel_mono_scope
92-
class TestDeng12345ParallelMonoScope:
90+
class TestSomething:
9391
9492
@classmethod
9593
@pytest.fixture(scope='class', autouse=True)
9694
def distributed_setup_and_teardown(
9795
cls,
98-
parallel_mono_scope: ParallelMonoScopeFixture:
96+
iso_scheduling: IsoSchedulingFixture:
9997
request: pytest.FixtureRequest):
10098
10199
# Distributed Setup and Teardown
102-
with parallel_mono_scope.coordinate_setup_teardown(
100+
with iso_scheduling.coordinate_setup_teardown(
103101
setup_request=request) as coordinator:
104102
# Distributed Setup
105103
coordinator.maybe_call_setup(cls.patch_system_under_test)
@@ -126,12 +124,13 @@ def revert_system_under_test(
126124
# Fetch state from `teardown_context.client_dir` and revert
127125
# changes made by `patch_system_under_test()`.
128126
129-
perms, tc_ids = generate_tests(
130-
os.path.realpath(__file__),
131-
TestDistributionModeEnum.PARALLEL_MONO_SCOPE)
127+
def test_case1(self)
128+
...
129+
130+
def test_case2(self)
131+
...
132132
133-
@pytest.mark.parametrize('test_data', perms, ids=tc_ids)
134-
def test_case(self, test_data: dict[str, dict])
133+
def test_case3(self)
135134
...
136135
```
137136
@@ -146,17 +145,17 @@ def test_case(self, test_data: dict[str, dict])
146145
yields an instance of `DistributedSetupCoordinator` for the current
147146
Pytest Session.
148147
"""
149-
return _ParallelMonoScopeFixtureImpl(tmp_path_factory=tmp_path_factory,
150-
testrun_uid=testrun_uid,
151-
worker_id=worker_id)
148+
return _IsoSchedulingFixtureImpl(tmp_path_factory=tmp_path_factory,
149+
testrun_uid=testrun_uid,
150+
worker_id=worker_id)
152151

153152

154-
class _ParallelMonoScopeFixtureImpl(ParallelMonoScopeFixture):
153+
class _IsoSchedulingFixtureImpl(IsoSchedulingFixture):
155154
"""Context manager yielding a new instance of the implementation of the
156155
`DistributedSetupCoordinator` interface.
157156
158-
An instance of _ParallelMonoScopeFixtureImpl is returned by our pytest
159-
fixture `parallel_mono_scope`.
157+
An instance of _IsoSchedulingFixtureImpl is returned by our pytest
158+
fixture `iso_scheduling`.
160159
"""
161160
# pylint: disable=too-few-public-methods
162161

@@ -206,11 +205,11 @@ def coordinate_setup_teardown(
206205

207206

208207
class _DistributedSetupCoordinatorImpl(DistributedSetupCoordinator):
209-
"""Distributed scope/class setup/teardown coordination for the
210-
`parallel_mono_scope` Test Distribution Mode.
208+
"""Distributed scope/class setup/teardown coordination for isoscope
209+
scheduling.
211210
212211
NOTE: do not instantiate this class directly. Use the
213-
`parallel_mono_scope` fixture instead!
212+
`iso_scheduling` fixture instead!
214213
215214
"""
216215
_DISTRIBUTED_SETUP_ROOT_DIR_LINK_NAME = 'distributed_setup'
@@ -257,7 +256,7 @@ def maybe_call_setup(
257256
Process-safe.
258257
259258
Call `maybe_call_setup` from the pytest setup-teardown fixture of your
260-
`PARALLEL_MONO_SCOPE` test (typically test class) if it needs to
259+
isoscope-scheduled test (typically test class) if it needs to
261260
initialize a resource which is common to all of its test cases which may
262261
be executing in different XDist worker processes (such as a subnet in
263262
`subnet.xml`).
@@ -272,8 +271,7 @@ def maybe_call_setup(
272271
:return: An instance of `DistributedSetupContext` which MUST be passed
273272
in the corresponding call to `maybe_call_teardown`.
274273
275-
:raise parallel_mono_scope.CoordinationTimeoutError: If attempt to
276-
acquire the lock times out.
274+
:raise CoordinationTimeoutError: If attempt to acquire the lock times out.
277275
"""
278276
# `maybe_call_setup()` may be called only once per instance of
279277
# `_SetupCoordinator`
@@ -307,7 +305,7 @@ def maybe_call_teardown(
307305
tests for your test scope. Process-safe.
308306
309307
Call `maybe_call_teardown` from the pytest setup-teardown fixture of
310-
your `PARALLEL_MONO_SCOPE` test (typically test class) if it needs to
308+
your isoscope-scheduled test (typically test class) if it needs to
311309
initialize a resource which is common to all of its test cases which may
312310
be executing in different XDist worker processes (such as a subnet in
313311
`subnet.xml`).
@@ -320,8 +318,7 @@ def maybe_call_teardown(
320318
invoked.
321319
:param timeout: Lock acquisition timeout in seconds
322320
323-
:raise parallel_mono_scope.CoordinationTimeoutError: If attempt to
324-
acquire the lock times out.
321+
:raise CoordinationTimeoutError: If attempt to acquire the lock times out.
325322
"""
326323
# Make sure `maybe_call_setup()` was already called on this instance
327324
# of `_SetupCoordinator`
@@ -359,8 +356,7 @@ def wrapper(*args, **kwargs):
359356

360357
class _DistributedSetupCoordinationImpl:
361358
"""Low-level implementation of Context Managers for Coordinating
362-
Distributed Setup and Teardown for the `parallel_mono_scope`
363-
Test Distribution Mode.
359+
Distributed Setup and Teardown for users of isoscope scheduling.
364360
"""
365361
_ROOT_STATE_FILE_NAME = 'root_state.json'
366362
_ROOT_LOCK_FILE_NAME = 'lock'
@@ -426,7 +422,7 @@ def acquire_distributed_setup(
426422
timeout: float
427423
) -> Generator[DistributedSetupContext, None, None]:
428424
"""Low-level implementation of Context Manager for Coordinating
429-
Distributed Setup for the `parallel_mono_scope` Test Distribution Mode.
425+
Distributed Setup for isoscope scheduling.
430426
431427
:param root_context_dir: Scope/class-specific root directory for
432428
saving this context manager's state. This directory is common to
@@ -436,8 +432,7 @@ def acquire_distributed_setup(
436432
directly by the calling setup-teardown fixture.
437433
:param timeout: Lock acquisition timeout in seconds
438434
439-
:raise parallel_mono_scope.CoordinationTimeoutError: If attempt to
440-
acquire the lock times out.
435+
:raise CoordinationTimeoutError: If attempt to acquire the lock times out.
441436
"""
442437
#
443438
# Before control passes to the managed code block
@@ -502,16 +497,14 @@ def acquire_distributed_teardown(
502497
timeout: float
503498
) -> Generator[DistributedTeardownContext, None, None]:
504499
"""Low-level implementation of Context Manager for Coordinating
505-
Distributed Teardown for the `parallel_mono_scope` Test Distribution
506-
Mode.
500+
Distributed Teardown for the isoscope scheduling.
507501
508502
:param setup_context: The instance of `DistributedSetupContext` that was
509503
yielded by the corresponding use of the
510504
`_distributed_setup_permission` context manager.
511505
:param timeout: Lock acquisition timeout in seconds
512506
513-
:raise parallel_mono_scope.CoordinationTimeoutError: If attempt to
514-
acquire the lock times out.
507+
:raise CoordinationTimeoutError: If attempt to acquire the lock times out.
515508
"""
516509
#
517510
# Before control passes to the managed code block

0 commit comments

Comments
 (0)