import unittest, os, sys, StringIO, itertools, inspect, re, tempfile, time from myghty import util, interp, exception, csource, importer from myghty.component import Component class MyghtyTest(unittest.TestCase): def __init__(self, *args, **params): unittest.TestCase.__init__(self, *args, **params) # make ourselves a Myghty environment self.root = os.path.abspath(os.path.join(os.getcwd(), 'testroot', self.__class__.__module__, self.__class__.__name__)) # some templates self.htdocs = os.path.join(self.root, 'htdocs') # some more templates self.components = os.path.join(self.root, 'components') # data dir for cache, sessions, compiled self.cache = os.path.join(self.root, 'cache') # lib dir for some module components self.lib = os.path.join(self.root, 'lib') sys.path.insert(0, self.lib) for path in (self.htdocs, self.components, self.cache, self.lib): util.verify_directory(path) self.class_set_up() def class_set_up(self): pass def class_tear_down(self): pass def __del__(self): self.class_tear_down() def create_file(self, dir, name, contents): file = os.path.join(dir, name) f = open(file, 'w') f.write(contents) f.close() def create_directory(self, dir, path): util.verify_directory(os.path.join(dir, path)) def remove_file(self, dir, name): if os.access(os.path.join(dir, name), os.F_OK): os.remove(os.path.join(dir, name)) def deindent(text): """De-indent docstring like text. """ # Hack use inspect.getdoc() to do the de-indentation return inspect.getdoc(property(doc=text)) class ComponentTester(MyghtyTest): config = {} srcFiles = {} def __init__(self, *args, **params): MyghtyTest.__init__(self, *args, **params) # Clean up from previous runs now = time.time() for root, dirs, files in os.walk(self.root): try: for fn in [ os.path.join(root, f) for f in files ]: if os.stat(fn).st_mtime < now - 60: os.unlink(fn) except IOError: pass def class_set_up(self): MyghtyTest.class_set_up(self) for name, content in self.srcFiles.items(): self.createSrcFile(name, content) def createSrcFile(self, filename, content): m = re.match(r'\A ( htdocs | lib | components ) /+', filename, re.X) if m: filename = filename[m.end():] srcdir = getattr(self, m.group(1)) else: srcdir = self.htdocs self.create_file(srcdir, filename, deindent(content)) def makeInterpreter(self): config = dict(component_root=self.htdocs, raise_error=True, data_dir=self.cache ) config.update(self.config) return interp.Interpreter(**config) def setUp(self): self.interpreter = self.makeInterpreter() self.outputBuffer = StringIO.StringIO() output = property(lambda self: self.outputBuffer.getvalue()) tidyoutput = property(lambda self: re.sub(r'\s+', ' ', self.output.strip())) def create_anonymous_file(self, src, dir=None, suffix='.myt'): if dir is None: dir = os.path.join(self.root, 'tmp') util.verify_directory(dir) fd, path = tempfile.mkstemp(prefix='anon', suffix=suffix, dir=dir, text=True) os.write(fd, deindent(src)) os.close(fd) return os.path.basename(path) def makeModule(self, src): # create a python module in self.lib name = self.create_anonymous_file(src, dir=self.lib, suffix='.py') return importer.module(os.path.splitext(name)[0]) def makeModuleComponent(self, arg): # src can be a string like "module:callable", etc... # or it can be an actually callable, etc... return self.interpreter.load_module_component(arg=arg) def makeFileBasedComponent(self, src): name = self.create_anonymous_file(src, dir=self.htdocs) return self.loadComponent("/%s" % name) def makeMemoryComponent(self, src): return self.interpreter.make_component(deindent(src)) def makeComponent(self, src): """Make a component of the default sort. The idea is that this can be specialized by subclasses to change the type of component that gets constructed by code like: self.runComponent('''# Here's source code for some component foo ''') """ return self.makeFileBasedComponent(src) def loadComponent(self, component): if not isinstance(component, basestring): # Wrap callable, etc... to a module component return self.makeModuleComponent(component) if re.search(r'\s', component): # if string contains white-space, interpret it as the # source for a myghty component, rather than the path # to be resolved. return self.makeComponent(component) return self.interpreter.load(component) def runComponent(self, component, config={}, **request_args): request_config = dict(request_args=request_args, out_buffer=self.outputBuffer ) request_config.update(config) if not isinstance(component, Component): component = self.loadComponent(component) return self.interpreter.execute(component, **request_config) def failUnlessRaisesWrappedError(self, wrapped_exception, func, *args, **kw): self.failUnlessRaises(wrapped_exception, unwrap_error, func, *args, **kw) def unwrap_error(func, *args, **kw): """Call function, if it raises exception.Error, raise the wrapped exception. """ try: return func(*args, **kw) except exception.Error, error: if hasattr(error, 'raw_excinfo'): type, value, tb = error.raw_excinfo raise type, value, tb elif error.wrapped is not None: raise error.wrapped raise # Add advice to unittest.TestResult.addError to unwrap wrapped Errors TestResult_addError = unittest.TestResult.addError instancemethod = type(TestResult_addError) def addError(self, test, err): if isinstance(err[1], exception.Error): err = getattr(err[1], 'raw_excinfo', err) TestResult_addError.im_func(self, test, err) unittest.TestResult.addError = addError def runTests(suite): runner = unittest.TextTestRunner(verbosity = 2, descriptions =1) runner.run(suite) if __name__ == '__main__': unittest.main()