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: ...@@ -154,7 +154,7 @@ affects what we see:
from copy import copy from copy import copy
from weakref import ref from weakref import ref
from gevent.hub import getcurrent from gevent.hub import getcurrent
from gevent._compat import PYPY
__all__ = ["local"] __all__ = ["local"]
...@@ -255,7 +255,7 @@ class local(object): ...@@ -255,7 +255,7 @@ class local(object):
def __new__(cls, *args, **kw): def __new__(cls, *args, **kw):
if args or 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") raise TypeError("Initialization arguments are not supported")
self = object.__new__(cls) self = object.__new__(cls)
impl = _localimpl() impl = _localimpl()
...@@ -291,6 +291,11 @@ class local(object): ...@@ -291,6 +291,11 @@ class local(object):
if type_self is local: if type_self is local:
return dct[name] if name in dct else _oga(self, name) 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) type_attr = getattr(type_self, name, _marker)
if name in dct: if name in dct:
if type_attr is _marker: if type_attr is _marker:
...@@ -386,7 +391,7 @@ class local(object): ...@@ -386,7 +391,7 @@ class local(object):
duplicate = copy(d) duplicate = copy(d)
cls = type(self) 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 args, kw = impl.localargs
instance = cls(*args, **kw) instance = cls(*args, **kw)
else: else:
......
...@@ -517,6 +517,8 @@ class TestCase(TestCaseMetaClass("NewBase", (BaseTestCase,), {})): ...@@ -517,6 +517,8 @@ class TestCase(TestCaseMetaClass("NewBase", (BaseTestCase,), {})):
self.assertEqual(sig.keywords, gevent_sig.keywords, func_name) self.assertEqual(sig.keywords, gevent_sig.keywords, func_name)
self.assertEqual(sig.defaults, gevent_sig.defaults, func_name) self.assertEqual(sig.defaults, gevent_sig.defaults, func_name)
if not hasattr(TestCase, 'assertRaisesRegex'):
TestCase.assertRaisesRegex = TestCase.assertRaisesRegexp
main = unittest.main main = unittest.main
_original_Hub = gevent.hub.Hub _original_Hub = gevent.hub.Hub
......
...@@ -5,6 +5,16 @@ from gevent import monkey; monkey.patch_all() ...@@ -5,6 +5,16 @@ from gevent import monkey; monkey.patch_all()
from threading import local 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): class A(local):
...@@ -12,7 +22,12 @@ class A(local): ...@@ -12,7 +22,12 @@ class A(local):
path = '' path = ''
type_path = 'MyPath'
read_property = ReadProperty()
def __init__(self, obj): def __init__(self, obj):
super(A, self).__init__()
if not hasattr(self, 'initialized'): if not hasattr(self, 'initialized'):
self.obj = obj self.obj = obj
self.path = '' self.path = ''
...@@ -36,20 +51,127 @@ class MyLocal(local): ...@@ -36,20 +51,127 @@ class MyLocal(local):
self.sentinel = Sentinel() self.sentinel = Sentinel()
created_sentinels.append(id(self.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): 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): def test_copy(self):
a = A(Obj()) a = A(Obj())
a.path = '123' a.path = '123'
a.obj.echo = 'test' a.obj.echo = 'test'
b = copy(a) b = copy(a)
"""
Copy makes a shallow copy. Meaning that the attribute path # Copy makes a shallow copy. Meaning that the attribute path
has to be independent in the original and the copied object because the # 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 # value is a string, but the attribute obj should be just reference to
the instance of the class Obj # the instance of the class Obj
"""
self.assertEqual(a.path, b.path, 'The values in the two objects must be equal') 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') self.assertEqual(a.obj, b.obj, 'The values must be equal')
...@@ -59,10 +181,17 @@ class GeventLocalTestCase(greentest.TestCase): ...@@ -59,10 +181,17 @@ class GeventLocalTestCase(greentest.TestCase):
a.obj.echo = "works" a.obj.echo = "works"
self.assertEqual(a.obj, b.obj, 'The values must be equal') 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): def test_objects(self):
""" # Test which failed in the eventlet?!
Test which failed in the eventlet?!
"""
a = A({}) a = A({})
a.path = '123' a.path = '123'
b = A({'one': 2}) b = A({'one': 2})
...@@ -90,13 +219,15 @@ class GeventLocalTestCase(greentest.TestCase): ...@@ -90,13 +219,15 @@ class GeventLocalTestCase(greentest.TestCase):
getattr(my_local, 'sentinel') getattr(my_local, 'sentinel')
# Create and reference greenlets # 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() gevent.sleep()
self.assertEqual(len(created_sentinels), len(greenlets)) self.assertEqual(len(created_sentinels), len(greenlets))
for g in greenlets: for g in greenlets:
assert g.dead assert not g.is_alive()
gevent.sleep() # let the callbacks run gevent.sleep() # let the callbacks run
if greentest.PYPY: if greentest.PYPY:
gc.collect() gc.collect()
...@@ -110,15 +241,12 @@ class GeventLocalTestCase(greentest.TestCase): ...@@ -110,15 +241,12 @@ class GeventLocalTestCase(greentest.TestCase):
import gc import gc
gc.collect() gc.collect()
del created_sentinels[:]
del deleted_sentinels[:]
count = 1000 count = 1000
running_greenlet = None running_greenlet = None
def demonstrate_my_local(): def demonstrate_my_local():
for i in range(1000): for _ in range(1000):
x = MyLocal() x = MyLocal()
self.assertIsNotNone(x.sentinel) self.assertIsNotNone(x.sentinel)
x = None 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