Merge pull request #541 from pytest-dev/emancipation

This commit is contained in:
Bruno Oliveira
2020-08-05 19:18:02 -03:00
committed by GitHub
24 changed files with 33 additions and 121 deletions

View File

@@ -1,6 +1,5 @@
environment: environment:
matrix: matrix:
- TOXENV: "py27-pytestlatest"
- TOXENV: "py35-pytestlatest" - TOXENV: "py35-pytestlatest"
- TOXENV: "py36-pytestlatest" - TOXENV: "py36-pytestlatest"
- TOXENV: "py37-pytestlatest" - TOXENV: "py37-pytestlatest"

View File

@@ -3,7 +3,7 @@ repos:
rev: 19.10b0 rev: 19.10b0
hooks: hooks:
- id: black - id: black
args: [--safe, --quiet] args: [--safe, --quiet, --target-version, py35]
language_version: python3.7 language_version: python3.7
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0 rev: v2.4.0

View File

@@ -32,8 +32,6 @@ jobs:
- $HOME/.cache/pre-commit - $HOME/.cache/pre-commit
- python: '3.8' - python: '3.8'
env: TOXENV=py38-pytestlatest env: TOXENV=py38-pytestlatest
- python: '2.7'
env: TOXENV=py27-pytestlatest
- stage: test - stage: test
python: "3.5" python: "3.5"

View File

@@ -1,31 +0,0 @@
next release critical
-----------------------------------------------
tag: bug
miserably fails: --dist=each --tx popen --tx socket=...
rename / hooks
-----------------------------------------------
tag: bug
node -> slave
transition for hooks?
configure_node -> configure_slave
allow to remotely run xdist tests with xdist
-----------------------------------------------
tag: feature
allow to run xdist own tests using its own mechanism.
currently this doesn't work because the remote side
has no pytest plugin. How to configure/do
register "xdist.plugin" on the remote side?
see to avoid any "from _pytest" internal imports
-----------------------------------------------
tag: feature
currently tests and even xdist core code imports
names from the internal _pytest namespace.
See to avoid it.

View File

@@ -0,0 +1 @@
Drop backward-compatibility "slave" aliases related to worker nodes. We deliberately moved away from this terminology years ago, and it seems like the right time to finish the deprecation and removal process.

View File

@@ -0,0 +1 @@
``pytest-xdist`` now requires ``pytest>=6.0``.

View File

@@ -0,0 +1 @@
``pytest-xdist`` no longer supports Python 2.7.

View File

