Skip to content

Commit 2622463

Browse files
committed
Register plugin exceptions as PluginExit events.
refs #149
1 parent 8c8c908 commit 2622463

File tree

2 files changed

+94
-45
lines changed

2 files changed

+94
-45
lines changed

sc2reader/engine/engine.py

Lines changed: 38 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ def handleAbilityEvent(self, event, replay):
102102
replay::
103103
104104
code, details = replay.plugins['MyPlugin']
105+
106+
If your plugin depends on another plugin, it is a good idea to implement handlePluginExit
107+
and be alerted if the plugin that you require fails. This way you can exit gracefully. You
108+
can also check to see if the plugin name is in ``replay.plugin_failures``::
109+
110+
if 'RequiredPlugin' in replay.plugin_failures:
111+
code, details = replay.plugins['RequiredPlugin']
112+
message = "RequiredPlugin failed with code: {0}. Cannot continue.".format(code)
113+
yield PluginExit(self, code=1, details=dict(msg=message))
105114
"""
106115
def __init__(self, plugins=[]):
107116
self._plugins = list()
@@ -123,8 +132,11 @@ def run(self, replay):
123132
# remove them from this list and regenerate event handlers.
124133
plugins = list(self._plugins)
125134

126-
# Create a dict for storing plugin exit codes and details
127-
replay.plugins = dict()
135+
# Create a dict for storing plugin exit codes and details.
136+
replay.plugin_result = replay.plugins = dict()
137+
138+
# Create a list storing replay.plugins keys for failures.
139+
replay.plugin_failures = list()
128140

129141
# Fill event event queue with the replay events, bookmarked by Init and End events.
130142
event_queue = collections.deque()
@@ -141,8 +153,9 @@ def run(self, replay):
141153
# Remove the plugin and reset the handlers.
142154
plugins.remove(event.plugin)
143155
handlers.clear()
144-
replay.plugins[event.plugin.name] = (event.code, event.details)
145-
continue
156+
replay.plugin_result[event.plugin.name] = (event.code, event.details)
157+
if event.code != 0:
158+
replay.plugin_failures.append(event.plugin.name)
146159

147160
# If we haven't compiled a list of handlers for this event yet, do so!
148161
if event.name not in handlers:
@@ -152,19 +165,31 @@ def run(self, replay):
152165
event_handlers = handlers[event.name]
153166

154167
# Events have the option of yielding one or more additional events
155-
# which get processed after the current event finishes.
156-
new_events = list()
168+
# which get processed after the current event finishes. The new_events
169+
# batch is constructed in reverse order because extendleft reverses
170+
# the order again with a series of appendlefts.
171+
new_events = collections.deque()
157172
for event_handler in event_handlers:
158-
new_events.extend(event_handler(event, replay) or [])
159-
160-
# extendleft does a series of appendlefts and reverses the order so we
161-
# need to reverse the list first to have them added in order.
173+
try:
174+
for new_event in (event_handler(event, replay) or []):
175+
if new_event.name == 'PluginExit':
176+
new_events.append(new_event)
177+
break
178+
else:
179+
new_events.appendleft(new_event)
180+
except Exception as e:
181+
if event_handler.im_self.name in ['ContextLoader']:
182+
# Certain built in plugins should probably still cause total failure
183+
raise # Maybe??
184+
else:
185+
new_event = PluginExit(event_handler.im_self, code=1, details=dict(error=e))
186+
new_events.append(new_event)
162187
event_queue.extendleft(new_events)
163188

164-
# For any plugins that didn't yield a PluginExit event, record a successful
165-
# completion.
189+
# For any plugins that didn't yield a PluginExit event or throw unexpected exceptions,
190+
# record a successful completion.
166191
for plugin in plugins:
167-
replay.plugins[plugin.name] = (0, dict())
192+
replay.plugin_result[plugin.name] = (0, dict())
168193

169194
def _get_event_handlers(self, event, plugins):
170195
return sum([self._get_plugin_event_handlers(plugin, event) for plugin in plugins], [])
@@ -194,35 +219,3 @@ def _has_event_handler(self, plugin, event):
194219

195220
def _get_event_handler(self, plugin, event):
196221
return getattr(plugin, 'handle'+event.name, None)
197-
198-
199-
if __name__ == '__main__':
200-
from sc2reader.events import UserOptionsEvent, GameStartEvent, PlayerLeaveEvent
201-
202-
class TestEvent(object):
203-
name = 'TestEvent'
204-
205-
def __init__(self, source):
206-
self.source = source
207-
208-
class TestPlugin(object):
209-
yields = TestEvent
210-
211-
def handleInitGame(self, event, replay,):
212-
yield TestEvent(event.name)
213-
214-
def handleTestEvent(self, event, replay):
215-
print(event.source)
216-
217-
def handleGameStartEvent(self, event, replay):
218-
yield TestEvent(event.name)
219-
220-
def handleEndGame(self, event, replay):
221-
yield TestEvent(event.name)
222-
223-
class TestReplay(object):
224-
events = [UserOptionsEvent, UserOptionsEvent, GameStartEvent, PlayerLeaveEvent]
225-
226-
engine = GameEngine()
227-
engine.register_plugin(TestPlugin())
228-
events = engine.run(TestReplay)

test_replays/test_all.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,5 +362,61 @@ def test_replay_event_order(self):
362362
replay = sc2reader.load_replay("test_replays/event_order.SC2Replay")
363363

364364

365+
class TestGameEngine(unittest.TestCase):
366+
class TestEvent(object):
367+
name='TestEvent'
368+
def __init__(self, value):
369+
self.value = value
370+
def __str__(self):
371+
return self.value
372+
373+
class TestPlugin1(object):
374+
name = 'TestPlugin1'
375+
376+
def handleInitGame(self, event, replay):
377+
yield TestGameEngine.TestEvent('b')
378+
yield TestGameEngine.TestEvent('c')
379+
380+
def handleTestEvent(self, event, replay):
381+
print("morestuff")
382+
if event.value == 'd':
383+
yield sc2reader.engine.PluginExit(self, code=1, details=dict(msg="Fail!"))
384+
else:
385+
yield TestGameEngine.TestEvent('d')
386+
387+
def handleEndGame(self, event, replay):
388+
yield TestGameEngine.TestEvent('g')
389+
390+
class TestPlugin2(object):
391+
name = 'TestPlugin2'
392+
def handleInitGame(self, event, replay):
393+
replay.engine_events = list()
394+
395+
def handleTestEvent(self, event, replay):
396+
replay.engine_events.append(event)
397+
398+
def handlePluginExit(self, event, replay):
399+
yield TestGameEngine.TestEvent('e')
400+
401+
def handleEndGame(self, event, replay):
402+
yield TestGameEngine.TestEvent('f')
403+
404+
class MockReplay(object):
405+
def __init__(self, events):
406+
self.events = events
407+
408+
def test_plugin1(self):
409+
engine = sc2reader.engine.GameEngine()
410+
engine.register_plugin(self.TestPlugin1())
411+
engine.register_plugin(self.TestPlugin2())
412+
replay = self.MockReplay([self.TestEvent('a')])
413+
engine.run(replay)
414+
self.assertEqual(''.join(str(e) for e in replay.engine_events), 'bdecaf')
415+
self.assertEqual(replay.plugin_failures, ['TestPlugin1'])
416+
self.assertEqual(replay.plugin_result['TestPlugin1'], (1, dict(msg="Fail!")))
417+
self.assertEqual(replay.plugin_result['TestPlugin2'], (0, dict()))
418+
419+
420+
365421
if __name__ == '__main__':
366422
unittest.main()

0 commit comments

Comments
 (0)