* 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:
holger krekel 2009-10-02 22:29:22 +02:00
parent ab9f6a75ad
commit 1f29529a24
16 changed files with 91 additions and 43 deletions

View File

@ -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

View File

@ -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

View File

@ -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
------------------------------------------------------------- -------------------------------------------------------------

View File

@ -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
================================ ================================

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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:

4
testing/pytest/dist/conftest.py vendored Normal file
View File

@ -0,0 +1,4 @@
try:
import execnet
except ImportError:
collect_ignore = ['.']

View File

@ -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:

View File

@ -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):

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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")