diff --git a/README.rst b/README.rst index b2060749..61bee8b7 100644 --- a/README.rst +++ b/README.rst @@ -22,8 +22,6 @@ test execution modes: those for a combined test run. This allows to speed up development or to use special resources of `remote machines`_. -* ``--boxed``: (not available on Windows) run each test in a boxed_ - subprocess to survive ``SEGFAULTS`` or otherwise dying processes * ``--looponfail``: run your tests repeatedly in a subprocess. After each run py.test waits until a file in your project changes and then re-runs diff --git a/changelog/1.removal b/changelog/1.removal new file mode 100644 index 00000000..57e70ba5 --- /dev/null +++ b/changelog/1.removal @@ -0,0 +1 @@ +``--boxed`` functionality has been moved to a separate plugin, `pytest-forked `_. This release now depends on `` pytest-forked`` and provides ``--boxed`` as a backward compatibility option. \ No newline at end of file diff --git a/example/boxed.txt b/example/boxed.txt index 00cec2fb..1c83e630 100644 --- a/example/boxed.txt +++ b/example/boxed.txt @@ -1,3 +1,9 @@ +.. note:: + + Since 1.19.0, the actual implementation of the ``--boxed`` option has been moved to a + separate plugin, `pytest-forked `_ + which can be installed independently. The ``--boxed`` command-line options remains + for backward compatibility reasons. If your testing involves C or C++ libraries you might have to deal @@ -14,19 +20,19 @@ to run each test in a controlled subprocess. Here is a basic example:: @pytest.mark.parametrize("arg", range(50)) def test_func(arg): time.sleep(0.05) # each tests takes a while - if arg % 19 == 0: + if arg % 19 == 0: os.kill(os.getpid(), 15) If you run this with:: - $ py.test --boxed + $ py.test -n1 =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov collecting ... collected 50 items - + test_module.py f..................f..................f........... - + ================================= FAILURES ================================= _______________________________ test_func[0] _______________________________ /home/hpk/tmp/doc-exec-420/test_module.py:6: running the test CRASHED with signal 15 @@ -40,13 +46,13 @@ You'll see that a couple of tests are reported as crashing, indicated by lower-case ``f`` and the respective failure summary. You can also use the xdist-provided parallelization feature to speed up your testing:: - $ py.test --boxed -n3 + $ py.test -n3 =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov gw0 I / gw1 I / gw2 I gw0 [50] / gw1 [50] / gw2 [50] - + scheduling tests via LoadScheduling ..f...............f..................f............ ================================= FAILURES ================================= diff --git a/setup.py b/setup.py index 64bb5f27..7f8b0573 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages -install_requires = ['execnet>=1.1', 'pytest>=3.0.0'] +install_requires = ['execnet>=1.1', 'pytest>=3.0.0', 'pytest-forked'] if version_info < (2, 7): install_requires.append('ordereddict') @@ -24,7 +24,6 @@ 'pytest11': [ 'xdist = xdist.plugin', 'xdist.looponfail = xdist.looponfail', - 'xdist.boxed = xdist.boxed', ], }, zip_safe=False, diff --git a/testing/test_boxed.py b/testing/test_boxed.py deleted file mode 100644 index d624a95c..00000000 --- a/testing/test_boxed.py +++ /dev/null @@ -1,58 +0,0 @@ -import pytest -import os - -needsfork = pytest.mark.skipif(not hasattr(os, "fork"), - reason="os.fork required") - - -@needsfork -def test_functional_boxed(testdir): - p1 = testdir.makepyfile(""" - import os - def test_function(): - os.kill(os.getpid(), 15) - """) - result = testdir.runpytest(p1, "--boxed") - result.stdout.fnmatch_lines([ - "*CRASHED*", - "*1 failed*" - ]) - - -@needsfork -@pytest.mark.parametrize("capmode", [ - "no", - pytest.mark.xfail("sys", reason="capture cleanup needed"), - pytest.mark.xfail("fd", reason="capture cleanup needed")]) -def test_functional_boxed_capturing(testdir, capmode): - p1 = testdir.makepyfile(""" - import os - import sys - def test_function(): - sys.stdout.write("hello\\n") - sys.stderr.write("world\\n") - os.kill(os.getpid(), 15) - """) - result = testdir.runpytest(p1, "--boxed", "--capture=%s" % capmode) - result.stdout.fnmatch_lines(""" - *CRASHED* - *stdout* - hello - *stderr* - world - *1 failed* - """) - - -class TestOptionEffects: - def test_boxed_option_default(self, testdir): - tmpdir = testdir.tmpdir.ensure("subdir", dir=1) - config = testdir.parseconfig() - assert not config.option.boxed - pytest.importorskip("execnet") - config = testdir.parseconfig('-d', tmpdir) - assert not config.option.boxed - - def test_is_not_boxed_by_default(self, testdir): - config = testdir.parseconfig(testdir.tmpdir) - assert not config.option.boxed diff --git a/xdist/boxed.py b/xdist/boxed.py deleted file mode 100644 index 009a1129..00000000 --- a/xdist/boxed.py +++ /dev/null @@ -1,60 +0,0 @@ - -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/plugin.py b/xdist/plugin.py index 47a9b54f..17c9e194 100644 --- a/xdist/plugin.py +++ b/xdist/plugin.py @@ -26,10 +26,10 @@ def pytest_addoption(parser): help="shortcut for '--dist=load --tx=NUM*popen', " "you can use 'auto' here for auto detection CPUs number on " "host system") - 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( + 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( '--dist', metavar="distmode", action="store", choices=['each', 'load', 'loadscope', 'no'], dest="dist", default="no", @@ -40,7 +40,7 @@ def pytest_addoption(parser): "loadscope: load balance by sending pending groups of tests in" " the same scope to any available environment.\n\n" "(default) no: run tests inprocess, don't distribute.")) - group._addoption( + group.addoption( '--tx', dest="tx", action="append", default=[], metavar="xspec", help=("add a test execution environment. some examples: " @@ -57,6 +57,9 @@ def pytest_addoption(parser): '--rsyncignore', action="append", default=[], metavar="GLOB", help="add expression for ignores when rsyncing to remote tx nodes.") + group.addoption( + "--boxed", action="store_true", + help="backward compatibility alias for pytest-forked --forked") parser.addini( 'rsyncdirs', 'list of (relative) paths to be rsynced for' ' remote distributed testing.', type="pathlist") @@ -67,6 +70,7 @@ def pytest_addoption(parser): "looponfailroots", type="pathlist", help="directories to check for changes", default=[py.path.local()]) + # ------------------------------------------------------------------------- # distributed testing hooks # ------------------------------------------------------------------------- @@ -93,6 +97,8 @@ def pytest_configure(config): config.pluginmanager.register(session, "dsession") tr = config.pluginmanager.getplugin("terminalreporter") tr.showfspath = False + if config.getoption("boxed"): + config.option.forked = True @pytest.mark.tryfirst