add xdist
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/145542d6 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/145542d6 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/145542d6 Branch: refs/heads/pytest Commit: 145542d662518266c8af86e59f70fed6eafa3c18 Parents: 9610685 Author: Edison Su <sudi...@gmail.com> Authored: Tue Sep 16 11:45:55 2014 -0700 Committer: Edison Su <sudi...@gmail.com> Committed: Tue Sep 16 11:45:55 2014 -0700 ---------------------------------------------------------------------- test/integration/smoke/test_CS-18306.py | 8 + .../marvin/pytest/pytest_marvin_plugin.py | 10 +- tools/pytest-xdist/CHANGELOG | 134 +++++ tools/pytest-xdist/ISSUES.txt | 31 ++ tools/pytest-xdist/LICENSE | 19 + tools/pytest-xdist/MANIFEST.in | 7 + tools/pytest-xdist/README.txt | 212 ++++++++ tools/pytest-xdist/example/boxed.txt | 62 +++ tools/pytest-xdist/setup.py | 30 ++ tools/pytest-xdist/testing/acceptance_test.py | 486 +++++++++++++++++++ tools/pytest-xdist/testing/conftest.py | 36 ++ tools/pytest-xdist/testing/test_boxed.py | 56 +++ tools/pytest-xdist/testing/test_dsession.py | 240 +++++++++ tools/pytest-xdist/testing/test_looponfail.py | 280 +++++++++++ tools/pytest-xdist/testing/test_plugin.py | 70 +++ tools/pytest-xdist/testing/test_remote.py | 248 ++++++++++ tools/pytest-xdist/testing/test_slavemanage.py | 241 +++++++++ tools/pytest-xdist/tox.ini | 32 ++ tools/pytest-xdist/xdist/__init__.py | 2 + tools/pytest-xdist/xdist/dsession.py | 460 ++++++++++++++++++ tools/pytest-xdist/xdist/looponfail.py | 230 +++++++++ tools/pytest-xdist/xdist/newhooks.py | 21 + tools/pytest-xdist/xdist/plugin.py | 131 +++++ tools/pytest-xdist/xdist/remote.py | 147 ++++++ tools/pytest-xdist/xdist/slavemanage.py | 316 ++++++++++++ 25 files changed, 3505 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/test/integration/smoke/test_CS-18306.py ---------------------------------------------------------------------- diff --git a/test/integration/smoke/test_CS-18306.py b/test/integration/smoke/test_CS-18306.py index 416cb5e..cb555ee 100644 --- a/test/integration/smoke/test_CS-18306.py +++ b/test/integration/smoke/test_CS-18306.py @@ -17,6 +17,14 @@ # under the License. import pytest +''' @pytest.mark.tags(tags=["advanced"], required_hardware="false") def test_01_create_disk_offering(vm): assert vm is not None +''' + +def test_a(): + assert True == True + +def test_b(): + assert True == True \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/marvin/marvin/pytest/pytest_marvin_plugin.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/pytest/pytest_marvin_plugin.py b/tools/marvin/marvin/pytest/pytest_marvin_plugin.py index b5a5ae4..abe844b 100644 --- a/tools/marvin/marvin/pytest/pytest_marvin_plugin.py +++ b/tools/marvin/marvin/pytest/pytest_marvin_plugin.py @@ -16,7 +16,6 @@ # under the License. import pytest import os -import distutils from marvin.utils import initTestClass,getMarvin from .VM import (vm,tiny_service_offering,template,test_client,account,domain,zone) @@ -25,9 +24,6 @@ def pytest_configure(config): config.addinivalue_line("markers", "tags(name): tag tests") - result = getMarvin() - if result is None: - pytest.fail("failed to init marvin plugin") marvin_init_tags() g_marvin_filter = { @@ -66,6 +62,12 @@ def pytest_runtest_setup(item): if found is not True: pytest.skip("doesn't match tags") +@pytest.fixture(scope="session", autouse=True) +def marvin_init_session(): + result = getMarvin() + if result is None: + pytest.fail("failed to init marvin plugin") + @pytest.fixture(scope="class", autouse=True) def marvin_inject_testclass(request): if request.cls is None: http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/CHANGELOG ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/CHANGELOG b/tools/pytest-xdist/CHANGELOG new file mode 100644 index 0000000..4e20fce --- /dev/null +++ b/tools/pytest-xdist/CHANGELOG @@ -0,0 +1,134 @@ +1.11 +------------------------- + +- fix pytest/xdist issue485 (also depends on py-1.4.22): + attach stdout/stderr on --boxed processes that die. + +- fix pytest/xdist issue503: make sure that a node has usually + two items to execute to avoid scoped fixtures to be torn down + pre-maturely (fixture teardown/setup is "nextitem" sensitive). + Thanks to Andreas Pelme for bug analysis and failing test. + +1.10 +------------------------- + +- add glob support for rsyncignores, add command line option to pass + additional rsyncignores. Thanks Anatoly Bubenkov. + +- fix pytest issue382 - produce "pytest_runtest_logstart" event again + in master. Thanks Aron Curzon. + +- 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) + +- send multiple "to test" indices in one network message to a slave + and improve heuristics for sending chunks where the chunksize + depends on the number of remaining tests rather than fixed numbers. + This reduces the number of master -> node messages (but not the + reverse direction) + + +1.9 +------------------------- + +- changed LICENSE to MIT + +- fix duplicate reported test ids with --looponfailing + (thanks Jeremy Thurgood) + +- fix pytest issue41: re-run tests on all file changes, not just + randomly select ones like .py/.c. + +- fix pytest issue347: slaves running on top of Python3.2 + will set PYTHONDONTWRITEYBTECODE to 1 to avoid import concurrency + bugs. + +1.8 +------------------------- + +- fix pytest-issue93 - use the refined pytest-2.2.1 runtestprotocol + interface to perform eager teardowns for test items. + +1.7 +------------------------- + +- fix incompatibilities with pytest-2.2.0 (allow multiple + pytest_runtest_logreport reports for a test item) + +1.6 +------------------------- + +- terser collection reporting + +- fix issue34 - distributed testing with -p plugin now works correctly + +- fix race condition in looponfail mode where a concurrent file removal + could cause a crash + +1.5 +------------------------- + +- adapt to and require pytest-2.0 changes, rsyncdirs and rsyncignore can now + only be specified in [pytest] sections of ini files, see "py.test -h" + for details. +- major internal refactoring to match the pytest-2.0 event refactoring + - perform test collection always at slave side instead of at the master + - make python2/python3 bridging work, remove usage of pickling +- improve initial reporting by using line-rewriting +- remove all trailing whitespace from source + +1.4 +------------------------- + +- perform distributed testing related reporting in the plugin + rather than having dist-related code in the generic py.test + distribution + +- depend on execnet-1.0.7 which adds "env1:NAME=value" keys to + gateway specification strings. + +- show detailed gateway setup and platform information only when + "-v" or "--verbose" is specified. + +1.3 +------------------------- + +- fix --looponfailing - it would not actually run against the fully changed + source tree when initial conftest files load application state. + +- adapt for py-1.3.1's new --maxfailure option + +1.2 +------------------------- + +- fix issue79: sessionfinish/teardown hooks are now called systematically + on the slave side +- introduce a new data input/output mechanism to allow the master side + to send and receive data from a slave. +- fix race condition in underlying pickling/unpickling handling +- use and require new register hooks facility of py.test>=1.3.0 +- require improved execnet>=1.0.6 because of various race conditions + that can arise in xdist testing modes. +- fix some python3 related pickling related race conditions +- fix PyPI description + +1.1 +------------------------- + +- fix an indefinite hang which would wait for events although no events + are pending - this happened if items arrive very quickly while + the "reschedule-event" tried unconditionally avoiding a busy-loop + and not schedule new work. + +1.0 +------------------------- + +- moved code out of py-1.1.1 into its own plugin +- use a new, faster and more sensible model to do load-balancing + of tests - now no magic "MAXITEMSPERHOST" is needed and load-testing + works effectively even with very few tests. +- cleaned up termination handling +- make -x cause hard killing of test nodes to decrease wait time + until the traceback shows up on first failure + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/ISSUES.txt ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/ISSUES.txt b/tools/pytest-xdist/ISSUES.txt new file mode 100644 index 0000000..7fe4579 --- /dev/null +++ b/tools/pytest-xdist/ISSUES.txt @@ -0,0 +1,31 @@ +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 py.test 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. http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/LICENSE ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/LICENSE b/tools/pytest-xdist/LICENSE new file mode 100644 index 0000000..31ecdfb --- /dev/null +++ b/tools/pytest-xdist/LICENSE @@ -0,0 +1,19 @@ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/MANIFEST.in ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/MANIFEST.in b/tools/pytest-xdist/MANIFEST.in new file mode 100644 index 0000000..cde85f9 --- /dev/null +++ b/tools/pytest-xdist/MANIFEST.in @@ -0,0 +1,7 @@ +include CHANGELOG +include LICENSE +include README.txt +include setup.py +include tox.ini +graft testing +prune .hg http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/README.txt ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/README.txt b/tools/pytest-xdist/README.txt new file mode 100644 index 0000000..39e76e8 --- /dev/null +++ b/tools/pytest-xdist/README.txt @@ -0,0 +1,212 @@ +xdist: pytest distributed testing plugin +=============================================================== + +The `pytest-xdist`_ plugin extends py.test with some unique +test execution modes: + +* test run parallelization_: if you have multiple CPUs or hosts you can use + 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 + the previously failing tests. This is repeated until all tests pass + after which again a full run is performed. + +* `Multi-Platform`_ coverage: you can specify different Python interpreters + or different platforms and run tests in parallel on all of them. + +Before running tests remotely, ``py.test`` efficiently "rsyncs" your +program source code to the remote place. All test results +are reported back and displayed to your local terminal. +You may specify different Python versions and interpreters. + + +Installation +----------------------- + +Install the plugin with:: + + easy_install pytest-xdist + + # or + + pip install pytest-xdist + +or use the package in develope/in-place mode with +a checkout of the `pytest-xdist repository`_ :: + + python setup.py develop + +Usage examples +--------------------- + +.. _parallelization: + +Speed up test runs by sending tests to multiple CPUs ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +To send tests to multiple CPUs, type:: + + py.test -n NUM + +Especially for longer running tests or tests requiring +a lot of IO this can lead to considerable speed ups. + + +Running tests in a Python subprocess ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +To instantiate a python2.5 sub process and send tests to it, you may type:: + + py.test -d --tx popen//python=python2.5 + +This will start a subprocess which is run with the "python2.5" +Python interpreter, found in your system binary lookup path. + +If you prefix the --tx option value like this:: + + --tx 3*popen//python=python2.5 + +then three subprocesses would be created and tests +will be load-balanced across these three processes. + +.. _boxed: + +Running tests in a boxed subprocess ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +If you have tests involving C or C++ libraries you might have to deal +with tests crashing the process. For this case you may use the boxing +options:: + + py.test --boxed + +which will run each test in a subprocess and will report if a test +crashed the process. You can also combine this option with +running multiple processes to speed up the test run and use your CPU cores:: + + py.test -n3 --boxed + +this would run 3 testing subprocesses in parallel which each +create new boxed subprocesses for each test. + + +.. _`remote machines`: + +Sending tests to remote SSH accounts ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Suppose you have a package ``mypkg`` which contains some +tests that you can successfully run locally. And you +have a ssh-reachable machine ``myhost``. Then +you can ad-hoc distribute your tests by typing:: + + py.test -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg + +This will synchronize your ``mypkg`` package directory +to an remote ssh account and then locally collect tests +and send them to remote places for execution. + +You can specify multiple ``--rsyncdir`` directories +to be sent to the remote side. + +**NOTE:** For py.test to collect and send tests correctly +you not only need to make sure all code and tests +directories are rsynced, but that any test (sub) directory +also has an ``__init__.py`` file because internally +py.test references tests as a fully qualified python +module path. **You will otherwise get strange errors** +during setup of the remote side. + +You can specify multiple ``--rsyncignore`` glob-patterns +to be ignored when file are sent to the remote side. +There are also internal ignores: .*, *.pyc, *.pyo, *~ +Those you cannot override using rsyncignore command-line or +ini-file option(s). + + +Sending tests to remote Socket Servers ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Download the single-module `socketserver.py`_ Python program +and run it like this:: + + python socketserver.py + +It will tell you that it starts listening on the default +port. You can now on your home machine specify this +new socket host with something like this:: + + py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg + + +.. _`atonce`: +.. _`Multi-Platform`: + + +Running tests on many platforms at once ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +The basic command to run tests on multiple platforms is:: + + py.test --dist=each --tx=spec1 --tx=spec2 + +If you specify a windows host, an OSX host and a Linux +environment this command will send each tests to all +platforms - and report back failures from all platforms +at once. The specifications strings use the `xspec syntax`_. + +.. _`xspec syntax`: http://codespeak.net/execnet/trunk/basics.html#xspec + +.. _`socketserver.py`: http://bitbucket.org/hpk42/execnet/raw/2af991418160/execnet/script/socketserver.py + +.. _`execnet`: http://codespeak.net/execnet + +Specifying test exec environments in an ini file ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +pytest (since version 2.0) supports ini-style cofiguration. +You can for example make running with three subprocesses +your default like this:: + + [pytest] + addopts = -n3 + +You can also add default environments like this:: + + [pytest] + addopts = --tx ssh=myhost//python=python2.5 --tx ssh=myhost//python=python2.6 + +and then just type:: + + py.test --dist=each + +to run tests in each of the environments. + +Specifying "rsync" dirs in an ini-file ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +In a ``tox.ini`` or ``setup.cfg`` file in your root project directory +you may specify directories to include or to exclude in synchronisation:: + + [pytest] + rsyncdirs = . mypkg helperpkg + rsyncignore = .hg + +These directory specifications are relative to the directory +where the configuration file was found. + +.. _`pytest-xdist`: http://pypi.python.org/pypi/pytest-xdist +.. _`pytest-xdist repository`: http://bitbucket.org/hpk42/pytest-xdist +.. _`pytest`: http://pytest.org + +Issue and Bug Tracker +------------------------ + +Please use the pytest issue tracker for bugs in this plugin, see https://bitbucket.org/hpk42/pytest/issues . + + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/example/boxed.txt ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/example/boxed.txt b/tools/pytest-xdist/example/boxed.txt new file mode 100644 index 0000000..af3b025 --- /dev/null +++ b/tools/pytest-xdist/example/boxed.txt @@ -0,0 +1,62 @@ + + +If your testing involves C or C++ libraries you might have to deal +with crashing processes. The xdist-plugin provides the ``--boxed`` option +to run each test in a controled subprocess. Here is a basic example:: + + # content of test_module.py + + import pytest + import os + import time + + # run test function 50 times with different argument + @pytest.mark.parametrize("arg", range(50)) + def test_func(arg): + time.sleep(0.05) # each tests takes a while + if arg % 19 == 0: + os.kill(os.getpid(), 15) + +If you run this with:: + + $ py.test --boxed + =========================== 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 + ______________________________ test_func[19] _______________________________ + /home/hpk/tmp/doc-exec-420/test_module.py:6: running the test CRASHED with signal 15 + ______________________________ test_func[38] _______________________________ + /home/hpk/tmp/doc-exec-420/test_module.py:6: running the test CRASHED with signal 15 + =================== 3 failed, 47 passed in 3.41 seconds ==================== + +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 + =========================== 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 ================================= + _______________________________ test_func[0] _______________________________ + [gw0] linux2 -- Python 2.7.3 /home/hpk/venv/1/bin/python + /home/hpk/tmp/doc-exec-420/test_module.py:6: running the test CRASHED with signal 15 + ______________________________ test_func[19] _______________________________ + [gw2] linux2 -- Python 2.7.3 /home/hpk/venv/1/bin/python + /home/hpk/tmp/doc-exec-420/test_module.py:6: running the test CRASHED with signal 15 + ______________________________ test_func[38] _______________________________ + [gw2] linux2 -- Python 2.7.3 /home/hpk/venv/1/bin/python + /home/hpk/tmp/doc-exec-420/test_module.py:6: running the test CRASHED with signal 15 + =================== 3 failed, 47 passed in 2.03 seconds ==================== http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/setup.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/setup.py b/tools/pytest-xdist/setup.py new file mode 100644 index 0000000..26bb42e --- /dev/null +++ b/tools/pytest-xdist/setup.py @@ -0,0 +1,30 @@ +from setuptools import setup + +setup( + name="pytest-xdist", + version='1.11', + description='py.test xdist plugin for distributed testing and loop-on-failing modes', + long_description=open('README.txt').read(), + license='MIT', + author='holger krekel and contributors', + author_email='pytest-...@python.org,hol...@merlinux.eu', + url='http://bitbucket.org/hpk42/pytest-xdist', + platforms=['linux', 'osx', 'win32'], + packages = ['xdist'], + entry_points = {'pytest11': ['xdist = xdist.plugin'],}, + zip_safe=False, + install_requires = ['execnet>=1.1', 'pytest>=2.4.2', 'py>=1.4.22'], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', + 'Topic :: Software Development :: Testing', + 'Topic :: Software Development :: Quality Assurance', + 'Topic :: Utilities', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + ], +) http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/testing/acceptance_test.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/testing/acceptance_test.py b/tools/pytest-xdist/testing/acceptance_test.py new file mode 100644 index 0000000..ea6ccce --- /dev/null +++ b/tools/pytest-xdist/testing/acceptance_test.py @@ -0,0 +1,486 @@ +import py + +class TestDistribution: + def test_n1_pass(self, testdir): + p1 = testdir.makepyfile(""" + def test_ok(): + pass + """) + result = testdir.runpytest(p1, "-n1") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*1 passed*", + ]) + + def test_n1_fail(self, testdir): + p1 = testdir.makepyfile(""" + def test_fail(): + assert 0 + """) + result = testdir.runpytest(p1, "-n1") + assert result.ret == 1 + result.stdout.fnmatch_lines([ + "*1 failed*", + ]) + + def test_n1_import_error(self, testdir): + p1 = testdir.makepyfile(""" + import __import_of_missing_module + def test_import(): + pass + """) + result = testdir.runpytest(p1, "-n1") + assert result.ret == 1 + result.stdout.fnmatch_lines([ + "E ImportError: *__import_of_missing_module*", + ]) + + def test_n2_import_error(self, testdir): + """Check that we don't report the same import error multiple times + in distributed mode.""" + p1 = testdir.makepyfile(""" + import __import_of_missing_module + def test_import(): + pass + """) + result1 = testdir.runpytest(p1, "-n2") + result2 = testdir.runpytest(p1, "-n1") + assert len(result1.stdout.lines) == len(result2.stdout.lines) + + def test_n1_skip(self, testdir): + p1 = testdir.makepyfile(""" + def test_skip(): + import py + py.test.skip("myreason") + """) + result = testdir.runpytest(p1, "-n1") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*1 skipped*", + ]) + + def test_manytests_to_one_import_error(self, testdir): + p1 = testdir.makepyfile(""" + import __import_of_missing_module + def test_import(): + pass + """) + result = testdir.runpytest(p1, '--tx=popen', '--tx=popen') + assert result.ret == 1 + result.stdout.fnmatch_lines([ + "E ImportError: *__import_of_missing_module*", + ]) + + def test_manytests_to_one_popen(self, testdir): + p1 = testdir.makepyfile(""" + import py + def test_fail0(): + assert 0 + def test_fail1(): + raise ValueError() + def test_ok(): + pass + def test_skip(): + py.test.skip("hello") + """, + ) + result = testdir.runpytest(p1, "-v", '-d', '--tx=popen', '--tx=popen') + result.stdout.fnmatch_lines([ + "*1*Python*", + "*2 failed, 1 passed, 1 skipped*", + ]) + assert result.ret == 1 + + def test_n1_fail_minus_x(self, testdir): + p1 = testdir.makepyfile(""" + def test_fail1(): + assert 0 + def test_fail2(): + assert 0 + """) + result = testdir.runpytest(p1, "-x", "-v", "-n1") + assert result.ret == 2 + result.stdout.fnmatch_lines([ + "*Interrupted: stopping*1*", + "*1 failed*", + ]) + + def test_basetemp_in_subprocesses(self, testdir): + p1 = testdir.makepyfile(""" + def test_send(tmpdir): + import py + assert tmpdir.relto(py.path.local(%r)), tmpdir + """ % str(testdir.tmpdir)) + result = testdir.runpytest(p1, "-n1") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*1 passed*", + ]) + + def test_dist_ini_specified(self, testdir): + p1 = testdir.makepyfile(""" + import py + def test_fail0(): + assert 0 + def test_fail1(): + raise ValueError() + def test_ok(): + pass + def test_skip(): + py.test.skip("hello") + """, + ) + testdir.makeini(""" + [pytest] + addopts = --tx=3*popen + """) + result = testdir.runpytest(p1, '-d', "-v") + result.stdout.fnmatch_lines([ + "*2*Python*", + "*2 failed, 1 passed, 1 skipped*", + ]) + assert result.ret == 1 + + @py.test.mark.xfail("sys.platform.startswith('java')", run=False) + def test_dist_tests_with_crash(self, testdir): + if not hasattr(py.std.os, 'kill'): + py.test.skip("no os.kill") + + p1 = testdir.makepyfile(""" + import py + def test_fail0(): + assert 0 + def test_fail1(): + raise ValueError() + def test_ok(): + pass + def test_skip(): + py.test.skip("hello") + def test_crash(): + import time + import os + time.sleep(0.5) + os.kill(os.getpid(), 15) + """ + ) + result = testdir.runpytest(p1, "-v", '-d', '-n1') + result.stdout.fnmatch_lines([ + "*Python*", + "*PASS**test_ok*", + "*node*down*", + "*3 failed, 1 passed, 1 skipped*" + ]) + assert result.ret == 1 + + def test_distribution_rsyncdirs_example(self, testdir): + source = testdir.mkdir("source") + dest = testdir.mkdir("dest") + subdir = source.mkdir("example_pkg") + subdir.ensure("__init__.py") + p = subdir.join("test_one.py") + p.write("def test_5():\n assert not __file__.startswith(%r)" % str(p)) + result = testdir.runpytest("-v", "-d", + "--rsyncdir=%(subdir)s" % locals(), + "--tx=popen//chdir=%(dest)s" % locals(), p) + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*0* *cwd*", + #"RSyncStart: [G1]", + #"RSyncFinished: [G1]", + "*1 passed*" + ]) + assert dest.join(subdir.basename).check(dir=1) + + def test_data_exchange(self, testdir): + testdir.makeconftest(""" + # This hook only called on master. + def pytest_configure_node(node): + node.slaveinput['a'] = 42 + node.slaveinput['b'] = 7 + + def pytest_configure(config): + # this attribute is only set on slaves + if hasattr(config, 'slaveinput'): + a = config.slaveinput['a'] + b = config.slaveinput['b'] + r = a + b + config.slaveoutput['r'] = r + + # This hook only called on master. + def pytest_testnodedown(node, error): + node.config.calc_result = node.slaveoutput['r'] + + def pytest_terminal_summary(terminalreporter): + if not hasattr(terminalreporter.config, 'slaveinput'): + calc_result = terminalreporter.config.calc_result + terminalreporter._tw.sep('-', + 'calculated result is %s' % calc_result) + """) + p1 = testdir.makepyfile("def test_func(): pass") + result = testdir.runpytest("-v", p1, '-d', '--tx=popen') + result.stdout.fnmatch_lines([ + "*0*Python*", + "*calculated result is 49*", + "*1 passed*" + ]) + assert result.ret == 0 + + def test_keyboardinterrupt_hooks_issue79(self, testdir): + testdir.makepyfile(__init__="", test_one=""" + def test_hello(): + raise KeyboardInterrupt() + """) + testdir.makeconftest(""" + def pytest_sessionfinish(session): + # on the slave + if hasattr(session.config, 'slaveoutput'): + session.config.slaveoutput['s2'] = 42 + # on the master + def pytest_testnodedown(node, error): + assert node.slaveoutput['s2'] == 42 + print ("s2call-finished") + """) + args = ["-n1", "--debug"] + result = testdir.runpytest(*args) + s = result.stdout.str() + assert result.ret == 2 + assert 's2call' in s + assert "Interrupted" in s + + def test_keyboard_interrupt_dist(self, testdir): + # xxx could be refined to check for return code + testdir.makepyfile(""" + def test_sleep(): + import time + time.sleep(10) + """) + child = testdir.spawn_pytest("-n1") + py.std.time.sleep(0.1) + child.expect(".*test session starts.*") + child.kill(2) # keyboard interrupt + child.expect(".*KeyboardInterrupt.*") + #child.expect(".*seconds.*") + child.close() + #assert ret == 2 + +class TestDistEach: + def test_simple(self, testdir): + testdir.makepyfile(""" + def test_hello(): + pass + """) + result = testdir.runpytest("--debug", "--dist=each", "--tx=2*popen") + assert not result.ret + result.stdout.fnmatch_lines(["*2 pass*"]) + + @py.test.mark.xfail(run=False, + reason="other python versions might not have py.test installed") + def test_simple_diffoutput(self, testdir): + interpreters = [] + for name in ("python2.5", "python2.6"): + interp = py.path.local.sysfind(name) + if interp is None: + py.test.skip("%s not found" % name) + interpreters.append(interp) + + testdir.makepyfile(__init__="", test_one=""" + import sys + def test_hello(): + print("%s...%s" % sys.version_info[:2]) + assert 0 + """) + args = ["--dist=each", "-v"] + args += ["--tx", "popen//python=%s" % interpreters[0]] + args += ["--tx", "popen//python=%s" % interpreters[1]] + result = testdir.runpytest(*args) + s = result.stdout.str() + assert "2...5" in s + assert "2...6" in s + +class TestTerminalReporting: + def test_pass_skip_fail(self, testdir): + testdir.makepyfile(""" + import py + def test_ok(): + pass + def test_skip(): + py.test.skip("xx") + def test_func(): + assert 0 + """) + result = testdir.runpytest("-n1", "-v") + result.stdout.fnmatch_lines_random([ + "*PASS*test_pass_skip_fail.py*test_ok*", + "*SKIP*test_pass_skip_fail.py*test_skip*", + "*FAIL*test_pass_skip_fail.py*test_func*", + ]) + result.stdout.fnmatch_lines([ + "*def test_func():", + "> assert 0", + "E assert 0", + ]) + + def test_fail_platinfo(self, testdir): + testdir.makepyfile(""" + def test_func(): + assert 0 + """) + result = testdir.runpytest("-n1", "-v") + result.stdout.fnmatch_lines([ + "*FAIL*test_fail_platinfo.py*test_func*", + "*0*Python*", + "*def test_func():", + "> assert 0", + "E assert 0", + ]) + +def test_teardownfails_one_function(testdir): + p = testdir.makepyfile(""" + def test_func(): + pass + def teardown_function(function): + assert 0 + """) + result = testdir.runpytest(p, '-n1', '--tx=popen') + result.stdout.fnmatch_lines([ + "*def teardown_function(function):*", + "*1 passed*1 error*" + ]) + +@py.test.mark.xfail +def test_terminate_on_hangingnode(testdir): + p = testdir.makeconftest(""" + def pytest_sessionfinishes(session): + if session.nodeid == "my": # running on slave + import time + time.sleep(3) + """) + result = testdir.runpytest(p, '--dist=each', '--tx=popen//id=my') + assert result.duration < 2.0 + result.stdout.fnmatch_lines([ + "*killed*my*", + ]) + + + +def test_session_hooks(testdir): + testdir.makeconftest(""" + import sys + def pytest_sessionstart(session): + sys.pytestsessionhooks = session + def pytest_sessionfinish(session): + if hasattr(session.config, 'slaveinput'): + name = "slave" + else: + name = "master" + f = open(name, "w") + f.write("xy") + f.close() + # let's fail on the slave + if name == "slave": + raise ValueError(42) + """) + p = testdir.makepyfile(""" + import sys + def test_hello(): + assert hasattr(sys, 'pytestsessionhooks') + """) + result = testdir.runpytest(p, "--dist=each", "--tx=popen") + result.stdout.fnmatch_lines([ + "*ValueError*", + "*1 passed*", + ]) + assert not result.ret + d = result.parseoutcomes() + assert d['passed'] == 1 + assert testdir.tmpdir.join("slave").check() + assert testdir.tmpdir.join("master").check() + +def test_funcarg_teardown_failure(testdir): + p = testdir.makepyfile(""" + def pytest_funcarg__myarg(request): + def teardown(val): + raise ValueError(val) + return request.cached_setup(setup=lambda: 42, teardown=teardown, + scope="module") + def test_hello(myarg): + pass + """) + result = testdir.runpytest("--debug", p) # , "-n1") + result.stdout.fnmatch_lines([ + "*ValueError*42*", + "*1 passed*1 error*", + ]) + assert result.ret + +def test_crashing_item(testdir): + p = testdir.makepyfile(""" + import py + import os + def test_crash(): + py.process.kill(os.getpid()) + def test_noncrash(): + pass + """) + result = testdir.runpytest("-n2", p) + result.stdout.fnmatch_lines([ + "*crashed*test_crash*", + "*1 failed*1 passed*" + ]) + + + +def test_skipping(testdir): + p = testdir.makepyfile(""" + import pytest + def test_crash(): + pytest.skip("hello") + """) + result = testdir.runpytest("-n1", '-rs', p) + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*hello*", + "*1 skipped*" + ]) + +def test_issue34_pluginloading_in_subprocess(testdir): + testdir.tmpdir.join("plugin123.py").write(py.code.Source(""" + def pytest_namespace(): + return {'sample_variable': 'testing'} + """)) + testdir.makepyfile(""" + import pytest + def test_hello(): + assert pytest.sample_variable == "testing" + """) + result = testdir.runpytest("-n1", "-p", "plugin123") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*1 passed*", + ]) + + +def test_fixture_scope_caching_issue503(testdir): + p1 = testdir.makepyfile(""" + import pytest + + @pytest.fixture(scope='session') + def fix(): + assert fix.counter == 0, 'session fixture was invoked multiple times' + fix.counter += 1 + fix.counter = 0 + + def test_a(fix): + pass + + def test_b(fix): + pass + """) + result = testdir.runpytest(p1, '-v', '-n1') + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*2 passed*", + ]) + + + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/testing/conftest.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/testing/conftest.py b/tools/pytest-xdist/testing/conftest.py new file mode 100644 index 0000000..d805f8c --- /dev/null +++ b/tools/pytest-xdist/testing/conftest.py @@ -0,0 +1,36 @@ +import py +import execnet + +pytest_plugins = "pytester" + +#rsyncdirs = ['.', '../xdist', py.path.local(execnet.__file__).dirpath()] + +def pytest_addoption(parser): + parser.addoption('--gx', + action="append", dest="gspecs", + help=("add a global test environment, XSpec-syntax. ")) + +def pytest_funcarg__specssh(request): + return getspecssh(request.config) + +# configuration information for tests +def getgspecs(config): + return [execnet.XSpec(spec) + for spec in config.getvalueorskip("gspecs")] + +def getspecssh(config): + xspecs = getgspecs(config) + for spec in xspecs: + if spec.ssh: + if not py.path.local.sysfind("ssh"): + py.test.skip("command not found: ssh") + return str(spec) + py.test.skip("need '--gx ssh=...'") + +def getsocketspec(config): + xspecs = getgspecs(config) + for spec in xspecs: + if spec.socket: + return spec + py.test.skip("need '--gx socket=...'") + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/testing/test_boxed.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/testing/test_boxed.py b/tools/pytest-xdist/testing/test_boxed.py new file mode 100644 index 0000000..bee9367 --- /dev/null +++ b/tools/pytest-xdist/testing/test_boxed.py @@ -0,0 +1,56 @@ +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 + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/testing/test_dsession.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/testing/test_dsession.py b/tools/pytest-xdist/testing/test_dsession.py new file mode 100644 index 0000000..90ba45b --- /dev/null +++ b/tools/pytest-xdist/testing/test_dsession.py @@ -0,0 +1,240 @@ +from xdist.dsession import ( + DSession, + LoadScheduling, + EachScheduling, + report_collection_diff, +) +import py +import pytest +import execnet + +XSpec = execnet.XSpec + +def run(item, node, excinfo=None): + runner = item.config.pluginmanager.getplugin("runner") + rep = runner.ItemTestReport(item=item, + excinfo=excinfo, when="call") + rep.node = node + return rep + +class MockGateway: + _count = 0 + def __init__(self): + self.id = str(self._count) + self._count += 1 + +class MockNode: + def __init__(self): + self.sent = [] + self.gateway = MockGateway() + + def send_runtest_some(self, indices): + self.sent.extend(indices) + + def send_runtest_all(self): + self.sent.append("ALL") + + def shutdown(self): + self._shutdown=True + +def dumpqueue(queue): + while queue.qsize(): + print(queue.get()) + +class TestEachScheduling: + def test_schedule_load_simple(self): + node1 = MockNode() + node2 = MockNode() + sched = EachScheduling(2) + sched.addnode(node1) + sched.addnode(node2) + collection = ["a.py::test_1", ] + assert not sched.collection_is_completed + sched.addnode_collection(node1, collection) + assert not sched.collection_is_completed + sched.addnode_collection(node2, collection) + assert sched.collection_is_completed + assert sched.node2collection[node1] == collection + assert sched.node2collection[node2] == collection + sched.init_distribute() + assert sched.tests_finished() + assert node1.sent == ['ALL'] + assert node2.sent == ['ALL'] + sched.remove_item(node1, 0) + assert sched.tests_finished() + sched.remove_item(node2, 0) + assert sched.tests_finished() + + def test_schedule_remove_node(self): + node1 = MockNode() + sched = EachScheduling(1) + sched.addnode(node1) + collection = ["a.py::test_1", ] + assert not sched.collection_is_completed + sched.addnode_collection(node1, collection) + assert sched.collection_is_completed + assert sched.node2collection[node1] == collection + sched.init_distribute() + assert sched.tests_finished() + crashitem = sched.remove_node(node1) + assert crashitem + assert sched.tests_finished() + assert not sched.hasnodes() + +class TestLoadScheduling: + def test_schedule_load_simple(self): + node1 = MockNode() + node2 = MockNode() + sched = LoadScheduling(2) + sched.addnode(node1) + sched.addnode(node2) + collection = ["a.py::test_1", "a.py::test_2"] + assert not sched.collection_is_completed + sched.addnode_collection(node1, collection) + assert not sched.collection_is_completed + sched.addnode_collection(node2, collection) + assert sched.collection_is_completed + assert sched.node2collection[node1] == collection + assert sched.node2collection[node2] == collection + sched.init_distribute() + assert not sched.pending + assert not sched.tests_finished() + assert len(node1.sent) == 2 + assert len(node2.sent) == 0 + assert node1.sent == [0, 1] + sched.remove_item(node1, node1.sent[0]) + assert sched.tests_finished() + sched.remove_item(node1, node1.sent[1]) + assert sched.tests_finished() + + def test_init_distribute_chunksize(self): + sched = LoadScheduling(2) + node1 = MockNode() + node2 = MockNode() + sched.addnode(node1) + sched.addnode(node2) + col = ["xyz"] * (6) + sched.addnode_collection(node1, col) + sched.addnode_collection(node2, col) + sched.init_distribute() + #assert not sched.tests_finished() + sent1 = node1.sent + sent2 = node2.sent + assert sent1 == [0, 1] + assert sent2 == [2, 3] + assert sched.pending == [4, 5] + assert sched.node2pending[node1] == sent1 + assert sched.node2pending[node2] == sent2 + assert len(sched.pending) == 2 + sched.remove_item(node1, 0) + assert node1.sent == [0, 1, 4] + assert sched.pending == [5] + assert node2.sent == [2, 3] + sched.remove_item(node1, 1) + assert node1.sent == [0, 1, 4, 5] + assert not sched.pending + + def test_add_remove_node(self): + node = MockNode() + sched = LoadScheduling(1) + sched.addnode(node) + collection = ["test_file.py::test_func"] + sched.addnode_collection(node, collection) + assert sched.collection_is_completed + sched.init_distribute() + assert not sched.pending + crashitem = sched.remove_node(node) + assert crashitem == collection[0] + + def test_schedule_different_tests_collected(self): + """ + Test that LoadScheduling is logging different tests were + collected by slaves when that happens. + """ + node1 = MockNode() + node2 = MockNode() + sched = LoadScheduling(2) + logged_messages = [] + py.log.setconsumer('loadsched', logged_messages.append) + sched.addnode(node1) + sched.addnode(node2) + sched.addnode_collection(node1, ["a.py::test_1"]) + sched.addnode_collection(node2, ["a.py::test_2"]) + sched.init_distribute() + logged_content = ''.join(x.content() for x in logged_messages) + assert 'Different tests were collected between' in logged_content + assert 'Different tests collected, aborting run' in logged_content + + +class TestDistReporter: + + @py.test.mark.xfail + def test_rsync_printing(self, testdir, linecomp): + config = testdir.parseconfig() + from _pytest.pytest_terminal import TerminalReporter + rep = TerminalReporter(config, file=linecomp.stringio) + config.pluginmanager.register(rep, "terminalreporter") + dsession = DSession(config) + class gw1: + id = "X1" + spec = execnet.XSpec("popen") + class gw2: + id = "X2" + spec = execnet.XSpec("popen") + #class rinfo: + # version_info = (2, 5, 1, 'final', 0) + # executable = "hello" + # platform = "xyz" + # cwd = "qwe" + + #dsession.pytest_xdist_newgateway(gw1, rinfo) + #linecomp.assert_contains_lines([ + # "*X1*popen*xyz*2.5*" + #]) + dsession.pytest_xdist_rsyncstart(source="hello", gateways=[gw1, gw2]) + linecomp.assert_contains_lines([ + "[X1,X2] rsyncing: hello", + ]) + + +def test_report_collection_diff_equal(): + """Test reporting of equal collections.""" + from_collection = to_collection = ['aaa', 'bbb', 'ccc'] + assert report_collection_diff(from_collection, to_collection, 1, 2) is None + + +def test_report_collection_diff_different(): + """Test reporting of different collections.""" + from_collection = ['aaa', 'bbb', 'ccc', 'YYY'] + to_collection = ['aZa', 'bbb', 'XXX', 'ccc'] + error_message = ( + 'Different tests were collected between 1 and 2. The difference is:\n' + '--- 1\n' + '\n' + '+++ 2\n' + '\n' + '@@ -1,4 +1,4 @@\n' + '\n' + '-aaa\n' + '+aZa\n' + ' bbb\n' + '+XXX\n' + ' ccc\n' + '-YYY' + ) + + msg = report_collection_diff(from_collection, to_collection, 1, 2) + assert msg == error_message + +@pytest.mark.xfail(reason="duplicate test ids not supported yet") +def test_pytest_issue419(testdir): + testdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize('birth_year', [1988, 1988, ]) + def test_2011_table(birth_year): + pass + """) + reprec = testdir.inline_run("-n1") + reprec.assertoutcome(passed=2) + assert 0 http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/testing/test_looponfail.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/testing/test_looponfail.py b/tools/pytest-xdist/testing/test_looponfail.py new file mode 100644 index 0000000..4aadb32 --- /dev/null +++ b/tools/pytest-xdist/testing/test_looponfail.py @@ -0,0 +1,280 @@ +import py +from xdist.looponfail import RemoteControl +from xdist.looponfail import StatRecorder + +class TestStatRecorder: + def test_filechange(self, tmpdir): + tmp = tmpdir + hello = tmp.ensure("hello.py") + sd = StatRecorder([tmp]) + changed = sd.check() + assert not changed + + hello.write("world") + changed = sd.check() + assert changed + + (hello + "c").write("hello") + changed = sd.check() + assert not changed + + p = tmp.ensure("new.py") + changed = sd.check() + assert changed + + p.remove() + changed = sd.check() + assert changed + + tmp.join("a", "b", "c.py").ensure() + changed = sd.check() + assert changed + + tmp.join("a", "c.txt").ensure() + changed = sd.check() + assert changed + changed = sd.check() + assert not changed + + tmp.join("a").remove() + changed = sd.check() + assert changed + + def test_dirchange(self, tmpdir): + tmp = tmpdir + tmp.ensure("dir", "hello.py") + sd = StatRecorder([tmp]) + assert not sd.fil(tmp.join("dir")) + + def test_filechange_deletion_race(self, tmpdir, monkeypatch): + tmp = tmpdir + sd = StatRecorder([tmp]) + changed = sd.check() + assert not changed + + p = tmp.ensure("new.py") + changed = sd.check() + assert changed + + p.remove() + # make check()'s visit() call return our just removed + # path as if we were in a race condition + monkeypatch.setattr(tmp, 'visit', lambda *args: [p]) + + changed = sd.check() + assert changed + + def test_pycremoval(self, tmpdir): + tmp = tmpdir + hello = tmp.ensure("hello.py") + sd = StatRecorder([tmp]) + changed = sd.check() + assert not changed + + pycfile = hello + "c" + pycfile.ensure() + hello.write("world") + changed = sd.check() + assert changed + assert not pycfile.check() + + def test_waitonchange(self, tmpdir, monkeypatch): + tmp = tmpdir + sd = StatRecorder([tmp]) + + l = [True, False] + monkeypatch.setattr(StatRecorder, 'check', lambda self: l.pop()) + sd.waitonchange(checkinterval=0.2) + assert not l + +class TestRemoteControl: + def test_nofailures(self, testdir): + item = testdir.getitem("def test_func(): pass\n") + control = RemoteControl(item.config) + control.setup() + topdir, failures = control.runsession()[:2] + assert not failures + + def test_failures_somewhere(self, testdir): + item = testdir.getitem("def test_func():\n assert 0\n") + control = RemoteControl(item.config) + control.setup() + failures = control.runsession() + assert failures + control.setup() + item.fspath.write("def test_func():\n assert 1\n") + removepyc(item.fspath) + topdir, failures = control.runsession()[:2] + assert not failures + + def test_failure_change(self, testdir): + modcol = testdir.getitem(""" + def test_func(): + assert 0 + """) + control = RemoteControl(modcol.config) + control.loop_once() + assert control.failures + modcol.fspath.write(py.code.Source(""" + def test_func(): + assert 1 + def test_new(): + assert 0 + """)) + removepyc(modcol.fspath) + control.loop_once() + assert not control.failures + control.loop_once() + assert control.failures + assert str(control.failures).find("test_new") != -1 + + def test_failure_subdir_no_init(self, testdir): + modcol = testdir.getitem(""" + def test_func(): + assert 0 + """) + parent = modcol.fspath.dirpath().dirpath() + parent.chdir() + modcol.config.args = [py.path.local(x).relto(parent) + for x in modcol.config.args] + control = RemoteControl(modcol.config) + control.loop_once() + assert control.failures + control.loop_once() + assert control.failures + +class TestLooponFailing: + def test_looponfail_from_fail_to_ok(self, testdir): + modcol = testdir.getmodulecol(""" + def test_one(): + x = 0 + assert x == 1 + def test_two(): + assert 1 + """) + remotecontrol = RemoteControl(modcol.config) + remotecontrol.loop_once() + assert len(remotecontrol.failures) == 1 + + modcol.fspath.write(py.code.Source(""" + def test_one(): + assert 1 + def test_two(): + assert 1 + """)) + removepyc(modcol.fspath) + remotecontrol.loop_once() + assert not remotecontrol.failures + + def test_looponfail_from_one_to_two_tests(self, testdir): + modcol = testdir.getmodulecol(""" + def test_one(): + assert 0 + """) + remotecontrol = RemoteControl(modcol.config) + remotecontrol.loop_once() + assert len(remotecontrol.failures) == 1 + assert 'test_one' in remotecontrol.failures[0] + + modcol.fspath.write(py.code.Source(""" + def test_one(): + assert 1 # passes now + def test_two(): + assert 0 # new and fails + """)) + removepyc(modcol.fspath) + remotecontrol.loop_once() + assert len(remotecontrol.failures) == 0 + remotecontrol.loop_once() + assert len(remotecontrol.failures) == 1 + assert 'test_one' not in remotecontrol.failures[0] + assert 'test_two' in remotecontrol.failures[0] + + def test_looponfail_removed_test(self, testdir): + modcol = testdir.getmodulecol(""" + def test_one(): + assert 0 + def test_two(): + assert 0 + """) + remotecontrol = RemoteControl(modcol.config) + remotecontrol.loop_once() + assert len(remotecontrol.failures) == 2 + + modcol.fspath.write(py.code.Source(""" + def test_xxx(): # renamed test + assert 0 + def test_two(): + assert 1 # pass now + """)) + removepyc(modcol.fspath) + remotecontrol.loop_once() + assert len(remotecontrol.failures) == 0 + + remotecontrol.loop_once() + assert len(remotecontrol.failures) == 1 + + def test_looponfail_multiple_errors(self, testdir, monkeypatch): + modcol = testdir.getmodulecol(""" + def test_one(): + assert 0 + """) + remotecontrol = RemoteControl(modcol.config) + orig_runsession = remotecontrol.runsession + + def runsession_dups(): + # twisted.trial test cases may report multiple errors. + failures, reports, collection_failed = orig_runsession() + print (failures) + return failures * 2, reports, collection_failed + + monkeypatch.setattr(remotecontrol, 'runsession', runsession_dups) + remotecontrol.loop_once() + assert len(remotecontrol.failures) == 1 + + +class TestFunctional: + def test_fail_to_ok(self, testdir): + p = testdir.makepyfile(""" + def test_one(): + x = 0 + assert x == 1 + """) + #p = testdir.mkdir("sub").join(p1.basename) + #p1.move(p) + child = testdir.spawn_pytest("-f %s --traceconfig" % p) + child.expect("def test_one") + child.expect("x == 1") + child.expect("1 failed") + child.expect("### LOOPONFAILING ####") + child.expect("waiting for changes") + p.write(py.code.Source(""" + def test_one(): + x = 1 + assert x == 1 + """)) + child.expect(".*1 passed.*") + child.kill(15) + + def test_xfail_passes(self, testdir): + p = testdir.makepyfile(""" + import py + @py.test.mark.xfail + def test_one(): + pass + """) + child = testdir.spawn_pytest("-f %s" % p) + child.expect("1 xpass") + child.expect("### LOOPONFAILING ####") + child.expect("waiting for changes") + child.kill(15) + +def removepyc(path): + # XXX damn those pyc files + pyc = path + "c" + if pyc.check(): + pyc.remove() + c = path.dirpath("__pycache__") + if c.check(): + c.remove() + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/testing/test_plugin.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/testing/test_plugin.py b/tools/pytest-xdist/testing/test_plugin.py new file mode 100644 index 0000000..91eda26 --- /dev/null +++ b/tools/pytest-xdist/testing/test_plugin.py @@ -0,0 +1,70 @@ +import py +import execnet +from xdist.slavemanage import NodeManager + +def test_dist_incompatibility_messages(testdir): + result = testdir.runpytest("--pdb", "--looponfail") + assert result.ret != 0 + result = testdir.runpytest("--pdb", "-n", "3") + assert result.ret != 0 + assert "incompatible" in result.stderr.str() + result = testdir.runpytest("--pdb", "-d", "--tx", "popen") + assert result.ret != 0 + assert "incompatible" in result.stderr.str() + +def test_dist_options(testdir): + from xdist.plugin import check_options + config = testdir.parseconfigure("-n 2") + check_options(config) + assert config.option.dist == "load" + assert config.option.tx == ['popen'] * 2 + config = testdir.parseconfigure("-d") + check_options(config) + assert config.option.dist == "load" + +class TestDistOptions: + def test_getxspecs(self, testdir): + config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz") + nodemanager = NodeManager(config) + xspecs = nodemanager._getxspecs() + assert len(xspecs) == 2 + print(xspecs) + assert xspecs[0].popen + assert xspecs[1].ssh == "xyz" + + def test_xspecs_multiplied(self, testdir): + config = testdir.parseconfigure("--tx=3*popen",) + xspecs = NodeManager(config)._getxspecs() + assert len(xspecs) == 3 + assert xspecs[1].popen + + def test_getrsyncdirs(self, testdir): + config = testdir.parseconfigure('--rsyncdir=' + str(testdir.tmpdir)) + nm = NodeManager(config, specs=[execnet.XSpec("popen")]) + assert not nm._getrsyncdirs() + nm = NodeManager(config, specs=[execnet.XSpec("popen//chdir=qwe")]) + assert nm.roots + assert testdir.tmpdir in nm.roots + + def test_getrsyncignore(self, testdir): + config = testdir.parseconfigure('--rsyncignore=fo*') + nm = NodeManager(config, specs=[execnet.XSpec("popen//chdir=qwe")]) + assert 'fo*' in nm.rsyncoptions['ignores'] + + def test_getrsyncdirs_with_conftest(self, testdir): + p = py.path.local() + for bn in 'x y z'.split(): + p.mkdir(bn) + testdir.makeini(""" + [pytest] + rsyncdirs= x + """) + config = testdir.parseconfigure( + testdir.tmpdir, '--rsyncdir=y', '--rsyncdir=z') + nm = NodeManager(config, specs=[execnet.XSpec("popen//chdir=xyz")]) + roots = nm._getrsyncdirs() + #assert len(roots) == 3 + 1 # pylib + assert py.path.local('y') in roots + assert py.path.local('z') in roots + assert testdir.tmpdir.join('x') in roots + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/testing/test_remote.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/testing/test_remote.py b/tools/pytest-xdist/testing/test_remote.py new file mode 100644 index 0000000..4a043e7 --- /dev/null +++ b/tools/pytest-xdist/testing/test_remote.py @@ -0,0 +1,248 @@ +import py +from xdist.slavemanage import SlaveController, unserialize_report +from xdist.remote import serialize_report +import execnet +queue = py.builtin._tryimport("queue", "Queue") +import marshal + +WAIT_TIMEOUT = 10.0 + +def check_marshallable(d): + try: + marshal.dumps(d) + except ValueError: + py.std.pprint.pprint(d) + raise ValueError("not marshallable") + +class EventCall: + def __init__(self, eventcall): + self.name, self.kwargs = eventcall + + def __str__(self): + return "<EventCall %s(**%s)>" %(self.name, self.kwargs) + +class SlaveSetup: + use_callback = False + + def __init__(self, request): + self.testdir = request.getfuncargvalue("testdir") + self.request = request + self.events = queue.Queue() + + def setup(self, ): + self.testdir.chdir() + #import os ; os.environ['EXECNET_DEBUG'] = "2" + self.gateway = execnet.makegateway() + self.config = config = self.testdir.parseconfigure() + putevent = self.use_callback and self.events.put or None + self.slp = SlaveController(None, self.gateway, config, putevent) + self.request.addfinalizer(self.slp.ensure_teardown) + self.slp.setup() + + def popevent(self, name=None): + while 1: + if self.use_callback: + data = self.events.get(timeout=WAIT_TIMEOUT) + else: + data = self.slp.channel.receive(timeout=WAIT_TIMEOUT) + ev = EventCall(data) + if name is None or ev.name == name: + return ev + print("skipping %s" % (ev,)) + + def sendcommand(self, name, **kwargs): + self.slp.sendcommand(name, **kwargs) + +def pytest_funcarg__slave(request): + return SlaveSetup(request) + +def test_remoteinitconfig(testdir): + from xdist.remote import remote_initconfig + config1 = testdir.parseconfig() + config2 = remote_initconfig(config1.option.__dict__, config1.args) + assert config2.option.__dict__ == config1.option.__dict__ + assert config2.pluginmanager.getplugin("terminal") in (-1, None) + +class TestReportSerialization: + def test_itemreport_outcomes(self, testdir): + reprec = testdir.inline_runsource(""" + import py + def test_pass(): pass + def test_fail(): 0/0 + @py.test.mark.skipif("True") + def test_skip(): pass + def test_skip_imperative(): + py.test.skip("hello") + @py.test.mark.xfail("True") + def test_xfail(): 0/0 + def test_xfail_imperative(): + py.test.xfail("hello") + """) + reports = reprec.getreports("pytest_runtest_logreport") + assert len(reports) == 17 # with setup/teardown "passed" reports + for rep in reports: + d = serialize_report(rep) + check_marshallable(d) + newrep = unserialize_report("testreport", d) + assert newrep.passed == rep.passed + assert newrep.failed == rep.failed + assert newrep.skipped == rep.skipped + if newrep.skipped and not hasattr(newrep, "wasxfail"): + assert len(newrep.longrepr) == 3 + assert newrep.outcome == rep.outcome + assert newrep.when == rep.when + assert newrep.keywords == rep.keywords + if rep.failed: + assert newrep.longrepr == str(rep.longrepr) + + def test_collectreport_passed(self, testdir): + reprec = testdir.inline_runsource("def test_func(): pass") + reports = reprec.getreports("pytest_collectreport") + for rep in reports: + d = serialize_report(rep) + check_marshallable(d) + newrep = unserialize_report("collectreport", d) + assert newrep.passed == rep.passed + assert newrep.failed == rep.failed + assert newrep.skipped == rep.skipped + + def test_collectreport_fail(self, testdir): + reprec = testdir.inline_runsource("qwe abc") + reports = reprec.getreports("pytest_collectreport") + assert reports + for rep in reports: + d = serialize_report(rep) + check_marshallable(d) + newrep = unserialize_report("collectreport", d) + assert newrep.passed == rep.passed + assert newrep.failed == rep.failed + assert newrep.skipped == rep.skipped + if rep.failed: + assert newrep.longrepr == str(rep.longrepr) + + def test_extended_report_deserialization(self, testdir): + reprec = testdir.inline_runsource("qwe abc") + reports = reprec.getreports("pytest_collectreport") + assert reports + for rep in reports: + rep.extra = True + d = serialize_report(rep) + check_marshallable(d) + newrep = unserialize_report("collectreport", d) + assert newrep.extra + assert newrep.passed == rep.passed + assert newrep.failed == rep.failed + assert newrep.skipped == rep.skipped + if rep.failed: + assert newrep.longrepr == str(rep.longrepr) + + +class TestSlaveInteractor: + def test_basic_collect_and_runtests(self, slave): + slave.testdir.makepyfile(""" + def test_func(): + pass + """) + slave.setup() + ev = slave.popevent() + assert ev.name == "slaveready" + ev = slave.popevent() + assert ev.name == "collectionstart" + assert not ev.kwargs + ev = slave.popevent("collectionfinish") + assert ev.kwargs['topdir'] == slave.testdir.tmpdir + ids = ev.kwargs['ids'] + assert len(ids) == 1 + slave.sendcommand("runtests", indices=list(range(len(ids)))) + slave.sendcommand("shutdown") + ev = slave.popevent("logstart") + assert ev.kwargs["nodeid"].endswith("test_func") + assert len(ev.kwargs["location"]) == 3 + ev = slave.popevent("testreport") # setup + ev = slave.popevent("testreport") + assert ev.name == "testreport" + rep = unserialize_report(ev.name, ev.kwargs['data']) + assert rep.nodeid.endswith("::test_func") + assert rep.passed + assert rep.when == "call" + ev = slave.popevent("slavefinished") + assert 'slaveoutput' in ev.kwargs + + def test_remote_collect_skip(self, slave): + slave.testdir.makepyfile(""" + import py + py.test.skip("hello") + """) + slave.setup() + ev = slave.popevent("collectionstart") + assert not ev.kwargs + ev = slave.popevent() + assert ev.name == "collectreport" + ev = slave.popevent() + assert ev.name == "collectreport" + rep = unserialize_report(ev.name, ev.kwargs['data']) + assert rep.skipped + ev = slave.popevent("collectionfinish") + assert not ev.kwargs['ids'] + + def test_remote_collect_fail(self, slave): + slave.testdir.makepyfile("""aasd qwe""") + slave.setup() + ev = slave.popevent("collectionstart") + assert not ev.kwargs + ev = slave.popevent() + assert ev.name == "collectreport" + ev = slave.popevent() + assert ev.name == "collectreport" + rep = unserialize_report(ev.name, ev.kwargs['data']) + assert rep.failed + ev = slave.popevent("collectionfinish") + assert not ev.kwargs['ids'] + + def test_runtests_all(self, slave): + slave.testdir.makepyfile(""" + def test_func(): pass + def test_func2(): pass + """) + slave.setup() + ev = slave.popevent() + assert ev.name == "slaveready" + ev = slave.popevent() + assert ev.name == "collectionstart" + assert not ev.kwargs + ev = slave.popevent("collectionfinish") + ids = ev.kwargs['ids'] + assert len(ids) == 2 + slave.sendcommand("runtests_all", ) + slave.sendcommand("shutdown", ) + for func in "::test_func", "::test_func2": + for i in range(3): # setup/call/teardown + ev = slave.popevent("testreport") + assert ev.name == "testreport" + rep = unserialize_report(ev.name, ev.kwargs['data']) + assert rep.nodeid.endswith(func) + ev = slave.popevent("slavefinished") + assert 'slaveoutput' in ev.kwargs + + def test_happy_run_events_converted(self, testdir, slave): + py.test.xfail("implement a simple test for event production") + assert not slave.use_callback + slave.testdir.makepyfile(""" + def test_func(): + pass + """) + slave.setup() + hookrec = testdir.getreportrecorder(slave.config) + for data in slave.slp.channel: + slave.slp.process_from_remote(data) + slave.slp.process_from_remote(slave.slp.ENDMARK) + py.std.pprint.pprint(hookrec.hookrecorder.calls) + hookrec.hookrecorder.contains([ + ("pytest_collectstart", "collector.fspath == aaa"), + ("pytest_pycollect_makeitem", "name == 'test_func'"), + ("pytest_collectreport", "report.collector.fspath == aaa"), + ("pytest_collectstart", "collector.fspath == bbb"), + ("pytest_pycollect_makeitem", "name == 'test_func'"), + ("pytest_collectreport", "report.collector.fspath == bbb"), + ]) + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/testing/test_slavemanage.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/testing/test_slavemanage.py b/tools/pytest-xdist/testing/test_slavemanage.py new file mode 100644 index 0000000..a94181b --- /dev/null +++ b/tools/pytest-xdist/testing/test_slavemanage.py @@ -0,0 +1,241 @@ +import py +import execnet +from xdist.slavemanage import HostRSync, NodeManager + +pytest_plugins = "pytester", + +def pytest_funcarg__hookrecorder(request): + _pytest = request.getfuncargvalue('_pytest') + config = request.getfuncargvalue('config') + return _pytest.gethookrecorder(config.hook) + +def pytest_funcarg__config(request): + testdir = request.getfuncargvalue("testdir") + config = testdir.parseconfig() + return config + +def pytest_funcarg__mysetup(request): + class mysetup: + def __init__(self, request): + temp = request.getfuncargvalue("tmpdir") + self.source = temp.mkdir("source") + self.dest = temp.mkdir("dest") + request.getfuncargvalue("_pytest") + return mysetup(request) + +class TestNodeManagerPopen: + def test_popen_no_default_chdir(self, config): + gm = NodeManager(config, ["popen"]) + assert gm.specs[0].chdir is None + + def test_default_chdir(self, config): + l = ["ssh=noco", "socket=xyz"] + for spec in NodeManager(config, l).specs: + assert spec.chdir == "pyexecnetcache" + for spec in NodeManager(config, l, defaultchdir="abc").specs: + assert spec.chdir == "abc" + + def test_popen_makegateway_events(self, config, hookrecorder, _pytest): + hm = NodeManager(config, ["popen"] * 2) + hm.makegateways() + call = hookrecorder.popcall("pytest_xdist_setupnodes") + assert len(call.specs) == 2 + + call = hookrecorder.popcall("pytest_xdist_newgateway") + assert call.gateway.spec == execnet.XSpec("popen") + assert call.gateway.id == "gw0" + call = hookrecorder.popcall("pytest_xdist_newgateway") + assert call.gateway.id == "gw1" + assert len(hm.group) == 2 + hm.teardown_nodes() + assert not len(hm.group) + + def test_popens_rsync(self, config, mysetup): + source = mysetup.source + hm = NodeManager(config, ["popen"] * 2) + hm.makegateways() + assert len(hm.group) == 2 + for gw in hm.group: + class pseudoexec: + args = [] + def __init__(self, *args): + self.args.extend(args) + def waitclose(self): + pass + gw.remote_exec = pseudoexec + l = [] + hm.rsync(source, notify=lambda *args: l.append(args)) + assert not l + hm.teardown_nodes() + assert not len(hm.group) + assert "sys.path.insert" in gw.remote_exec.args[0] + + def test_rsync_popen_with_path(self, config, mysetup): + source, dest = mysetup.source, mysetup.dest + hm = NodeManager(config, ["popen//chdir=%s" %dest] * 1) + hm.makegateways() + source.ensure("dir1", "dir2", "hello") + l = [] + hm.rsync(source, notify=lambda *args: l.append(args)) + assert len(l) == 1 + assert l[0] == ("rsyncrootready", hm.group['gw0'].spec, source) + hm.teardown_nodes() + dest = dest.join(source.basename) + assert dest.join("dir1").check() + assert dest.join("dir1", "dir2").check() + assert dest.join("dir1", "dir2", 'hello').check() + + def test_rsync_same_popen_twice(self, config, mysetup, hookrecorder): + source, dest = mysetup.source, mysetup.dest + hm = NodeManager(config, ["popen//chdir=%s" %dest] * 2) + hm.makegateways() + source.ensure("dir1", "dir2", "hello") + hm.rsync(source) + call = hookrecorder.popcall("pytest_xdist_rsyncstart") + assert call.source == source + assert len(call.gateways) == 1 + assert call.gateways[0] in hm.group + call = hookrecorder.popcall("pytest_xdist_rsyncfinish") + +class TestHRSync: + def pytest_funcarg__mysetup(self, request): + class mysetup: + def __init__(self, request): + tmp = request.getfuncargvalue('tmpdir') + self.source = tmp.mkdir("source") + self.dest = tmp.mkdir("dest") + return mysetup(request) + + def test_hrsync_filter(self, mysetup): + source, _ = mysetup.source, mysetup.dest # noqa + source.ensure("dir", "file.txt") + source.ensure(".svn", "entries") + source.ensure(".somedotfile", "moreentries") + source.ensure("somedir", "editfile~") + syncer = HostRSync(source, ignores=NodeManager.DEFAULT_IGNORES) + l = list(source.visit(rec=syncer.filter, + fil=syncer.filter)) + assert len(l) == 3 + basenames = [x.basename for x in l] + assert 'dir' in basenames + assert 'file.txt' in basenames + assert 'somedir' in basenames + + def test_hrsync_one_host(self, mysetup): + source, dest = mysetup.source, mysetup.dest + gw = execnet.makegateway("popen//chdir=%s" % dest) + finished = [] + rsync = HostRSync(source) + rsync.add_target_host(gw, finished=lambda: finished.append(1)) + source.join("hello.py").write("world") + rsync.send() + gw.exit() + assert dest.join(source.basename, "hello.py").check() + assert len(finished) == 1 + + +class TestNodeManager: + @py.test.mark.xfail + def test_rsync_roots_no_roots(self, testdir, mysetup): + mysetup.source.ensure("dir1", "file1").write("hello") + config = testdir.parseconfig(mysetup.source) + nodemanager = NodeManager(config, ["popen//chdir=%s" % mysetup.dest]) + #assert nodemanager.config.topdir == source == config.topdir + nodemanager.makegateways() + nodemanager.rsync_roots() + p, = nodemanager.gwmanager.multi_exec( + "import os ; channel.send(os.getcwd())").receive_each() + p = py.path.local(p) + py.builtin.print_("remote curdir", p) + assert p == mysetup.dest.join(config.topdir.basename) + assert p.join("dir1").check() + assert p.join("dir1", "file1").check() + + def test_popen_rsync_subdir(self, testdir, mysetup): + source, dest = mysetup.source, mysetup.dest + dir1 = mysetup.source.mkdir("dir1") + dir2 = dir1.mkdir("dir2") + dir2.ensure("hello") + for rsyncroot in (dir1, source): + dest.remove() + nodemanager = NodeManager(testdir.parseconfig( + "--tx", "popen//chdir=%s" % dest, + "--rsyncdir", rsyncroot, + source, + )) + nodemanager.makegateways() + nodemanager.rsync_roots() + if rsyncroot == source: + dest = dest.join("source") + assert dest.join("dir1").check() + assert dest.join("dir1", "dir2").check() + assert dest.join("dir1", "dir2", 'hello').check() + nodemanager.teardown_nodes() + + def test_init_rsync_roots(self, testdir, mysetup): + source, dest = mysetup.source, mysetup.dest + dir2 = source.ensure("dir1", "dir2", dir=1) + source.ensure("dir1", "somefile", dir=1) + dir2.ensure("hello") + source.ensure("bogusdir", "file") + source.join("tox.ini").write(py.std.textwrap.dedent(""" + [pytest] + rsyncdirs=dir1/dir2 + """)) + config = testdir.parseconfig(source) + nodemanager = NodeManager(config, ["popen//chdir=%s" % dest]) + nodemanager.makegateways() + nodemanager.rsync_roots() + assert dest.join("dir2").check() + assert not dest.join("dir1").check() + assert not dest.join("bogus").check() + + def test_rsyncignore(self, testdir, mysetup): + source, dest = mysetup.source, mysetup.dest + dir2 = source.ensure("dir1", "dir2", dir=1) + source.ensure("dir5", "dir6", "bogus") + source.ensure("dir5", "file") + dir2.ensure("hello") + source.ensure("foo", "bar") + source.ensure("bar", "foo") + source.join("tox.ini").write(py.std.textwrap.dedent(""" + [pytest] + rsyncdirs = dir1 dir5 + rsyncignore = dir1/dir2 dir5/dir6 foo* + """)) + config = testdir.parseconfig(source) + config.option.rsyncignore = ['bar'] + nodemanager = NodeManager(config, ["popen//chdir=%s" % dest]) + nodemanager.makegateways() + nodemanager.rsync_roots() + assert dest.join("dir1").check() + assert not dest.join("dir1", "dir2").check() + assert dest.join("dir5","file").check() + assert not dest.join("dir6").check() + assert not dest.join('foo').check() + assert not dest.join('bar').check() + + def test_optimise_popen(self, testdir, mysetup): + source = mysetup.source + specs = ["popen"] * 3 + source.join("conftest.py").write("rsyncdirs = ['a']") + source.ensure('a', dir=1) + config = testdir.parseconfig(source) + nodemanager = NodeManager(config, specs) + nodemanager.makegateways() + nodemanager.rsync_roots() + for gwspec in nodemanager.specs: + assert gwspec._samefilesystem() + assert not gwspec.chdir + + def test_ssh_setup_nodes(self, specssh, testdir): + testdir.makepyfile(__init__="", test_x=""" + def test_one(): + pass + """) + reprec = testdir.inline_run("-d", "--rsyncdir=%s" % testdir.tmpdir, + "--tx", specssh, testdir.tmpdir) + rep, = reprec.getreports("pytest_runtest_logreport") + assert rep.passed + + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/tox.ini ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/tox.ini b/tools/pytest-xdist/tox.ini new file mode 100644 index 0000000..2ab3247 --- /dev/null +++ b/tools/pytest-xdist/tox.ini @@ -0,0 +1,32 @@ +[tox] +envlist=py26,py32,py33,py34,py27,py27-pexpect,py33-pexpect,py26,py26-old,py33-old,flakes + +[testenv] +changedir=testing +deps=pytest>=2.5.1 +commands= py.test --junitxml={envlogdir}/junit-{envname}.xml [] + +[testenv:py27-pexpect] +deps={[testenv]deps} + pexpect +[testenv:py33-pexpect] +deps={[testenv]deps} + pexpect + +[testenv:flakes] +changedir= +deps = pytest-flakes>=0.2 +commands = py.test --flakes -m flakes testing xdist + +[testenv:py26-old] +deps= + pytest==2.4.2 + +[testenv:py33-old] +basepython = python3.3 +deps= + pytest==2.4.2 + +[pytest] +addopts = -rsfxX +;; hello http://git-wip-us.apache.org/repos/asf/cloudstack/blob/145542d6/tools/pytest-xdist/xdist/__init__.py ---------------------------------------------------------------------- diff --git a/tools/pytest-xdist/xdist/__init__.py b/tools/pytest-xdist/xdist/__init__.py new file mode 100644 index 0000000..0606670 --- /dev/null +++ b/tools/pytest-xdist/xdist/__init__.py @@ -0,0 +1,2 @@ +# +__version__ = '1.11'