From 0f29b503ef804c0a7dbec91d927968ee1ad814d2 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 7 Sep 2009 17:53:50 +0200 Subject: [PATCH] monkeypatch, doc, apiwarn, deprecation fixes --HG-- branch : trunk --- doc/confrest.py | 2 +- doc/download.txt | 17 ++---- doc/test/plugin/capture.txt | 3 +- doc/test/plugin/monkeypatch.txt | 49 ++++++++-------- py/log/warning.py | 4 +- py/test/plugin/pytest_capture.py | 2 +- py/test/plugin/pytest_monkeypatch.py | 56 +++++++++++-------- testing/log/test_warning.py | 2 +- .../pytest/plugin/test_pytest_monkeypatch.py | 15 +++-- 9 files changed, 79 insertions(+), 71 deletions(-) diff --git a/doc/confrest.py b/doc/confrest.py index e6af6fde8..9bc5ee364 100644 --- a/doc/confrest.py +++ b/doc/confrest.py @@ -148,7 +148,7 @@ def getrealname(username): class Project: - mydir = py.magic.autopath().dirpath() + mydir = py.path.local(__file__).dirpath() title = "py lib" prefix_title = "" # we have a logo already containing "py lib" encoding = 'latin1' diff --git a/doc/download.txt b/doc/download.txt index fca8b4a82..9ef4abc85 100644 --- a/doc/download.txt +++ b/doc/download.txt @@ -10,7 +10,8 @@ using easy_install =================================================== -With a working `setuptools installation`_ you can type:: +With a working `setuptools installation`_ or `distribute installation`_ +you can type:: easy_install -U py @@ -19,19 +20,11 @@ will trigger an upgrade if you already have an older version installed. On Linux systems you may need to execute the command as superuser and on Windows you might need to write down the full path to ``easy_install``. The py lib and its tools are expected to work well on Linux, -Windows and OSX, Python versions 2.3, 2.4, 2.5 and 2.6. - -**IMPORTANT NOTE**: if you are using Windows and have -0.8 versions of the py lib on your system, please download -and execute http://codespeak.net/svn/py/build/winpathclean.py -This will check that no previous files are getting in the way. -You can find out the py lib version with:: - - import py - print py.version - +Windows and OSX, Python versions 2.4, 2.5, 2.6 through to +the Python3 versions 3.0 and 3.1. .. _mercurial: http://mercurial.selenic.com/wiki/ +.. _`distribute installation`: http://pypi.python.org/pypi/distribute .. _checkout: .. _tarball: diff --git a/doc/test/plugin/capture.txt b/doc/test/plugin/capture.txt index f2cc86f0b..6e89a3b2c 100644 --- a/doc/test/plugin/capture.txt +++ b/doc/test/plugin/capture.txt @@ -74,8 +74,7 @@ per-test capturing. Here is an example test function: def test_myoutput(capsys): print ("hello") - sys.stderr.write("world -") + sys.stderr.write("world\n") out, err = capsys.readouterr() assert out == "hello\n" assert err == "world\n" diff --git a/doc/test/plugin/monkeypatch.txt b/doc/test/plugin/monkeypatch.txt index 527cd61b0..0e20c3c9c 100644 --- a/doc/test/plugin/monkeypatch.txt +++ b/doc/test/plugin/monkeypatch.txt @@ -7,49 +7,54 @@ safely patch object attributes, dicts and environment variables. .. contents:: :local: -Usage +Usage ---------------- -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: +Use the `monkeypatch funcarg`_ to tweak your global test environment +for running a particular test. You can safely set/del an attribute, +dictionary item or environment variable by respective methods +on the monkeypatch funcarg. If you want e.g. to set an ENV1 variable +and have os.path.expanduser return a particular directory, you can +write it down like this: .. sourcecode:: python def test_mytest(monkeypatch): monkeypatch.setenv('ENV1', 'myval') - monkeypatch.setattr(os.path, 'abspath', lambda x: '/') - ... # your test code + monkeypatch.setattr(os.path, 'expanduser', lambda x: '/tmp/xyz') + ... # your test code that uses those patched values implicitely -The function argument will do the modifications and memorize the -old state. After the test function finished execution all -modifications will be reverted. See the `monkeypatch blog post`_ -for an extensive discussion. +After the test function finished all modifications will be undone, +because the ``monkeypatch.undo()`` method is registered as a finalizer. -To add to a possibly existing environment parameter you -can use this example: +``monkeypatch.setattr/delattr/delitem/delenv()`` all +by default raise an Exception if the target does not exist. +Pass ``raising=False`` if you want to skip this check. + +prepending to PATH or other environment variables +--------------------------------------------------------- + +To prepend a value to an already existing environment parameter: .. sourcecode:: python def test_mypath_finding(monkeypatch): monkeypatch.setenv('PATH', 'x/y', prepend=":") - # x/y will be at the beginning of $PATH + # in bash language: export PATH=x/y:$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:: +At the end of function execution py.test invokes +a teardown hook which undoes all monkeypatch changes. +If you do not want to wait that long you can 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. +undo stack. Calling it a second time has no effect unless +you start monkeypatching after the undo call. .. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ @@ -62,7 +67,7 @@ the 'monkeypatch' test function argument The returned ``monkeypatch`` funcarg provides these helper methods to modify objects, dictionaries or os.environ:: - monkeypatch.setattr(obj, name, value) + monkeypatch.setattr(obj, name, value, raising=True) monkeypatch.delattr(obj, name, raising=True) monkeypatch.setitem(mapping, name, value) monkeypatch.delitem(obj, name, raising=True) diff --git a/py/log/warning.py b/py/log/warning.py index d9557d1c7..6a71b6d5c 100644 --- a/py/log/warning.py +++ b/py/log/warning.py @@ -10,11 +10,11 @@ class Warning(DeprecationWarning): def __str__(self): return self.msg -def _apiwarn(startversion, msg, stacklevel=1, function=None): +def _apiwarn(startversion, msg, stacklevel=2, function=None): # below is mostly COPIED from python2.4/warnings.py's def warn() # Get context information if stacklevel == "initpkg": - frame = sys._getframe(1) + frame = sys._getframe(stacklevel == "initpkg" and 1 or stacklevel) level = 2 while frame: co = frame.f_code diff --git a/py/test/plugin/pytest_capture.py b/py/test/plugin/pytest_capture.py index d79709c3b..9c3121bd2 100644 --- a/py/test/plugin/pytest_capture.py +++ b/py/test/plugin/pytest_capture.py @@ -68,7 +68,7 @@ per-test capturing. Here is an example test function: def test_myoutput(capsys): print ("hello") - sys.stderr.write("world\n") + sys.stderr.write("world\\n") out, err = capsys.readouterr() assert out == "hello\\n" assert err == "world\\n" diff --git a/py/test/plugin/pytest_monkeypatch.py b/py/test/plugin/pytest_monkeypatch.py index bb245cd5e..9df5d6ca8 100644 --- a/py/test/plugin/pytest_monkeypatch.py +++ b/py/test/plugin/pytest_monkeypatch.py @@ -1,49 +1,54 @@ """ safely patch object attributes, dicts and environment variables. -Usage +Usage ---------------- -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: +Use the `monkeypatch funcarg`_ to tweak your global test environment +for running a particular test. You can safely set/del an attribute, +dictionary item or environment variable by respective methods +on the monkeypatch funcarg. If you want e.g. to set an ENV1 variable +and have os.path.expanduser return a particular directory, you can +write it down like this: .. sourcecode:: python def test_mytest(monkeypatch): monkeypatch.setenv('ENV1', 'myval') - monkeypatch.setattr(os.path, 'abspath', lambda x: '/') - ... # your test code + monkeypatch.setattr(os.path, 'expanduser', lambda x: '/tmp/xyz') + ... # your test code that uses those patched values implicitely -The function argument will do the modifications and memorize the -old state. After the test function finished execution all -modifications will be reverted. See the `monkeypatch blog post`_ -for an extensive discussion. +After the test function finished all modifications will be undone, +because the ``monkeypatch.undo()`` method is registered as a finalizer. -To add to a possibly existing environment parameter you -can use this example: +``monkeypatch.setattr/delattr/delitem/delenv()`` all +by default raise an Exception if the target does not exist. +Pass ``raising=False`` if you want to skip this check. + +prepending to PATH or other environment variables +--------------------------------------------------------- + +To prepend a value to an already existing environment parameter: .. sourcecode:: python def test_mypath_finding(monkeypatch): monkeypatch.setenv('PATH', 'x/y', prepend=":") - # x/y will be at the beginning of $PATH + # in bash language: export PATH=x/y:$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:: +At the end of function execution py.test invokes +a teardown hook which undoes all monkeypatch changes. +If you do not want to wait that long you can 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. +undo stack. Calling it a second time has no effect unless +you start monkeypatching after the undo call. .. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ """ @@ -54,7 +59,7 @@ def pytest_funcarg__monkeypatch(request): """The returned ``monkeypatch`` funcarg provides these helper methods to modify objects, dictionaries or os.environ:: - monkeypatch.setattr(obj, name, value) + monkeypatch.setattr(obj, name, value, raising=True) monkeypatch.delattr(obj, name, raising=True) monkeypatch.setitem(mapping, name, value) monkeypatch.delitem(obj, name, raising=True) @@ -79,8 +84,11 @@ class MonkeyPatch: self._setattr = [] self._setitem = [] - def setattr(self, obj, name, value): - self._setattr.insert(0, (obj, name, getattr(obj, name, notset))) + def setattr(self, obj, name, value, raising=True): + oldval = getattr(obj, name, notset) + if raising and oldval is notset: + raise AttributeError("%r has no attribute %r" %(obj, name)) + self._setattr.insert(0, (obj, name, oldval)) setattr(obj, name, value) def delattr(self, obj, name, raising=True): diff --git a/testing/log/test_warning.py b/testing/log/test_warning.py index 672a5c980..ef9c5770f 100644 --- a/testing/log/test_warning.py +++ b/testing/log/test_warning.py @@ -6,7 +6,7 @@ def test_forwarding_to_warnings_module(): def test_apiwarn_functional(): capture = py.io.StdCapture() - py.log._apiwarn("x.y.z", "something") + py.log._apiwarn("x.y.z", "something", stacklevel=1) out, err = capture.reset() py.builtin.print_("out", out) py.builtin.print_("err", err) diff --git a/testing/pytest/plugin/test_pytest_monkeypatch.py b/testing/pytest/plugin/test_pytest_monkeypatch.py index 49d2f4d26..a81d2badb 100644 --- a/testing/pytest/plugin/test_pytest_monkeypatch.py +++ b/testing/pytest/plugin/test_pytest_monkeypatch.py @@ -5,6 +5,13 @@ from py.__.test.plugin.pytest_monkeypatch import MonkeyPatch def test_setattr(): class A: x = 1 + monkeypatch = MonkeyPatch() + py.test.raises(AttributeError, "monkeypatch.setattr(A, 'notexists', 2)") + monkeypatch.setattr(A, 'y', 2, raising=False) + assert A.y == 2 + monkeypatch.undo() + assert not hasattr(A, 'y') + monkeypatch = MonkeyPatch() monkeypatch.setattr(A, 'x', 2) assert A.x == 2 @@ -17,11 +24,6 @@ def test_setattr(): monkeypatch.undo() # double-undo makes no modification assert A.x == 5 - monkeypatch.setattr(A, 'y', 3) - assert A.y == 3 - monkeypatch.undo() - assert not hasattr(A, 'y') - def test_delattr(): class A: x = 1 @@ -35,7 +37,7 @@ def test_delattr(): monkeypatch.delattr(A, 'x') py.test.raises(AttributeError, "monkeypatch.delattr(A, 'y')") monkeypatch.delattr(A, 'y', raising=False) - monkeypatch.setattr(A, 'x', 5) + monkeypatch.setattr(A, 'x', 5, raising=False) assert A.x == 5 monkeypatch.undo() assert A.x == 1 @@ -45,6 +47,7 @@ def test_setitem(): monkeypatch = MonkeyPatch() monkeypatch.setitem(d, 'x', 2) monkeypatch.setitem(d, 'y', 1700) + monkeypatch.setitem(d, 'y', 1700) assert d['x'] == 2 assert d['y'] == 1700 monkeypatch.setitem(d, 'x', 3)