diff --git a/CHANGELOG b/CHANGELOG index 874adb1..2fe6c90 100644 --- a/CHANGELOG +++ b/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 - diff --git a/setup.py b/setup.py index 0677799..f08d288 100644 --- a/setup.py +++ b/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=[ diff --git a/testing/test_plugin.py b/testing/test_plugin.py index 91eda26..b856cde 100644 --- a/testing/test_plugin.py +++ b/testing/test_plugin.py @@ -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 - diff --git a/tox.ini b/tox.ini index 8f7dece..b7e86f0 100644 --- a/tox.ini +++ b/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 diff --git a/xdist/boxed.py b/xdist/boxed.py new file mode 100644 index 0000000..6bd920a --- /dev/null +++ b/xdist/boxed.py @@ -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 diff --git a/xdist/looponfail.py b/xdist/looponfail.py index e5675a2..2c12de8 100644 --- a/xdist/looponfail.py +++ b/xdist/looponfail.py @@ -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 - diff --git a/xdist/plugin.py b/xdist/plugin.py index fc4bf00..4b5fad4 100644 --- a/xdist/plugin.py +++ b/xdist/plugin.py @@ -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