Make MumoManager testable. Add first basic tests to test case. MumoManager is not yet completed.

This commit is contained in:
Stefan Hacker
2010-12-03 06:50:00 +01:00
parent 30738329e1
commit e35452d8b3
6 changed files with 122 additions and 64 deletions

View File

@@ -37,26 +37,36 @@ class Config(object):
""" """
def __init__(self, filename = None, default = None): def __init__(self, filename = None, default = None):
if not filename or not default: return if (filename and not default) or \
cfg = ConfigParser.ConfigParser() (not filename and not default): return
cfg.optionxform = str
cfg.read(filename) if filename:
cfg = ConfigParser.ConfigParser()
cfg.optionxform = str
cfg.read(filename)
for h,v in default.iteritems(): for h,v in default.iteritems():
if not v: if not v:
# Output this whole section as a list of raw key/value tuples # Output this whole section as a list of raw key/value tuples
try: if not filename:
self.__dict__[h] = cfg.items(h)
except ConfigParser.NoSectionError:
self.__dict__[h] = [] self.__dict__[h] = []
else:
try:
self.__dict__[h] = cfg.items(h)
except ConfigParser.NoSectionError:
self.__dict__[h] = []
else: else:
self.__dict__[h] = Config() self.__dict__[h] = Config()
for name, val in v.iteritems(): for name, val in v.iteritems():
conv, vdefault = val conv, vdefault = val
try:
self.__dict__[h].__dict__[name] = conv(cfg.get(h, name)) if not filename:
except (ValueError, ConfigParser.NoSectionError, ConfigParser.NoOptionError):
self.__dict__[h].__dict__[name] = vdefault self.__dict__[h].__dict__[name] = vdefault
else:
try:
self.__dict__[h].__dict__[name] = conv(cfg.get(h, name))
except (ValueError, ConfigParser.NoSectionError, ConfigParser.NoOptionError):
self.__dict__[h].__dict__[name] = vdefault
def x2bool(s): def x2bool(s):
"""Helper function to convert strings from the config to bool""" """Helper function to convert strings from the config to bool"""

View File

