Commit efe16a2c authored by Jason Madden's avatar Jason Madden

100% coverage for local.py; verified it does what threading.local does.

parent df90b2c6
......@@ -154,7 +154,7 @@ affects what we see:
from copy import copy
from weakref import ref
from gevent.hub import getcurrent
from gevent._compat import PYPY
__all__ = ["local"]
......@@ -255,7 +255,7 @@ class local(object):
def __new__(cls, *args, **kw):
if args or kw:
if (PYPY and cls.__init__ == object.__init__) or (not PYPY and cls.__init__ is object.__init__):
if cls.__init__ == object.__init__:
raise TypeError("Initialization arguments are not supported")
self = object.__new__(cls)
impl = _localimpl()
......@@ -291,6 +291,11 @@ class local(object):
if type_self is local:
return dct[name] if name in dct else _oga(self, name)
# NOTE: If this is a descriptor, this will invoke its __get__.
# A broken descriptor that doesn't return itself when called with
# a None for the instance argument could mess us up here.
# But this is faster than a loop over mro() checking each class __dict__
# manually.
type_attr = getattr(type_self, name, _marker)
if name in dct:
if type_attr is _marker:
......@@ -386,7 +391,7 @@ class local(object):
duplicate = copy(d)
cls = type(self)
if (PYPY and cls.__init__ != object.__init__) or (not PYPY and cls.__init__ is not object.__init__):
if cls.__init__ != object.__init__:
args, kw = impl.localargs
instance = cls(*args, **kw)
else:
......
......@@ -517,6 +517,8 @@ class TestCase(TestCaseMetaClass("NewBase", (BaseTestCase,), {})):
self.assertEqual(sig.keywords, gevent_sig.keywords, func_name)
self.assertEqual(sig.defaults, gevent_sig.defaults, func_name)
if not hasattr(TestCase, 'assertRaisesRegex'):
TestCase.assertRaisesRegex = TestCase.assertRaisesRegexp
main = unittest.main
_original_Hub = gevent.hub.Hub
......
......@@ -5,6 +5,16 @@ from gevent import monkey; monkey.patch_all()
from threading import local
from threading import Thread
class ReadProperty(object):
"""A property that can be overridden"""
# A non-data descriptor
def __get__(self, inst, klass):
return 42 if inst is not None else self
class A(local):
......@@ -12,7 +22,12 @@ class A(local):
path = ''
type_path = 'MyPath'
read_property = ReadProperty()
def __init__(self, obj):
super(A, self).__init__()
if not hasattr(self, 'initialized'):
self.obj = obj
self.path = ''
......@@ -36,20 +51,127 @@ class MyLocal(local):
self.sentinel = Sentinel()
created_sentinels.append(id(self.sentinel))
class WithGetattr(local):
def __getattr__(self, name):
if name == 'foo':
return 42
return super(WithGetattr, self).__getattr__(name)
class GeventLocalTestCase(greentest.TestCase):
# pylint:disable=attribute-defined-outside-init,blacklisted-name
def setUp(self):
del deleted_sentinels[:]
del created_sentinels[:]
tearDown = setUp
def test_create_localot_subclass_init_args(self):
with self.assertRaisesRegex(TypeError,
"Initialization arguments are not supported"):
local("foo")
with self.assertRaisesRegex(TypeError,
"Initialization arguments are not supported"):
local(kw="foo")
def test_local_opts_not_subclassed(self):
l = local()
l.attr = 1
self.assertEqual(l.attr, 1)
def test_cannot_set_delete_dict(self):
l = local()
with self.assertRaises(AttributeError):
l.__dict__ = 1
with self.assertRaises(AttributeError):
del l.__dict__
def test_slot_and_type_attributes(self):
a = A(Obj())
a.initialized = 1
self.assertEqual(a.initialized, 1)
# The slot is shared
def demonstrate_slots_shared():
self.assertEqual(a.initialized, 1)
a.initialized = 2
greenlet = Thread(target=demonstrate_slots_shared)
greenlet.start()
greenlet.join()
self.assertEqual(a.initialized, 2)
# The slot overrides dict values
a.__dict__['initialized'] = 42
self.assertEqual(a.initialized, 2)
# Deleting the slot deletes the slot, but not the dict
del a.initialized
self.assertFalse(hasattr(a, 'initialized'))
self.assertIn('initialized', a.__dict__)
# We can delete the 'path' ivar
# and fall back to the type
del a.path
self.assertEqual(a.path, '')
with self.assertRaises(AttributeError):
del a.path
# A read property calls get
self.assertEqual(a.read_property, 42)
a.read_property = 1
self.assertEqual(a.read_property, 1)
self.assertIsInstance(A.read_property, ReadProperty)
# Type attributes can be read
self.assertEqual(a.type_path, 'MyPath')
self.assertNotIn('type_path', a.__dict__)
# and replaced in the dict
a.type_path = 'Local'
self.assertEqual(a.type_path, 'Local')
self.assertIn('type_path', a.__dict__)
def test_attribute_error(self):
# pylint:disable=attribute-defined-outside-init
a = A(Obj())
with self.assertRaises(AttributeError):
getattr(a, 'fizz_buzz')
def set_fizz_buzz():
a.fizz_buzz = 1
greenlet = Thread(target=set_fizz_buzz)
greenlet.start()
greenlet.join()
with self.assertRaises(AttributeError):
getattr(a, 'fizz_buzz')
def test_getattr_called(self):
getter = WithGetattr()
self.assertEqual(42, getter.foo)
getter.foo = 'baz'
self.assertEqual('baz', getter.foo)
def test_copy(self):
a = A(Obj())
a.path = '123'
a.obj.echo = 'test'
b = copy(a)
"""
Copy makes a shallow copy. Meaning that the attribute path
has to be independent in the original and the copied object because the
value is a string, but the attribute obj should be just reference to
the instance of the class Obj
"""
# Copy makes a shallow copy. Meaning that the attribute path
# has to be independent in the original and the copied object because the
# value is a string, but the attribute obj should be just reference to
# the instance of the class Obj
self.assertEqual(a.path, b.path, 'The values in the two objects must be equal')
self.assertEqual(a.obj, b.obj, 'The values must be equal')
......@@ -59,10 +181,17 @@ class GeventLocalTestCase(greentest.TestCase):
a.obj.echo = "works"
self.assertEqual(a.obj, b.obj, 'The values must be equal')
def test_copy_no_subclass(self):
a = local()
setattr(a, 'thing', 42)
b = copy(a)
self.assertEqual(b.thing, 42)
self.assertIsNot(a.__dict__, b.__dict__)
def test_objects(self):
"""
Test which failed in the eventlet?!
"""
# Test which failed in the eventlet?!
a = A({})
a.path = '123'
b = A({'one': 2})
......@@ -90,13 +219,15 @@ class GeventLocalTestCase(greentest.TestCase):
getattr(my_local, 'sentinel')
# Create and reference greenlets
greenlets = [gevent.spawn(demonstrate_my_local) for _ in range(5)]
greenlets = [Thread(target=demonstrate_my_local) for _ in range(5)]
for t in greenlets:
t.start()
gevent.sleep()
self.assertEqual(len(created_sentinels), len(greenlets))
for g in greenlets:
assert g.dead
assert not g.is_alive()
gevent.sleep() # let the callbacks run
if greentest.PYPY:
gc.collect()
......@@ -110,15 +241,12 @@ class GeventLocalTestCase(greentest.TestCase):
import gc
gc.collect()
del created_sentinels[:]
del deleted_sentinels[:]
count = 1000
running_greenlet = None
def demonstrate_my_local():
for i in range(1000):
for _ in range(1000):
x = MyLocal()
self.assertIsNotNone(x.sentinel)
x = None
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment