Merge pull request #1051 from bluetech/ruff
Use ruff instead of black, flake8, autoflake, pyupgrade
This commit is contained in:
@@ -1,14 +1,10 @@
|
||||
repos:
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.3.1
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: "v0.3.5"
|
||||
hooks:
|
||||
- id: autoflake
|
||||
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet, --target-version, py35]
|
||||
- id: ruff
|
||||
args: ["--fix"]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: 1.16.0
|
||||
hooks:
|
||||
@@ -17,19 +13,7 @@ repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py3-plus]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rst
|
||||
|
||||
@@ -67,10 +67,72 @@ include-package-data = false
|
||||
[tool.setuptools_scm]
|
||||
write_to = "src/xdist/_version.py"
|
||||
|
||||
[tool.flake8]
|
||||
# Ignore any errors related to formatting, let black worry/fix them.
|
||||
ignore = ["E501", "W503", "E203"]
|
||||
max-line-length = 100
|
||||
[tool.ruff]
|
||||
src = ["src"]
|
||||
|
||||
[tool.ruff.format]
|
||||
docstring-code-format = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"B", # bugbear
|
||||
"D", # pydocstyle
|
||||
"E", # pycodestyle
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"PYI", # flake8-pyi
|
||||
"UP", # pyupgrade
|
||||
"RUF", # ruff
|
||||
"W", # pycodestyle
|
||||
"T10", # flake8-debugger
|
||||
"PIE", # flake8-pie
|
||||
"PGH", # pygrep-hooks
|
||||
"PLE", # pylint error
|
||||
"PLW", # pylint warning
|
||||
"PLR1714", # Consider merging multiple comparisons
|
||||
]
|
||||
ignore = [
|
||||
# bugbear ignore
|
||||
"B011", # Do not `assert False` (`python -O` removes these calls)
|
||||
"B028", # No explicit `stacklevel` keyword argument found
|
||||
# pydocstyle ignore
|
||||
"D100", # Missing docstring in public module
|
||||
"D101", # Missing docstring in public class
|
||||
"D102", # Missing docstring in public method
|
||||
"D103", # Missing docstring in public function
|
||||
"D104", # Missing docstring in public package
|
||||
"D105", # Missing docstring in magic method
|
||||
"D106", # Missing docstring in public nested class
|
||||
"D107", # Missing docstring in `__init__`
|
||||
"D209", # Multi-line docstring closing quotes should be on a separate line
|
||||
"D205", # 1 blank line required between summary line and description
|
||||
"D400", # First line should end with a period
|
||||
"D401", # First line of docstring should be in imperative mood
|
||||
# ruff ignore
|
||||
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
|
||||
# pylint ignore
|
||||
"PLW0603", # Using the global statement
|
||||
"PLW0120", # remove the else and dedent its contents
|
||||
"PLW2901", # for loop variable overwritten by assignment target
|
||||
"PLR5501", # Use `elif` instead of `else` then `if`
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pycodestyle]
|
||||
# In order to be able to format for 88 char in ruff format
|
||||
max-line-length = 120
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "pep257"
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
force-single-line = true
|
||||
combine-as-imports = true
|
||||
force-sort-within-sections = true
|
||||
order-by-type = false
|
||||
lines-after-imports = 2
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"src/xdist/_version.py" = ["I001"]
|
||||
|
||||
[tool.mypy]
|
||||
mypy_path = ["src"]
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from xdist.plugin import (
|
||||
is_xdist_worker,
|
||||
is_xdist_master,
|
||||
get_xdist_worker_id,
|
||||
is_xdist_controller,
|
||||
)
|
||||
from xdist._version import version as __version__
|
||||
from xdist.plugin import get_xdist_worker_id
|
||||
from xdist.plugin import is_xdist_controller
|
||||
from xdist.plugin import is_xdist_master
|
||||
from xdist.plugin import is_xdist_worker
|
||||
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import os
|
||||
from itertools import chain
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Callable, Iterator
|
||||
from typing import Callable
|
||||
from typing import Iterator
|
||||
|
||||
|
||||
def visit_path(
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import auto
|
||||
from enum import Enum
|
||||
from queue import Empty
|
||||
from queue import Queue
|
||||
import sys
|
||||
from enum import Enum, auto
|
||||
from typing import Sequence
|
||||
|
||||
import pytest
|
||||
|
||||
from xdist.remote import Producer
|
||||
from xdist.scheduler import EachScheduling
|
||||
from xdist.scheduler import LoadFileScheduling
|
||||
from xdist.scheduler import LoadGroupScheduling
|
||||
from xdist.scheduler import LoadScheduling
|
||||
from xdist.scheduler import LoadScopeScheduling
|
||||
from xdist.scheduler import WorkStealingScheduling
|
||||
from xdist.workermanage import NodeManager
|
||||
from xdist.scheduler import (
|
||||
EachScheduling,
|
||||
LoadScheduling,
|
||||
LoadScopeScheduling,
|
||||
LoadFileScheduling,
|
||||
LoadGroupScheduling,
|
||||
WorkStealingScheduling,
|
||||
)
|
||||
|
||||
|
||||
from queue import Empty, Queue
|
||||
|
||||
|
||||
class Interrupted(KeyboardInterrupt):
|
||||
@@ -25,7 +24,7 @@ class Interrupted(KeyboardInterrupt):
|
||||
|
||||
|
||||
class DSession:
|
||||
"""A pytest plugin which runs a distributed test session
|
||||
"""A pytest plugin which runs a distributed test session.
|
||||
|
||||
At the beginning of the test session this creates a NodeManager
|
||||
instance which creates and starts all nodes. Nodes then emit
|
||||
@@ -61,7 +60,7 @@ class DSession:
|
||||
|
||||
@property
|
||||
def session_finished(self):
|
||||
"""Return True if the distributed session has finished
|
||||
"""Return True if the distributed session has finished.
|
||||
|
||||
This means all nodes have executed all test items. This is
|
||||
used by pytest_runtestloop to break out of its loop.
|
||||
@@ -231,9 +230,7 @@ class DSession:
|
||||
)
|
||||
if maximum_reached:
|
||||
if self._max_worker_restart == 0:
|
||||
msg = "worker {} crashed and worker restarting disabled".format(
|
||||
node.gateway.id
|
||||
)
|
||||
msg = f"worker {node.gateway.id} crashed and worker restarting disabled"
|
||||
else:
|
||||
msg = "maximum crashed workers reached: %d" % self._max_worker_restart
|
||||
self._summary_report = msg
|
||||
@@ -251,7 +248,7 @@ class DSession:
|
||||
terminalreporter.write_sep("=", f"xdist: {self._summary_report}")
|
||||
|
||||
def worker_collectionfinish(self, node, ids):
|
||||
"""worker has finished test collection.
|
||||
"""Worker has finished test collection.
|
||||
|
||||
This adds the collection for this node to the scheduler. If
|
||||
the scheduler indicates collection is finished (i.e. all
|
||||
@@ -464,7 +461,7 @@ class TerminalDistReporter:
|
||||
rinfo = gateway._rinfo()
|
||||
different_interpreter = rinfo.executable != sys.executable
|
||||
if different_interpreter:
|
||||
version = "%s.%s.%s" % rinfo.version_info[:3]
|
||||
version = "{}.{}.{}".format(*rinfo.version_info[:3])
|
||||
self.rewrite(
|
||||
f"[{gateway.id}] {rinfo.platform} Python {version} cwd: {rinfo.cwd}",
|
||||
newline=True,
|
||||
@@ -491,7 +488,7 @@ class TerminalDistReporter:
|
||||
|
||||
|
||||
def get_default_max_worker_restart(config):
|
||||
"""gets the default value of --max-worker-restart option if it is not provided.
|
||||
"""Gets the default value of --max-worker-restart option if it is not provided.
|
||||
|
||||
Use a reasonable default to avoid workers from restarting endlessly due to crashing collections (#226).
|
||||
"""
|
||||
@@ -505,7 +502,7 @@ def get_default_max_worker_restart(config):
|
||||
|
||||
|
||||
def get_workers_status_line(
|
||||
status_and_items: Sequence[tuple[WorkerStatus, int]]
|
||||
status_and_items: Sequence[tuple[WorkerStatus, int]],
|
||||
) -> str:
|
||||
"""
|
||||
Return the line to display during worker setup/collection based on the
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, Sequence
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
import time
|
||||
import execnet
|
||||
from typing import Dict
|
||||
from typing import Sequence
|
||||
|
||||
from _pytest._io import TerminalWriter
|
||||
import execnet
|
||||
import pytest
|
||||
|
||||
from xdist._path import visit_path
|
||||
|
||||
@@ -253,7 +254,7 @@ class StatRecorder:
|
||||
return
|
||||
time.sleep(checkinterval)
|
||||
|
||||
def check(self, removepycfiles: bool = True) -> bool: # noqa, too complex
|
||||
def check(self, removepycfiles: bool = True) -> bool:
|
||||
changed = False
|
||||
newstat: Dict[Path, os.stat_result] = {}
|
||||
for rootdir in self.rootdirlist:
|
||||
|
||||
@@ -17,12 +17,12 @@ import pytest
|
||||
|
||||
@pytest.hookspec()
|
||||
def pytest_xdist_setupnodes(config, specs):
|
||||
"""called before any remote node is set up."""
|
||||
"""Called before any remote node is set up."""
|
||||
|
||||
|
||||
@pytest.hookspec()
|
||||
def pytest_xdist_newgateway(gateway):
|
||||
"""called on new raw gateway creation."""
|
||||
"""Called on new raw gateway creation."""
|
||||
|
||||
|
||||
@pytest.hookspec(
|
||||
@@ -31,7 +31,7 @@ def pytest_xdist_newgateway(gateway):
|
||||
)
|
||||
)
|
||||
def pytest_xdist_rsyncstart(source, gateways):
|
||||
"""called before rsyncing a directory to remote gateways takes place."""
|
||||
"""Called before rsyncing a directory to remote gateways takes place."""
|
||||
|
||||
|
||||
@pytest.hookspec(
|
||||
@@ -40,17 +40,17 @@ def pytest_xdist_rsyncstart(source, gateways):
|
||||
)
|
||||
)
|
||||
def pytest_xdist_rsyncfinish(source, gateways):
|
||||
"""called after rsyncing a directory to remote gateways takes place."""
|
||||
"""Called after rsyncing a directory to remote gateways takes place."""
|
||||
|
||||
|
||||
@pytest.hookspec(firstresult=True)
|
||||
def pytest_xdist_getremotemodule():
|
||||
"""called when creating remote node"""
|
||||
"""Called when creating remote node."""
|
||||
|
||||
|
||||
@pytest.hookspec()
|
||||
def pytest_configure_node(node):
|
||||
"""configure node information before it gets instantiated."""
|
||||
"""Configure node information before it gets instantiated."""
|
||||
|
||||
|
||||
@pytest.hookspec()
|
||||
@@ -65,12 +65,12 @@ def pytest_testnodedown(node, error):
|
||||
|
||||
@pytest.hookspec()
|
||||
def pytest_xdist_node_collection_finished(node, ids):
|
||||
"""called by the controller node when a worker node finishes collecting."""
|
||||
"""Called by the controller node when a worker node finishes collecting."""
|
||||
|
||||
|
||||
@pytest.hookspec(firstresult=True)
|
||||
def pytest_xdist_make_scheduler(config, log):
|
||||
"""return a node scheduler implementation"""
|
||||
"""Return a node scheduler implementation."""
|
||||
|
||||
|
||||
@pytest.hookspec(firstresult=True)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
import uuid
|
||||
import sys
|
||||
import uuid
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
@@ -296,7 +296,7 @@ def pytest_cmdline_main(config):
|
||||
if not val("collectonly") and _is_distribution_mode(config) and usepdb:
|
||||
raise pytest.UsageError(
|
||||
"--pdb is incompatible with distributing tests; try using -n0 or -nauto."
|
||||
) # noqa: E501
|
||||
)
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -305,7 +305,7 @@ def pytest_cmdline_main(config):
|
||||
|
||||
|
||||
def is_xdist_worker(request_or_session) -> bool:
|
||||
"""Return `True` if this is an xdist worker, `False` otherwise
|
||||
"""Return `True` if this is an xdist worker, `False` otherwise.
|
||||
|
||||
:param request_or_session: the `pytest` `request` or `session` object
|
||||
"""
|
||||
@@ -313,7 +313,7 @@ def is_xdist_worker(request_or_session) -> bool:
|
||||
|
||||
|
||||
def is_xdist_controller(request_or_session) -> bool:
|
||||
"""Return `True` if this is the xdist controller, `False` otherwise
|
||||
"""Return `True` if this is the xdist controller, `False` otherwise.
|
||||
|
||||
Note: this method also returns `False` when distribution has not been
|
||||
activated at all.
|
||||
|
||||
@@ -7,15 +7,16 @@
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import sys
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from execnet.gateway_base import dumps, DumpError
|
||||
|
||||
from _pytest.config import _prepareconfig
|
||||
from execnet.gateway_base import DumpError
|
||||
from execnet.gateway_base import dumps
|
||||
import pytest
|
||||
|
||||
|
||||
try:
|
||||
from setproctitle import setproctitle
|
||||
@@ -324,7 +325,7 @@ def setup_config(config, basetemp):
|
||||
|
||||
|
||||
if __name__ == "__channelexec__":
|
||||
channel = channel # type: ignore[name-defined] # noqa: F821
|
||||
channel = channel # type: ignore[name-defined] # noqa: F821, PLW0127
|
||||
workerinput, args, option_dict, change_sys_path = channel.receive() # type: ignore[name-defined]
|
||||
|
||||
if change_sys_path is None:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from xdist.scheduler.each import EachScheduling # noqa
|
||||
from xdist.scheduler.load import LoadScheduling # noqa
|
||||
from xdist.scheduler.loadfile import LoadFileScheduling # noqa
|
||||
from xdist.scheduler.loadscope import LoadScopeScheduling # noqa
|
||||
from xdist.scheduler.loadgroup import LoadGroupScheduling # noqa
|
||||
from xdist.scheduler.worksteal import WorkStealingScheduling # noqa
|
||||
from xdist.scheduler.each import EachScheduling as EachScheduling
|
||||
from xdist.scheduler.load import LoadScheduling as LoadScheduling
|
||||
from xdist.scheduler.loadfile import LoadFileScheduling as LoadFileScheduling
|
||||
from xdist.scheduler.loadgroup import LoadGroupScheduling as LoadGroupScheduling
|
||||
from xdist.scheduler.loadscope import LoadScopeScheduling as LoadScopeScheduling
|
||||
from xdist.scheduler.worksteal import WorkStealingScheduling as WorkStealingScheduling
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from xdist.remote import Producer
|
||||
from xdist.workermanage import parse_spec_config
|
||||
from xdist.report import report_collection_diff
|
||||
from xdist.workermanage import parse_spec_config
|
||||
|
||||
|
||||
class EachScheduling:
|
||||
"""Implement scheduling of test items on all nodes
|
||||
"""Implement scheduling of test items on all nodes.
|
||||
|
||||
If a node gets added after the test run is started then it is
|
||||
assumed to replace a node which got removed before it finished
|
||||
@@ -48,7 +48,7 @@ class EachScheduling:
|
||||
|
||||
@property
|
||||
def has_pending(self):
|
||||
"""Return True if there are pending test items
|
||||
"""Return True if there are pending test items.
|
||||
|
||||
This indicates that collection has finished and nodes are
|
||||
still processing test items, so this can be thought of as
|
||||
@@ -64,7 +64,7 @@ class EachScheduling:
|
||||
self.node2pending[node] = []
|
||||
|
||||
def add_node_collection(self, node, collection):
|
||||
"""Add the collected test items from a node
|
||||
"""Add the collected test items from a node.
|
||||
|
||||
Collection is complete once all nodes have submitted their
|
||||
collection. In this case its pending list is set to an empty
|
||||
@@ -119,7 +119,7 @@ class EachScheduling:
|
||||
return crashitem
|
||||
|
||||
def schedule(self):
|
||||
"""Schedule the test items on the nodes
|
||||
"""Schedule the test items on the nodes.
|
||||
|
||||
If the node's pending list is empty it is a new node which
|
||||
needs to run all the tests. If the pending list is already
|
||||
|
||||
@@ -3,8 +3,8 @@ from itertools import cycle
|
||||
from _pytest.runner import CollectReport
|
||||
|
||||
from xdist.remote import Producer
|
||||
from xdist.workermanage import parse_spec_config
|
||||
from xdist.report import report_collection_diff
|
||||
from xdist.workermanage import parse_spec_config
|
||||
|
||||
|
||||
class LoadScheduling:
|
||||
@@ -23,7 +23,7 @@ class LoadScheduling:
|
||||
submit a collection. This is used to know when all nodes have
|
||||
finished collection or how large the chunks need to be created.
|
||||
|
||||
Attributes:
|
||||
Attributes::
|
||||
|
||||
:numnodes: The expected number of nodes taking part. The actual
|
||||
number of nodes will vary during the scheduler's lifetime as
|
||||
@@ -95,7 +95,7 @@ class LoadScheduling:
|
||||
|
||||
@property
|
||||
def has_pending(self):
|
||||
"""Return True if there are pending test items
|
||||
"""Return True if there are pending test items.
|
||||
|
||||
This indicates that collection has finished and nodes are
|
||||
still processing test items, so this can be thought of as
|
||||
@@ -121,7 +121,7 @@ class LoadScheduling:
|
||||
self.node2pending[node] = []
|
||||
|
||||
def add_node_collection(self, node, collection):
|
||||
"""Add the collected test items from a node
|
||||
"""Add the collected test items from a node.
|
||||
|
||||
The collection is stored in the ``.node2collection`` map.
|
||||
Called by the ``DSession.worker_collectionfinish`` hook.
|
||||
@@ -142,7 +142,7 @@ class LoadScheduling:
|
||||
self.node2collection[node] = list(collection)
|
||||
|
||||
def mark_test_complete(self, node, item_index, duration=0):
|
||||
"""Mark test item as completed by node
|
||||
"""Mark test item as completed by node.
|
||||
|
||||
The duration it took to execute the item is used as a hint to
|
||||
the scheduler.
|
||||
@@ -161,7 +161,7 @@ class LoadScheduling:
|
||||
self.check_schedule(node)
|
||||
|
||||
def check_schedule(self, node, duration=0):
|
||||
"""Maybe schedule new items on the node
|
||||
"""Maybe schedule new items on the node.
|
||||
|
||||
If there are any globally pending nodes left then this will
|
||||
check if the given node should be given any more tests. The
|
||||
@@ -195,7 +195,7 @@ class LoadScheduling:
|
||||
self.log("num items waiting for node:", len(self.pending))
|
||||
|
||||
def remove_node(self, node):
|
||||
"""Remove a node from the scheduler
|
||||
"""Remove a node from the scheduler.
|
||||
|
||||
This should be called either when the node crashed or at
|
||||
shutdown time. In the former case any pending items assigned
|
||||
@@ -219,7 +219,7 @@ class LoadScheduling:
|
||||
return crashitem
|
||||
|
||||
def schedule(self):
|
||||
"""Initiate distribution of the test collection
|
||||
"""Initiate distribution of the test collection.
|
||||
|
||||
Initiate scheduling of the items across the nodes. If this
|
||||
gets called again later it behaves the same as calling
|
||||
@@ -243,7 +243,7 @@ class LoadScheduling:
|
||||
return
|
||||
|
||||
# Collections are identical, create the index of pending items.
|
||||
self.collection = list(self.node2collection.values())[0]
|
||||
self.collection = next(iter(self.node2collection.values()))
|
||||
self.pending[:] = range(len(self.collection))
|
||||
if not self.collection:
|
||||
return
|
||||
@@ -260,7 +260,7 @@ class LoadScheduling:
|
||||
# to each node - which is suboptimal when you have less than
|
||||
# 2 * len(nodes) tests.
|
||||
nodes = cycle(self.nodes)
|
||||
for i in range(len(self.pending)):
|
||||
for _ in range(len(self.pending)):
|
||||
self._send_tests(next(nodes), 1)
|
||||
else:
|
||||
# Send batches of consecutive tests. By default, pytest sorts tests
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from .loadscope import LoadScopeScheduling
|
||||
from xdist.remote import Producer
|
||||
|
||||
from .loadscope import LoadScopeScheduling
|
||||
|
||||
|
||||
class LoadFileScheduling(LoadScopeScheduling):
|
||||
"""Implement load scheduling across nodes, but grouping test test file.
|
||||
@@ -43,8 +44,11 @@ class LoadFileScheduling(LoadScopeScheduling):
|
||||
This function will group tests with the scope determined by splitting
|
||||
the first ``::`` from the left. That is, test will be grouped in a
|
||||
single work unit when they reside in the same file.
|
||||
|
||||
In the above example, scopes will be::
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
example/loadsuite/test/test_beta.py
|
||||
example/loadsuite/test/test_delta.py
|
||||
example/loadsuite/epsilon/__init__.py
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from .loadscope import LoadScopeScheduling
|
||||
from xdist.remote import Producer
|
||||
|
||||
from .loadscope import LoadScopeScheduling
|
||||
|
||||
|
||||
class LoadGroupScheduling(LoadScopeScheduling):
|
||||
"""Implement load scheduling across nodes, but grouping test by xdist_group mark.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
from _pytest.runner import CollectReport
|
||||
|
||||
from xdist.remote import Producer
|
||||
from xdist.report import report_collection_diff
|
||||
from xdist.workermanage import parse_spec_config
|
||||
@@ -21,7 +22,7 @@ class LoadScopeScheduling:
|
||||
When created, ``numnodes`` defines how many nodes are expected to submit a
|
||||
collection. This is used to know when all nodes have finished collection.
|
||||
|
||||
Attributes:
|
||||
Attributes::
|
||||
|
||||
:numnodes: The expected number of nodes taking part. The actual number of
|
||||
nodes will vary during the scheduler's lifetime as nodes are added by
|
||||
@@ -207,7 +208,6 @@ class LoadScopeScheduling:
|
||||
|
||||
- ``DSession.worker_collectionfinish``.
|
||||
"""
|
||||
|
||||
# Check that add_node() was called on the node before
|
||||
assert node in self.assigned_work
|
||||
|
||||
@@ -300,7 +300,6 @@ class LoadScopeScheduling:
|
||||
If there are any globally pending work units left then this will check
|
||||
if the given node should be given any more tests.
|
||||
"""
|
||||
|
||||
# Do not add more work to a node shutting down
|
||||
if node.shutting_down:
|
||||
return
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
from collections import namedtuple
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import NamedTuple
|
||||
|
||||
from _pytest.runner import CollectReport
|
||||
|
||||
from xdist.remote import Producer
|
||||
from xdist.workermanage import parse_spec_config
|
||||
from xdist.report import report_collection_diff
|
||||
from xdist.workermanage import parse_spec_config
|
||||
|
||||
|
||||
NodePending = namedtuple("NodePending", ["node", "pending"])
|
||||
class NodePending(NamedTuple):
|
||||
node: Any
|
||||
pending: list[int]
|
||||
|
||||
|
||||
# Every worker needs at least 2 tests in queue - the current and the next one.
|
||||
MIN_PENDING = 2
|
||||
@@ -22,7 +28,7 @@ class WorkStealingScheduling:
|
||||
test remains), an attempt is made to reassign ("steal") some tests from
|
||||
other nodes to this node.
|
||||
|
||||
Attributes:
|
||||
Attributes::
|
||||
|
||||
:numnodes: The expected number of nodes taking part. The actual
|
||||
number of nodes will vary during the scheduler's lifetime as
|
||||
@@ -101,7 +107,7 @@ class WorkStealingScheduling:
|
||||
|
||||
@property
|
||||
def has_pending(self):
|
||||
"""Return True if there are pending test items
|
||||
"""Return True if there are pending test items.
|
||||
|
||||
This indicates that collection has finished and nodes are
|
||||
still processing test items, so this can be thought of as
|
||||
@@ -127,7 +133,7 @@ class WorkStealingScheduling:
|
||||
self.node2pending[node] = []
|
||||
|
||||
def add_node_collection(self, node, collection):
|
||||
"""Add the collected test items from a node
|
||||
"""Add the collected test items from a node.
|
||||
|
||||
The collection is stored in the ``.node2collection`` map.
|
||||
Called by the ``DSession.worker_collectionfinish`` hook.
|
||||
@@ -148,7 +154,7 @@ class WorkStealingScheduling:
|
||||
self.node2collection[node] = list(collection)
|
||||
|
||||
def mark_test_complete(self, node, item_index, duration=None):
|
||||
"""Mark test item as completed by node
|
||||
"""Mark test item as completed by node.
|
||||
|
||||
This is called by the ``DSession.worker_testreport`` hook.
|
||||
"""
|
||||
@@ -233,7 +239,7 @@ class WorkStealingScheduling:
|
||||
self.steal_requested_from_node = steal_from.node
|
||||
|
||||
def remove_node(self, node):
|
||||
"""Remove a node from the scheduler
|
||||
"""Remove a node from the scheduler.
|
||||
|
||||
This should be called either when the node crashed or at
|
||||
shutdown time. In the former case any pending items assigned
|
||||
@@ -263,7 +269,7 @@ class WorkStealingScheduling:
|
||||
return crashitem
|
||||
|
||||
def schedule(self):
|
||||
"""Initiate distribution of the test collection
|
||||
"""Initiate distribution of the test collection.
|
||||
|
||||
Initiate scheduling of the items across the nodes. If this
|
||||
gets called again later it behaves the same as calling
|
||||
@@ -285,7 +291,7 @@ class WorkStealingScheduling:
|
||||
return
|
||||
|
||||
# Collections are identical, create the index of pending items.
|
||||
self.collection = list(self.node2collection.values())[0]
|
||||
self.collection = next(iter(self.node2collection.values()))
|
||||
self.pending[:] = range(len(self.collection))
|
||||
if not self.collection:
|
||||
return
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import fnmatch
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import List, Union, Sequence, Optional, Any, Tuple, Set
|
||||
|
||||
import pytest
|
||||
import execnet
|
||||
import pytest
|
||||
|
||||
from xdist.plugin import _sys_path
|
||||
import xdist.remote
|
||||
from xdist.remote import Producer
|
||||
from xdist.plugin import _sys_path
|
||||
|
||||
|
||||
def parse_spec_config(config):
|
||||
@@ -89,8 +95,8 @@ class NodeManager:
|
||||
break
|
||||
else:
|
||||
return []
|
||||
import pytest
|
||||
import _pytest
|
||||
import pytest
|
||||
|
||||
def get_dir(p):
|
||||
"""Return the directory path if p is a package or the path to the .py file otherwise."""
|
||||
@@ -160,7 +166,7 @@ class NodeManager:
|
||||
|
||||
|
||||
class HostRSync(execnet.RSync):
|
||||
"""RSyncer that filters out common files"""
|
||||
"""RSyncer that filters out common files."""
|
||||
|
||||
PathLike = Union[str, "os.PathLike[str]"]
|
||||
|
||||
@@ -169,7 +175,7 @@ class HostRSync(execnet.RSync):
|
||||
sourcedir: PathLike,
|
||||
*,
|
||||
ignores: Optional[Sequence[PathLike]] = None,
|
||||
**kwargs: object
|
||||
**kwargs: object,
|
||||
) -> None:
|
||||
if ignores is None:
|
||||
ignores = []
|
||||
@@ -308,7 +314,7 @@ class WorkerController:
|
||||
self._shutdown_sent = True
|
||||
|
||||
def sendcommand(self, name, **kwargs):
|
||||
"""send a named parametrized command to the other side."""
|
||||
"""Send a named parametrized command to the other side."""
|
||||
self.log(f"sending command {name}(**{kwargs})")
|
||||
self.channel.send((name, kwargs))
|
||||
|
||||
@@ -316,8 +322,8 @@ class WorkerController:
|
||||
self.log(f"queuing {eventname}(**{kwargs})")
|
||||
self.putevent((eventname, kwargs))
|
||||
|
||||
def process_from_remote(self, eventcall): # noqa too complex
|
||||
"""this gets called for each object we receive from
|
||||
def process_from_remote(self, eventcall):
|
||||
"""This gets called for each object we receive from
|
||||
the other side and if the channel closes.
|
||||
|
||||
Note that channel callbacks run in the receiver
|
||||
@@ -394,7 +400,7 @@ class WorkerController:
|
||||
except KeyboardInterrupt:
|
||||
# should not land in receiver-thread
|
||||
raise
|
||||
except: # noqa
|
||||
except BaseException:
|
||||
from _pytest._code import ExceptionInfo
|
||||
|
||||
excinfo = ExceptionInfo.from_current()
|
||||
@@ -405,8 +411,8 @@ class WorkerController:
|
||||
|
||||
|
||||
def unserialize_warning_message(data):
|
||||
import warnings
|
||||
import importlib
|
||||
import warnings
|
||||
|
||||
if data["message_module"]:
|
||||
mod = importlib.import_module(data["message_module"])
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
import xdist
|
||||
|
||||
|
||||
@@ -265,8 +266,8 @@ class TestDistribution:
|
||||
"-pfoobarplugin",
|
||||
"--foobar=123",
|
||||
"--dist=load",
|
||||
"--rsyncdir=%(subdir)s" % locals(),
|
||||
"--tx=popen//chdir=%(dest)s" % locals(),
|
||||
f"--rsyncdir={subdir}",
|
||||
f"--tx=popen//chdir={dest}",
|
||||
p,
|
||||
)
|
||||
assert result.ret == 0
|
||||
@@ -481,7 +482,7 @@ class TestTerminalReporting:
|
||||
)
|
||||
|
||||
def test_logfinish_hook(self, pytester: pytest.Pytester) -> None:
|
||||
"""Ensure the pytest_runtest_logfinish hook is being properly handled"""
|
||||
"""Ensure the pytest_runtest_logfinish hook is being properly handled."""
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
def pytest_runtest_logfinish():
|
||||
@@ -612,7 +613,7 @@ def test_fixture_teardown_failure(pytester: pytest.Pytester) -> None:
|
||||
def test_config_initialization(
|
||||
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, pytestconfig
|
||||
) -> None:
|
||||
"""Ensure workers and controller are initialized consistently. Integration test for #445"""
|
||||
"""Ensure workers and controller are initialized consistently. Integration test for #445."""
|
||||
pytester.makepyfile(
|
||||
**{
|
||||
"dir_a/test_foo.py": """
|
||||
@@ -636,7 +637,7 @@ def test_config_initialization(
|
||||
|
||||
@pytest.mark.parametrize("when", ["setup", "call", "teardown"])
|
||||
def test_crashing_item(pytester, when) -> None:
|
||||
"""Ensure crashing item is correctly reported during all testing stages"""
|
||||
"""Ensure crashing item is correctly reported during all testing stages."""
|
||||
code = dict(setup="", call="", teardown="")
|
||||
code[when] = "os._exit(1)"
|
||||
p = pytester.makepyfile(
|
||||
@@ -656,9 +657,7 @@ def test_crashing_item(pytester, when) -> None:
|
||||
|
||||
def test_ok():
|
||||
pass
|
||||
""".format(
|
||||
**code
|
||||
)
|
||||
""".format(**code)
|
||||
)
|
||||
passes = 2 if when == "teardown" else 1
|
||||
result = pytester.runpytest("-n2", p)
|
||||
@@ -769,7 +768,7 @@ def test_tmpdir_disabled(pytester: pytest.Pytester) -> None:
|
||||
|
||||
@pytest.mark.parametrize("plugin", ["xdist.looponfail"])
|
||||
def test_sub_plugins_disabled(pytester, plugin) -> None:
|
||||
"""Test that xdist doesn't break if we disable any of its sub-plugins. (#32)"""
|
||||
"""Test that xdist doesn't break if we disable any of its sub-plugins (#32)."""
|
||||
p1 = pytester.makepyfile(
|
||||
"""
|
||||
def test_ok():
|
||||
@@ -799,9 +798,7 @@ class TestWarnings:
|
||||
def test_warning_captured_deprecated_in_pytest_6(
|
||||
self, pytester: pytest.Pytester
|
||||
) -> None:
|
||||
"""
|
||||
Do not trigger the deprecated pytest_warning_captured hook in pytest 6+ (#562)
|
||||
"""
|
||||
"""Do not trigger the deprecated pytest_warning_captured hook in pytest 6+ (#562)."""
|
||||
from _pytest import hookspec
|
||||
|
||||
if not hasattr(hookspec, "pytest_warning_captured"):
|
||||
@@ -833,7 +830,7 @@ class TestWarnings:
|
||||
@pytest.mark.parametrize("n", ["-n0", "-n1"])
|
||||
def test_custom_subclass(self, pytester, n) -> None:
|
||||
"""Check that warning subclasses that don't honor the args attribute don't break
|
||||
pytest-xdist (#344)
|
||||
pytest-xdist (#344).
|
||||
"""
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
@@ -1116,7 +1113,7 @@ def test_error_report_styles(pytester, tb) -> None:
|
||||
|
||||
|
||||
def test_color_yes_collection_on_non_atty(pytester, request) -> None:
|
||||
"""skip collect progress report when working on non-terminals.
|
||||
"""Skip collect progress report when working on non-terminals.
|
||||
|
||||
Similar to pytest-dev/pytest#1397
|
||||
"""
|
||||
@@ -1141,9 +1138,7 @@ def test_color_yes_collection_on_non_atty(pytester, request) -> None:
|
||||
|
||||
|
||||
def test_without_terminal_plugin(pytester, request) -> None:
|
||||
"""
|
||||
No output when terminal plugin is disabled
|
||||
"""
|
||||
"""No output when terminal plugin is disabled."""
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
def test_1():
|
||||
@@ -1157,9 +1152,7 @@ def test_without_terminal_plugin(pytester, request) -> None:
|
||||
|
||||
|
||||
def test_internal_error_with_maxfail(pytester: pytest.Pytester) -> None:
|
||||
"""
|
||||
Internal error when using --maxfail option (#62, #65).
|
||||
"""
|
||||
"""Internal error when using --maxfail option (#62, #65)."""
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
@@ -1180,9 +1173,7 @@ def test_internal_error_with_maxfail(pytester: pytest.Pytester) -> None:
|
||||
|
||||
|
||||
def test_maxfail_causes_early_termination(pytester: pytest.Pytester) -> None:
|
||||
"""
|
||||
Ensure subsequent tests on a worker aren't run when using --maxfail (#1024).
|
||||
"""
|
||||
"""Ensure subsequent tests on a worker aren't run when using --maxfail (#1024)."""
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
def test1():
|
||||
@@ -1520,9 +1511,7 @@ class TestLocking:
|
||||
|
||||
FILE_LOCK = filelock.FileLock("test.lock")
|
||||
|
||||
""" + (
|
||||
(_test_content * 4) % ("A", "B", "C", "D")
|
||||
)
|
||||
""" + ((_test_content * 4) % ("A", "B", "C", "D"))
|
||||
|
||||
@pytest.mark.parametrize("scope", ["each", "load", "loadscope", "loadfile", "no"])
|
||||
def test_single_file(self, pytester, scope) -> None:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import execnet
|
||||
import pytest
|
||||
import shutil
|
||||
from typing import List
|
||||
|
||||
import execnet
|
||||
import pytest
|
||||
|
||||
|
||||
pytest_plugins = "pytester"
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
from __future__ import annotations
|
||||
from xdist.dsession import (
|
||||
DSession,
|
||||
get_default_max_worker_restart,
|
||||
get_workers_status_line,
|
||||
WorkerStatus,
|
||||
)
|
||||
from xdist.report import report_collection_diff
|
||||
from xdist.scheduler import EachScheduling, LoadScheduling, WorkStealingScheduling
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
import pytest
|
||||
import execnet
|
||||
import pytest
|
||||
|
||||
from xdist.dsession import DSession
|
||||
from xdist.dsession import get_default_max_worker_restart
|
||||
from xdist.dsession import get_workers_status_line
|
||||
from xdist.dsession import WorkerStatus
|
||||
from xdist.report import report_collection_diff
|
||||
from xdist.scheduler import EachScheduling
|
||||
from xdist.scheduler import LoadScheduling
|
||||
from xdist.scheduler import WorkStealingScheduling
|
||||
|
||||
|
||||
class MockGateway:
|
||||
@@ -163,7 +165,7 @@ class TestLoadScheduling:
|
||||
|
||||
for i in range(7, 16):
|
||||
sched.mark_test_complete(node1, i - 3)
|
||||
assert node1.sent == [0, 1] + list(range(4, i))
|
||||
assert node1.sent == [0, 1, *range(4, i)]
|
||||
assert node2.sent == [2, 3]
|
||||
assert sched.pending == list(range(i, 16))
|
||||
|
||||
@@ -185,7 +187,7 @@ class TestLoadScheduling:
|
||||
|
||||
for complete_index, first_pending in enumerate(range(5, 16)):
|
||||
sched.mark_test_complete(node1, node1.sent[complete_index])
|
||||
assert node1.sent == [0, 1] + list(range(4, first_pending))
|
||||
assert node1.sent == [0, 1, *range(4, first_pending)]
|
||||
assert node2.sent == [2, 3]
|
||||
assert sched.pending == list(range(first_pending, 16))
|
||||
|
||||
@@ -249,9 +251,7 @@ class TestLoadScheduling:
|
||||
"""
|
||||
|
||||
class CollectHook:
|
||||
"""
|
||||
Dummy hook that stores collection reports.
|
||||
"""
|
||||
"""Dummy hook that stores collection reports."""
|
||||
|
||||
def __init__(self):
|
||||
self.reports = []
|
||||
@@ -293,7 +293,7 @@ class TestWorkStealingScheduling:
|
||||
sched.schedule()
|
||||
assert not sched.pending
|
||||
assert not sched.tests_finished
|
||||
assert node1.sent == list(range(0, 8))
|
||||
assert node1.sent == list(range(8))
|
||||
assert node2.sent == list(range(8, 16))
|
||||
for i in range(8):
|
||||
sched.mark_test_complete(node1, node1.sent[i])
|
||||
@@ -313,7 +313,7 @@ class TestWorkStealingScheduling:
|
||||
sched.add_node_collection(node2, collection)
|
||||
assert sched.collection_is_completed
|
||||
sched.schedule()
|
||||
assert node1.sent == list(range(0, 8))
|
||||
assert node1.sent == list(range(8))
|
||||
assert node2.sent == list(range(8, 16))
|
||||
for i in range(8):
|
||||
sched.mark_test_complete(node1, node1.sent[i])
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import pathlib
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest.mock
|
||||
import textwrap
|
||||
from typing import List
|
||||
import unittest.mock
|
||||
|
||||
import pytest
|
||||
import shutil
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from xdist.looponfail import RemoteControl
|
||||
from xdist.looponfail import StatRecorder
|
||||
|
||||
@@ -14,9 +14,9 @@ class TestHooks:
|
||||
)
|
||||
|
||||
def test_runtest_logreport(self, pytester: pytest.Pytester) -> None:
|
||||
"""Test that log reports from pytest_runtest_logreport when running
|
||||
with xdist contain "node", "nodeid", "worker_id", and "testrun_uid" attributes. (#8)
|
||||
"""
|
||||
"""Test that log reports from pytest_runtest_logreport when running with
|
||||
xdist contain "node", "nodeid", "worker_id", and "testrun_uid"
|
||||
attributes (#8)."""
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
def pytest_runtest_logreport(report):
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
from contextlib import suppress
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import os
|
||||
|
||||
import execnet
|
||||
from xdist.workermanage import NodeManager
|
||||
|
||||
import pytest
|
||||
|
||||
from xdist.workermanage import NodeManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def monkeypatch_3_cpus(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Make pytest-xdist believe the system has 3 CPUs"""
|
||||
"""Make pytest-xdist believe the system has 3 CPUs."""
|
||||
# block import
|
||||
monkeypatch.setitem(sys.modules, "psutil", None) # type: ignore
|
||||
monkeypatch.setitem(sys.modules, "psutil", None)
|
||||
monkeypatch.delattr(os, "sched_getaffinity", raising=False)
|
||||
monkeypatch.setattr(os, "cpu_count", lambda: 3)
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import marshal
|
||||
import pprint
|
||||
import pytest
|
||||
from queue import Queue
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from xdist.workermanage import WorkerController
|
||||
import execnet
|
||||
import marshal
|
||||
import pytest
|
||||
|
||||
from xdist.workermanage import WorkerController
|
||||
|
||||
from queue import Queue
|
||||
|
||||
WAIT_TIMEOUT = 10.0
|
||||
|
||||
@@ -15,9 +16,9 @@ WAIT_TIMEOUT = 10.0
|
||||
def check_marshallable(d):
|
||||
try:
|
||||
marshal.dumps(d)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
pprint.pprint(d)
|
||||
raise ValueError("not marshallable")
|
||||
raise ValueError("not marshallable") from e
|
||||
|
||||
|
||||
class EventCall:
|
||||
@@ -163,7 +164,7 @@ class TestWorkerInteractor:
|
||||
worker.sendcommand("runtests_all")
|
||||
worker.sendcommand("shutdown")
|
||||
for func in "::test_func", "::test_func2":
|
||||
for i in range(3): # setup/call/teardown
|
||||
for _ in range(3): # setup/call/teardown
|
||||
ev = worker.popevent("testreport")
|
||||
assert ev.name == "testreport"
|
||||
rep = unserialize_report(ev.kwargs["data"])
|
||||
@@ -328,12 +329,10 @@ def test_remote_mainargv(pytester: pytest.Pytester) -> None:
|
||||
outer_argv = sys.argv
|
||||
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
f"""
|
||||
def test_mainargv(request):
|
||||
assert request.config.workerinput["mainargv"] == {!r}
|
||||
""".format(
|
||||
outer_argv
|
||||
)
|
||||
assert request.config.workerinput["mainargv"] == {outer_argv!r}
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("-n1")
|
||||
assert result.ret == 0
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import execnet
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import textwrap
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
import execnet
|
||||
import pytest
|
||||
from util import generate_warning
|
||||
|
||||
from xdist import workermanage
|
||||
from xdist._path import visit_path
|
||||
from xdist.remote import serialize_warning_message
|
||||
from xdist.workermanage import HostRSync, NodeManager, unserialize_warning_message
|
||||
from xdist.workermanage import HostRSync
|
||||
from xdist.workermanage import NodeManager
|
||||
from xdist.workermanage import unserialize_warning_message
|
||||
|
||||
|
||||
pytest_plugins = "pytester"
|
||||
|
||||
@@ -345,8 +350,7 @@ class MyWarning(UserWarning):
|
||||
],
|
||||
)
|
||||
def test_unserialize_warning_msg(w_cls):
|
||||
"""Test that warning serialization process works well"""
|
||||
|
||||
"""Test that warning serialization process works well."""
|
||||
# Create a test warning message
|
||||
with pytest.warns(UserWarning) as w:
|
||||
if not isinstance(w_cls, str):
|
||||
@@ -387,8 +391,7 @@ class MyWarningUnknown(UserWarning):
|
||||
|
||||
|
||||
def test_warning_serialization_tweaked_module():
|
||||
"""Test for GH#404"""
|
||||
|
||||
"""Test for GH#404."""
|
||||
# Create a test warning message
|
||||
with pytest.warns(UserWarning) as w:
|
||||
warnings.warn("hello", MyWarningUnknown)
|
||||
|
||||
Reference in New Issue
Block a user