#!/cygdrive/c/Python27/python -i 'c:\crunch6SVN\python\pyTest.py'
#!/cygdrive/c/Python26_64/python 'c:\crunch6SVN\python\pyTest.py'
#!/usr/bin/env python
#!/usr/local/python/bin/python
# this works from powershell, but not from xterm or within spyder:
# C:\Python27\python ..\..\pyTest.py .\scanCoverage.py -g
# i think spyder puts in some trace hooks into pdb of its own
import sys
import os
#import ipdb
#dbg = ipdb.set_trace
from pdb import set_trace as dbg
def lineProfile(runStr,runContext={},module=None,moduleOnly=False):
# with the run string set up, i can use cProfile to find the worst offenders
import cProfile,pstats
import line_profiler
import sys
prof = cProfile.Profile()
#r = prof.runctx(runStr,{},{'p':p,'sout':sout})
r = prof.runctx(runStr,{},runContext)
# maybe use prof.dump_stats() to spit out to a file
r = pstats.Stats(prof).strip_dirs().sort_stats('time').print_stats(5)
#get line profiling on top 3 time hog functions
ss = pstats.Stats(prof).sort_stats('time')
def b(fn): return fn.rstrip('.py').rstrip('.pyc') #### rstrip takes or
if moduleOnly:
# only show functions in this file
hogs = [f[2] for f in ss.fcn_list if b(f[0])==b(__file__)][:3]
ts = [ss.stats[f][2] for f in ss.fcn_list if b(f[0])==b(__file__)][:3]
else:
#hogs = [f[2] for f in ss.fcn_list][:3]
hogs = ss.fcn_list[:3]
ts = [ss.stats[f][2] for f in ss.fcn_list][:3]
fts = [t/ss.total_tt for t in ts]
# ignore any functions beyond what accounts for 80% of the time
for i in range(len(fts)):
if sum(fts[:i])>.8: break
hogs,ts,fts = hogs[:i],ts[:i],fts[:i]
hogs.reverse();ts.reverse();fts.reverse() # i want longest time last
# can't line prof builtins, so take them out of the list
fts = [f for f,h in zip(fts,hogs) if not h[0]=='~']
ts = [t for t,h in zip(ts,hogs) if not h[0]=='~']
hogs = [h for h in hogs if not h[0]=='~']
# this probably won't work in pyTest:
#fs = [[getattr(x,h) for x in locals().values() if hasattr(x,h)][0]
#fs = [[getattr(x,h) for x in sys.modules.values() if hasattr(x,h)][0]
# pstats only saves module filename, so match files and search within them
# rstrip for .pyc, .pyo
modules = [x.__file__.rstrip('oc') for x in sys.modules.values() if hasattr(x,'__file__')]
indices = [modules.index(h[0].rstrip('oc')) for h in hogs]
modules = [x for x in sys.modules.values() if hasattr(x,'__file__')]
hogMods = [modules[i] for i in indices]
# find functions/methods within module
# only searches down one level instead of a full tree search, so don't
# get too crazy with deeply nested defs
fs = []
for ln,h,m in zip(*zip(*hogs)[1:3]+[hogMods]):
#import pdb;pdb.set_trace()
if hasattr(m,h) and hasattr(getattr(m,h),'__code__') and getattr(m,h).__code__.co_firstlineno == ln: fs.append(getattr(m,h))
else:
for a in [getattr(m,x) for x in dir(m)]:
if hasattr(a,h) and hasattr(getattr(a,h),'__code__') and getattr(a,h).__code__.co_firstlineno == ln:
fs.append(getattr(a,h))
break
#fs = [[getattr(x,h) for x in runContext.values() if hasattr(x,h)][0]
# for h in hogs]
lprof = line_profiler.LineProfiler()
for f in fs: lprof.add_function(f)
#stats = lprof.runctx(runStr,{},{'p':p,'sout':sout}).get_stats()
stats = lprof.runctx(runStr,{},runContext).get_stats()
for ((fn,lineno,name),timings),ft in zip(sorted(stats.timings.items(),reverse=True),fts):
line_profiler.show_func(fn,lineno,name,stats.timings[fn,lineno,name],stats.unit)
print 'this function accounted for \033[0;31m%2.2f%%\033[m of total time'%(ft*100)
#import pdb;pdb.set_trace()
# monkey patches to allow coverage analysis to work
# just a little disturbing that (as of 2.4) doctest and trace coverage
# don't work together...
def monkeypatchDoctest():
# stolen from http://coltrane.bx.psu.edu:8192/svn/bx-python/trunk/setup.py
#
# Doctest and coverage don't get along, so we need to create
# a monkeypatch that will replace the part of doctest that
# interferes with coverage reports.
#
# The monkeypatch is based on this zope patch:
# http://svn.zope.org/Zope3/trunk/src/zope/testing/doctest.py?rev=28679&r1=28703&r2=28705
#
try:
import doctest
_orp = doctest._OutputRedirectingPdb
class NoseOutputRedirectingPdb(_orp):
def __init__(self, out):
self.__debugger_used = False
_orp.__init__(self, out)
def set_trace(self):
self.__debugger_used = True
#_orp.set_trace(self)
pdb.Pdb.set_trace(self)
def set_continue(self):
# Calling set_continue unconditionally would break unit test coverage
# reporting, as Bdb.set_continue calls sys.settrace(None).
if self.__debugger_used:
#_orp.set_continue(self)
pdb.Pdb.set_continue(self)
doctest._OutputRedirectingPdb = NoseOutputRedirectingPdb
except:
raise #pass
return doctest
def monkeypatchTrace():
import trace
try:
t = trace.Trace
class NoDoctestCounts(t):
def results(self):
cs = self.counts
newcs = {}
# throw away 'files' that start with = 2.6 will not allow import by filename
# i should refactor the whole thing to use imp module
sys.path.insert(0,os.path.dirname(n))
n = os.path.splitext(os.path.basename(n))[0]
if not n.startswith('-'):
if True:#try:
if debug:
# __import__ needs a non-empty fromlist if it's a submodule
if '.' in n:
try: m = __import__(n,None,None,[True,])
except ImportError: # just run doctests for an object
modName = '.'.join(n.split('.')[:-1])
#objName = n.split('.')[-1]
m = __import__(modName,None,None,[True,])
#doctest.run_docstring_examples(m.__dict__[objName],m.__dict__,name=objName)
doctest.debug(m,n,True)
import sys
sys.exit()
else: m = __import__(n)
for i in m.__dict__.values():
import abc
# if it's a class (from a metaclass or metametaclass) or function
if type(i) == type or type(i) == abc.ABCMeta or \
(type(type(i)) == type and hasattr(i,'__name__')) \
or type(i) == type(lineProfile):
try:
print 'Testing',i.__name__
doctest.debug(m,n+'.'+i.__name__,True)
except ValueError:
print 'No doctests for', i.__name__
else:
import pdb
if coverage:
#### need a better way to get module filenames without
# importing them. (after initial import, the class and
# def lines will not be executed, so will erroneously
# be flagged as not tested.)
#d,name = os.path.split(m.__file__)
d,name = '.',n
#bn = trace.fullmodname(name)
bn = name.split('.')[-1]
# ignore all modules except the one being tested
ignoremods = []
mods = [trace.fullmodname(x) for x in os.listdir(d)]
for ignore,mod in zip([bn != x for x in mods], mods):
if ignore: ignoremods.append(mod)
tracer = trace.Trace(
ignoredirs=[sys.prefix, sys.exec_prefix],
ignoremods=ignoremods,
trace=0,
count=1)
if '.' in n:
tracer.run('m = __import__(n,None,None,[True,])')
else: tracer.run('m = __import__(n)')
tracer.run('doctest.testmod(m)')
r = tracer.results()
r.write_results(show_missing=True, coverdir='.')
else:
# __import__ needs a non-empty fromlist if it's a submodule
if '.' in n:
try: m = __import__(n,None,None,[True,])
except ImportError: # just run doctests for an object
modName = '.'.join(n.split('.')[:-1])
objName = n.split('.')[-1]
m = __import__(modName,None,None,[True,])
doctest.run_docstring_examples(m.__dict__[objName],m.__dict__,name=objName)
import sys
sys.exit()
else:
#import pdb; pdb.set_trace()
m = __import__(n)
# dangerously convenient deletion of any old coverage files
try: os.remove(trace.modname(m.__file__)+'.cover')
except OSError: pass
# need to call profile function from the doctest
# so that it can set up the context and identify the run string, because anything not passed back will get garbage collected
# and there's no way to pass anything back
# but how can i call something within pyTest from the doctest string? some kind of callback?
# i want pyTest to decide if it gets called, so i can switch from the command line
doctest.testmod(m)
if profile:
runStr,runContext = m._profile()
lineProfile(runStr,runContext,m)
else:#except Exception,e:
print 'Could not test '+n
print e
raise e
q = quit
from sys import exit as e
Sunday, April 1, 2012
my own doctest runner
Subscribe to:
Comments (Atom)