import gevent.testing as greentest from copy import copy # Comment the line below to see that the standard thread.local is working correct from gevent import monkey; monkey.patch_all() from threading import local from threading import Thread try: from collections.abc import Mapping except ImportError: from collections import Mapping 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): __slots__ = ['initialized', 'obj'] 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 = '' class Obj(object): pass # These next two classes have to be global to avoid the leakchecks deleted_sentinels = [] created_sentinels = [] class Sentinel(object): def __del__(self): deleted_sentinels.append(id(self)) class MyLocal(local): CLASS_PROP = 42 def __init__(self): local.__init__(self) self.sentinel = Sentinel() created_sentinels.append(id(self.sentinel)) @property def desc(self): return self class MyLocalSubclass(MyLocal): pass class WithGetattr(local): def __getattr__(self, name): if name == 'foo': return 42 return super(WithGetattr, self).__getattr__(name) class LocalWithABC(local, Mapping): def __getitem__(self, name): return self.d[name] def __iter__(self): return iter(self.d) def __len__(self): return len(self.d) class LocalWithStaticMethod(local): @staticmethod def a_staticmethod(): return 42 class LocalWithClassMethod(local): @classmethod def a_classmethod(cls): return cls class TestGeventLocal(greentest.TestCase): # pylint:disable=attribute-defined-outside-init,blacklisted-name def setUp(self): del deleted_sentinels[:] del created_sentinels[:] tearDown = setUp def test_create_local_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_delete_with_no_dict(self): l = local() with self.assertRaises(AttributeError): delattr(l, 'thing') def del_local(): with self.assertRaises(AttributeError): delattr(l, 'thing') t = Thread(target=del_local) t.start() t.join() 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 # pylint:disable=unsupported-assignment-operation 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 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') b.path = '321' self.assertNotEqual(a.path, b.path, 'The values in the two objects must be different') 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?! a = A({}) a.path = '123' b = A({'one': 2}) b.path = '123' self.assertEqual(a.path, b.path, 'The values in the two objects must be equal') b.path = '321' self.assertNotEqual(a.path, b.path, 'The values in the two objects must be different') def test_class_attr(self, kind=MyLocal): mylocal = kind() self.assertEqual(42, mylocal.CLASS_PROP) mylocal.CLASS_PROP = 1 self.assertEqual(1, mylocal.CLASS_PROP) self.assertEqual(mylocal.__dict__['CLASS_PROP'], 1) del mylocal.CLASS_PROP self.assertEqual(42, mylocal.CLASS_PROP) self.assertIs(mylocal, mylocal.desc) def test_class_attr_subclass(self): self.test_class_attr(kind=MyLocalSubclass) def test_locals_collected_when_greenlet_dead_but_still_referenced(self): # https://github.com/gevent/gevent/issues/387 import gevent my_local = MyLocal() my_local.sentinel = None greentest.gc_collect_if_needed() del created_sentinels[:] del deleted_sentinels[:] def demonstrate_my_local(): # Get the important parts getattr(my_local, 'sentinel') # Create and reference greenlets 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 not g.is_alive() gevent.sleep() # let the callbacks run greentest.gc_collect_if_needed() # The sentinels should be gone too self.assertEqual(len(deleted_sentinels), len(greenlets)) @greentest.skipOnLibuvOnPyPyOnWin("GC makes this non-deterministic, especially on Windows") def test_locals_collected_when_unreferenced_even_in_running_greenlet(self): # In fact only on Windows do we see GC being an issue; # pypy2 5.0 on macos and travis don't have a problem. # https://github.com/gevent/gevent/issues/981 import gevent import gc gc.collect() count = 1000 running_greenlet = None def demonstrate_my_local(): for _ in range(1000): x = MyLocal() self.assertIsNotNone(x.sentinel) x = None gc.collect() gc.collect() self.assertEqual(count, len(created_sentinels)) # They're all dead, even though this greenlet is # still running self.assertEqual(count, len(deleted_sentinels)) # The links were removed as well. self.assertFalse(running_greenlet.has_links()) running_greenlet = gevent.spawn(demonstrate_my_local) gevent.sleep() running_greenlet.join() self.assertEqual(count, len(deleted_sentinels)) @greentest.ignores_leakcheck def test_local_dicts_for_greenlet(self): import gevent from gevent.local import all_local_dicts_for_greenlet class MyGreenlet(gevent.Greenlet): results = None id_x = None def _run(self): # pylint:disable=method-hidden x = local() x.foo = 42 self.id_x = id(x) self.results = all_local_dicts_for_greenlet(self) g = MyGreenlet() g.start() g.join() self.assertTrue(g.successful, g) self.assertEqual(g.results, [((local, g.id_x), {'foo': 42})]) def test_local_with_abc(self): # an ABC (or generally any non-exact-type) in the MRO doesn't # break things. See https://github.com/gevent/gevent/issues/1201 x = LocalWithABC() x.d = {'a': 1} self.assertEqual({'a': 1}, x.d) # The ABC part works self.assertIn('a', x.d) self.assertEqual(['a'], list(x.keys())) def test_local_with_staticmethod(self): x = LocalWithStaticMethod() self.assertEqual(42, x.a_staticmethod()) def test_local_with_classmethod(self): x = LocalWithClassMethod() self.assertIs(LocalWithClassMethod, x.a_classmethod()) try: from zope import interface except ImportError: interface = None @greentest.skipIf(interface is None, "Needs zope.interface") class TestLocalInterface(greentest.TestCase): __timeout__ = None @greentest.ignores_leakcheck def test_provides(self): # https://github.com/gevent/gevent/issues/1122 # pylint:disable=inherit-non-class class IFoo(interface.Interface): pass @interface.implementer(IFoo) class Base(object): pass class Derived(Base, local): pass d = Derived() p = list(interface.providedBy(d)) self.assertEqual([IFoo], p) @greentest.skipOnPurePython("Needs C extension") class TestCExt(greentest.TestCase): # pragma: no cover def test_c_extension(self): self.assertEqual(local.__module__, 'gevent._local') @greentest.skipWithCExtensions("Needs pure-python") class TestPure(greentest.TestCase): def test_extension(self): self.assertEqual(local.__module__, 'gevent.local') if __name__ == '__main__': greentest.main()