@@ -104,6 +104,12 @@ testfallbacknum = asdas
assert(cfg.somethingelse.bla == "test") assert(cfg.somethingelse.bla == "test")
finally: finally:
os.remove(path) os.remove(path)
def testLoadDefault(self):
cfg = Config(default=self.cfg_default)
assert(cfg.world.domination == False)
assert(cfg.somethingelse.bla == "test")
assert(cfg.world.somenum == 0)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -31,6 +31,7 @@
import Queue import Queue
from worker import Worker, local_thread, local_thread_blocking from worker import Worker, local_thread, local_thread_blocking
from config import Config
import sys import sys
import os import os
@@ -47,18 +48,22 @@ class FailedLoadModuleInitializationException(FailedLoadModuleException):
pass pass
def debug_log(fu, enable = True): def debug_log(fu, enable = True):
def new_fu(*args, **kwargs): def new_dec(fu):
self = args[0] def new_fu(*args, **kwargs):
log = self.log() self = args[0]
skwargs = [','.join(['%s=%s' % (karg,repr(arg)) for karg, arg in kwargs])] log = self.log()
sargs = [','.join([str(arg) for arg in args[1:]])] + '' if not skwargs else (',' + skwargs) skwargs = ','.join(['%s=%s' % (karg,repr(arg)) for karg, arg in kwargs])
sargs = ','.join([str(arg) for arg in args[1:]]) + '' if not skwargs else (',' + str(skwargs))
call = "%s(%s)" % (fu.__name__, sargs)
log.debug() call = "%s(%s)" % (fu.__name__, sargs)
res = fu(*args, **kwargs) log.debug(call)
log.debug("%s -> %s", call, repr(res)) res = fu(*args, **kwargs)
return res log.debug("%s -> %s", call, repr(res))
return new_fu if enable else fu return res
return new_fu if enable else fu
return new_dec
debug_me = True debug_me = True
@@ -80,7 +85,7 @@ class MumoManagerRemote(object):
def getQueue(self): def getQueue(self):
return self.__queue return self.__queue
def subscribeMetaCallbacks(self, handler, servers = MumoManagerRemote.SERVERS_ALL): def subscribeMetaCallbacks(self, handler, servers = SERVERS_ALL):
""" """
Subscribe to meta callbacks. Subscribes the given handler to the following Subscribe to meta callbacks. Subscribes the given handler to the following
callbacks: callbacks:
@@ -94,7 +99,7 @@ class MumoManagerRemote(object):
""" """
return self.__master.subscribeMetaCallbacks(self.__queue, handler, servers) return self.__master.subscribeMetaCallbacks(self.__queue, handler, servers)
def unsubscribeMetaCallbacks(self, handler, servers = MumoManagerRemote.SERVERS_ALL): def unsubscribeMetaCallbacks(self, handler, servers = SERVERS_ALL):
""" """
Unsubscribe from meta callbacks. Unsubscribes the given handler from callbacks Unsubscribe from meta callbacks. Unsubscribes the given handler from callbacks
for the given servers. for the given servers.
@@ -105,7 +110,7 @@ class MumoManagerRemote(object):
""" """
return self.__master.unscubscribeMetaCallbacks(self.__queue, handler, servers) return self.__master.unscubscribeMetaCallbacks(self.__queue, handler, servers)
def subscribeServerCallbacks(self, handler, servers = MumoManagerRemote.SERVERS_ALL): def subscribeServerCallbacks(self, handler, servers = SERVERS_ALL):
""" """
Subscribe to server callbacks. Subscribes the given handler to the following Subscribe to server callbacks. Subscribes the given handler to the following
callbacks: callbacks:
@@ -123,7 +128,7 @@ class MumoManagerRemote(object):
""" """
return self.__master.subscribeServerCallbacks(self.__queue, handler, servers) return self.__master.subscribeServerCallbacks(self.__queue, handler, servers)
def unsubscribeServerCallbacks(self, handler, servers = MumoManagerRemote.SERVERS_ALL): def unsubscribeServerCallbacks(self, handler, servers = SERVERS_ALL):
""" """
Unsubscribe from server callbacks. Unsubscribes the given handler from callbacks Unsubscribe from server callbacks. Unsubscribes the given handler from callbacks
for the given servers. for the given servers.
@@ -134,7 +139,7 @@ class MumoManagerRemote(object):
""" """
return self.__master.unsubscribeServerCallbacks(self.__queue, handler, servers) return self.__master.unsubscribeServerCallbacks(self.__queue, handler, servers)
def subscribeContextCallbacks(self, handler, servers = MumoManagerRemote.SERVERS_ALL): def subscribeContextCallbacks(self, handler, servers = SERVERS_ALL):
""" """
Subscribe to context callbacks. Subscribes the given handler to the following Subscribe to context callbacks. Subscribes the given handler to the following
callbacks: callbacks:
@@ -147,7 +152,7 @@ class MumoManagerRemote(object):
""" """
return self.__master.subscribeContextCallbacks(self.__queue, handler, servers) return self.__master.subscribeContextCallbacks(self.__queue, handler, servers)
def unsubscribeContextCallbacks(self, handler, servers = MumoManagerRemote.SERVERS_ALL): def unsubscribeContextCallbacks(self, handler, servers = SERVERS_ALL):
""" """
Unsubscribe from context callbacks. Unsubscribes the given handler from callbacks Unsubscribe from context callbacks. Unsubscribes the given handler from callbacks
for the given servers. for the given servers.
@@ -162,8 +167,12 @@ class MumoManagerRemote(object):
class MumoManager(Worker): class MumoManager(Worker):
MAGIC_ALL = -1 MAGIC_ALL = -1
cfg_default = {'modules':{'mod_dir':(str, "modules/"),
'cfg_dir':(str, "modules-enabled/"),
'timeout':(int, 2)}}
def __init__(self, cfg): def __init__(self, cfg = Config(default = cfg_default)):
Worker.__init__(self, "MumoManager") Worker.__init__(self, "MumoManager")
self.queues = {} # {queue:module} self.queues = {} # {queue:module}
self.modules = {} # {name:module} self.modules = {} # {name:module}
@@ -285,7 +294,7 @@ class MumoManager(Worker):
if not names: if not names:
# If no names are given load all modules that have a configuration in the cfg_dir # If no names are given load all modules that have a configuration in the cfg_dir
if not os.path.isdir(self.cfg.modules.cfg_dir): if not os.path.isdir(self.cfg.modules.cfg_dir):
msg = "Module directory '%s' not found" % self.cfg.mumo.mod_dir msg = "Module directory '%s' not found" % self.cfg.modules.mod_dir
self.log().error(msg) self.log().error(msg)
raise FailedLoadModuleImportException(msg) raise FailedLoadModuleImportException(msg)
@@ -356,12 +365,12 @@ class MumoManager(Worker):
raise FailedLoadModuleConfigException(msg) raise FailedLoadModuleConfigException(msg)
# Make sure the module directory is in our python path and exists # Make sure the module directory is in our python path and exists
if not self.cfg.mumo.mod_dir in sys.path: if not self.cfg.modules.mod_dir in sys.path:
if not os.path.isdir(self.cfg.mumo.mod_dir): if not os.path.isdir(self.cfg.modules.mod_dir):
msg = "Module directory '%s' not found" % self.cfg.mumo.mod_dir msg = "Module directory '%s' not found" % self.cfg.modules.mod_dir
log.error(msg) log.error(msg)
raise FailedLoadModuleImportException(msg) raise FailedLoadModuleImportException(msg)
sys.path.append(self.cfg.mumo.mod_dir) sys.path.append(self.cfg.modules.mod_dir)
# Import the module and instanciate it # Import the module and instanciate it
try: try:

