From 1f29529a2450dfea7bc77fc47f09b44041645958 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 2 Oct 2009 22:29:22 +0200 Subject: [PATCH] * 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 --- doc/index.txt | 3 -- doc/path.txt | 4 +-- doc/test/dist.txt | 14 ++++---- doc/xml.txt | 1 - py/test/dist/mypickle.py | 2 +- py/test/parseopt.py | 5 +++ py/test/plugin/pytest_default.py | 32 +++++++++++++------ py/test/plugin/pytest_pdb.py | 13 +++++--- py/test/plugin/pytest_terminal.py | 2 +- testing/pytest/dist/conftest.py | 4 +++ testing/pytest/looponfail/test_remote.py | 1 + testing/pytest/plugin/test_pytest_default.py | 17 ++++++---- testing/pytest/plugin/test_pytest_pdb.py | 3 +- testing/pytest/plugin/test_pytest_terminal.py | 17 ++++++---- testing/pytest/test_config.py | 1 + testing/pytest/test_parseopt.py | 15 +++++++++ 16 files changed, 91 insertions(+), 43 deletions(-) create mode 100644 testing/pytest/dist/conftest.py diff --git a/doc/index.txt b/doc/index.txt index 77b607f46..46f720a5f 100644 --- a/doc/index.txt +++ b/doc/index.txt @@ -7,8 +7,6 @@ documentation on the most interesting ones: `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.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 .. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev -.. _`py.execnet`: execnet.html .. _`py.log`: log.html .. _`py.io`: io.html .. _`py.path`: path.html diff --git a/doc/path.txt b/doc/path.txt index 640f8dbcf..8b82aaab7 100644 --- a/doc/path.txt +++ b/doc/path.txt @@ -246,7 +246,7 @@ on the `svn` command line, not on the bindings. It makes sense now to directly use the bindings. 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 and vice versa. So we'd like to consider refactoring the path implementations @@ -269,5 +269,5 @@ the quite full interface without requiring to know about all details of the full path implementation. -.. _`py.execnet`: execnet.html +.. _`execnet`: execnet.html diff --git a/doc/test/dist.txt b/doc/test/dist.txt index 45fcf05f3..6b44cd69b 100644 --- a/doc/test/dist.txt +++ b/doc/test/dist.txt @@ -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 specify different Python versions and interpreters. -Synchronisation and running of tests only requires -a bare Python installation on the remote side. No -special software is installed - this is realized -by use of the **zero installation** `py.execnet`_ mechanisms. +**Requirements**: you need to install the `execnet`_ package +to perform distributed test runs. 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 environment this command will send each tests to all platforms - and report back failures from all platforms -at once. The provided specifications strings -use the `xspec syntax`_. +at once. The specifications strings 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 -.. _`py.execnet`: ../execnet.html + +.. _`execnet`: http://codespeak.net/execnet Specifying test exec environments in a conftest.py ------------------------------------------------------------- diff --git a/doc/xml.txt b/doc/xml.txt index ba4a2ef73..21cb8913e 100644 --- a/doc/xml.txt +++ b/doc/xml.txt @@ -15,7 +15,6 @@ The py lib strives to offer enough functionality to represent itself and especially its API in html or xml. .. _xist: http://www.livinglogic.de/Python/xist/index.html -.. _`exchange data`: execnet.html#exchange-data a pythonic object model , please ================================ diff --git a/py/test/dist/mypickle.py b/py/test/dist/mypickle.py index d1aa14842..fb2bdbd9f 100644 --- a/py/test/dist/mypickle.py +++ b/py/test/dist/mypickle.py @@ -13,7 +13,6 @@ """ import py -from execnet.gateway_base import Channel import sys, os, struct #debug = open("log-mypickle-%d" % os.getpid(), 'w') @@ -139,6 +138,7 @@ class PickleChannel(object): self.RemoteError = channel.RemoteError def send(self, obj): + from execnet.gateway_base import Channel if not isinstance(obj, Channel): pickled_obj = self._ipickle.dumps(obj) self._channel.send(pickled_obj) diff --git a/py/test/parseopt.py b/py/test/parseopt.py index d3a084894..7b10c0a9b 100644 --- a/py/test/parseopt.py +++ b/py/test/parseopt.py @@ -24,12 +24,16 @@ class Parser: self._groups = [self._anonymous] self._processopt = processopt self._usage = usage + self.epilog = "" def processoption(self, option): if self._processopt: if option.dest: self._processopt(option) + def addnote(self, note): + self._notes.append(note) + def addgroup(self, name, description=""): for group in self._groups: if group.name == name: @@ -51,6 +55,7 @@ class Parser: def parse(self, args): optparser = optparse.OptionParser(usage=self._usage) # make sure anaonymous group is at the end + optparser.epilog = self.epilog groups = self._groups[1:] + [self._groups[0]] for group in groups: if group.options: diff --git a/py/test/plugin/pytest_default.py b/py/test/plugin/pytest_default.py index 18655dd71..543f11f14 100644 --- a/py/test/plugin/pytest_default.py +++ b/py/test/plugin/pytest_default.py @@ -3,6 +3,11 @@ import sys import py +try: + import execnet +except ImportError: + execnet = None + def pytest_pyfunc_call(__multicall__, pyfuncitem): if not __multicall__.execute(): testfunction = pyfuncitem.obj @@ -63,14 +68,22 @@ def pytest_addoption(parser): help="traceback verboseness (long/short/no).") group._addoption('-p', action="append", dest="plugins", default = [], help=("load the specified plugin after command line parsing. ")) - group._addoption('-f', '--looponfail', - action="store_true", dest="looponfail", default=False, - help="run tests, re-run failing test set until all pass.") + if execnet: + group._addoption('-f', '--looponfail', + 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.addoption('--basetemp', dest="basetemp", default=None, metavar="dir", 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._addoption('--dist', metavar="distmode", action="store", choices=['load', 'each', 'no'], @@ -97,18 +110,19 @@ def pytest_configure(config): setsession(config) def fixoptions(config): - if config.option.numprocesses: - config.option.dist = "load" - config.option.tx = ['popen'] * int(config.option.numprocesses) - if config.option.distload: - config.option.dist = "load" + if execnet: + if config.option.numprocesses: + config.option.dist = "load" + config.option.tx = ['popen'] * int(config.option.numprocesses) + if config.option.distload: + config.option.dist = "load" def setsession(config): val = config.getvalue if val("collectonly"): from py.__.test.session import Session config.setsessionclass(Session) - else: + elif execnet: if val("looponfail"): from py.__.test.looponfail.remote import LooponfailingSession config.setsessionclass(LooponfailingSession) diff --git a/py/test/plugin/pytest_pdb.py b/py/test/plugin/pytest_pdb.py index 13708f8b9..8925c5cc8 100644 --- a/py/test/plugin/pytest_pdb.py +++ b/py/test/plugin/pytest_pdb.py @@ -4,6 +4,10 @@ interactive debugging with the Python Debugger. import py import pdb, sys, linecache from py.__.test.outcome import Skipped +try: + import execnet +except ImportError: + execnet = None def pytest_addoption(parser): group = parser.getgroup("general") @@ -14,10 +18,11 @@ def pytest_addoption(parser): def pytest_configure(config): if config.option.usepdb: - if config.getvalue("looponfail"): - raise config.Error("--pdb incompatible with --looponfail.") - if config.option.dist != "no": - raise config.Error("--pdb incompatible with distributing tests.") + if execnet: + if config.getvalue("looponfail"): + raise config.Error("--pdb incompatible with --looponfail.") + if config.option.dist != "no": + raise config.Error("--pdb incompatible with distributing tests.") config.pluginmanager.register(PdbInvoke()) class PdbInvoke: diff --git a/py/test/plugin/pytest_terminal.py b/py/test/plugin/pytest_terminal.py index 4996d0fbe..e6775de1f 100644 --- a/py/test/plugin/pytest_terminal.py +++ b/py/test/plugin/pytest_terminal.py @@ -165,7 +165,7 @@ class TerminalReporter: self.stats.setdefault('deselected', []).append(items) 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 # queued the item for sending, not interesting (unless debugging) if self.config.option.debug: diff --git a/testing/pytest/dist/conftest.py b/testing/pytest/dist/conftest.py new file mode 100644 index 000000000..970af937a --- /dev/null +++ b/testing/pytest/dist/conftest.py @@ -0,0 +1,4 @@ +try: + import execnet +except ImportError: + collect_ignore = ['.'] diff --git a/testing/pytest/looponfail/test_remote.py b/testing/pytest/looponfail/test_remote.py index 5cd039e45..031db33e8 100644 --- a/testing/pytest/looponfail/test_remote.py +++ b/testing/pytest/looponfail/test_remote.py @@ -1,4 +1,5 @@ import py +py.test.importorskip("execnet") from py.__.test.looponfail.remote import LooponfailingSession, LoopState, RemoteControl class TestRemoteControl: diff --git a/testing/pytest/plugin/test_pytest_default.py b/testing/pytest/plugin/test_pytest_default.py index 0268837d5..7c972ae73 100644 --- a/testing/pytest/plugin/test_pytest_default.py +++ b/testing/pytest/plugin/test_pytest_default.py @@ -10,6 +10,7 @@ def test_implied_different_sessions(tmpdir): return Exception return getattr(config._sessionclass, '__name__', None) assert x() == None + py.test.importorskip("execnet") assert x('-d') == 'DSession' assert x('--dist=each') == 'DSession' assert x('-n3') == 'DSession' @@ -31,6 +32,8 @@ def test_plugin_already_exists(testdir): class TestDistOptions: + def setup_method(self, method): + py.test.importorskip("execnet") def test_getxspecs(self, testdir): config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz") xspecs = config.getxspecs() @@ -64,13 +67,13 @@ class TestDistOptions: assert py.path.local('z') in roots assert testdir.tmpdir.join('x') in roots -def test_dist_options(testdir): - config = testdir.parseconfigure("-n 2") - assert config.option.dist == "load" - assert config.option.tx == ['popen'] * 2 - - config = testdir.parseconfigure("-d") - assert config.option.dist == "load" + def test_dist_options(self, testdir): + config = testdir.parseconfigure("-n 2") + assert config.option.dist == "load" + assert config.option.tx == ['popen'] * 2 + + config = testdir.parseconfigure("-d") + assert config.option.dist == "load" def test_pytest_report_iteminfo(): class FakeItem(object): diff --git a/testing/pytest/plugin/test_pytest_pdb.py b/testing/pytest/plugin/test_pytest_pdb.py index fd8608ccb..638a98922 100644 --- a/testing/pytest/plugin/test_pytest_pdb.py +++ b/testing/pytest/plugin/test_pytest_pdb.py @@ -44,7 +44,8 @@ class TestPDB: if child.isalive(): child.wait() - def test_incompatibility_messages(self, testdir): + def test_dist_incompatibility_messages(self, testdir): + py.test.importorskip("execnet") Error = py.test.config.Error py.test.raises(Error, "testdir.parseconfigure('--pdb', '--looponfail')") result = testdir.runpytest("--pdb", "-n", "3") diff --git a/testing/pytest/plugin/test_pytest_terminal.py b/testing/pytest/plugin/test_pytest_terminal.py index c2e09bb37..c0621a334 100644 --- a/testing/pytest/plugin/test_pytest_terminal.py +++ b/testing/pytest/plugin/test_pytest_terminal.py @@ -3,6 +3,10 @@ terminal reporting of the full testing process. """ import py import sys +try: + import execnet +except ImportError: + execnet = None # =============================================================================== # plugin tests @@ -42,7 +46,7 @@ def pytest_generate_tests(metafunc): funcargs={'option': Option(verbose=True)} ) nodist = getattr(metafunc.function, 'nodist', False) - if not nodist: + if execnet and not nodist: metafunc.addcall( id="verbose-dist", funcargs={'option': Option(dist='each', verbose=True)} @@ -602,9 +606,10 @@ class TestTerminalFunctional: "*test_verbose_reporting.py:10: test_gen*FAIL*", ]) assert result.ret == 1 - result = testdir.runpytest(p1, '-v', '-n 1') - result.stdout.fnmatch_lines([ - "*FAIL*test_verbose_reporting.py:2: test_fail*", - ]) - assert result.ret == 1 + if execnet: + result = testdir.runpytest(p1, '-v', '-n 1') + result.stdout.fnmatch_lines([ + "*FAIL*test_verbose_reporting.py:2: test_fail*", + ]) + assert result.ret == 1 diff --git a/testing/pytest/test_config.py b/testing/pytest/test_config.py index bb6505305..04011773f 100644 --- a/testing/pytest/test_config.py +++ b/testing/pytest/test_config.py @@ -218,6 +218,7 @@ class TestOptionEffects: config = py.test.config._reparse([tmpdir]) config.initsession() assert not config.option.boxed + py.test.importorskip("execnet") config = py.test.config._reparse(['-d', tmpdir]) config.initsession() assert not config.option.boxed diff --git a/testing/pytest/test_parseopt.py b/testing/pytest/test_parseopt.py index 50ee7b90c..6d344f246 100644 --- a/testing/pytest/test_parseopt.py +++ b/testing/pytest/test_parseopt.py @@ -8,6 +8,12 @@ class TestParser: out, err = capsys.readouterr() 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): parser = parseopt.Parser() group = parser.addgroup("hello", description="desc") @@ -70,6 +76,15 @@ class TestParser: args = parser.parse_setoption([], option) 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): parser = parseopt.Parser() parser.addoption("--hello", dest="hello", action="store")