Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ea07f73a7 | ||
|
|
faf2e0861f | ||
|
|
eb53a5f8a0 | ||
|
|
5da798f01f | ||
|
|
94a7723ba8 | ||
|
|
87be3b7582 | ||
|
|
d84f1f08d8 |
1
.hgtags
1
.hgtags
@@ -15,3 +15,4 @@ cd44a941c833c098e4899fe3d42a96703754d0d5 1.5
|
||||
1d27987c267577899350a25ba5828d55d87083ad 1.8
|
||||
5c5cb6d59e12e566fbb0217aea718dc31578bee1 1.9
|
||||
4406fc2a6427fadc021ed7e43e7aa5032b1ea91f 1.10
|
||||
220f6e46eb71a6212ccbe6b67b9e6edcf8ee4fa5 1.11
|
||||
|
||||
10
CHANGELOG
10
CHANGELOG
@@ -1,3 +1,13 @@
|
||||
1.12
|
||||
-------------------------
|
||||
|
||||
- fix issue594: properly report errors when the test collection
|
||||
is random. Thanks Bruno Oliveira.
|
||||
|
||||
- some internal test suite adaptation (to become forward
|
||||
compatible with the upcoming pytest-2.8)
|
||||
|
||||
|
||||
1.11
|
||||
-------------------------
|
||||
|
||||
|
||||
427
README.txt
427
README.txt
@@ -1,212 +1,215 @@
|
||||
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 .
|
||||
|
||||
|
||||
.. image:: https://drone.io/bitbucket.org/pytest-dev/pytest-xdist/status.png
|
||||
:target: https://drone.io/bitbucket.org/pytest-dev/pytest-xdist/latest
|
||||
.. image:: https://pypip.in/v/pytest-xdist/badge.png
|
||||
:target: https://pypi.python.org/pypi/pytest-xdist
|
||||
|
||||
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/pytest-dev/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 .
|
||||
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="pytest-xdist",
|
||||
version='1.11',
|
||||
version='1.12',
|
||||
description='py.test xdist plugin for distributed testing and loop-on-failing modes',
|
||||
long_description=open('README.txt').read(),
|
||||
license='MIT',
|
||||
|
||||
@@ -113,7 +113,7 @@ class TestDistribution:
|
||||
import py
|
||||
assert tmpdir.relto(py.path.local(%r)), tmpdir
|
||||
""" % str(testdir.tmpdir))
|
||||
result = testdir.runpytest(p1, "-n1")
|
||||
result = testdir.runpytest_subprocess(p1, "-n1")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
@@ -243,7 +243,7 @@ class TestDistribution:
|
||||
print ("s2call-finished")
|
||||
""")
|
||||
args = ["-n1", "--debug"]
|
||||
result = testdir.runpytest(*args)
|
||||
result = testdir.runpytest_subprocess(*args)
|
||||
s = result.stdout.str()
|
||||
assert result.ret == 2
|
||||
assert 's2call' in s
|
||||
@@ -256,9 +256,8 @@ class TestDistribution:
|
||||
import time
|
||||
time.sleep(10)
|
||||
""")
|
||||
child = testdir.spawn_pytest("-n1")
|
||||
py.std.time.sleep(0.1)
|
||||
child.expect(".*test session starts.*")
|
||||
child = testdir.spawn_pytest("-n1 -v")
|
||||
child.expect(".*test_sleep.*")
|
||||
child.kill(2) # keyboard interrupt
|
||||
child.expect(".*KeyboardInterrupt.*")
|
||||
#child.expect(".*seconds.*")
|
||||
@@ -271,7 +270,7 @@ class TestDistEach:
|
||||
def test_hello():
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest("--debug", "--dist=each", "--tx=2*popen")
|
||||
result = testdir.runpytest_subprocess("--debug", "--dist=each", "--tx=2*popen")
|
||||
assert not result.ret
|
||||
result.stdout.fnmatch_lines(["*2 pass*"])
|
||||
|
||||
@@ -408,7 +407,7 @@ def test_funcarg_teardown_failure(testdir):
|
||||
def test_hello(myarg):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest("--debug", p) # , "-n1")
|
||||
result = testdir.runpytest_subprocess("--debug", p) # , "-n1")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*ValueError*42*",
|
||||
"*1 passed*1 error*",
|
||||
@@ -455,7 +454,7 @@ def test_issue34_pluginloading_in_subprocess(testdir):
|
||||
def test_hello():
|
||||
assert pytest.sample_variable == "testing"
|
||||
""")
|
||||
result = testdir.runpytest("-n1", "-p", "plugin123")
|
||||
result = testdir.runpytest_subprocess("-n1", "-p", "plugin123")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
@@ -485,6 +484,28 @@ def test_fixture_scope_caching_issue503(testdir):
|
||||
])
|
||||
|
||||
|
||||
def test_issue_594_random_parametrize(testdir):
|
||||
"""
|
||||
Make sure that tests that are randomly parametrized display an appropriate
|
||||
error message, instead of silently skipping the entire test run.
|
||||
"""
|
||||
p1 = testdir.makepyfile("""
|
||||
import pytest
|
||||
import random
|
||||
|
||||
xs = list(range(10))
|
||||
random.shuffle(xs)
|
||||
@pytest.mark.parametrize('x', xs)
|
||||
def test_foo(x):
|
||||
assert 1
|
||||
""")
|
||||
result = testdir.runpytest(p1, '-v', '-n4')
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines([
|
||||
"Different tests were collected between gw* and gw*",
|
||||
])
|
||||
|
||||
|
||||
class TestNodeFailure:
|
||||
|
||||
def test_load_single(self, testdir):
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
import py
|
||||
import pytest
|
||||
import execnet
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def _ensure_imports():
|
||||
# we import some modules because pytest-2.8's testdir fixture
|
||||
# will unload all modules after each test and this cause
|
||||
# (unknown) problems with execnet.Group()
|
||||
execnet.Group
|
||||
execnet.makegateway
|
||||
|
||||
pytest_plugins = "pytester"
|
||||
|
||||
#rsyncdirs = ['.', '../xdist', py.path.local(execnet.__file__).dirpath()]
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _divert_atexit(request, monkeypatch):
|
||||
import atexit
|
||||
l = []
|
||||
def finish():
|
||||
while l:
|
||||
l.pop()()
|
||||
monkeypatch.setattr(atexit, "register", l.append)
|
||||
request.addfinalizer(finish)
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption('--gx',
|
||||
action="append", dest="gspecs",
|
||||
@@ -13,6 +32,13 @@ def pytest_addoption(parser):
|
||||
def pytest_funcarg__specssh(request):
|
||||
return getspecssh(request.config)
|
||||
|
||||
@pytest.fixture
|
||||
def testdir(testdir):
|
||||
# pytest before 2.8 did not have a runpytest_subprocess
|
||||
if not hasattr(testdir, "runpytest_subprocess"):
|
||||
testdir.runpytest_subprocess = testdir.runpytest
|
||||
return testdir
|
||||
|
||||
# configuration information for tests
|
||||
def getgspecs(config):
|
||||
return [execnet.XSpec(spec)
|
||||
|
||||
@@ -144,24 +144,36 @@ class TestLoadScheduling:
|
||||
crashitem = sched.remove_node(node)
|
||||
assert crashitem == collection[0]
|
||||
|
||||
def test_schedule_different_tests_collected(self):
|
||||
def test_different_tests_collected(self, testdir):
|
||||
"""
|
||||
Test that LoadScheduling is logging different tests were
|
||||
collected by slaves when that happens.
|
||||
Test that LoadScheduling is reporting collection errors when
|
||||
different test ids are collected by slaves.
|
||||
"""
|
||||
class CollectHook(object):
|
||||
"""
|
||||
Dummy hook that stores collection reports.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.reports = []
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
self.reports.append(report)
|
||||
|
||||
collect_hook = CollectHook()
|
||||
config = testdir.parseconfig()
|
||||
config.pluginmanager.register(collect_hook, "collect_hook")
|
||||
node1 = MockNode()
|
||||
node2 = MockNode()
|
||||
sched = LoadScheduling(2)
|
||||
logged_messages = []
|
||||
py.log.setconsumer('loadsched', logged_messages.append)
|
||||
sched = LoadScheduling(2, config=config)
|
||||
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
|
||||
assert len(collect_hook.reports) == 1
|
||||
rep = collect_hook.reports[0]
|
||||
assert 'Different tests were collected between' in rep.longrepr
|
||||
|
||||
|
||||
class TestDistReporter:
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
import py
|
||||
import pytest
|
||||
import execnet
|
||||
from xdist import slavemanage
|
||||
from _pytest.pytester import HookRecorder
|
||||
from xdist import slavemanage, newhooks
|
||||
from xdist.slavemanage import HostRSync, NodeManager
|
||||
|
||||
pytest_plugins = "pytester",
|
||||
pytest_plugins = "pytester"
|
||||
|
||||
def pytest_funcarg__hookrecorder(request):
|
||||
_pytest = request.getfuncargvalue('_pytest')
|
||||
config = request.getfuncargvalue('config')
|
||||
return _pytest.gethookrecorder(config.hook)
|
||||
def pytest_funcarg__hookrecorder(request, config):
|
||||
hookrecorder = HookRecorder(config.pluginmanager)
|
||||
if hasattr(hookrecorder, "start_recording"):
|
||||
hookrecorder.start_recording(newhooks)
|
||||
request.addfinalizer(hookrecorder.finish_recording)
|
||||
return hookrecorder
|
||||
|
||||
def pytest_funcarg__config(request):
|
||||
testdir = request.getfuncargvalue("testdir")
|
||||
config = testdir.parseconfig()
|
||||
return config
|
||||
def pytest_funcarg__config(testdir):
|
||||
return testdir.parseconfig()
|
||||
|
||||
def pytest_funcarg__mysetup(request):
|
||||
def pytest_funcarg__mysetup(tmpdir):
|
||||
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)
|
||||
source = tmpdir.mkdir("source")
|
||||
dest = tmpdir.mkdir("dest")
|
||||
return mysetup()
|
||||
|
||||
@pytest.fixture
|
||||
def slavecontroller(monkeypatch):
|
||||
@@ -45,8 +43,7 @@ class TestNodeManagerPopen:
|
||||
for spec in NodeManager(config, l, defaultchdir="abc").specs:
|
||||
assert spec.chdir == "abc"
|
||||
|
||||
def test_popen_makegateway_events(self, config,
|
||||
hookrecorder, _pytest, slavecontroller):
|
||||
def test_popen_makegateway_events(self, config, hookrecorder, slavecontroller):
|
||||
hm = NodeManager(config, ["popen"] * 2)
|
||||
hm.setup_nodes(None)
|
||||
call = hookrecorder.popcall("pytest_xdist_setupnodes")
|
||||
@@ -114,14 +111,6 @@ class TestNodeManagerPopen:
|
||||
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")
|
||||
@@ -151,7 +140,7 @@ class TestHRSync:
|
||||
|
||||
|
||||
class TestNodeManager:
|
||||
@py.test.mark.xfail
|
||||
@py.test.mark.xfail(run=False)
|
||||
def test_rsync_roots_no_roots(self, testdir, mysetup):
|
||||
mysetup.source.ensure("dir1", "file1").write("hello")
|
||||
config = testdir.parseconfig(mysetup.source)
|
||||
|
||||
2
tox.ini
2
tox.ini
@@ -1,5 +1,5 @@
|
||||
[tox]
|
||||
envlist=py26,py33,py34,py27,py27-pexpect,py33-pexpect,py26,py26-old,py33-old,flakes
|
||||
envlist=py26,py33,py34,py27,py27-pexpect,py33-pexpect,py26-old,py33-old,flakes
|
||||
|
||||
[testenv]
|
||||
changedir=testing
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#
|
||||
__version__ = '1.11'
|
||||
__version__ = '1.12'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import difflib
|
||||
from _pytest.runner import CollectReport
|
||||
|
||||
import pytest
|
||||
import py
|
||||
@@ -88,8 +89,9 @@ class EachScheduling:
|
||||
elif self._removed2pending:
|
||||
for deadnode in self._removed2pending:
|
||||
if deadnode.gateway.spec == node.gateway.spec:
|
||||
if collection != self.node2collection[deadnode]:
|
||||
msg = report_collection_diff(self.collection,
|
||||
dead_collection = self.node2collection[deadnode]
|
||||
if collection != dead_collection:
|
||||
msg = report_collection_diff(dead_collection,
|
||||
collection,
|
||||
deadnode.gateway.id,
|
||||
node.gateway.id)
|
||||
@@ -175,9 +177,10 @@ class LoadScheduling:
|
||||
|
||||
:log: A py.log.Producer instance.
|
||||
|
||||
:config: Config object, used for handling hooks.
|
||||
"""
|
||||
|
||||
def __init__(self, numnodes, log=None):
|
||||
def __init__(self, numnodes, log=None, config=None):
|
||||
self.numnodes = numnodes
|
||||
self.node2collection = {}
|
||||
self.node2pending = {}
|
||||
@@ -187,6 +190,7 @@ class LoadScheduling:
|
||||
self.log = py.log.Producer("loadsched")
|
||||
else:
|
||||
self.log = log.loadsched
|
||||
self.config = config
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
@@ -376,8 +380,9 @@ class LoadScheduling:
|
||||
def _check_nodes_have_same_collection(self):
|
||||
"""Return True if all nodes have collected the same items.
|
||||
|
||||
If collections differ this returns False and logs the
|
||||
collection differences as they are found.
|
||||
If collections differ, this method returns False while logging
|
||||
the collection differences and posting collection errors to
|
||||
pytest_collectreport hook.
|
||||
"""
|
||||
node_collection_items = list(self.node2collection.items())
|
||||
first_node, col = node_collection_items[0]
|
||||
@@ -390,8 +395,12 @@ class LoadScheduling:
|
||||
node.gateway.id,
|
||||
)
|
||||
if msg:
|
||||
self.log(msg)
|
||||
same_collection = False
|
||||
self.log(msg)
|
||||
if self.config is not None:
|
||||
rep = CollectReport(node.gateway.id, 'failed', longrepr=msg,
|
||||
result=[])
|
||||
self.config.hook.pytest_collectreport(report=rep)
|
||||
|
||||
return same_collection
|
||||
|
||||
@@ -494,7 +503,8 @@ class DSession:
|
||||
numnodes = len(self.nodemanager.specs)
|
||||
dist = self.config.getvalue("dist")
|
||||
if dist == "load":
|
||||
self.sched = LoadScheduling(numnodes, log=self.log)
|
||||
self.sched = LoadScheduling(numnodes, log=self.log,
|
||||
config=self.config)
|
||||
elif dist == "each":
|
||||
self.sched = EachScheduling(numnodes, log=self.log)
|
||||
else:
|
||||
|
||||
@@ -45,7 +45,11 @@ def pytest_addoption(parser):
|
||||
# -------------------------------------------------------------------------
|
||||
def pytest_addhooks(pluginmanager):
|
||||
from xdist import newhooks
|
||||
pluginmanager.addhooks(newhooks)
|
||||
# avoid warnings with pytest-2.8
|
||||
method = getattr(pluginmanager, "add_hookspecs", None)
|
||||
if method is None:
|
||||
method = pluginmanager.addhooks
|
||||
method(newhooks)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# distributed testing initialization
|
||||
@@ -58,8 +62,8 @@ def pytest_cmdline_main(config):
|
||||
looponfail_main(config)
|
||||
return 2 # looponfail only can get stop with ctrl-C anyway
|
||||
|
||||
def pytest_configure(config, __multicall__):
|
||||
__multicall__.execute()
|
||||
@pytest.mark.trylast
|
||||
def pytest_configure(config):
|
||||
if config.getoption("dist") != "no":
|
||||
from xdist.dsession import DSession
|
||||
session = DSession(config)
|
||||
|
||||
Reference in New Issue
Block a user