Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions changelog/1170.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add the `--px` arg to create proxy gateways.

Proxy gateways are passed to additional gateways using the `via` keyword.
They can serve as a way to run multiple workers on remote machines.
14 changes: 14 additions & 0 deletions docs/remote.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ new socket host with something like this::
pytest -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg


Using proxies to run multiple workers on remote machines
---------------------------------------

In case you want to run multiple workers on a remote machine,
you can create a proxy gateway for the machine, and run multiple
workers using the `via` attribute.::

pytest -d --px id=my_proxy//socket=192.168.1.102:8888 --tx 5*popen//via=my_proxy

Here we declare a proxy gateway using the `--px` arg, and
create 5 workers that run on the remote server using the proxy.
Note that the proxy gateway does not run a worker, thus only 5
workers are created.


Running tests on many platforms at once
---------------------------------------
Expand Down
11 changes: 11 additions & 0 deletions src/xdist/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ def pytest_addoption(parser: pytest.Parser) -> None:
"--tx ssh=user@codespeak.net//chdir=testcache"
),
)
group.addoption(
"--px",
dest="px",
action="append",
default=[],
metavar="xspec",
help=(
"Add a proxy gateway to pass to test execution environments using `via`. Example:\n"
"--px id=my_proxy//socket=192.168.1.102:8888 --tx 5*popen//via=my_proxy"
),
)
group._addoption(
"-d",
action="store_true",
Expand Down
4 changes: 2 additions & 2 deletions src/xdist/scheduler/each.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from xdist.remote import Producer
from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config
from xdist.workermanage import parse_tx_spec_config
from xdist.workermanage import WorkerController


Expand All @@ -26,7 +26,7 @@ class EachScheduling:

def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.config = config
self.numnodes = len(parse_spec_config(config))
self.numnodes = len(parse_tx_spec_config(config))
self.node2collection: dict[WorkerController, list[str]] = {}
self.node2pending: dict[WorkerController, list[int]] = {}
self._started: list[WorkerController] = []
Expand Down
4 changes: 2 additions & 2 deletions src/xdist/scheduler/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from xdist.remote import Producer
from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config
from xdist.workermanage import parse_tx_spec_config
from xdist.workermanage import WorkerController


Expand Down Expand Up @@ -58,7 +58,7 @@ class LoadScheduling:
"""

def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.numnodes = len(parse_spec_config(config))
self.numnodes = len(parse_tx_spec_config(config))
self.node2collection: dict[WorkerController, list[str]] = {}
self.node2pending: dict[WorkerController, list[int]] = {}
self.pending: list[int] = []
Expand Down
4 changes: 2 additions & 2 deletions src/xdist/scheduler/loadscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from xdist.remote import Producer
from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config
from xdist.workermanage import parse_tx_spec_config
from xdist.workermanage import WorkerController


Expand Down Expand Up @@ -91,7 +91,7 @@ class LoadScopeScheduling:
"""

def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.numnodes = len(parse_spec_config(config))
self.numnodes = len(parse_tx_spec_config(config))
self.collection: list[str] | None = None

self.workqueue: OrderedDict[str, dict[str, bool]] = OrderedDict()
Expand Down
4 changes: 2 additions & 2 deletions src/xdist/scheduler/worksteal.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from xdist.remote import Producer
from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config
from xdist.workermanage import parse_tx_spec_config
from xdist.workermanage import WorkerController


Expand Down Expand Up @@ -65,7 +65,7 @@ class WorkStealingScheduling:
"""

def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.numnodes = len(parse_spec_config(config))
self.numnodes = len(parse_tx_spec_config(config))
self.node2collection: dict[WorkerController, list[str]] = {}
self.node2pending: dict[WorkerController, list[int]] = {}
self.pending: list[int] = []
Expand Down
20 changes: 16 additions & 4 deletions src/xdist/workermanage.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from xdist.remote import WorkerInfo


def parse_spec_config(config: pytest.Config) -> list[str]:
def parse_tx_spec_config(config: pytest.Config) -> list[str]:
xspeclist = []
tx: list[str] = config.getvalue("tx")
for xspec in tx:
Expand Down Expand Up @@ -57,8 +57,17 @@ def __init__(
if self.testrunuid is None:
self.testrunuid = uuid.uuid4().hex
self.group = execnet.Group(execmodel="main_thread_only")
for proxy_spec in self._getpxspecs():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to crosscheck in execnet on how proxy startup is managed

I vaguely recall that main thread only may cause trouble when more than one worker is behind a proxy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested locally using 5 workers behind a local socket proxy, and it worked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized my mistake

Proxy doesn't get thr default value of main thread only as its using a own spec

Thanks for validation

# Proxy gateways do not run workers, and are meant to be passed with the `via` attribute
# to additional gateways.
# They are useful for running multiple workers on remote machines.
if getattr(proxy_spec, "id", None) is None:
raise pytest.UsageError(
f"Proxy gateway {proxy_spec} must include an id"
)
self.group.makegateway(proxy_spec)
if specs is None:
specs = self._getxspecs()
specs = self._gettxspecs()
self.specs: list[execnet.XSpec] = []
for spec in specs:
if not isinstance(spec, execnet.XSpec):
Expand Down Expand Up @@ -107,8 +116,11 @@ def setup_node(
def teardown_nodes(self) -> None:
self.group.terminate(self.EXIT_TIMEOUT)

def _getxspecs(self) -> list[execnet.XSpec]:
return [execnet.XSpec(x) for x in parse_spec_config(self.config)]
def _gettxspecs(self) -> list[execnet.XSpec]:
return [execnet.XSpec(x) for x in parse_tx_spec_config(self.config)]

def _getpxspecs(self) -> list[execnet.XSpec]:
return [execnet.XSpec(x) for x in self.config.getoption("px")]

def _getrsyncdirs(self) -> list[Path]:
for spec in self.specs:
Expand Down
4 changes: 2 additions & 2 deletions testing/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,15 +295,15 @@ class TestDistOptions:
def test_getxspecs(self, pytester: pytest.Pytester) -> None:
config = pytester.parseconfigure("--tx=popen", "--tx", "ssh=xyz")
nodemanager = NodeManager(config)
xspecs = nodemanager._getxspecs()
xspecs = nodemanager._gettxspecs()
assert len(xspecs) == 2
print(xspecs)
assert xspecs[0].popen
assert xspecs[1].ssh == "xyz"

def test_xspecs_multiplied(self, pytester: pytest.Pytester) -> None:
config = pytester.parseconfigure("--tx=3*popen")
xspecs = NodeManager(config)._getxspecs()
xspecs = NodeManager(config)._gettxspecs()
assert len(xspecs) == 3
assert xspecs[1].popen

Expand Down
Loading