added python handler, and reworked handler and conductor event/job discovery to be more modular

This commit is contained in:
PatchOfScotland
2023-02-01 17:43:16 +01:00
parent 5acb8c230e
commit 636d70f4e8
17 changed files with 537 additions and 259 deletions

View File

@ -2,8 +2,8 @@
import os
import unittest
from core.correctness.vars import PYTHON_TYPE, SHA256, WATCHDOG_TYPE, \
WATCHDOG_BASE, WATCHDOG_RULE, WATCHDOG_HASH, JOB_PARAMETERS, JOB_HASH, \
from core.correctness.vars import JOB_TYPE_PYTHON, SHA256, EVENT_TYPE_WATCHDOG, \
WATCHDOG_BASE, EVENT_RULE, WATCHDOG_HASH, JOB_PARAMETERS, JOB_HASH, \
PYTHON_FUNC, PYTHON_OUTPUT_DIR, PYTHON_EXECUTION_BASE, JOB_ID, META_FILE, \
BASE_FILE, PARAMS_FILE, JOB_FILE, RESULT_FILE
from core.functionality import get_file_hash, create_event, create_job, \
@ -31,11 +31,9 @@ class MeowTests(unittest.TestCase):
# Test LocalPythonConductor creation and job types
def testLocalPythonConductorCreation(self)->None:
lpc = LocalPythonConductor()
LocalPythonConductor()
valid_jobs = lpc.valid_job_types()
self.assertEqual(valid_jobs, [PYTHON_TYPE])
#TODO Test LocalPythonConductor execution criteria
# Test LocalPythonConductor executes valid jobs
def testLocalPythonConductorValidJob(self)->None:
@ -70,13 +68,14 @@ class MeowTests(unittest.TestCase):
}
job_dict = create_job(
PYTHON_TYPE,
JOB_TYPE_PYTHON,
create_event(
WATCHDOG_TYPE,
EVENT_TYPE_WATCHDOG,
file_path,
rule,
{
WATCHDOG_BASE: TEST_MONITOR_BASE,
WATCHDOG_RULE: rule,
EVENT_RULE: rule,
WATCHDOG_HASH: file_hash
}
),
@ -146,13 +145,14 @@ class MeowTests(unittest.TestCase):
}
bad_job_dict = create_job(
PYTHON_TYPE,
JOB_TYPE_PYTHON,
create_event(
WATCHDOG_TYPE,
EVENT_TYPE_WATCHDOG,
file_path,
rule,
{
WATCHDOG_BASE: TEST_MONITOR_BASE,
WATCHDOG_RULE: rule,
EVENT_RULE: rule,
WATCHDOG_HASH: file_hash
}
),
@ -177,13 +177,14 @@ class MeowTests(unittest.TestCase):
# Ensure execution can continue after one failed job
good_job_dict = create_job(
PYTHON_TYPE,
JOB_TYPE_PYTHON,
create_event(
WATCHDOG_TYPE,
EVENT_TYPE_WATCHDOG,
file_path,
rule,
{
WATCHDOG_BASE: TEST_MONITOR_BASE,
WATCHDOG_RULE: rule,
EVENT_RULE: rule,
WATCHDOG_HASH: file_hash
}
),
@ -247,13 +248,14 @@ class MeowTests(unittest.TestCase):
rule = create_rule(pattern, recipe)
job_dict = create_job(
PYTHON_TYPE,
JOB_TYPE_PYTHON,
create_event(
WATCHDOG_TYPE,
EVENT_TYPE_WATCHDOG,
file_path,
rule,
{
WATCHDOG_BASE: TEST_MONITOR_BASE,
WATCHDOG_RULE: rule,
EVENT_RULE: rule,
WATCHDOG_HASH: file_hash
}
),

View File

@ -8,8 +8,8 @@ from multiprocessing import Pipe, Queue
from time import sleep
from core.correctness.vars import CHAR_LOWERCASE, CHAR_UPPERCASE, \
SHA256, EVENT_TYPE, EVENT_PATH, WATCHDOG_TYPE, PYTHON_TYPE, \
WATCHDOG_BASE, WATCHDOG_HASH, WATCHDOG_RULE, JOB_PARAMETERS, JOB_HASH, \
SHA256, EVENT_TYPE, EVENT_PATH, EVENT_TYPE_WATCHDOG, JOB_TYPE_PYTHON, \
WATCHDOG_BASE, WATCHDOG_HASH, EVENT_RULE, JOB_PARAMETERS, JOB_HASH, \
PYTHON_FUNC, PYTHON_OUTPUT_DIR, PYTHON_EXECUTION_BASE, JOB_ID, JOB_EVENT, \
JOB_TYPE, JOB_PATTERN, JOB_RECIPE, JOB_RULE, JOB_STATUS, JOB_CREATE_TIME, \
JOB_REQUIREMENTS, STATUS_QUEUED
@ -241,21 +241,41 @@ class CorrectnessTests(unittest.TestCase):
# Test that create_event produces valid event dictionary
def testCreateEvent(self)->None:
event = create_event("test", "path")
pattern = FileEventPattern(
"pattern",
"file_path",
"recipe_one",
"infile",
parameters={
"extra":"A line from a test Pattern",
"outfile":"result_path"
})
recipe = JupyterNotebookRecipe(
"recipe_one", APPENDING_NOTEBOOK)
rule = create_rule(pattern, recipe)
event = create_event("test", "path", rule)
self.assertEqual(type(event), dict)
self.assertEqual(len(event.keys()), 3)
self.assertTrue(EVENT_TYPE in event.keys())
self.assertEqual(len(event.keys()), 2)
self.assertTrue(EVENT_PATH in event.keys())
self.assertTrue(EVENT_RULE in event.keys())
self.assertEqual(event[EVENT_TYPE], "test")
self.assertEqual(event[EVENT_PATH], "path")
self.assertEqual(event[EVENT_RULE], rule)
event2 = create_event("test2", "path2", {"a":1})
event2 = create_event("test2", "path2", rule, {"a":1})
self.assertEqual(type(event2), dict)
self.assertTrue(EVENT_TYPE in event2.keys())
self.assertEqual(len(event2.keys()), 3)
self.assertTrue(EVENT_PATH in event.keys())
self.assertTrue(EVENT_RULE in event.keys())
self.assertEqual(len(event2.keys()), 4)
self.assertEqual(event2[EVENT_TYPE], "test2")
self.assertEqual(event2[EVENT_PATH], "path2")
self.assertEqual(event2[EVENT_RULE], rule)
self.assertEqual(event2["a"], 1)
# Test that create_job produces valid job dictionary
@ -275,17 +295,18 @@ class CorrectnessTests(unittest.TestCase):
rule = create_rule(pattern, recipe)
event = create_event(
WATCHDOG_TYPE,
EVENT_TYPE_WATCHDOG,
"file_path",
rule,
{
WATCHDOG_BASE: TEST_MONITOR_BASE,
WATCHDOG_RULE: rule,
EVENT_RULE: rule,
WATCHDOG_HASH: "file_hash"
}
)
job_dict = create_job(
PYTHON_TYPE,
JOB_TYPE_PYTHON,
event,
{
JOB_PARAMETERS:{
@ -306,7 +327,7 @@ class CorrectnessTests(unittest.TestCase):
self.assertIn(JOB_EVENT, job_dict)
self.assertEqual(job_dict[JOB_EVENT], event)
self.assertIn(JOB_TYPE, job_dict)
self.assertEqual(job_dict[JOB_TYPE], PYTHON_TYPE)
self.assertEqual(job_dict[JOB_TYPE], JOB_TYPE_PYTHON)
self.assertIn(JOB_PATTERN, job_dict)
self.assertEqual(job_dict[JOB_PATTERN], pattern.name)
self.assertIn(JOB_RECIPE, job_dict)

View File

@ -198,7 +198,7 @@ class MeowTests(unittest.TestCase):
pass
def _is_valid_inputs(self, inputs:Any)->None:
pass
def valid_event_types(self)->list[str]:
def valid_handle_criteria(self, event:dict[str,Any])->bool:
pass
FullTestHandler()
@ -218,7 +218,7 @@ class MeowTests(unittest.TestCase):
def execute(self, job:dict[str,Any])->None:
pass
def valid_job_types(self)->list[str]:
def valid_execute_criteria(self, job:dict[str,Any])->bool:
pass
FullTestConductor()

View File

@ -6,7 +6,7 @@ import unittest
from multiprocessing import Pipe
from core.correctness.vars import FILE_CREATE_EVENT, EVENT_TYPE, \
WATCHDOG_RULE, WATCHDOG_BASE, WATCHDOG_TYPE, EVENT_PATH
EVENT_RULE, WATCHDOG_BASE, EVENT_TYPE_WATCHDOG, EVENT_PATH
from core.functionality import make_dir
from patterns.file_event_pattern import FileEventPattern, WatchdogMonitor, \
_DEFAULT_MASK, SWEEP_START, SWEEP_STOP, SWEEP_JUMP
@ -15,24 +15,24 @@ from shared import setup, teardown, BAREBONES_NOTEBOOK, TEST_MONITOR_BASE
def patterns_equal(tester, pattern_one, pattern_two):
tester.assertEqual(pattern_one.name, pattern_two.name)
tester.assertEqual(pattern_one.recipe, pattern_two.recipe)
tester.assertEqual(pattern_one.parameters, pattern_two.parameters)
tester.assertEqual(pattern_one.outputs, pattern_two.outputs)
tester.assertEqual(pattern_one.triggering_path,
pattern_two.triggering_path)
tester.assertEqual(pattern_one.triggering_file,
pattern_two.triggering_file)
tester.assertEqual(pattern_one.event_mask, pattern_two.event_mask)
tester.assertEqual(pattern_one.sweep, pattern_two.sweep)
tester.assertEqual(pattern_one.name, pattern_two.name)
tester.assertEqual(pattern_one.recipe, pattern_two.recipe)
tester.assertEqual(pattern_one.parameters, pattern_two.parameters)
tester.assertEqual(pattern_one.outputs, pattern_two.outputs)
tester.assertEqual(pattern_one.triggering_path,
pattern_two.triggering_path)
tester.assertEqual(pattern_one.triggering_file,
pattern_two.triggering_file)
tester.assertEqual(pattern_one.event_mask, pattern_two.event_mask)
tester.assertEqual(pattern_one.sweep, pattern_two.sweep)
def recipes_equal(tester, recipe_one, recipe_two):
tester.assertEqual(recipe_one.name, recipe_two.name)
tester.assertEqual(recipe_one.recipe, recipe_two.recipe)
tester.assertEqual(recipe_one.parameters, recipe_two.parameters)
tester.assertEqual(recipe_one.requirements, recipe_two.requirements)
tester.assertEqual(recipe_one.source, recipe_two.source)
tester.assertEqual(recipe_one.name, recipe_two.name)
tester.assertEqual(recipe_one.recipe, recipe_two.recipe)
tester.assertEqual(recipe_one.parameters, recipe_two.parameters)
tester.assertEqual(recipe_one.requirements, recipe_two.requirements)
tester.assertEqual(recipe_one.source, recipe_two.source)
class CorrectnessTests(unittest.TestCase):
@ -225,12 +225,12 @@ class CorrectnessTests(unittest.TestCase):
self.assertTrue(EVENT_TYPE in event.keys())
self.assertTrue(EVENT_PATH in event.keys())
self.assertTrue(WATCHDOG_BASE in event.keys())
self.assertTrue(WATCHDOG_RULE in event.keys())
self.assertEqual(event[EVENT_TYPE], WATCHDOG_TYPE)
self.assertTrue(EVENT_RULE in event.keys())
self.assertEqual(event[EVENT_TYPE], EVENT_TYPE_WATCHDOG)
self.assertEqual(event[EVENT_PATH],
os.path.join(TEST_MONITOR_BASE, "A"))
self.assertEqual(event[WATCHDOG_BASE], TEST_MONITOR_BASE)
self.assertEqual(event[WATCHDOG_RULE].name, rule.name)
self.assertEqual(event[EVENT_RULE].name, rule.name)
open(os.path.join(TEST_MONITOR_BASE, "B"), "w")
if from_monitor_reader.poll(3):
@ -289,14 +289,14 @@ class CorrectnessTests(unittest.TestCase):
self.assertEqual(type(message), dict)
self.assertIn(EVENT_TYPE, message)
self.assertEqual(message[EVENT_TYPE], WATCHDOG_TYPE)
self.assertEqual(message[EVENT_TYPE], EVENT_TYPE_WATCHDOG)
self.assertIn(WATCHDOG_BASE, message)
self.assertEqual(message[WATCHDOG_BASE], TEST_MONITOR_BASE)
self.assertIn(EVENT_PATH, message)
self.assertEqual(message[EVENT_PATH],
os.path.join(start_dir, "A.txt"))
self.assertIn(WATCHDOG_RULE, message)
self.assertEqual(message[WATCHDOG_RULE].name, rule.name)
self.assertIn(EVENT_RULE, message)
self.assertEqual(message[EVENT_RULE].name, rule.name)
wm.stop()
@ -353,14 +353,14 @@ class CorrectnessTests(unittest.TestCase):
self.assertEqual(type(message), dict)
self.assertIn(EVENT_TYPE, message)
self.assertEqual(message[EVENT_TYPE], WATCHDOG_TYPE)
self.assertEqual(message[EVENT_TYPE], EVENT_TYPE_WATCHDOG)
self.assertIn(WATCHDOG_BASE, message)
self.assertEqual(message[WATCHDOG_BASE], TEST_MONITOR_BASE)
self.assertIn(EVENT_PATH, message)
self.assertEqual(message[EVENT_PATH],
os.path.join(start_dir, "A.txt"))
self.assertIn(WATCHDOG_RULE, message)
self.assertEqual(message[WATCHDOG_RULE].name, rule.name)
self.assertIn(EVENT_RULE, message)
self.assertEqual(message[EVENT_RULE].name, rule.name)
wm.stop()

View File

@ -5,11 +5,11 @@ import unittest
from multiprocessing import Pipe
from core.correctness.vars import EVENT_TYPE, WATCHDOG_BASE, WATCHDOG_RULE, \
WATCHDOG_TYPE, EVENT_PATH, SHA256, WATCHDOG_HASH, JOB_ID, PYTHON_TYPE, \
JOB_PARAMETERS, JOB_HASH, PYTHON_FUNC, PYTHON_OUTPUT_DIR, \
PYTHON_EXECUTION_BASE, META_FILE, BASE_FILE, PARAMS_FILE, JOB_FILE, \
RESULT_FILE
from core.correctness.vars import EVENT_TYPE, WATCHDOG_BASE, EVENT_RULE, \
EVENT_TYPE_WATCHDOG, EVENT_PATH, SHA256, WATCHDOG_HASH, JOB_ID, \
JOB_TYPE_PYTHON, JOB_PARAMETERS, JOB_HASH, PYTHON_FUNC, \
PYTHON_OUTPUT_DIR, PYTHON_EXECUTION_BASE, META_FILE, BASE_FILE, \
PARAMS_FILE, JOB_FILE, RESULT_FILE
from core.correctness.validation import valid_job
from core.functionality import get_file_hash, create_job, create_event, \
make_dir, write_yaml, write_notebook, read_yaml
@ -22,7 +22,7 @@ from rules.file_event_jupyter_notebook_rule import FileEventJupyterNotebookRule
from shared import setup, teardown, TEST_HANDLER_BASE, TEST_MONITOR_BASE, \
TEST_JOB_OUTPUT, BAREBONES_NOTEBOOK, APPENDING_NOTEBOOK, COMPLETE_NOTEBOOK
class CorrectnessTests(unittest.TestCase):
class JupyterNotebookTests(unittest.TestCase):
def setUp(self)->None:
super().setUp()
setup()
@ -144,10 +144,10 @@ class CorrectnessTests(unittest.TestCase):
self.assertEqual(len(os.listdir(TEST_JOB_OUTPUT)), 0)
event = {
EVENT_TYPE: WATCHDOG_TYPE,
EVENT_TYPE: EVENT_TYPE_WATCHDOG,
EVENT_PATH: os.path.join(TEST_MONITOR_BASE, "A"),
WATCHDOG_BASE: TEST_MONITOR_BASE,
WATCHDOG_RULE: rule,
EVENT_RULE: rule,
WATCHDOG_HASH: get_file_hash(
os.path.join(TEST_MONITOR_BASE, "A"), SHA256
)
@ -198,10 +198,10 @@ class CorrectnessTests(unittest.TestCase):
self.assertEqual(len(os.listdir(TEST_JOB_OUTPUT)), 0)
event = {
EVENT_TYPE: WATCHDOG_TYPE,
EVENT_TYPE: EVENT_TYPE_WATCHDOG,
EVENT_PATH: os.path.join(TEST_MONITOR_BASE, "A"),
WATCHDOG_BASE: TEST_MONITOR_BASE,
WATCHDOG_RULE: rule,
EVENT_RULE: rule,
WATCHDOG_HASH: get_file_hash(
os.path.join(TEST_MONITOR_BASE, "A"), SHA256
)
@ -271,10 +271,10 @@ class CorrectnessTests(unittest.TestCase):
self.assertEqual(len(os.listdir(TEST_JOB_OUTPUT)), 0)
event = {
EVENT_TYPE: WATCHDOG_TYPE,
EVENT_TYPE: EVENT_TYPE_WATCHDOG,
EVENT_PATH: os.path.join(TEST_MONITOR_BASE, "A"),
WATCHDOG_BASE: TEST_MONITOR_BASE,
WATCHDOG_RULE: rule,
EVENT_RULE: rule,
WATCHDOG_HASH: get_file_hash(
os.path.join(TEST_MONITOR_BASE, "A"), SHA256
)
@ -350,13 +350,14 @@ class CorrectnessTests(unittest.TestCase):
}
job_dict = create_job(
PYTHON_TYPE,
JOB_TYPE_PYTHON,
create_event(
WATCHDOG_TYPE,
EVENT_TYPE_WATCHDOG,
file_path,
rule,
{
WATCHDOG_BASE: TEST_MONITOR_BASE,
WATCHDOG_RULE: rule,
EVENT_RULE: rule,
WATCHDOG_HASH: file_hash
}
),
@ -403,3 +404,15 @@ class CorrectnessTests(unittest.TestCase):
self.assertEqual(len(os.listdir(TEST_HANDLER_BASE)), 0)
self.assertEqual(len(os.listdir(TEST_JOB_OUTPUT)), 0)
#TODO Test handling criteria function
# TODO implement me
class PythonTests(unittest.TestCase):
def setUp(self)->None:
super().setUp()
setup()
def tearDown(self)->None:
super().tearDown()
teardown()

View File

@ -58,20 +58,13 @@ class MeowTests(unittest.TestCase):
self.assertIsNotNone(message)
self.assertEqual(message, "monitor test message")
self.assertIsInstance(runner.handlers, dict)
for handler_list in runner.handlers.values():
for h in handler_list:
self.assertIsInstance(h, BaseHandler)
self.assertEqual(
len(runner.handlers.keys()), len(handler_one.valid_event_types()))
for event_type in handler_one.valid_event_types():
self.assertIn(event_type, runner.handlers.keys())
self.assertEqual(len(runner.handlers[event_type]), 1)
self.assertEqual(runner.handlers[event_type][0], handler_one)
self.assertIsInstance(runner.handlers, list)
for handler in runner.handlers:
self.assertIsInstance(handler, BaseHandler)
self.assertIsInstance(runner.from_handlers, list)
self.assertEqual(len(runner.from_handlers), 1)
runner.handlers[handler_one.valid_event_types()[0]][0].to_runner.send(
runner.handlers[0].to_runner.send(
"handler test message")
message = None
if runner.from_handlers[0].poll(3):
@ -79,16 +72,9 @@ class MeowTests(unittest.TestCase):
self.assertIsNotNone(message)
self.assertEqual(message, "handler test message")
self.assertIsInstance(runner.conductors, dict)
for conductor_list in runner.conductors.values():
for c in conductor_list:
self.assertIsInstance(c, BaseConductor)
self.assertEqual(
len(runner.conductors.keys()), len(conductor_one.valid_job_types()))
for job_type in conductor_one.valid_job_types():
self.assertIn(job_type, runner.conductors.keys())
self.assertEqual(len(runner.conductors[job_type]), 1)
self.assertEqual(runner.conductors[job_type][0], conductor_one)
self.assertIsInstance(runner.conductors, list)
for conductor in runner.conductors:
self.assertIsInstance(conductor, BaseConductor)
runner = MeowRunner(monitors, handlers, conductors)
@ -111,35 +97,13 @@ class MeowTests(unittest.TestCase):
self.assertIsNotNone(m)
self.assertEqual(m, "monitor test message")
self.assertIsInstance(runner.handlers, dict)
for handler_list in runner.handlers.values():
for h in handler_list:
self.assertIsInstance(h, BaseHandler)
all_events = []
for h in handlers:
for e in h.valid_event_types():
if e not in all_events:
all_events.append(e)
self.assertEqual(len(runner.handlers.keys()), len(all_events))
for handler in handlers:
for event_type in handler.valid_event_types():
relevent_handlers = [h for h in handlers
if event_type in h.valid_event_types()]
self.assertIn(event_type, runner.handlers.keys())
self.assertEqual(len(runner.handlers[event_type]),
len(relevent_handlers))
for rh in relevent_handlers:
self.assertIn(rh, runner.handlers[event_type])
self.assertIsInstance(runner.handlers, list)
for handler in runner.handlers:
self.assertIsInstance(handler, BaseHandler)
self.assertIsInstance(runner.from_handlers, list)
self.assertEqual(len(runner.from_handlers), len(handlers))
runner_handlers = []
for handler_list in runner.handlers.values():
for h in handler_list:
runner_handlers.append(h)
runner_handlers = [h for h in handler_list for
handler_list in runner.handlers.values()]
for rh in handler_list:
for rh in runner.handlers:
rh.to_runner.send("handler test message")
message = None
if runner.from_handlers[0].poll(3):
@ -147,25 +111,9 @@ class MeowTests(unittest.TestCase):
self.assertIsNotNone(message)
self.assertEqual(message, "handler test message")
self.assertIsInstance(runner.conductors, dict)
for conductor_list in runner.conductors.values():
for c in conductor_list:
self.assertIsInstance(c, BaseConductor)
all_jobs = []
for c in conductors:
for j in c.valid_job_types():
if j not in all_jobs:
all_jobs.append(j)
self.assertEqual(len(runner.conductors.keys()), len(all_jobs))
for conductor in conductors:
for job_type in conductor.valid_job_types():
relevent_conductors = [c for c in conductors
if job_type in c.valid_job_types()]
self.assertIn(job_type, runner.conductors.keys())
self.assertEqual(len(runner.conductors[job_type]),
len(relevent_conductors))
for rc in relevent_conductors:
self.assertIn(rc, runner.conductors[job_type])
self.assertIsInstance(runner.conductors, list)
for conductor in runner.conductors:
self.assertIsInstance(conductor, BaseConductor)
# Test single meow job execution
def testMeowRunnerExecution(self)->None:
@ -361,3 +309,9 @@ class MeowTests(unittest.TestCase):
# TODO adding tests with numpy
# TODO test getting job cannot handle
# TODO test getting event cannot handle
# TODO test with several matched monitors
# TODO test with several mismatched monitors
# TODO test with several matched handlers
# TODO test with several mismatched handlers
# TODO test with several matched conductors
# TODO test with several mismatched conductors

View File

@ -12,7 +12,7 @@ from core.correctness.validation import check_type, check_implementation, \
setup_debugging
from core.correctness.vars import VALID_NAME_CHARS, SHA256, EVENT_TYPE, \
EVENT_PATH, JOB_TYPE, JOB_EVENT, JOB_ID, JOB_PATTERN, JOB_RECIPE, \
JOB_RULE, JOB_STATUS, JOB_CREATE_TIME
JOB_RULE, JOB_STATUS, JOB_CREATE_TIME, EVENT_RULE
from core.functionality import make_dir
from shared import setup, teardown, TEST_MONITOR_BASE
@ -242,10 +242,20 @@ class CorrectnessTests(unittest.TestCase):
with self.assertRaises(ValueError):
valid_non_existing_path("first/second")
# TODO modify so tests for actual rule values
# Test valid_event can check given event dictionary
def testEventValidation(self)->None:
valid_event({EVENT_TYPE: "test", EVENT_PATH: "path"})
valid_event({EVENT_TYPE: "anything", EVENT_PATH: "path", "a": 1})
valid_event({
EVENT_TYPE: "test",
EVENT_PATH: "path",
EVENT_RULE: "rule"
})
valid_event({
EVENT_TYPE: "anything",
EVENT_PATH: "path",
EVENT_RULE: "rule",
"a": 1
})
with self.assertRaises(KeyError):
valid_event({EVENT_TYPE: "test"})
@ -292,3 +302,5 @@ class CorrectnessTests(unittest.TestCase):
with self.assertRaises(TypeError):
setup_debugging(stream, "1")
#TODO test watchdog event dict