# 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="_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="_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="_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="_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="_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()