summaryrefslogtreecommitdiff
path: root/bzrlib/tests/test_decorators.py
diff options
context:
space:
mode:
Diffstat (limited to 'bzrlib/tests/test_decorators.py')
-rw-r--r--bzrlib/tests/test_decorators.py321
1 files changed, 321 insertions, 0 deletions
diff --git a/bzrlib/tests/test_decorators.py b/bzrlib/tests/test_decorators.py
new file mode 100644
index 0000000..be25bb1
--- /dev/null
+++ b/bzrlib/tests/test_decorators.py
@@ -0,0 +1,321 @@
+# Copyright (C) 2006-2010 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+"""Tests for decorator functions"""
+
+import inspect
+
+from bzrlib import decorators
+from bzrlib.tests import TestCase
+
+
+class SampleUnlockError(Exception):
+ pass
+
+
+def create_decorator_sample(style, unlock_error=None, meth=None):
+ """Create a DecoratorSample object, using specific lock operators.
+
+ :param style: The type of lock decorators to use (fast/pretty/None)
+ :param unlock_error: If specified, an error to raise from unlock.
+ :param meth: a function to be decorated and added as a 'meth_read' and
+ 'meth_write' to the object.
+ :return: An instantiated DecoratorSample object.
+ """
+
+ if style is None:
+ # Default
+ needs_read_lock = decorators.needs_read_lock
+ needs_write_lock = decorators.needs_write_lock
+ elif style == 'pretty':
+ needs_read_lock = decorators._pretty_needs_read_lock
+ needs_write_lock = decorators._pretty_needs_write_lock
+ else:
+ needs_read_lock = decorators._fast_needs_read_lock
+ needs_write_lock = decorators._fast_needs_write_lock
+
+ class DecoratorSample(object):
+ """Sample class that uses decorators.
+
+ Log when requests go through lock_read()/unlock() or
+ lock_write()/unlock.
+ """
+
+ def __init__(self):
+ self.actions = []
+
+ def lock_read(self):
+ self.actions.append('lock_read')
+
+ def lock_write(self):
+ self.actions.append('lock_write')
+
+ @decorators.only_raises(SampleUnlockError)
+ def unlock(self):
+ if unlock_error:
+ self.actions.append('unlock_fail')
+ raise unlock_error
+ else:
+ self.actions.append('unlock')
+
+ @needs_read_lock
+ def frob(self):
+ """Frob the sample object"""
+ self.actions.append('frob')
+ return 'newbie'
+
+ @needs_write_lock
+ def bank(self, bar, biz=None):
+ """Bank the sample, but using bar and biz."""
+ self.actions.append(('bank', bar, biz))
+ return (bar, biz)
+
+ @needs_read_lock
+ def fail_during_read(self):
+ self.actions.append('fail_during_read')
+ raise TypeError('during read')
+
+ @needs_write_lock
+ def fail_during_write(self):
+ self.actions.append('fail_during_write')
+ raise TypeError('during write')
+
+ if meth is not None:
+ meth_read = needs_read_lock(meth)
+ meth_write = needs_write_lock(meth)
+
+ return DecoratorSample()
+
+
+class TestDecoratorActions(TestCase):
+
+ _decorator_style = None # default
+
+ def test_read_lock_locks_and_unlocks(self):
+ sam = create_decorator_sample(self._decorator_style)
+ self.assertEqual('newbie', sam.frob())
+ self.assertEqual(['lock_read', 'frob', 'unlock'], sam.actions)
+
+ def test_write_lock_locks_and_unlocks(self):
+ sam = create_decorator_sample(self._decorator_style)
+ self.assertEqual(('bar', 'bing'), sam.bank('bar', biz='bing'))
+ self.assertEqual(['lock_write', ('bank', 'bar', 'bing'), 'unlock'],
+ sam.actions)
+
+ def test_read_lock_unlocks_during_failure(self):
+ sam = create_decorator_sample(self._decorator_style)
+ self.assertRaises(TypeError, sam.fail_during_read)
+ self.assertEqual(['lock_read', 'fail_during_read', 'unlock'],
+ sam.actions)
+
+ def test_write_lock_unlocks_during_failure(self):
+ sam = create_decorator_sample(self._decorator_style)
+ self.assertRaises(TypeError, sam.fail_during_write)
+ self.assertEqual(['lock_write', 'fail_during_write', 'unlock'],
+ sam.actions)
+
+ def test_read_lock_raises_original_error(self):
+ sam = create_decorator_sample(self._decorator_style,
+ unlock_error=SampleUnlockError())
+ self.assertRaises(TypeError, sam.fail_during_read)
+ self.assertEqual(['lock_read', 'fail_during_read', 'unlock_fail'],
+ sam.actions)
+
+ def test_write_lock_raises_original_error(self):
+ sam = create_decorator_sample(self._decorator_style,
+ unlock_error=SampleUnlockError())
+ self.assertRaises(TypeError, sam.fail_during_write)
+ self.assertEqual(['lock_write', 'fail_during_write', 'unlock_fail'],
+ sam.actions)
+
+ def test_read_lock_raises_unlock_error(self):
+ sam = create_decorator_sample(self._decorator_style,
+ unlock_error=SampleUnlockError())
+ self.assertRaises(SampleUnlockError, sam.frob)
+ self.assertEqual(['lock_read', 'frob', 'unlock_fail'], sam.actions)
+
+ def test_write_lock_raises_unlock_error(self):
+ sam = create_decorator_sample(self._decorator_style,
+ unlock_error=SampleUnlockError())
+ self.assertRaises(SampleUnlockError, sam.bank, 'bar', biz='bing')
+ self.assertEqual(['lock_write', ('bank', 'bar', 'bing'),
+ 'unlock_fail'], sam.actions)
+
+ def test_read_lock_preserves_default_str_kwarg_identity(self):
+ a_constant = 'A str used as a constant'
+ def meth(self, param=a_constant):
+ return param
+ sam = create_decorator_sample(self._decorator_style, meth=meth)
+ self.assertIs(a_constant, sam.meth_read())
+
+ def test_write_lock_preserves_default_str_kwarg_identity(self):
+ a_constant = 'A str used as a constant'
+ def meth(self, param=a_constant):
+ return param
+ sam = create_decorator_sample(self._decorator_style, meth=meth)
+ self.assertIs(a_constant, sam.meth_write())
+
+
+class TestFastDecoratorActions(TestDecoratorActions):
+
+ _decorator_style = 'fast'
+
+
+class TestPrettyDecoratorActions(TestDecoratorActions):
+
+ _decorator_style = 'pretty'
+
+
+class TestDecoratorDocs(TestCase):
+ """Test method decorators"""
+
+ def test_read_lock_passthrough(self):
+ """@needs_read_lock exposes underlying name and doc."""
+ sam = create_decorator_sample(None)
+ self.assertEqual('frob', sam.frob.__name__)
+ self.assertDocstring('Frob the sample object', sam.frob)
+
+ def test_write_lock_passthrough(self):
+ """@needs_write_lock exposes underlying name and doc."""
+ sam = create_decorator_sample(None)
+ self.assertEqual('bank', sam.bank.__name__)
+ self.assertDocstring('Bank the sample, but using bar and biz.',
+ sam.bank)
+
+ def test_argument_passthrough(self):
+ """Test that arguments get passed around properly."""
+ sam = create_decorator_sample(None)
+ sam.bank('1', biz='2')
+ self.assertEqual(['lock_write',
+ ('bank', '1', '2'),
+ 'unlock',
+ ], sam.actions)
+
+
+class TestPrettyDecorators(TestCase):
+ """Test that pretty decorators generate nice looking wrappers."""
+
+ def get_formatted_args(self, func):
+ """Return a nicely formatted string for the arguments to a function.
+
+ This generates something like "(foo, bar=None)".
+ """
+ return inspect.formatargspec(*inspect.getargspec(func))
+
+ def test__pretty_needs_read_lock(self):
+ """Test that _pretty_needs_read_lock generates a nice wrapper."""
+
+ @decorators._pretty_needs_read_lock
+ def my_function(foo, bar, baz=None, biz=1):
+ """Just a function that supplies several arguments."""
+
+ self.assertEqual('my_function', my_function.__name__)
+ self.assertEqual('my_function_read_locked',
+ my_function.func_code.co_name)
+ self.assertEqual('(foo, bar, baz=None, biz=1)',
+ self.get_formatted_args(my_function))
+ self.assertDocstring(
+ 'Just a function that supplies several arguments.', my_function)
+
+ def test__fast_needs_read_lock(self):
+ """Test the output of _fast_needs_read_lock."""
+
+ @decorators._fast_needs_read_lock
+ def my_function(foo, bar, baz=None, biz=1):
+ """Just a function that supplies several arguments."""
+
+ self.assertEqual('my_function', my_function.__name__)
+ self.assertEqual('read_locked', my_function.func_code.co_name)
+ self.assertEqual('(self, *args, **kwargs)',
+ self.get_formatted_args(my_function))
+ self.assertDocstring(
+ 'Just a function that supplies several arguments.', my_function)
+
+ def test__pretty_needs_write_lock(self):
+ """Test that _pretty_needs_write_lock generates a nice wrapper."""
+
+ @decorators._pretty_needs_write_lock
+ def my_function(foo, bar, baz=None, biz=1):
+ """Just a function that supplies several arguments."""
+
+ self.assertEqual('my_function', my_function.__name__)
+ self.assertEqual('my_function_write_locked',
+ my_function.func_code.co_name)
+ self.assertEqual('(foo, bar, baz=None, biz=1)',
+ self.get_formatted_args(my_function))
+ self.assertDocstring(
+ 'Just a function that supplies several arguments.', my_function)
+
+ def test__fast_needs_write_lock(self):
+ """Test the output of _fast_needs_write_lock."""
+
+ @decorators._fast_needs_write_lock
+ def my_function(foo, bar, baz=None, biz=1):
+ """Just a function that supplies several arguments."""
+
+ self.assertEqual('my_function', my_function.__name__)
+ self.assertEqual('write_locked', my_function.func_code.co_name)
+ self.assertEqual('(self, *args, **kwargs)',
+ self.get_formatted_args(my_function))
+ self.assertDocstring(
+ 'Just a function that supplies several arguments.', my_function)
+
+ def test_use_decorators(self):
+ """Test that you can switch the type of the decorators."""
+ cur_read = decorators.needs_read_lock
+ cur_write = decorators.needs_write_lock
+ try:
+ decorators.use_fast_decorators()
+ self.assertIs(decorators._fast_needs_read_lock,
+ decorators.needs_read_lock)
+ self.assertIs(decorators._fast_needs_write_lock,
+ decorators.needs_write_lock)
+
+ decorators.use_pretty_decorators()
+ self.assertIs(decorators._pretty_needs_read_lock,
+ decorators.needs_read_lock)
+ self.assertIs(decorators._pretty_needs_write_lock,
+ decorators.needs_write_lock)
+
+ # One more switch to make sure it wasn't just good luck that the
+ # functions pointed to the correct version
+ decorators.use_fast_decorators()
+ self.assertIs(decorators._fast_needs_read_lock,
+ decorators.needs_read_lock)
+ self.assertIs(decorators._fast_needs_write_lock,
+ decorators.needs_write_lock)
+ finally:
+ decorators.needs_read_lock = cur_read
+ decorators.needs_write_lock = cur_write
+
+
+class TestOnlyRaisesDecorator(TestCase):
+
+ def raise_ZeroDivisionError(self):
+ 1/0
+
+ def test_raises_approved_error(self):
+ decorator = decorators.only_raises(ZeroDivisionError)
+ decorated_meth = decorator(self.raise_ZeroDivisionError)
+ self.assertRaises(ZeroDivisionError, decorated_meth)
+
+ def test_quietly_logs_unapproved_errors(self):
+ decorator = decorators.only_raises(IOError)
+ decorated_meth = decorator(self.raise_ZeroDivisionError)
+ self.assertLogsError(ZeroDivisionError, decorated_meth)
+
+