* don't add distributed command line options when 'execnet' is not
installed, report a nice message. * fix tests and code to work with non-existing execnet * point execnet doc to the new package --HG-- branch : trunk
This commit is contained in:
parent
ab9f6a75ad
commit
1f29529a24
|
@ -7,8 +7,6 @@ documentation on the most interesting ones:
|
||||||
|
|
||||||
`py.test`_ write and deploy unit- and functional tests to multiple machines.
|
`py.test`_ write and deploy unit- and functional tests to multiple machines.
|
||||||
|
|
||||||
`py.execnet`_ elastic distributed programming.
|
|
||||||
|
|
||||||
`py.code`_: generate code and use advanced introspection/traceback support.
|
`py.code`_: generate code and use advanced introspection/traceback support.
|
||||||
|
|
||||||
`py.path`_: use path objects to transparently access local and svn filesystems.
|
`py.path`_: use path objects to transparently access local and svn filesystems.
|
||||||
|
@ -33,7 +31,6 @@ For the latest Release, see `PyPI project page`_
|
||||||
|
|
||||||
.. _`download and installation`: download.html
|
.. _`download and installation`: download.html
|
||||||
.. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev
|
.. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev
|
||||||
.. _`py.execnet`: execnet.html
|
|
||||||
.. _`py.log`: log.html
|
.. _`py.log`: log.html
|
||||||
.. _`py.io`: io.html
|
.. _`py.io`: io.html
|
||||||
.. _`py.path`: path.html
|
.. _`py.path`: path.html
|
||||||
|
|
|
@ -246,7 +246,7 @@ on the `svn` command line, not on the bindings.
|
||||||
It makes sense now to directly use the bindings.
|
It makes sense now to directly use the bindings.
|
||||||
|
|
||||||
Moreover, it would be good, also considering
|
Moreover, it would be good, also considering
|
||||||
`py.execnet`_ distribution of programs, to
|
`execnet`_ distribution of programs, to
|
||||||
be able to manipulate Windows Paths on Linux
|
be able to manipulate Windows Paths on Linux
|
||||||
and vice versa. So we'd like to consider
|
and vice versa. So we'd like to consider
|
||||||
refactoring the path implementations
|
refactoring the path implementations
|
||||||
|
@ -269,5 +269,5 @@ the quite full interface without requiring
|
||||||
to know about all details of the full path
|
to know about all details of the full path
|
||||||
implementation.
|
implementation.
|
||||||
|
|
||||||
.. _`py.execnet`: execnet.html
|
.. _`execnet`: execnet.html
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,8 @@ synchronizes your program source code to the remote place. All test results
|
||||||
are reported back and displayed to your local test session. You may
|
are reported back and displayed to your local test session. You may
|
||||||
specify different Python versions and interpreters.
|
specify different Python versions and interpreters.
|
||||||
|
|
||||||
Synchronisation and running of tests only requires
|
**Requirements**: you need to install the `execnet`_ package
|
||||||
a bare Python installation on the remote side. No
|
to perform distributed test runs.
|
||||||
special software is installed - this is realized
|
|
||||||
by use of the **zero installation** `py.execnet`_ mechanisms.
|
|
||||||
|
|
||||||
Speed up test runs by sending tests to multiple CPUs
|
Speed up test runs by sending tests to multiple CPUs
|
||||||
----------------------------------------------------------
|
----------------------------------------------------------
|
||||||
|
@ -90,13 +88,13 @@ The basic command to run tests on multiple platforms is::
|
||||||
If you specify a windows host, an OSX host and a Linux
|
If you specify a windows host, an OSX host and a Linux
|
||||||
environment this command will send each tests to all
|
environment this command will send each tests to all
|
||||||
platforms - and report back failures from all platforms
|
platforms - and report back failures from all platforms
|
||||||
at once. The provided specifications strings
|
at once. The specifications strings use the `xspec syntax`_.
|
||||||
use the `xspec syntax`_.
|
|
||||||
|
|
||||||
.. _`xspec syntax`: ../execnet.html#xspec
|
.. _`xspec syntax`: http://codespeak.net/execnet/trunk/basics.html#xspec
|
||||||
|
|
||||||
.. _`socketserver.py`: http://codespeak.net/svn/py/dist/py/execnet/script/socketserver.py
|
.. _`socketserver.py`: http://codespeak.net/svn/py/dist/py/execnet/script/socketserver.py
|
||||||
.. _`py.execnet`: ../execnet.html
|
|
||||||
|
.. _`execnet`: http://codespeak.net/execnet
|
||||||
|
|
||||||
Specifying test exec environments in a conftest.py
|
Specifying test exec environments in a conftest.py
|
||||||
-------------------------------------------------------------
|
-------------------------------------------------------------
|
||||||
|
|
|
@ -15,7 +15,6 @@ The py lib strives to offer enough functionality to represent
|
||||||
itself and especially its API in html or xml.
|
itself and especially its API in html or xml.
|
||||||
|
|
||||||
.. _xist: http://www.livinglogic.de/Python/xist/index.html
|
.. _xist: http://www.livinglogic.de/Python/xist/index.html
|
||||||
.. _`exchange data`: execnet.html#exchange-data
|
|
||||||
|
|
||||||
a pythonic object model , please
|
a pythonic object model , please
|
||||||
================================
|
================================
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py
|
import py
|
||||||
from execnet.gateway_base import Channel
|
|
||||||
import sys, os, struct
|
import sys, os, struct
|
||||||
#debug = open("log-mypickle-%d" % os.getpid(), 'w')
|
#debug = open("log-mypickle-%d" % os.getpid(), 'w')
|
||||||
|
|
||||||
|
@ -139,6 +138,7 @@ class PickleChannel(object):
|
||||||
self.RemoteError = channel.RemoteError
|
self.RemoteError = channel.RemoteError
|
||||||
|
|
||||||
def send(self, obj):
|
def send(self, obj):
|
||||||
|
from execnet.gateway_base import Channel
|
||||||
if not isinstance(obj, Channel):
|
if not isinstance(obj, Channel):
|
||||||
pickled_obj = self._ipickle.dumps(obj)
|
pickled_obj = self._ipickle.dumps(obj)
|
||||||
self._channel.send(pickled_obj)
|
self._channel.send(pickled_obj)
|
||||||
|
|
|
@ -24,12 +24,16 @@ class Parser:
|
||||||
self._groups = [self._anonymous]
|
self._groups = [self._anonymous]
|
||||||
self._processopt = processopt
|
self._processopt = processopt
|
||||||
self._usage = usage
|
self._usage = usage
|
||||||
|
self.epilog = ""
|
||||||
|
|
||||||
def processoption(self, option):
|
def processoption(self, option):
|
||||||
if self._processopt:
|
if self._processopt:
|
||||||
if option.dest:
|
if option.dest:
|
||||||
self._processopt(option)
|
self._processopt(option)
|
||||||
|
|
||||||
|
def addnote(self, note):
|
||||||
|
self._notes.append(note)
|
||||||
|
|
||||||
def addgroup(self, name, description=""):
|
def addgroup(self, name, description=""):
|
||||||
for group in self._groups:
|
for group in self._groups:
|
||||||
if group.name == name:
|
if group.name == name:
|
||||||
|
@ -51,6 +55,7 @@ class Parser:
|
||||||
def parse(self, args):
|
def parse(self, args):
|
||||||
optparser = optparse.OptionParser(usage=self._usage)
|
optparser = optparse.OptionParser(usage=self._usage)
|
||||||
# make sure anaonymous group is at the end
|
# make sure anaonymous group is at the end
|
||||||
|
optparser.epilog = self.epilog
|
||||||
groups = self._groups[1:] + [self._groups[0]]
|
groups = self._groups[1:] + [self._groups[0]]
|
||||||
for group in groups:
|
for group in groups:
|
||||||
if group.options:
|
if group.options:
|
||||||
|
|
|
@ -3,6 +3,11 @@
|
||||||
import sys
|
import sys
|
||||||
import py
|
import py
|
||||||
|
|
||||||
|
try:
|
||||||
|
import execnet
|
||||||
|
except ImportError:
|
||||||
|
execnet = None
|
||||||
|
|
||||||
def pytest_pyfunc_call(__multicall__, pyfuncitem):
|
def pytest_pyfunc_call(__multicall__, pyfuncitem):
|
||||||
if not __multicall__.execute():
|
if not __multicall__.execute():
|
||||||
testfunction = pyfuncitem.obj
|
testfunction = pyfuncitem.obj
|
||||||
|
@ -63,14 +68,22 @@ def pytest_addoption(parser):
|
||||||
help="traceback verboseness (long/short/no).")
|
help="traceback verboseness (long/short/no).")
|
||||||
group._addoption('-p', action="append", dest="plugins", default = [],
|
group._addoption('-p', action="append", dest="plugins", default = [],
|
||||||
help=("load the specified plugin after command line parsing. "))
|
help=("load the specified plugin after command line parsing. "))
|
||||||
group._addoption('-f', '--looponfail',
|
if execnet:
|
||||||
action="store_true", dest="looponfail", default=False,
|
group._addoption('-f', '--looponfail',
|
||||||
help="run tests, re-run failing test set until all pass.")
|
action="store_true", dest="looponfail", default=False,
|
||||||
|
help="run tests, re-run failing test set until all pass.")
|
||||||
|
|
||||||
group = parser.addgroup("debugconfig", "test process debugging and configuration")
|
group = parser.addgroup("debugconfig", "test process debugging and configuration")
|
||||||
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
|
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
|
||||||
help="base temporary directory for this test run.")
|
help="base temporary directory for this test run.")
|
||||||
|
|
||||||
|
if execnet:
|
||||||
|
add_dist_options(parser)
|
||||||
|
else:
|
||||||
|
parser.epilog = (
|
||||||
|
"execnet missing: --looponfailing and distributed testing not available.")
|
||||||
|
|
||||||
|
def add_dist_options(parser):
|
||||||
group = parser.addgroup("dist", "distributed testing") # see http://pytest.org/help/dist")
|
group = parser.addgroup("dist", "distributed testing") # see http://pytest.org/help/dist")
|
||||||
group._addoption('--dist', metavar="distmode",
|
group._addoption('--dist', metavar="distmode",
|
||||||
action="store", choices=['load', 'each', 'no'],
|
action="store", choices=['load', 'each', 'no'],
|
||||||
|
@ -97,18 +110,19 @@ def pytest_configure(config):
|
||||||
setsession(config)
|
setsession(config)
|
||||||
|
|
||||||
def fixoptions(config):
|
def fixoptions(config):
|
||||||
if config.option.numprocesses:
|
if execnet:
|
||||||
config.option.dist = "load"
|
if config.option.numprocesses:
|
||||||
config.option.tx = ['popen'] * int(config.option.numprocesses)
|
config.option.dist = "load"
|
||||||
if config.option.distload:
|
config.option.tx = ['popen'] * int(config.option.numprocesses)
|
||||||
config.option.dist = "load"
|
if config.option.distload:
|
||||||
|
config.option.dist = "load"
|
||||||
|
|
||||||
def setsession(config):
|
def setsession(config):
|
||||||
val = config.getvalue
|
val = config.getvalue
|
||||||
if val("collectonly"):
|
if val("collectonly"):
|
||||||
from py.__.test.session import Session
|
from py.__.test.session import Session
|
||||||
config.setsessionclass(Session)
|
config.setsessionclass(Session)
|
||||||
else:
|
elif execnet:
|
||||||
if val("looponfail"):
|
if val("looponfail"):
|
||||||
from py.__.test.looponfail.remote import LooponfailingSession
|
from py.__.test.looponfail.remote import LooponfailingSession
|
||||||
config.setsessionclass(LooponfailingSession)
|
config.setsessionclass(LooponfailingSession)
|
||||||
|
|
|
@ -4,6 +4,10 @@ interactive debugging with the Python Debugger.
|
||||||
import py
|
import py
|
||||||
import pdb, sys, linecache
|
import pdb, sys, linecache
|
||||||
from py.__.test.outcome import Skipped
|
from py.__.test.outcome import Skipped
|
||||||
|
try:
|
||||||
|
import execnet
|
||||||
|
except ImportError:
|
||||||
|
execnet = None
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
group = parser.getgroup("general")
|
group = parser.getgroup("general")
|
||||||
|
@ -14,10 +18,11 @@ def pytest_addoption(parser):
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
if config.option.usepdb:
|
if config.option.usepdb:
|
||||||
if config.getvalue("looponfail"):
|
if execnet:
|
||||||
raise config.Error("--pdb incompatible with --looponfail.")
|
if config.getvalue("looponfail"):
|
||||||
if config.option.dist != "no":
|
raise config.Error("--pdb incompatible with --looponfail.")
|
||||||
raise config.Error("--pdb incompatible with distributing tests.")
|
if config.option.dist != "no":
|
||||||
|
raise config.Error("--pdb incompatible with distributing tests.")
|
||||||
config.pluginmanager.register(PdbInvoke())
|
config.pluginmanager.register(PdbInvoke())
|
||||||
|
|
||||||
class PdbInvoke:
|
class PdbInvoke:
|
||||||
|
|
|
@ -165,7 +165,7 @@ class TerminalReporter:
|
||||||
self.stats.setdefault('deselected', []).append(items)
|
self.stats.setdefault('deselected', []).append(items)
|
||||||
|
|
||||||
def pytest_itemstart(self, item, node=None):
|
def pytest_itemstart(self, item, node=None):
|
||||||
if self.config.option.dist != "no":
|
if getattr(self.config.option, 'dist', 'no') != "no":
|
||||||
# for dist-testing situations itemstart means we
|
# for dist-testing situations itemstart means we
|
||||||
# queued the item for sending, not interesting (unless debugging)
|
# queued the item for sending, not interesting (unless debugging)
|
||||||
if self.config.option.debug:
|
if self.config.option.debug:
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
try:
|
||||||
|
import execnet
|
||||||
|
except ImportError:
|
||||||
|
collect_ignore = ['.']
|
|
@ -1,4 +1,5 @@
|
||||||
import py
|
import py
|
||||||
|
py.test.importorskip("execnet")
|
||||||
from py.__.test.looponfail.remote import LooponfailingSession, LoopState, RemoteControl
|
from py.__.test.looponfail.remote import LooponfailingSession, LoopState, RemoteControl
|
||||||
|
|
||||||
class TestRemoteControl:
|
class TestRemoteControl:
|
||||||
|
|
|
@ -10,6 +10,7 @@ def test_implied_different_sessions(tmpdir):
|
||||||
return Exception
|
return Exception
|
||||||
return getattr(config._sessionclass, '__name__', None)
|
return getattr(config._sessionclass, '__name__', None)
|
||||||
assert x() == None
|
assert x() == None
|
||||||
|
py.test.importorskip("execnet")
|
||||||
assert x('-d') == 'DSession'
|
assert x('-d') == 'DSession'
|
||||||
assert x('--dist=each') == 'DSession'
|
assert x('--dist=each') == 'DSession'
|
||||||
assert x('-n3') == 'DSession'
|
assert x('-n3') == 'DSession'
|
||||||
|
@ -31,6 +32,8 @@ def test_plugin_already_exists(testdir):
|
||||||
|
|
||||||
|
|
||||||
class TestDistOptions:
|
class TestDistOptions:
|
||||||
|
def setup_method(self, method):
|
||||||
|
py.test.importorskip("execnet")
|
||||||
def test_getxspecs(self, testdir):
|
def test_getxspecs(self, testdir):
|
||||||
config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz")
|
config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz")
|
||||||
xspecs = config.getxspecs()
|
xspecs = config.getxspecs()
|
||||||
|
@ -64,13 +67,13 @@ class TestDistOptions:
|
||||||
assert py.path.local('z') in roots
|
assert py.path.local('z') in roots
|
||||||
assert testdir.tmpdir.join('x') in roots
|
assert testdir.tmpdir.join('x') in roots
|
||||||
|
|
||||||
def test_dist_options(testdir):
|
def test_dist_options(self, testdir):
|
||||||
config = testdir.parseconfigure("-n 2")
|
config = testdir.parseconfigure("-n 2")
|
||||||
assert config.option.dist == "load"
|
assert config.option.dist == "load"
|
||||||
assert config.option.tx == ['popen'] * 2
|
assert config.option.tx == ['popen'] * 2
|
||||||
|
|
||||||
config = testdir.parseconfigure("-d")
|
config = testdir.parseconfigure("-d")
|
||||||
assert config.option.dist == "load"
|
assert config.option.dist == "load"
|
||||||
|
|
||||||
def test_pytest_report_iteminfo():
|
def test_pytest_report_iteminfo():
|
||||||
class FakeItem(object):
|
class FakeItem(object):
|
||||||
|
|
|
@ -44,7 +44,8 @@ class TestPDB:
|
||||||
if child.isalive():
|
if child.isalive():
|
||||||
child.wait()
|
child.wait()
|
||||||
|
|
||||||
def test_incompatibility_messages(self, testdir):
|
def test_dist_incompatibility_messages(self, testdir):
|
||||||
|
py.test.importorskip("execnet")
|
||||||
Error = py.test.config.Error
|
Error = py.test.config.Error
|
||||||
py.test.raises(Error, "testdir.parseconfigure('--pdb', '--looponfail')")
|
py.test.raises(Error, "testdir.parseconfigure('--pdb', '--looponfail')")
|
||||||
result = testdir.runpytest("--pdb", "-n", "3")
|
result = testdir.runpytest("--pdb", "-n", "3")
|
||||||
|
|
|
@ -3,6 +3,10 @@ terminal reporting of the full testing process.
|
||||||
"""
|
"""
|
||||||
import py
|
import py
|
||||||
import sys
|
import sys
|
||||||
|
try:
|
||||||
|
import execnet
|
||||||
|
except ImportError:
|
||||||
|
execnet = None
|
||||||
|
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
# plugin tests
|
# plugin tests
|
||||||
|
@ -42,7 +46,7 @@ def pytest_generate_tests(metafunc):
|
||||||
funcargs={'option': Option(verbose=True)}
|
funcargs={'option': Option(verbose=True)}
|
||||||
)
|
)
|
||||||
nodist = getattr(metafunc.function, 'nodist', False)
|
nodist = getattr(metafunc.function, 'nodist', False)
|
||||||
if not nodist:
|
if execnet and not nodist:
|
||||||
metafunc.addcall(
|
metafunc.addcall(
|
||||||
id="verbose-dist",
|
id="verbose-dist",
|
||||||
funcargs={'option': Option(dist='each', verbose=True)}
|
funcargs={'option': Option(dist='each', verbose=True)}
|
||||||
|
@ -602,9 +606,10 @@ class TestTerminalFunctional:
|
||||||
"*test_verbose_reporting.py:10: test_gen*FAIL*",
|
"*test_verbose_reporting.py:10: test_gen*FAIL*",
|
||||||
])
|
])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
result = testdir.runpytest(p1, '-v', '-n 1')
|
if execnet:
|
||||||
result.stdout.fnmatch_lines([
|
result = testdir.runpytest(p1, '-v', '-n 1')
|
||||||
"*FAIL*test_verbose_reporting.py:2: test_fail*",
|
result.stdout.fnmatch_lines([
|
||||||
])
|
"*FAIL*test_verbose_reporting.py:2: test_fail*",
|
||||||
assert result.ret == 1
|
])
|
||||||
|
assert result.ret == 1
|
||||||
|
|
||||||
|
|
|
@ -218,6 +218,7 @@ class TestOptionEffects:
|
||||||
config = py.test.config._reparse([tmpdir])
|
config = py.test.config._reparse([tmpdir])
|
||||||
config.initsession()
|
config.initsession()
|
||||||
assert not config.option.boxed
|
assert not config.option.boxed
|
||||||
|
py.test.importorskip("execnet")
|
||||||
config = py.test.config._reparse(['-d', tmpdir])
|
config = py.test.config._reparse(['-d', tmpdir])
|
||||||
config.initsession()
|
config.initsession()
|
||||||
assert not config.option.boxed
|
assert not config.option.boxed
|
||||||
|
|
|
@ -8,6 +8,12 @@ class TestParser:
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
assert out.find("xyz") != -1
|
assert out.find("xyz") != -1
|
||||||
|
|
||||||
|
def test_epilog(self):
|
||||||
|
parser = parseopt.Parser()
|
||||||
|
assert not parser.epilog
|
||||||
|
parser.epilog += "hello"
|
||||||
|
assert parser.epilog == "hello"
|
||||||
|
|
||||||
def test_group_add_and_get(self):
|
def test_group_add_and_get(self):
|
||||||
parser = parseopt.Parser()
|
parser = parseopt.Parser()
|
||||||
group = parser.addgroup("hello", description="desc")
|
group = parser.addgroup("hello", description="desc")
|
||||||
|
@ -70,6 +76,15 @@ class TestParser:
|
||||||
args = parser.parse_setoption([], option)
|
args = parser.parse_setoption([], option)
|
||||||
assert option.hello == "x"
|
assert option.hello == "x"
|
||||||
|
|
||||||
|
def test_parser_epilog(self, testdir):
|
||||||
|
testdir.makeconftest("""
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.epilog = "hello world"
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest('--help')
|
||||||
|
#assert result.ret != 0
|
||||||
|
assert result.stdout.fnmatch_lines(["*hello world*"])
|
||||||
|
|
||||||
def test_parse_setoption(self):
|
def test_parse_setoption(self):
|
||||||
parser = parseopt.Parser()
|
parser = parseopt.Parser()
|
||||||
parser.addoption("--hello", dest="hello", action="store")
|
parser.addoption("--hello", dest="hello", action="store")
|
||||||
|
|
Loading…
Reference in New Issue