@@ -1,12 +1,6 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
install_requires = [ install_requires = ["execnet>=1.1", "psutil>=3.0.0", "pytest>=6.0.0", "pytest-forked"]
"execnet>=1.1",
"psutil>=3.0.0",
"pytest>=4.4.0",
"pytest-forked",
"six",
]
with open("README.rst") as f: with open("README.rst") as f:
@@ -15,8 +9,7 @@ with open("README.rst") as f:
setup( setup(
name="pytest-xdist", name="pytest-xdist",
use_scm_version={"write_to": "src/xdist/_version.py"}, use_scm_version={"write_to": "src/xdist/_version.py"},
description="pytest xdist plugin for distributed testing" description="pytest xdist plugin for distributed testing and loop-on-failing modes",
" and loop-on-failing modes",
long_description=long_description, long_description=long_description,
license="MIT", license="MIT",
author="holger krekel and contributors", author="holger krekel and contributors",
@@ -30,7 +23,7 @@ setup(
"pytest11": ["xdist = xdist.plugin", "xdist.looponfail = xdist.looponfail"] "pytest11": ["xdist = xdist.plugin", "xdist.looponfail = xdist.looponfail"]
}, },
zip_safe=False, zip_safe=False,
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", python_requires=">=3.5",
install_requires=install_requires, install_requires=install_requires,
setup_requires=["setuptools_scm"], setup_requires=["setuptools_scm"],
classifiers=[ classifiers=[
@@ -45,9 +38,8 @@ setup(
"Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Quality Assurance",
"Topic :: Utilities", "Topic :: Utilities",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",

View File

@@ -10,14 +10,14 @@ from xdist.scheduler import (
) )
from six.moves.queue import Empty, Queue from queue import Empty, Queue
class Interrupted(KeyboardInterrupt): class Interrupted(KeyboardInterrupt):
""" signals an immediate interruption. """ """ signals an immediate interruption. """
class DSession(object): 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 At the beginning of the test session this creates a NodeManager
@@ -150,9 +150,6 @@ class DSession(object):
node.workerinfo["id"] = node.gateway.id node.workerinfo["id"] = node.gateway.id
node.workerinfo["spec"] = node.gateway.spec node.workerinfo["spec"] = node.gateway.spec
# TODO: (#234 task) needs this for pytest. Remove when refactor in pytest repo
node.slaveinfo = node.workerinfo
self.config.hook.pytest_testnodeready(node=node) self.config.hook.pytest_testnodeready(node=node)
if self.shuttingdown: if self.shuttingdown:
node.shutdown() node.shutdown()
@@ -336,7 +333,7 @@ class DSession(object):
self.config.hook.pytest_runtest_logreport(report=rep) self.config.hook.pytest_runtest_logreport(report=rep)
class TerminalDistReporter(object): class TerminalDistReporter:
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.tr = config.pluginmanager.getplugin("terminalreporter") self.tr = config.pluginmanager.getplugin("terminalreporter")

View File

@@ -6,7 +6,6 @@
processes) otherwise changes to source code can crash processes) otherwise changes to source code can crash
the controlling process which should best never happen. the controlling process which should best never happen.
""" """
from __future__ import print_function
import py import py
import pytest import pytest
import sys import sys
@@ -55,7 +54,7 @@ def looponfail_main(config):
print() print()
class RemoteControl(object): class RemoteControl:
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.failures = [] self.failures = []
@@ -165,7 +164,7 @@ def init_worker_session(channel, args, option_dict):
WorkerFailSession(config, channel).main() WorkerFailSession(config, channel).main()
class WorkerFailSession(object): class WorkerFailSession:
def __init__(self, config, channel): def __init__(self, config, channel):
self.config = config self.config = config
self.channel = channel self.channel = channel
@@ -220,7 +219,7 @@ class WorkerFailSession(object):
self.channel.send((trails, failreports, self.collection_failed)) self.channel.send((trails, failreports, self.collection_failed))
class StatRecorder(object): class StatRecorder:
def __init__(self, rootdirlist): def __init__(self, rootdirlist):
self.rootdirlist = rootdirlist self.rootdirlist = rootdirlist
self.statcache = {} self.statcache = {}

View File

@@ -43,14 +43,11 @@ def pytest_addoption(parser):
) )
group.addoption( group.addoption(
"--max-worker-restart", "--max-worker-restart",
"--max-slave-restart",
action="store", action="store",
default=None, default=None,
dest="maxworkerrestart", dest="maxworkerrestart",
help="maximum number of workers that can be restarted " help="maximum number of workers that can be restarted "
"when crashed (set to zero to disable this feature)\n" "when crashed (set to zero to disable this feature)",
"'--max-slave-restart' option is deprecated and will be removed in "
"a future release",
) )
group.addoption( group.addoption(
"--dist", "--dist",

View File

@@ -18,7 +18,7 @@ from execnet.gateway_base import dumps, DumpError
from _pytest.config import _prepareconfig, Config from _pytest.config import _prepareconfig, Config
class WorkerInteractor(object): class WorkerInteractor:
def __init__(self, config, channel): def __init__(self, config, channel):
self.config = config self.config = config
self.workerid = config.workerinput.get("workerid", "?") self.workerid = config.workerinput.get("workerid", "?")
@@ -266,8 +266,5 @@ if __name__ == "__channelexec__":
config._parser.prog = os.path.basename(workerinput["mainargv"][0]) config._parser.prog = os.path.basename(workerinput["mainargv"][0])
config.workerinput = workerinput config.workerinput = workerinput
config.workeroutput = {} config.workeroutput = {}
# TODO: deprecated name, backward compatibility only. Remove it in future
config.slaveinput = config.workerinput
config.slaveoutput = config.workeroutput
interactor = WorkerInteractor(config, channel) interactor = WorkerInteractor(config, channel)
config.hook.pytest_cmdline_main(config=config) config.hook.pytest_cmdline_main(config=config)

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
from difflib import unified_diff from difflib import unified_diff
@@ -13,9 +12,9 @@ def report_collection_diff(from_collection, to_collection, from_id, to_id):
diff = unified_diff(from_collection, to_collection, fromfile=from_id, tofile=to_id) diff = unified_diff(from_collection, to_collection, fromfile=from_id, tofile=to_id)
error_message = ( error_message = (
u"Different tests were collected between {from_id} and {to_id}. " "Different tests were collected between {from_id} and {to_id}. "
u"The difference is:\n" "The difference is:\n"
u"{diff}" "{diff}"
).format(from_id=from_id, to_id=to_id, diff="\n".join(diff)) ).format(from_id=from_id, to_id=to_id, diff="\n".join(diff))
msg = "\n".join([x.rstrip() for x in error_message.split("\n")]) msg = "\n".join([x.rstrip() for x in error_message.split("\n")])
return msg return msg

View File

@@ -4,7 +4,7 @@ from xdist.workermanage import parse_spec_config
from xdist.report import report_collection_diff from xdist.report import report_collection_diff
class EachScheduling(object): 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 If a node gets added after the test run is started then it is

View File

@@ -7,7 +7,7 @@ from xdist.workermanage import parse_spec_config
from xdist.report import report_collection_diff from xdist.report import report_collection_diff
class LoadScheduling(object): class LoadScheduling:
"""Implement load scheduling across nodes. """Implement load scheduling across nodes.
This distributes the tests collected across all nodes so each test This distributes the tests collected across all nodes so each test

View File

@@ -21,7 +21,7 @@ class LoadFileScheduling(LoadScopeScheduling):
""" """
def __init__(self, config, log=None): def __init__(self, config, log=None):
super(LoadFileScheduling, self).__init__(config, log) super().__init__(config, log)
if log is None: if log is None:
self.log = Producer("loadfilesched") self.log = Producer("loadfilesched")
else: else:

View File

@@ -6,7 +6,7 @@ from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config from xdist.workermanage import parse_spec_config
class LoadScopeScheduling(object): class LoadScopeScheduling:
"""Implement load scheduling across nodes, but grouping test by scope. """Implement load scheduling across nodes, but grouping test by scope.
This distributes the tests collected across all nodes so each test is run This distributes the tests collected across all nodes so each test is run

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
import fnmatch import fnmatch
import os import os
import re import re
@@ -29,7 +28,7 @@ def parse_spec_config(config):
return xspeclist return xspeclist
class NodeManager(object): class NodeManager:
EXIT_TIMEOUT = 10 EXIT_TIMEOUT = 10
DEFAULT_IGNORES = [".*", "*.pyc", "*.pyo", "*~"] DEFAULT_IGNORES = [".*", "*.pyc", "*.pyo", "*~"]
@@ -167,7 +166,7 @@ class HostRSync(execnet.RSync):
self._ignores = [ self._ignores = [
re.compile(fnmatch.translate(getattr(x, "strpath", x))) for x in ignores re.compile(fnmatch.translate(getattr(x, "strpath", x))) for x in ignores
] ]
super(HostRSync, self).__init__(sourcedir=sourcedir, **kwargs) super().__init__(sourcedir=sourcedir, **kwargs)
def filter(self, path): def filter(self, path):
path = py.path.local(path) path = py.path.local(path)
@@ -179,9 +178,7 @@ class HostRSync(execnet.RSync):
def add_target_host(self, gateway, finished=None): def add_target_host(self, gateway, finished=None):
remotepath = os.path.basename(self._sourcedir) remotepath = os.path.basename(self._sourcedir)
super(HostRSync, self).add_target( super().add_target(gateway, remotepath, finishedcallback=finished, delete=True)
gateway, remotepath, finishedcallback=finished, delete=True
)
def _report_send_file(self, gateway, modified_rel_path): def _report_send_file(self, gateway, modified_rel_path):
if self._verbose: if self._verbose:
@@ -211,7 +208,7 @@ def make_reltoroot(roots, args):
return result return result
class WorkerController(object): class WorkerController:
ENDMARK = -1 ENDMARK = -1
class RemoteHook: class RemoteHook:
@@ -228,13 +225,9 @@ class WorkerController(object):
self.workerinput = { self.workerinput = {
"workerid": gateway.id, "workerid": gateway.id,
"workercount": len(nodemanager.specs), "workercount": len(nodemanager.specs),
"slaveid": gateway.id,
"slavecount": len(nodemanager.specs),
"testrunuid": nodemanager.testrunuid, "testrunuid": nodemanager.testrunuid,
"mainargv": sys.argv, "mainargv": sys.argv,
} }
# TODO: deprecated name, backward compatibility only. Remove it in future
self.slaveinput = self.workerinput
self._down = False self._down = False
self._shutdown_sent = False self._shutdown_sent = False
self.log = py.log.Producer("workerctl-%s" % gateway.id) self.log = py.log.Producer("workerctl-%s" % gateway.id)
@@ -296,7 +289,7 @@ class WorkerController(object):
if not self._down: if not self._down:
try: try:
self.sendcommand("shutdown") self.sendcommand("shutdown")
except (IOError, OSError): except OSError:
pass pass
self._shutdown_sent = True self._shutdown_sent = True
@@ -333,7 +326,7 @@ class WorkerController(object):
self.notify_inproc(eventname, node=self, **kwargs) self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "workerfinished": elif eventname == "workerfinished":
self._down = True self._down = True
self.workeroutput = self.slaveoutput = kwargs["workeroutput"] self.workeroutput = kwargs["workeroutput"]
self.notify_inproc("workerfinished", node=self) self.notify_inproc("workerfinished", node=self)
elif eventname in ("logstart", "logfinish"): elif eventname in ("logstart", "logfinish"):
self.notify_inproc(eventname, node=self, **kwargs) self.notify_inproc(eventname, node=self, **kwargs)

View File

@@ -245,22 +245,6 @@ class TestDistribution:
result.stderr.fnmatch_lines(["--foobar=123 active! *"]) result.stderr.fnmatch_lines(["--foobar=123 active! *"])
assert dest.join(subdir.basename).check(dir=1) assert dest.join(subdir.basename).check(dir=1)
def test_backward_compatibility_worker_terminology(self, testdir):
"""Ensure that we still support "config.slaveinput" for backward compatibility (#234).
Keep in mind that removing this compatibility will break a ton of plugins and user code.
"""
testdir.makepyfile(
"""
def test(pytestconfig):
assert hasattr(pytestconfig, 'slaveinput')
assert hasattr(pytestconfig, 'workerinput')
"""
)
result = testdir.runpytest("-n1")
result.stdout.fnmatch_lines("*1 passed*")
assert result.ret == 0
def test_data_exchange(self, testdir): def test_data_exchange(self, testdir):
testdir.makeconftest( testdir.makeconftest(
""" """
@@ -899,9 +883,6 @@ class TestWarnings:
"""Check that warnings with unserializable _WARNING_DETAILS are """Check that warnings with unserializable _WARNING_DETAILS are
handled correctly (#379). handled correctly (#379).
""" """
if sys.version_info[0] < 3:
# The issue is only present in Python 3 warnings
return
testdir.makepyfile( testdir.makepyfile(
""" """
import warnings, pytest import warnings, pytest

View File

@@ -1,20 +1,9 @@
import six
import py import py
import pytest import pytest
import execnet import execnet
pytest_plugins = "pytester" pytest_plugins = "pytester"
if six.PY2:
@pytest.fixture(scope="session", autouse=True)
def _ensure_imports():
# we import some modules because pytest-2.8's testdir fixture
# will unload all modules after each test and this cause
# (unknown) problems with execnet.Group()
execnet.Group
execnet.makegateway
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def _divert_atexit(request, monkeypatch): def _divert_atexit(request, monkeypatch):

View File

@@ -203,7 +203,7 @@ class TestLoadScheduling:
different test ids are collected by workers. different test ids are collected by workers.
""" """
class CollectHook(object): class CollectHook:
""" """
Dummy hook that stores collection reports. Dummy hook that stores collection reports.
""" """

View File

@@ -8,7 +8,7 @@ from xdist.workermanage import WorkerController
import execnet import execnet
import marshal import marshal
from six.moves.queue import Queue from queue import Queue
WAIT_TIMEOUT = 10.0 WAIT_TIMEOUT = 10.0

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
import py import py
import pytest import pytest
import textwrap import textwrap
@@ -35,7 +34,7 @@ def mysetup(tmpdir):
@pytest.fixture @pytest.fixture
def workercontroller(monkeypatch): def workercontroller(monkeypatch):
class MockController(object): class MockController:
def __init__(self, *args): def __init__(self, *args):
pass pass

View File

@@ -1,7 +1,7 @@
[tox] [tox]
envlist= envlist=
linting linting
py{27,35,36,37,38}-pytestlatest py{35,36,37,38}-pytestlatest
py38-pytestmaster py38-pytestmaster
[testenv] [testenv]