Fixes how instances methods are not deregistered
It appears that instance methods do not get removed due to not passing the 'is' check, so switch to using a new reflection utility method which can handle this equality check in a way that actually works. Also adds tests to make sure this does not occur again. Closes-Bug: 1257550 Change-Id: Iab47dd62cb61de0d93d0fe8d90e59772beebeaeb
This commit is contained in:
@@ -16,8 +16,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import functools
|
||||
import sys
|
||||
|
||||
from taskflow import states
|
||||
from taskflow import test
|
||||
from taskflow.tests import utils as test_utils
|
||||
from taskflow.utils import lock_utils
|
||||
@@ -61,6 +64,46 @@ class ClassWithInit(object):
|
||||
pass
|
||||
|
||||
|
||||
class CallbackEqualityTest(test.TestCase):
|
||||
def test_different_simple_callbacks(self):
|
||||
|
||||
def a():
|
||||
pass
|
||||
|
||||
def b():
|
||||
pass
|
||||
|
||||
self.assertFalse(reflection.is_same_callback(a, b))
|
||||
|
||||
def test_static_instance_callbacks(self):
|
||||
|
||||
class A(object):
|
||||
|
||||
@staticmethod
|
||||
def b(a, b, c):
|
||||
pass
|
||||
|
||||
a = A()
|
||||
b = A()
|
||||
|
||||
self.assertTrue(reflection.is_same_callback(a.b, b.b))
|
||||
|
||||
def test_different_instance_callbacks(self):
|
||||
|
||||
class A(object):
|
||||
def b(self):
|
||||
pass
|
||||
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
b = A()
|
||||
c = A()
|
||||
|
||||
self.assertFalse(reflection.is_same_callback(b.b, c.b))
|
||||
self.assertTrue(reflection.is_same_callback(b.b, c.b, strict=False))
|
||||
|
||||
|
||||
class GetCallableNameTest(test.TestCase):
|
||||
|
||||
def test_mere_function(self):
|
||||
@@ -99,6 +142,88 @@ class GetCallableNameTest(test.TestCase):
|
||||
'__call__')))
|
||||
|
||||
|
||||
class NotifierTest(test.TestCase):
|
||||
|
||||
def test_notify_called(self):
|
||||
call_collector = []
|
||||
|
||||
def call_me(state, details):
|
||||
call_collector.append((state, details))
|
||||
|
||||
notifier = misc.TransitionNotifier()
|
||||
notifier.register(misc.TransitionNotifier.ANY, call_me)
|
||||
notifier.notify(states.SUCCESS, {})
|
||||
notifier.notify(states.SUCCESS, {})
|
||||
|
||||
self.assertEqual(2, len(call_collector))
|
||||
self.assertEqual(1, len(notifier))
|
||||
|
||||
def test_notify_register_deregister(self):
|
||||
|
||||
def call_me(state, details):
|
||||
pass
|
||||
|
||||
class A(object):
|
||||
def call_me_too(self, state, details):
|
||||
pass
|
||||
|
||||
notifier = misc.TransitionNotifier()
|
||||
notifier.register(misc.TransitionNotifier.ANY, call_me)
|
||||
a = A()
|
||||
notifier.register(misc.TransitionNotifier.ANY, a.call_me_too)
|
||||
|
||||
self.assertEqual(2, len(notifier))
|
||||
notifier.deregister(misc.TransitionNotifier.ANY, call_me)
|
||||
notifier.deregister(misc.TransitionNotifier.ANY, a.call_me_too)
|
||||
self.assertEqual(0, len(notifier))
|
||||
|
||||
def test_notify_reset(self):
|
||||
|
||||
def call_me(state, details):
|
||||
pass
|
||||
|
||||
notifier = misc.TransitionNotifier()
|
||||
notifier.register(misc.TransitionNotifier.ANY, call_me)
|
||||
self.assertEqual(1, len(notifier))
|
||||
|
||||
notifier.reset()
|
||||
self.assertEqual(0, len(notifier))
|
||||
|
||||
def test_bad_notify(self):
|
||||
|
||||
def call_me(state, details):
|
||||
pass
|
||||
|
||||
notifier = misc.TransitionNotifier()
|
||||
self.assertRaises(KeyError, notifier.register,
|
||||
misc.TransitionNotifier.ANY, call_me,
|
||||
kwargs={'details': 5})
|
||||
|
||||
def test_selective_notify(self):
|
||||
call_counts = collections.defaultdict(list)
|
||||
|
||||
def call_me_on(registered_state, state, details):
|
||||
call_counts[registered_state].append((state, details))
|
||||
|
||||
notifier = misc.TransitionNotifier()
|
||||
notifier.register(states.SUCCESS,
|
||||
functools.partial(call_me_on, states.SUCCESS))
|
||||
notifier.register(misc.TransitionNotifier.ANY,
|
||||
functools.partial(call_me_on,
|
||||
misc.TransitionNotifier.ANY))
|
||||
|
||||
self.assertEqual(2, len(notifier))
|
||||
notifier.notify(states.SUCCESS, {})
|
||||
|
||||
self.assertEqual(1, len(call_counts[misc.TransitionNotifier.ANY]))
|
||||
self.assertEqual(1, len(call_counts[states.SUCCESS]))
|
||||
|
||||
notifier.notify(states.FAILURE, {})
|
||||
self.assertEqual(2, len(call_counts[misc.TransitionNotifier.ANY]))
|
||||
self.assertEqual(1, len(call_counts[states.SUCCESS]))
|
||||
self.assertEqual(2, len(call_counts))
|
||||
|
||||
|
||||
class GetCallableArgsTest(test.TestCase):
|
||||
|
||||
def test_mere_function(self):
|
||||
|
||||
@@ -214,8 +214,16 @@ class TransitionNotifier(object):
|
||||
def __init__(self):
|
||||
self._listeners = collections.defaultdict(list)
|
||||
|
||||
def __len__(self):
|
||||
"""Returns how many callbacks are registered"""
|
||||
|
||||
count = 0
|
||||
for (_s, callbacks) in six.iteritems(self._listeners):
|
||||
count += len(callbacks)
|
||||
return count
|
||||
|
||||
def reset(self):
|
||||
self._listeners = collections.defaultdict(list)
|
||||
self._listeners.clear()
|
||||
|
||||
def notify(self, state, details):
|
||||
listeners = list(self._listeners.get(self.ANY, []))
|
||||
@@ -255,7 +263,7 @@ class TransitionNotifier(object):
|
||||
if state not in self._listeners:
|
||||
return
|
||||
for i, (cb, args, kwargs) in enumerate(self._listeners[state]):
|
||||
if cb is callback:
|
||||
if reflection.is_same_callback(cb, callback):
|
||||
self._listeners[state].pop(i)
|
||||
break
|
||||
|
||||
|
||||
@@ -93,6 +93,29 @@ def get_method_self(method):
|
||||
return None
|
||||
|
||||
|
||||
def is_same_callback(callback1, callback2, strict=True):
|
||||
"""Returns if the two callbacks are the same."""
|
||||
if callback1 is callback2:
|
||||
# This happens when plain methods are given (or static/non-bound
|
||||
# methods).
|
||||
return True
|
||||
if callback1 == callback2:
|
||||
if not strict:
|
||||
return True
|
||||
# If two bound method are equal if functions themselves are equal
|
||||
# and objects they are applied to are equal. This means that a bound
|
||||
# method could be the same bound method on another object if the
|
||||
# objects have __eq__ methods that return true (when in fact it is a
|
||||
# different bound method). Python u so crazy!
|
||||
try:
|
||||
self1 = six.get_method_self(callback1)
|
||||
self2 = six.get_method_self(callback2)
|
||||
return self1 is self2
|
||||
except AttributeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def is_bound_method(method):
|
||||
"""Returns if the method given is a bound to a object or not."""
|
||||
return bool(get_method_self(method))
|
||||
|
||||
Reference in New Issue
Block a user