From 953a3f0b4d52df99f202796fba06c1a3cc70e111 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 2 Jul 2019 19:33:50 -0300 Subject: [PATCH] Use Config.invocation_params for consistent worker initialization Decided to keep the old way still working for now. Fix #6 Fix #445 --- changelog/448.feature.rst | 9 +++++++++ src/xdist/remote.py | 20 ++++++++++++++------ src/xdist/workermanage.py | 10 ++++++++-- testing/acceptance_test.py | 24 ++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 changelog/448.feature.rst diff --git a/changelog/448.feature.rst b/changelog/448.feature.rst new file mode 100644 index 00000000..2a5410aa --- /dev/null +++ b/changelog/448.feature.rst @@ -0,0 +1,9 @@ +Initialization between workers and master nodes is now more consistent, which fixes a number of +long-standing issues related to startup with the ``-c`` option. + +Issues: + +* `#6 `__: Poor interaction between ``-n#`` and ``-c X.cfg`` +* `#445 `__: pytest-xdist is not reporting the same nodeid as pytest does + +This however only works with **pytest 5.1 or later**, as it required changes in pytest itself. diff --git a/src/xdist/remote.py b/src/xdist/remote.py index d06096cb..94cebd68 100644 --- a/src/xdist/remote.py +++ b/src/xdist/remote.py @@ -15,6 +15,8 @@ import pytest from execnet.gateway_base import dumps, DumpError +from _pytest.config import _prepareconfig, Config + class WorkerInteractor(object): def __init__(self, config, channel): @@ -211,18 +213,18 @@ def getinfodict(): def remote_initconfig(option_dict, args): - from _pytest.config import Config - option_dict["plugins"].append("no:terminal") - config = Config.fromdictargs(option_dict, args) + return Config.fromdictargs(option_dict, args) + + +def setup_config(config, basetemp): config.option.looponfail = False config.option.usepdb = False config.option.dist = "no" config.option.distload = False config.option.numprocesses = None config.option.maxprocesses = None - config.args = args - return config + config.option.basetemp = basetemp if __name__ == "__channelexec__": @@ -239,7 +241,13 @@ def remote_initconfig(option_dict, args): os.environ["PYTEST_XDIST_WORKER"] = workerinput["workerid"] os.environ["PYTEST_XDIST_WORKER_COUNT"] = str(workerinput["workercount"]) - config = remote_initconfig(option_dict, args) + if hasattr(Config, "InvocationParams"): + config = _prepareconfig(args, None) + else: + config = remote_initconfig(option_dict, args) + config.args = args + + setup_config(config, option_dict.get("basetemp")) config._parser.prog = os.path.basename(workerinput["mainargv"][0]) config.workerinput = workerinput config.workeroutput = {} diff --git a/src/xdist/workermanage.py b/src/xdist/workermanage.py index daf20425..09479726 100644 --- a/src/xdist/workermanage.py +++ b/src/xdist/workermanage.py @@ -186,6 +186,8 @@ def make_reltoroot(roots, args): for arg in args: parts = arg.split(splitcode) fspath = py.path.local(parts[0]) + if not fspath.exists(): + continue for root in roots: x = fspath.relto(root) if x or fspath == root: @@ -236,10 +238,14 @@ def shutting_down(self): def setup(self): self.log("setting up worker session") spec = self.gateway.spec - args = self.config.args + if hasattr(self.config, "invocation_params"): + args = [str(x) for x in self.config.invocation_params.args or ()] + option_dict = {} + else: + args = self.config.args + option_dict = vars(self.config.option) if not spec.popen or spec.chdir: args = make_reltoroot(self.nodemanager.roots, args) - option_dict = vars(self.config.option) if spec.popen: name = "popen-%s" % self.gateway.id if hasattr(self.config, "_tmpdirhandler"): diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 2d4a6b16..c7c739f6 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -559,6 +559,30 @@ def test_hello(myarg): assert result.ret +def test_config_initialization(testdir, pytestconfig): + """Ensure workers and master are initialized consistently. Integration test for #445""" + if not hasattr(pytestconfig, "invocation_params"): + pytest.skip( + "requires pytest >=5.1 (config has no attribute 'invocation_params')" + ) + testdir.makepyfile( + **{ + "dir_a/test_foo.py": """ + def test_1(): pass + """ + } + ) + testdir.makefile( + ".ini", + myconfig=""" + [pytest] + testpaths=dir_a + """, + ) + result = testdir.runpytest("-n2", "-c", "myconfig.ini", "-v") + result.stdout.fnmatch_lines(["dir_a/test_foo.py::test_1*"]) + + @pytest.mark.parametrize("when", ["setup", "call", "teardown"]) def test_crashing_item(testdir, when): """Ensure crashing item is correctly reported during all testing stages"""