######################################################################## # $Header: /var/local/cvsroot/4Suite/Ft/Lib/TestSuite/TestCoverage.py,v 1.11 2006/08/11 15:50:12 jkloth Exp $ """ A class that uses Python's profiler to help TestModule know which functions have been called in a particular module. Copyright 2004 Fourthought, Inc. (USA). Detailed license and copyright information: http://4suite.org/COPYRIGHT Project home, documentation, distributions: http://4suite.org/ """ import sys, os, inspect from Ft.Lib import ImportUtil class TestCoverage: def __init__(self, moduleName, ignored=None): self.module = moduleName self.path = ImportUtil.GetSearchPath(moduleName) self.ignored = ignored or [] self.data = {} return def _getFunctionList(self): modules = [] deferred = [] for importer, name, ispkg in ImportUtil.IterModules(self.path): fullname = self.module + '.' + name try: __import__(fullname) except ImportError: deferred.append(fullname) else: modules.append(sys.modules[fullname]) while deferred: changed = False for fullname in tuple(deferred): try: __import__(fullname) except ImportError: pass else: changed = True modules.append(sys.modules[fullname]) deferred.remove(fullname) if not changed: # no more modules able to be loaded raise ValueError(",".join([ item[1] for item in deferred ])) break data = {} for module in modules: for value in vars(module).values(): if value in self.ignored: continue if inspect.isfunction(value): data[value.func_code] = value elif inspect.isclass(value): # Get the methods defined on this class methods = inspect.getmembers(value, inspect.ismethod) for name, method in methods: if method in self.ignored or \ name in ('__str__', '__repr__', 'pprint'): continue # Make sure this method is not a C function if inspect.isfunction(method.im_func): data[method.im_func.func_code] = method # Only watch the objects that are defined directly in this package self.data = {} for code, object in data.items(): if os.path.dirname(code.co_filename) in self.path: self.data[code] = object return def _dispatch(self, frame, event, arg): if event == 'call': fcode = frame.f_code # Any items remaining in self.data will be those not called if self.data.has_key(fcode): del self.data[fcode] return 1 def _start(self, tester): self._getFunctionList() sys.setprofile(self._dispatch) return def _end(self, tester): # Stop watching function calls sys.setprofile(None) modules = {} for code, object in self.data.items(): if inspect.ismethod(object): type = 'method' name = object.im_class.__name__ + '.' + object.__name__ module = object.im_class.__module__ else: type = 'function' name = object.__name__ module = object.func_globals['__name__'] if not modules.has_key(module): modules[module] = {'method' : [], 'function' : [], } modules[module][type].append((name, code.co_firstlineno)) tester.startGroup('Coverage Test') tester.startTest("Verifying called functions") keys = modules.keys() keys.sort() for module in keys: lines = [] if modules[module]['function']: funcs = modules[module]['function'] lines.append('Functions not called in %r:' % module) funcs.sort() for name, line in funcs: lines.append(' %r on line %s' % (name, line)) if modules[module]['method']: lines.append('Methods not called in %r:' % module) meths = modules[module]['method'] meths.sort() for name, line in meths: lines.append(' %r on line %s' % (name, line)) tester.warning('\n'.join(lines)) tester.testDone() tester.groupDone() return