Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfd6029102 | ||
|
|
3f5a8eeb7f | ||
|
|
d5b61e72d4 | ||
|
|
f224c4b00e | ||
|
|
ddefd3f453 | ||
|
|
5ce8a6ec1a | ||
|
|
fe06f27b8b | ||
|
|
86e2fb5a5a | ||
|
|
ec6129639e | ||
|
|
f6ac209cd5 | ||
|
|
b176b37606 | ||
|
|
22e36c9cb4 | ||
|
|
8825e7e234 | ||
|
|
5d5f87b600 | ||
|
|
571d08fb8b | ||
|
|
36c28ee154 | ||
|
|
af3e975664 | ||
|
|
bc2ba55f70 | ||
|
|
8475876ce3 |
@@ -1,3 +1,27 @@
|
|||||||
|
pytest-xdist 1.24.0 (2018-10-18)
|
||||||
|
================================
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- `#337 <https://github.com/pytest-dev/pytest-xdist/issues/337>`_: New ``--maxprocesses`` command-line option that limits the maximum number of workers when using ``--numprocesses=auto``.
|
||||||
|
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#351 <https://github.com/pytest-dev/pytest-xdist/issues/351>`_: Fix scheduling deadlock in case of inter-test locking.
|
||||||
|
|
||||||
|
|
||||||
|
pytest-xdist 1.23.2 (2018-09-28)
|
||||||
|
================================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#344 <https://github.com/pytest-dev/pytest-xdist/issues/344>`_: Fix issue where Warnings could cause pytest to fail if they do not set the args attribute correctly.
|
||||||
|
|
||||||
|
|
||||||
pytest-xdist 1.23.1 (2018-09-25)
|
pytest-xdist 1.23.1 (2018-09-25)
|
||||||
================================
|
================================
|
||||||
|
|
||||||
|
|||||||
@@ -429,6 +429,35 @@ class TestTerminalReporting:
|
|||||||
result = testdir.runpytest(n)
|
result = testdir.runpytest(n)
|
||||||
result.stdout.fnmatch_lines(["*this is a warning*", "*1 passed, 1 warnings*"])
|
result.stdout.fnmatch_lines(["*this is a warning*", "*1 passed, 1 warnings*"])
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("n", ["-n0", "-n1"])
|
||||||
|
def test_custom_subclass(self, testdir, n):
|
||||||
|
"""Check that warning subclasses that don't honor the args attribute don't break
|
||||||
|
pytest-xdist (#344)
|
||||||
|
"""
|
||||||
|
from pkg_resources import parse_version
|
||||||
|
|
||||||
|
if parse_version(pytest.__version__) < parse_version("3.1"):
|
||||||
|
pytest.skip("pytest warnings requires >= 3.1")
|
||||||
|
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import warnings, py, pytest
|
||||||
|
|
||||||
|
class MyWarning(UserWarning):
|
||||||
|
|
||||||
|
def __init__(self, p1, p2):
|
||||||
|
self.p1 = p1
|
||||||
|
self.p2 = p2
|
||||||
|
self.args = ()
|
||||||
|
|
||||||
|
def test_func(request):
|
||||||
|
warnings.warn(MyWarning("foo", 1))
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.syspathinsert()
|
||||||
|
result = testdir.runpytest(n)
|
||||||
|
result.stdout.fnmatch_lines(["*MyWarning*", "*1 passed, 1 warnings*"])
|
||||||
|
|
||||||
def test_logfinish_hook(self, testdir):
|
def test_logfinish_hook(self, testdir):
|
||||||
"""Ensure the pytest_runtest_logfinish hook is being properly handled"""
|
"""Ensure the pytest_runtest_logfinish hook is being properly handled"""
|
||||||
from _pytest import hookspec
|
from _pytest import hookspec
|
||||||
@@ -1103,6 +1132,56 @@ class TestFileScope:
|
|||||||
assert c1 == c2
|
assert c1 == c2
|
||||||
|
|
||||||
|
|
||||||
|
class TestLocking:
|
||||||
|
_test_content = """
|
||||||
|
class TestClassName%s(object):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_class(cls):
|
||||||
|
FILE_LOCK.acquire()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def teardown_class(cls):
|
||||||
|
FILE_LOCK.release()
|
||||||
|
|
||||||
|
def test_a(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_b(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_c(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_file1 = """
|
||||||
|
import filelock
|
||||||
|
|
||||||
|
FILE_LOCK = filelock.FileLock("test.lock")
|
||||||
|
|
||||||
|
""" + (
|
||||||
|
(_test_content * 4) % ("A", "B", "C", "D")
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("scope", ["each", "load", "loadscope", "loadfile", "no"])
|
||||||
|
def test_single_file(self, testdir, scope):
|
||||||
|
testdir.makepyfile(test_a=self.test_file1)
|
||||||
|
result = testdir.runpytest("-n2", "--dist=%s" % scope, "-v")
|
||||||
|
result.assert_outcomes(passed=(12 if scope != "each" else 12 * 2))
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("scope", ["each", "load", "loadscope", "loadfile", "no"])
|
||||||
|
def test_multi_file(self, testdir, scope):
|
||||||
|
testdir.makepyfile(
|
||||||
|
test_a=self.test_file1,
|
||||||
|
test_b=self.test_file1,
|
||||||
|
test_c=self.test_file1,
|
||||||
|
test_d=self.test_file1,
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("-n2", "--dist=%s" % scope, "-v")
|
||||||
|
result.assert_outcomes(passed=(48 if scope != "each" else 48 * 2))
|
||||||
|
|
||||||
|
|
||||||
def parse_tests_and_workers_from_output(lines):
|
def parse_tests_and_workers_from_output(lines):
|
||||||
result = []
|
result = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ def test_dist_options(testdir):
|
|||||||
check_options(config)
|
check_options(config)
|
||||||
assert config.option.dist == "load"
|
assert config.option.dist == "load"
|
||||||
assert config.option.tx == ["popen"] * 2
|
assert config.option.tx == ["popen"] * 2
|
||||||
|
config = testdir.parseconfigure("--numprocesses", "3", "--maxprocesses", "2")
|
||||||
|
check_options(config)
|
||||||
|
assert config.option.dist == "load"
|
||||||
|
assert config.option.tx == ["popen"] * 2
|
||||||
config = testdir.parseconfigure("-d")
|
config = testdir.parseconfigure("-d")
|
||||||
check_options(config)
|
check_options(config)
|
||||||
assert config.option.dist == "load"
|
assert config.option.dist == "load"
|
||||||
|
|||||||
1
tox.ini
1
tox.ini
@@ -23,6 +23,7 @@ deps =
|
|||||||
pytestmaster: git+https://github.com/pytest-dev/pytest.git@master
|
pytestmaster: git+https://github.com/pytest-dev/pytest.git@master
|
||||||
pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features
|
pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features
|
||||||
pexpect: pexpect
|
pexpect: pexpect
|
||||||
|
filelock
|
||||||
platform=
|
platform=
|
||||||
pexpect: linux|darwin
|
pexpect: linux|darwin
|
||||||
commands=
|
commands=
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ def pytest_addoption(parser):
|
|||||||
"you can use 'auto' here for auto detection CPUs number on "
|
"you can use 'auto' here for auto detection CPUs number on "
|
||||||
"host system",
|
"host system",
|
||||||
)
|
)
|
||||||
|
group.addoption(
|
||||||
|
"--maxprocesses",
|
||||||
|
dest="maxprocesses",
|
||||||
|
metavar="maxprocesses",
|
||||||
|
action="store",
|
||||||
|
type=int,
|
||||||
|
help="limit the maximum number of workers to process the tests when using --numprocesses=auto",
|
||||||
|
)
|
||||||
group.addoption(
|
group.addoption(
|
||||||
"--max-worker-restart",
|
"--max-worker-restart",
|
||||||
"--max-slave-restart",
|
"--max-slave-restart",
|
||||||
@@ -172,7 +180,10 @@ def pytest_cmdline_main(config):
|
|||||||
if config.option.numprocesses:
|
if config.option.numprocesses:
|
||||||
if config.option.dist == "no":
|
if config.option.dist == "no":
|
||||||
config.option.dist = "load"
|
config.option.dist = "load"
|
||||||
config.option.tx = ["popen"] * config.option.numprocesses
|
numprocesses = config.option.numprocesses
|
||||||
|
if config.option.maxprocesses:
|
||||||
|
numprocesses = min(numprocesses, config.option.maxprocesses)
|
||||||
|
config.option.tx = ["popen"] * numprocesses
|
||||||
if config.option.distload:
|
if config.option.distload:
|
||||||
config.option.dist = "load"
|
config.option.dist = "load"
|
||||||
val = config.getvalue
|
val = config.getvalue
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ def serialize_warning_message(warning_message):
|
|||||||
message_module = type(warning_message.message).__module__
|
message_module = type(warning_message.message).__module__
|
||||||
message_class_name = type(warning_message.message).__name__
|
message_class_name = type(warning_message.message).__name__
|
||||||
message_args = warning_message.message.args
|
message_args = warning_message.message.args
|
||||||
message_str = None
|
message_str = str(warning_message.message)
|
||||||
else:
|
else:
|
||||||
message_str = warning_message.message
|
message_str = warning_message.message
|
||||||
message_module = None
|
message_module = None
|
||||||
@@ -234,6 +234,7 @@ def remote_initconfig(option_dict, args):
|
|||||||
config.option.dist = "no"
|
config.option.dist = "no"
|
||||||
config.option.distload = False
|
config.option.distload = False
|
||||||
config.option.numprocesses = None
|
config.option.numprocesses = None
|
||||||
|
config.option.maxprocesses = None
|
||||||
config.args = args
|
config.args = args
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ class EachScheduling(object):
|
|||||||
if not pending:
|
if not pending:
|
||||||
pending[:] = range(len(self.node2collection[node]))
|
pending[:] = range(len(self.node2collection[node]))
|
||||||
node.send_runtest_all()
|
node.send_runtest_all()
|
||||||
|
node.shutdown()
|
||||||
else:
|
else:
|
||||||
node.send_runtest_some(pending)
|
node.send_runtest_some(pending)
|
||||||
self._started.append(node)
|
self._started.append(node)
|
||||||
|
|||||||
@@ -178,6 +178,9 @@ class LoadScheduling(object):
|
|||||||
return
|
return
|
||||||
num_send = items_per_node_max - len(node_pending)
|
num_send = items_per_node_max - len(node_pending)
|
||||||
self._send_tests(node, num_send)
|
self._send_tests(node, num_send)
|
||||||
|
else:
|
||||||
|
node.shutdown()
|
||||||
|
|
||||||
self.log("num items waiting for node:", len(self.pending))
|
self.log("num items waiting for node:", len(self.pending))
|
||||||
|
|
||||||
def remove_node(self, node):
|
def remove_node(self, node):
|
||||||
|
|||||||
@@ -306,6 +306,7 @@ class LoadScopeScheduling(object):
|
|||||||
|
|
||||||
# Check that more work is available
|
# Check that more work is available
|
||||||
if not self.workqueue:
|
if not self.workqueue:
|
||||||
|
node.shutdown()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log("Number of units waiting for node:", len(self.workqueue))
|
self.log("Number of units waiting for node:", len(self.workqueue))
|
||||||
|
|||||||
@@ -426,7 +426,15 @@ def unserialize_warning_message(data):
|
|||||||
if data["message_module"]:
|
if data["message_module"]:
|
||||||
mod = importlib.import_module(data["message_module"])
|
mod = importlib.import_module(data["message_module"])
|
||||||
cls = getattr(mod, data["message_class_name"])
|
cls = getattr(mod, data["message_class_name"])
|
||||||
|
try:
|
||||||
message = cls(*data["message_args"])
|
message = cls(*data["message_args"])
|
||||||
|
except TypeError:
|
||||||
|
message_text = "{mod}.{cls}: {msg}".format(
|
||||||
|
mod=data["message_module"],
|
||||||
|
cls=data["message_class_name"],
|
||||||
|
msg=data["message_str"],
|
||||||
|
)
|
||||||
|
message = Warning(message_text)
|
||||||
else:
|
else:
|
||||||
message = data["message_str"]
|
message = data["message_str"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user