split up the plugin and extend tox test matrix
This commit is contained in:
15
CHANGELOG
15
CHANGELOG
@@ -1,6 +1,14 @@
|
||||
1.13.dev1
|
||||
1.13.dev
|
||||
-------------------------
|
||||
|
||||
- extended the tox matrix with the supported py.test versions
|
||||
|
||||
- split up the plugin into 3 plugin's
|
||||
to prepare the departure of boxed and looponfail
|
||||
looponfail will become apart of core
|
||||
and forked boxed will be replaced
|
||||
with a more reliable primitive based on xdist
|
||||
|
||||
- conforming with new pytest-2.8 behavior of returning non-zero when all
|
||||
tests were skipped or deselected.
|
||||
|
||||
@@ -34,7 +42,7 @@
|
||||
Thanks to Andreas Pelme for bug analysis and failing test.
|
||||
|
||||
- restart crashed nodes by internally refactoring setup handling
|
||||
of nodes. Also includes better code documentation.
|
||||
of nodes. Also includes better code documentation.
|
||||
Many thanks to Floris Bruynooghe for the complete PR.
|
||||
|
||||
|
||||
@@ -47,7 +55,7 @@
|
||||
- fix pytest issue382 - produce "pytest_runtest_logstart" event again
|
||||
in master. Thanks Aron Curzon.
|
||||
|
||||
- fix pytest issue419 by sending/receiving indices into the test
|
||||
- fix pytest issue419 by sending/receiving indices into the test
|
||||
collection instead of node ids (which are not neccessarily unique
|
||||
for functions parametrized with duplicate values)
|
||||
|
||||
@@ -160,4 +168,3 @@
|
||||
- cleaned up termination handling
|
||||
- make -x cause hard killing of test nodes to decrease wait time
|
||||
until the traceback shows up on first failure
|
||||
|
||||
|
||||
8
setup.py
8
setup.py
@@ -11,7 +11,13 @@ setup(
|
||||
url='http://bitbucket.org/hpk42/pytest-xdist',
|
||||
platforms=['linux', 'osx', 'win32'],
|
||||
packages = ['xdist'],
|
||||
entry_points = {'pytest11': ['xdist = xdist.plugin'],},
|
||||
entry_points = {
|
||||
'pytest11': [
|
||||
'xdist = xdist.plugin',
|
||||
'xdist.looponfail = xdist.looponfail',
|
||||
'xdist.boxed = xdist.boxed',
|
||||
],
|
||||
},
|
||||
zip_safe=False,
|
||||
install_requires = ['execnet>=1.1', 'pytest>=2.4.2', 'py>=1.4.22'],
|
||||
classifiers=[
|
||||
|
||||
@@ -13,7 +13,7 @@ def test_dist_incompatibility_messages(testdir):
|
||||
assert "incompatible" in result.stderr.str()
|
||||
|
||||
def test_dist_options(testdir):
|
||||
from xdist.plugin import check_options
|
||||
from xdist.plugin import pytest_cmdline_main as check_options
|
||||
config = testdir.parseconfigure("-n 2")
|
||||
check_options(config)
|
||||
assert config.option.dist == "load"
|
||||
@@ -67,4 +67,3 @@ class TestDistOptions:
|
||||
assert py.path.local('y') in roots
|
||||
assert py.path.local('z') in roots
|
||||
assert testdir.tmpdir.join('x') in roots
|
||||
|
||||
|
||||
43
tox.ini
43
tox.ini
@@ -1,43 +1,28 @@
|
||||
[tox]
|
||||
envlist=py26,py33,py34,py27,py27-pexpect,py33-pexpect,py26-old,py33-old,flakes
|
||||
envlist=
|
||||
py{26,33,34,27}-pytest2{4,5,6,7},py{27,34}-pytest27-pexpect,flakes
|
||||
|
||||
|
||||
[testenv]
|
||||
changedir=testing
|
||||
deps=pytest>=2.5.1
|
||||
commands= py.test {posargs}
|
||||
deps =
|
||||
pycmd
|
||||
pytest24: pytest~=2.4.0
|
||||
pytest25: pytest~=2.5.0
|
||||
|
||||
[testenv:py27-pexpect]
|
||||
deps={[testenv]deps}
|
||||
pexpect
|
||||
[testenv:py33-pexpect]
|
||||
deps={[testenv]deps}
|
||||
pexpect
|
||||
pytest26: pytest~=2.6.1
|
||||
pytest27: pytest~=2.7.2
|
||||
pexpect: pexpect
|
||||
commands=
|
||||
# always clean to avoid code unmarshal mismatch on old python/pytest
|
||||
py.cleanup -aq
|
||||
py.test {posargs}
|
||||
|
||||
[testenv:flakes]
|
||||
changedir=
|
||||
deps = pytest-flakes>=0.2
|
||||
commands = py.test --flakes -m flakes testing xdist
|
||||
|
||||
[testenv:py26-old]
|
||||
basepython = python2.6
|
||||
deps=
|
||||
pytest==2.5.2
|
||||
pycmd
|
||||
|
||||
commands=
|
||||
py.cleanup -a
|
||||
py.test {posargs}
|
||||
|
||||
[testenv:py33-old]
|
||||
basepython = python3.3
|
||||
deps=
|
||||
pytest==2.5.2
|
||||
pycmd
|
||||
|
||||
commands=
|
||||
py.cleanup -a
|
||||
py.test {posargs}
|
||||
|
||||
[pytest]
|
||||
addopts = -rsfxX
|
||||
;; hello
|
||||
|
||||
56
xdist/boxed.py
Normal file
56
xdist/boxed.py
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
import py
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("xdist", "distributed and subprocess testing")
|
||||
group.addoption('--boxed',
|
||||
action="store_true", dest="boxed", default=False,
|
||||
help="box each test run in a separate process (unix)")
|
||||
|
||||
|
||||
def pytest_runtest_protocol(item):
|
||||
if item.config.getvalue("boxed"):
|
||||
reports = forked_run_report(item)
|
||||
for rep in reports:
|
||||
item.ihook.pytest_runtest_logreport(report=rep)
|
||||
return True
|
||||
|
||||
def forked_run_report(item):
|
||||
# for now, we run setup/teardown in the subprocess
|
||||
# XXX optionally allow sharing of setup/teardown
|
||||
from _pytest.runner import runtestprotocol
|
||||
EXITSTATUS_TESTEXIT = 4
|
||||
import marshal
|
||||
from xdist.remote import serialize_report
|
||||
from xdist.slavemanage import unserialize_report
|
||||
def runforked():
|
||||
try:
|
||||
reports = runtestprotocol(item, log=False)
|
||||
except KeyboardInterrupt:
|
||||
py.std.os._exit(EXITSTATUS_TESTEXIT)
|
||||
return marshal.dumps([serialize_report(x) for x in reports])
|
||||
|
||||
ff = py.process.ForkedFunc(runforked)
|
||||
result = ff.waitfinish()
|
||||
if result.retval is not None:
|
||||
report_dumps = marshal.loads(result.retval)
|
||||
return [unserialize_report("testreport", x) for x in report_dumps]
|
||||
else:
|
||||
if result.exitstatus == EXITSTATUS_TESTEXIT:
|
||||
py.test.exit("forked test item %s raised Exit" %(item,))
|
||||
return [report_process_crash(item, result)]
|
||||
|
||||
def report_process_crash(item, result):
|
||||
path, lineno = item._getfslineno()
|
||||
info = ("%s:%s: running the test CRASHED with signal %d" %
|
||||
(path, lineno, result.signal))
|
||||
from _pytest import runner
|
||||
call = runner.CallInfo(lambda: 0/0, "???")
|
||||
call.excinfo = info
|
||||
rep = runner.pytest_runtest_makereport(item, call)
|
||||
if result.out:
|
||||
rep.sections.append(("captured stdout", result.out))
|
||||
if result.err:
|
||||
rep.sections.append(("captured stderr", result.err))
|
||||
return rep
|
||||
@@ -11,6 +11,21 @@ import py, pytest
|
||||
import sys
|
||||
import execnet
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("xdist", "distributed and subprocess testing")
|
||||
group._addoption('-f', '--looponfail',
|
||||
action="store_true", dest="looponfail", default=False,
|
||||
help="run tests in subprocess, wait for modified files "
|
||||
"and re-run failing test set until all pass.")
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
|
||||
if config.getoption("looponfail"):
|
||||
looponfail_main(config)
|
||||
return 2 # looponfail only can get stop with ctrl-C anyway
|
||||
|
||||
|
||||
|
||||
def looponfail_main(config):
|
||||
remotecontrol = RemoteControl(config)
|
||||
rootdirs = config.getini("looponfailroots")
|
||||
@@ -227,4 +242,3 @@ class StatRecorder:
|
||||
changed = True
|
||||
self.statcache = newstat
|
||||
return changed
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@ import pytest
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("xdist", "distributed and subprocess testing")
|
||||
group._addoption('-f', '--looponfail',
|
||||
action="store_true", dest="looponfail", default=False,
|
||||
help="run tests in subprocess, wait for modified files "
|
||||
"and re-run failing test set until all pass.")
|
||||
group._addoption('-n', dest="numprocesses", metavar="numprocesses",
|
||||
action="store",
|
||||
help="shortcut for '--dist=load --tx=NUM*popen', "
|
||||
@@ -17,9 +13,6 @@ def pytest_addoption(parser):
|
||||
group._addoption('--max-slave-restart', action="store", default=None,
|
||||
help="maximum number of slaves that can be restarted "
|
||||
"when crashed (set to zero to disable this feature)")
|
||||
group.addoption('--boxed',
|
||||
action="store_true", dest="boxed", default=False,
|
||||
help="box each test run in a separate process (unix)")
|
||||
group._addoption('--dist', metavar="distmode",
|
||||
action="store", choices=['load', 'each', 'no'],
|
||||
type="choice", dest="dist", default="no",
|
||||
@@ -62,12 +55,6 @@ def pytest_addhooks(pluginmanager):
|
||||
# distributed testing initialization
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
check_options(config)
|
||||
if config.getoption("looponfail"):
|
||||
from xdist.looponfail import looponfail_main
|
||||
looponfail_main(config)
|
||||
return 2 # looponfail only can get stop with ctrl-C anyway
|
||||
|
||||
@pytest.mark.trylast
|
||||
def pytest_configure(config):
|
||||
@@ -78,7 +65,8 @@ def pytest_configure(config):
|
||||
tr = config.pluginmanager.getplugin("terminalreporter")
|
||||
tr.showfspath = False
|
||||
|
||||
def check_options(config):
|
||||
@pytest.mark.tryfirst
|
||||
def pytest_cmdline_main(config):
|
||||
if config.option.numprocesses:
|
||||
if config.option.numprocesses == 'auto':
|
||||
config.option.numprocesses = multiprocessing.cpu_count()
|
||||
@@ -100,50 +88,3 @@ def check_options(config):
|
||||
elif val("dist") != "no":
|
||||
if usepdb:
|
||||
raise pytest.UsageError("--pdb incompatible with distributing tests.")
|
||||
|
||||
|
||||
def pytest_runtest_protocol(item):
|
||||
if item.config.getvalue("boxed"):
|
||||
reports = forked_run_report(item)
|
||||
for rep in reports:
|
||||
item.ihook.pytest_runtest_logreport(report=rep)
|
||||
return True
|
||||
|
||||
def forked_run_report(item):
|
||||
# for now, we run setup/teardown in the subprocess
|
||||
# XXX optionally allow sharing of setup/teardown
|
||||
from _pytest.runner import runtestprotocol
|
||||
EXITSTATUS_TESTEXIT = 4
|
||||
import marshal
|
||||
from xdist.remote import serialize_report
|
||||
from xdist.slavemanage import unserialize_report
|
||||
def runforked():
|
||||
try:
|
||||
reports = runtestprotocol(item, log=False)
|
||||
except KeyboardInterrupt:
|
||||
py.std.os._exit(EXITSTATUS_TESTEXIT)
|
||||
return marshal.dumps([serialize_report(x) for x in reports])
|
||||
|
||||
ff = py.process.ForkedFunc(runforked)
|
||||
result = ff.waitfinish()
|
||||
if result.retval is not None:
|
||||
report_dumps = marshal.loads(result.retval)
|
||||
return [unserialize_report("testreport", x) for x in report_dumps]
|
||||
else:
|
||||
if result.exitstatus == EXITSTATUS_TESTEXIT:
|
||||
py.test.exit("forked test item %s raised Exit" %(item,))
|
||||
return [report_process_crash(item, result)]
|
||||
|
||||
def report_process_crash(item, result):
|
||||
path, lineno = item._getfslineno()
|
||||
info = ("%s:%s: running the test CRASHED with signal %d" %
|
||||
(path, lineno, result.signal))
|
||||
from _pytest import runner
|
||||
call = runner.CallInfo(lambda: 0/0, "???")
|
||||
call.excinfo = info
|
||||
rep = runner.pytest_runtest_makereport(item, call)
|
||||
if result.out:
|
||||
rep.sections.append(("captured stdout", result.out))
|
||||
if result.err:
|
||||
rep.sections.append(("captured stderr", result.err))
|
||||
return rep
|
||||
|
||||
Reference in New Issue
Block a user