########################################################################
# $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
|