View File

@@ -33,6 +33,8 @@ import unittest
import Queue import Queue
from mumo_manager import MumoManager, MumoManagerRemote from mumo_manager import MumoManager, MumoManagerRemote
from mumo_module import MumoModule from mumo_module import MumoModule
import logging
from threading import Event
class MumoManagerTest(unittest.TestCase): class MumoManagerTest(unittest.TestCase):
@@ -40,45 +42,73 @@ class MumoManagerTest(unittest.TestCase):
class MyModule(MumoModule): class MyModule(MumoModule):
def __init__(self, name, manager, configuration = None): def __init__(self, name, manager, configuration = None):
MumoModule.__init__(self, name, manager, configuration) MumoModule.__init__(self, name, manager, configuration)
self.was_called = False
self.par1 = None
self.par2 = None
self.par3 = None
def last_call(self): self.estarted = Event()
ret = (self.was_called, self.par1, self.par2, self.par3) self.estopped = Event()
self.was_called = False self.econnected = Event()
return ret self.edisconnected = Event()
def call_me(self, par1, par2 = None, par3 = None): def onStart(self):
self.was_called = True self.estarted.set()
self.par1 = par1
self.par2 = par2 def onStop(self):
self.par3 = par3 self.estopped.set()
self.man = MumoManager() def connected(self):
self.man.start() self.econnected.set()
def disconnected(self):
self.edisconnected.set()
self.mymod = MyModule
class conf(object): class conf(object):
pass # Dummy class pass # Dummy class
cfg = conf() self.cfg = conf()
cfg.test = 10 self.cfg.test = 10
self.mod = self.man.loadModuleCls("MyModule", MyModule, cfg)
self.man.startModules()
#
#--- Helpers for independent test env creation
#
def up(self):
man = MumoManager()
man.start()
mod = man.loadModuleCls("MyModule", self.mymod, self.cfg)
man.startModules()
return (man, mod)
def down(self, man, mod):
man.stopModules()
man.stop()
man.join(timeout=1)
#
#--- Tests
#
def testModuleStarted(self):
man, mod = self.up()
mod.estarted.wait(timeout=1)
assert(mod.estarted.is_set())
self.down(man, mod)
def testModuleStopStart(self):
man ,mod = self.up()
tos = ["MyModule"]
self.assertEquals(list(man.stopModules(tos).iterkeys()), tos)
mod.estopped.wait(timeout=1)
assert(mod.estopped.is_set())
self.down(man, mod)
def tearDown(self): def tearDown(self):
self.man.stopModules()
self.man.stop()
self.man.join(timeout=2)
def testName(self):
pass pass
if __name__ == "__main__": if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName'] #import sys;sys.argv = ['', 'Test.testName']

View File

@@ -43,6 +43,8 @@ class MumoModule(Worker):
# If we are passed a string expect a config file there # If we are passed a string expect a config file there
if configuration: if configuration:
self.__cfg = Config(configuration, self.default_config) self.__cfg = Config(configuration, self.default_config)
elif self.default_config:
self.__cfg = Config(default = self.default_config)
else: else:
self.__cfg = None self.__cfg = None
else: else:

View File

@@ -44,6 +44,7 @@ mumo
[modules] [modules]
mod_dir = /usr/sbin/modules/ mod_dir = /usr/sbin/modules/
cfg_dir = /etc/modules-enabled/ cfg_dir = /etc/modules-enabled/
timeout = 2
[Ice] [Ice]
ip = 127.0.0.1 ip = 127.0.0.1