# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of logilab-common.
#
# logilab-common is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option) any
# later version.
#
# logilab-common 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with logilab-common. If not, see .
"""unit tests for the decorators module
"""
import sys
import types
from logilab.common.testlib import TestCase, unittest_main
from logilab.common.decorators import (monkeypatch, cached, clear_cache,
copy_cache, cachedproperty)
class DecoratorsTC(TestCase):
def test_monkeypatch_instance_method(self):
class MyClass: pass
@monkeypatch(MyClass)
def meth1(self):
return 12
class XXX(object):
@monkeypatch(MyClass)
def meth2(self):
return 12
if sys.version_info < (3, 0):
self.assertIsInstance(MyClass.meth1, types.MethodType)
self.assertIsInstance(MyClass.meth2, types.MethodType)
else:
# with python3, unbound method are functions
self.assertIsInstance(MyClass.meth1, types.FunctionType)
self.assertIsInstance(MyClass.meth2, types.FunctionType)
self.assertEqual(MyClass().meth1(), 12)
self.assertEqual(MyClass().meth2(), 12)
def test_monkeypatch_property(self):
class MyClass: pass
@monkeypatch(MyClass, methodname='prop1')
@property
def meth1(self):
return 12
self.assertIsInstance(MyClass.prop1, property)
self.assertEqual(MyClass().prop1, 12)
def test_monkeypatch_arbitrary_callable(self):
class MyClass: pass
class ArbitraryCallable(object):
def __call__(self):
return 12
# ensure it complains about missing __name__
with self.assertRaises(AttributeError) as cm:
monkeypatch(MyClass)(ArbitraryCallable())
self.assertTrue(str(cm.exception).endswith('has no __name__ attribute: you should provide an explicit `methodname`'))
# ensure no black magic under the hood
monkeypatch(MyClass, 'foo')(ArbitraryCallable())
self.assertTrue(callable(MyClass.foo))
self.assertEqual(MyClass().foo(), 12)
def test_monkeypatch_with_same_name(self):
class MyClass: pass
@monkeypatch(MyClass)
def meth1(self):
return 12
self.assertEqual([attr for attr in dir(MyClass) if attr[:2] != '__'],
['meth1'])
inst = MyClass()
self.assertEqual(inst.meth1(), 12)
def test_monkeypatch_with_custom_name(self):
class MyClass: pass
@monkeypatch(MyClass, 'foo')
def meth2(self, param):
return param + 12
self.assertEqual([attr for attr in dir(MyClass) if attr[:2] != '__'],
['foo'])
inst = MyClass()
self.assertEqual(inst.foo(4), 16)
def test_cannot_cache_generator(self):
def foo():
yield 42
self.assertRaises(AssertionError, cached, foo)
def test_cached_preserves_docstrings_and_name(self):
class Foo(object):
@cached
def foo(self):
""" what's up doc ? """
def bar(self, zogzog):
""" what's up doc ? """
bar = cached(bar, 1)
@cached
def quux(self, zogzog):
""" what's up doc ? """
self.assertEqual(Foo.foo.__doc__, """ what's up doc ? """)
self.assertEqual(Foo.foo.__name__, 'foo')
self.assertEqual(Foo.bar.__doc__, """ what's up doc ? """)
self.assertEqual(Foo.bar.__name__, 'bar')
self.assertEqual(Foo.quux.__doc__, """ what's up doc ? """)
self.assertEqual(Foo.quux.__name__, 'quux')
def test_cached_single_cache(self):
class Foo(object):
@cached(cacheattr=u'_foo')
def foo(self):
""" what's up doc ? """
foo = Foo()
foo.foo()
self.assertTrue(hasattr(foo, '_foo'))
clear_cache(foo, 'foo')
self.assertFalse(hasattr(foo, '_foo'))
def test_cached_multi_cache(self):
class Foo(object):
@cached(cacheattr=u'_foo')
def foo(self, args):
""" what's up doc ? """
foo = Foo()
foo.foo(1)
self.assertEqual(foo._foo, {(1,): None})
clear_cache(foo, 'foo')
self.assertFalse(hasattr(foo, '_foo'))
def test_cached_keyarg_cache(self):
class Foo(object):
@cached(cacheattr=u'_foo', keyarg=1)
def foo(self, other, args):
""" what's up doc ? """
foo = Foo()
foo.foo(2, 1)
self.assertEqual(foo._foo, {2: None})
clear_cache(foo, 'foo')
self.assertFalse(hasattr(foo, '_foo'))
def test_cached_property(self):
class Foo(object):
@property
@cached(cacheattr=u'_foo')
def foo(self):
""" what's up doc ? """
foo = Foo()
foo.foo
self.assertEqual(foo._foo, None)
clear_cache(foo, 'foo')
self.assertFalse(hasattr(foo, '_foo'))
def test_copy_cache(self):
class Foo(object):
@cached(cacheattr=u'_foo')
def foo(self, args):
""" what's up doc ? """
foo = Foo()
foo.foo(1)
self.assertEqual(foo._foo, {(1,): None})
foo2 = Foo()
self.assertFalse(hasattr(foo2, '_foo'))
copy_cache(foo2, 'foo', foo)
self.assertEqual(foo2._foo, {(1,): None})
def test_cachedproperty(self):
class Foo(object):
x = 0
@cachedproperty
def bar(self):
self.__class__.x += 1
return self.__class__.x
@cachedproperty
def quux(self):
""" some prop """
return 42
foo = Foo()
self.assertEqual(Foo.x, 0)
self.assertFalse('bar' in foo.__dict__)
self.assertEqual(foo.bar, 1)
self.assertTrue('bar' in foo.__dict__)
self.assertEqual(foo.bar, 1)
self.assertEqual(foo.quux, 42)
self.assertEqual(Foo.bar.__doc__,
'')
self.assertEqual(Foo.quux.__doc__,
'\n some prop ')
foo2 = Foo()
self.assertEqual(foo2.bar, 2)
# make sure foo.foo is cached
self.assertEqual(foo.bar, 1)
class Kallable(object):
def __call__(self):
return 42
self.assertRaises(TypeError, cachedproperty, Kallable())
if __name__ == '__main__':
unittest_main()