From 783c714a2c9fc7463e795d22b187f81ca6080cc4 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 5 Sep 2009 16:54:52 +0200 Subject: [PATCH] get py.test to run at least basically on top of jython * allow and document calling of monkeypatch.undo() from a test * default to 'sys' on platforms there no 'os.dup' is available * use "compile" to perform "tryparsing" checks --HG-- branch : trunk --- .hgignore | 1 + py/code/source.py | 5 ++- py/test/plugin/pytest_capture.py | 15 +++++--- py/test/plugin/pytest_monkeypatch.py | 51 +++++++++++++++++++-------- py/test/plugin/test_pytest_capture.py | 10 ++++++ 5 files changed, 60 insertions(+), 22 deletions(-) diff --git a/.hgignore b/.hgignore index 358a6203e..00b593659 100644 --- a/.hgignore +++ b/.hgignore @@ -11,6 +11,7 @@ syntax:glob *.pyo *.swp *.html +*.class build/ py.egg-info diff --git a/py/code/source.py b/py/code/source.py index 609524ba2..4e16e2094 100644 --- a/py/code/source.py +++ b/py/code/source.py @@ -157,14 +157,13 @@ class Source(object): """ return True if source is parseable, heuristically deindenting it by default. """ - import parser if deindent: source = str(self.deindent()) else: source = str(self) try: - parser.suite(source+'\n') - except (parser.ParserError, SyntaxError): + compile(source+'\n', "x", "exec") + except SyntaxError: return False else: return True diff --git a/py/test/plugin/pytest_capture.py b/py/test/plugin/pytest_capture.py index 0f586dd8d..d79709c3b 100644 --- a/py/test/plugin/pytest_capture.py +++ b/py/test/plugin/pytest_capture.py @@ -85,6 +85,7 @@ argument which offers the same interface. """ import py +import os def pytest_addoption(parser): group = parser.getgroup("general") @@ -131,11 +132,15 @@ class CaptureManager: def _getmethod(self, config, fspath): if config.option.capture: - return config.option.capture - try: - return config._conftest.rget("option_capture", path=fspath) - except KeyError: - return "fd" + method = config.option.capture + else: + try: + method = config._conftest.rget("option_capture", path=fspath) + except KeyError: + method = "fd" + if method == "fd" and not hasattr(os, 'dup'): # e.g. jython + method = "sys" + return method def resumecapture_item(self, item): method = self._getmethod(item.config, item.fspath) diff --git a/py/test/plugin/pytest_monkeypatch.py b/py/test/plugin/pytest_monkeypatch.py index 9403fe9a6..f58182628 100644 --- a/py/test/plugin/pytest_monkeypatch.py +++ b/py/test/plugin/pytest_monkeypatch.py @@ -4,8 +4,8 @@ safely patch object attributes, dicts and environment variables. Usage ---------------- -Use the `monkeypatch funcarg`_ to safely patch environment -variables, object attributes or dictionaries. For example, if you want +Use the `monkeypatch funcarg`_ to safely modify or delete environment +variables, object attributes or dictionary values. For example, if you want to set the environment variable ``ENV1`` and patch the ``os.path.abspath`` function to return a particular value during a test function execution you can write it down like this: @@ -31,6 +31,20 @@ can use this example: monkeypatch.setenv('PATH', 'x/y', prepend=":") # x/y will be at the beginning of $PATH +calling "undo" finalization explicitely +----------------------------------------- + +Usually at the end of function execution py.test will invoke +a teardown hook which undoes the changes. If you cannot wait +that long you can also call finalization explicitely:: + + monkeypatch.undo() + +This will undo previous changes. This call consumes the +undo stack. Calling it a second time has no effect. +Within a test you can continue to use the monkeypatch +object, however. + .. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ """ @@ -54,7 +68,7 @@ def pytest_funcarg__monkeypatch(request): deletion has no target. """ monkeypatch = MonkeyPatch() - request.addfinalizer(monkeypatch.finalize) + request.addfinalizer(monkeypatch.undo) return monkeypatch notset = object() @@ -97,17 +111,19 @@ class MonkeyPatch: def delenv(self, name, raising=True): self.delitem(os.environ, name, raising=raising) - def finalize(self): + def undo(self): for obj, name, value in self._setattr: if value is not notset: setattr(obj, name, value) else: delattr(obj, name) + self._setattr[:] = [] for dictionary, name, value in self._setitem: if value is notset: del dictionary[name] else: dictionary[name] = value + self._setitem[:] = [] def test_setattr(): @@ -118,12 +134,16 @@ def test_setattr(): assert A.x == 2 monkeypatch.setattr(A, 'x', 3) assert A.x == 3 - monkeypatch.finalize() + monkeypatch.undo() assert A.x == 1 + A.x = 5 + monkeypatch.undo() # double-undo makes no modification + assert A.x == 5 + monkeypatch.setattr(A, 'y', 3) assert A.y == 3 - monkeypatch.finalize() + monkeypatch.undo() assert not hasattr(A, 'y') def test_delattr(): @@ -132,7 +152,7 @@ def test_delattr(): monkeypatch = MonkeyPatch() monkeypatch.delattr(A, 'x') assert not hasattr(A, 'x') - monkeypatch.finalize() + monkeypatch.undo() assert A.x == 1 monkeypatch = MonkeyPatch() @@ -141,7 +161,7 @@ def test_delattr(): monkeypatch.delattr(A, 'y', raising=False) monkeypatch.setattr(A, 'x', 5) assert A.x == 5 - monkeypatch.finalize() + monkeypatch.undo() assert A.x == 1 def test_setitem(): @@ -153,9 +173,12 @@ def test_setitem(): assert d['y'] == 1700 monkeypatch.setitem(d, 'x', 3) assert d['x'] == 3 - monkeypatch.finalize() + monkeypatch.undo() assert d['x'] == 1 assert 'y' not in d + d['x'] = 5 + monkeypatch.undo() + assert d['x'] == 5 def test_delitem(): d = {'x': 1} @@ -170,7 +193,7 @@ def test_delitem(): d['hello'] = 'world' monkeypatch.setitem(d, 'x', 1500) assert d['x'] == 1500 - monkeypatch.finalize() + monkeypatch.undo() assert d == {'hello': 'world', 'x': 1} def test_setenv(): @@ -178,7 +201,7 @@ def test_setenv(): monkeypatch.setenv('XYZ123', 2) import os assert os.environ['XYZ123'] == "2" - monkeypatch.finalize() + monkeypatch.undo() assert 'XYZ123' not in os.environ def test_delenv(): @@ -187,7 +210,7 @@ def test_delenv(): monkeypatch = MonkeyPatch() py.test.raises(KeyError, "monkeypatch.delenv(%r, raising=True)" % name) monkeypatch.delenv(name, raising=False) - monkeypatch.finalize() + monkeypatch.undo() os.environ[name] = "1" try: monkeypatch = MonkeyPatch() @@ -195,7 +218,7 @@ def test_delenv(): assert name not in os.environ monkeypatch.setenv(name, "3") assert os.environ[name] == "3" - monkeypatch.finalize() + monkeypatch.undo() assert os.environ[name] == "1" finally: if name in os.environ: @@ -208,7 +231,7 @@ def test_setenv_prepend(): assert os.environ['XYZ123'] == "2" monkeypatch.setenv('XYZ123', 3, prepend="-") assert os.environ['XYZ123'] == "3-2" - monkeypatch.finalize() + monkeypatch.undo() assert 'XYZ123' not in os.environ def test_monkeypatch_plugin(testdir): diff --git a/py/test/plugin/test_pytest_capture.py b/py/test/plugin/test_pytest_capture.py index d3e05dc6e..8b58a3553 100644 --- a/py/test/plugin/test_pytest_capture.py +++ b/py/test/plugin/test_pytest_capture.py @@ -2,6 +2,16 @@ import py, os, sys from py.__.test.plugin.pytest_capture import CaptureManager class TestCaptureManager: + def test_getmethod_default_no_fd(self, testdir, monkeypatch): + config = testdir.parseconfig(testdir.tmpdir) + assert config.getvalue("capture") is None + capman = CaptureManager() + monkeypatch.delattr(os, 'dup') + try: + assert capman._getmethod(config, None) == "sys" + finally: + monkeypatch.finalize() + def test_configure_per_fspath(self, testdir): config = testdir.parseconfig(testdir.tmpdir) assert config.getvalue("capture") is None