#!/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:
Posts (Atom)