Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor events and test_events #1314

Merged
merged 1 commit into from
Dec 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 20 additions & 23 deletions manticore/utils/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@


class EventsGatherMetaclass(type):
'''
"""
Metaclass that is used for Eventful to gather events that classes declare to
publish.
'''
"""
def __new__(cls, name, parents, d):
eventful_sub = super(EventsGatherMetaclass, cls).__new__(cls, name, parents, d)

Expand All @@ -37,17 +37,17 @@ def __new__(cls, name, parents, d):


class Eventful(object, metaclass=EventsGatherMetaclass):
'''
Abstract class for objects emitting and receiving events
An eventful object can:
- publish an event with arbitrary arguments to its subscribers
- let foreign objects subscribe their methods to events emitted here
- forward events to/from other eventful objects

Any time an Eventful object is unserialized:
- All previous subscriptions need to be resubscribed
- All objects that would previously receive forwarded events need to be reconnected
'''
"""
Abstract class for objects emitting and receiving events
An eventful object can:
- publish an event with arbitrary arguments to its subscribers
- let foreign objects subscribe their methods to events emitted here
- forward events to/from other eventful objects

Any time an Eventful object is deserialized:
- All previous subscriptions need to be resubscribed
- All objects that would previously receive forwarded events need to be reconnected
"""

# Maps an Eventful subclass with a set of all the events it publishes.
__all_events__ = dict()
Expand All @@ -60,9 +60,9 @@ class Eventful(object, metaclass=EventsGatherMetaclass):

@classmethod
def all_events(cls):
'''
"""
Return all events that all subclasses have so far registered to publish.
'''
"""
all_evts = set()
for cls, evts in cls.__all_events__.items():
all_evts.update(evts)
Expand All @@ -77,7 +77,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def __setstate__(self, state):
''' It wont get serialized by design, user is responsible to reconnect'''
"""It wont get serialized by design, user is responsible to reconnect"""
self._signals = dict()
self._forwards = WeakKeyDictionary()
return True
Expand Down Expand Up @@ -139,22 +139,19 @@ def _publish_impl(self, _name, *args, **kwargs):
sink._publish_impl(_name, *args, **kwargs)

def subscribe(self, name, method):
if not inspect.ismethod(method):
raise TypeError
assert inspect.ismethod(method), f'{method.__class__.__name__} is not a method'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure if asserts are more appropriate for this situation? these are not invariants, it's more argument checking, so i think raising TypeErrors might be better (although the error messages could be improved)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do our users call subscribe? It seems to me that subscribing to an event is an internal thing and the events are exposed to users only through plugins/detectors.

If it is internal - then having an assertion here should be good enough ( ? ).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right, generally users should opt for the Plugin/Detector interface rather than using the raw subscribe one. however, i'm not sure whether this is an external or internal interface matters too much; imo, it's more about whether the condition being checked is an invariant or for input error checking. in this case, it seems more for error checking, and TypeError actually seems like a more semantically correct and descriptive exception to raise, since this is actually a type check

Copy link
Member Author

@disconnect3d disconnect3d Dec 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I think that in the end ("in a production build") such checks are not needed (the code itself should never call this with bad type) but it is good to have it as a defence against ourselves when making changes. This way, having it as assertion is better, at least in theory (I wonder if there is a way to ship code without assertions in Python).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I see your point. i think for this particular case it doesn't matter too much, i don't think subscribe is in any hot code paths. at the end of the day i think i do slightly prefer the TypeError since it is more specific, and from what i can see, having it shouldn't affect performance in a noticeable way. but it's only a slight preference :)

obj, callback = method.__self__, method.__func__
bucket = self._get_signal_bucket(name)
robj = ref(obj, self._unref) # see unref() for explanation
bucket.setdefault(robj, set()).add(callback)

def forward_events_from(self, source, include_source=False):
if not isinstance(source, Eventful):
raise TypeError(f'{source.__class__.__name__} is not Eventful')
assert isinstance(source, Eventful), f'{source.__class__.__name__} is not Eventful'
source.forward_events_to(self, include_source=include_source)

def forward_events_to(self, sink, include_source=False):
''' This forwards signal to sink '''
if not isinstance(sink, Eventful):
raise TypeError
"""This forwards signal to sink"""
assert isinstance(sink, Eventful), f'{sink.__class__.__name__} is not Eventful'
self._forwards[sink] = include_source

def copy_eventful_state(self, new_object: 'Eventful'):
Expand Down
23 changes: 10 additions & 13 deletions tests/test_events.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@

import unittest

from manticore.utils.event import Eventful


class A(Eventful):
_published_events = set(['eventA'])
_published_events = {'eventA'}

def do_stuff(self):
self._publish("eventA",1, 'a')
self._publish("eventA", 1, 'a')


class B(Eventful):
_published_events = set(['eventB'])
_published_events = {'eventB'}

def __init__(self, child, **kwargs):
super().__init__(**kwargs)
self.child = child
Expand All @@ -19,19 +22,16 @@ def do_stuff(self):
self._publish("eventB", 2, 'b')


class C():
class C:
def __init__(self):
self.received = []

def callback(self, *args):
self.received.append(args)


class ManticoreDriver(unittest.TestCase):
_multiprocess_can_split_ = True
def setUp(self):
self.state = {}

def tearDown(self):
pass

def test_weak_references(self):
a = A()
Expand All @@ -57,7 +57,6 @@ def test_weak_references(self):
del b
self.assertSequenceEqual([len(s) for s in (a._signals, a._forwards)], (0, 0))


def test_basic(self):
a = A()
b = B(a)
Expand All @@ -71,5 +70,3 @@ def test_basic(self):

b.do_stuff()
self.assertSequenceEqual(c.received, [(1, 'a'), (2, 'b')])