📄 trace.py
字号:
lines_hit[lineno] = self.counts[(filename, lineno)] # there are many places where this is insufficient, like a blank # line embedded in a multiline string. blank = re.compile(r'^\s*(#.*)?$') # accumulate summary info, if needed sums = {} # generate file paths for the coverage files we are going to write... fnlist = [] tfdir = tempfile.gettempdir() for key in per_file.keys(): filename = key # skip some "files" we don't care about... if filename == "<string>": continue # are these caused by code compiled using exec or something? if filename.startswith(tfdir): continue modulename = inspect.getmodulename(filename) if filename.endswith(".pyc") or filename.endswith(".pyo"): filename = filename[:-1] if coverdir: thiscoverdir = coverdir else: thiscoverdir = os.path.dirname(os.path.abspath(filename)) # the code from here to "<<<" is the contents of the `fileutil.make_dirs()' function in the Mojo Nation project. --Zooko 2001-10-14 # http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/mojonation/evil/common/fileutil.py?rev=HEAD&content-type=text/vnd.viewcvs-markup tx = None try: os.makedirs(thiscoverdir) except OSError, x: tx = x if not os.path.isdir(thiscoverdir): if tx: raise tx raise exceptions.IOError, "unknown error prevented creation of directory: %s" % thiscoverdir # careful not to construct an IOError with a 2-tuple, as that has a special meaning... # <<< # build list file name by appending a ".cover" to the module name # and sticking it into the specified directory if "." in modulename: # A module in a package finalname = modulename.split(".")[-1] listfilename = os.path.join(thiscoverdir, finalname + ".cover") else: listfilename = os.path.join(thiscoverdir, modulename + ".cover") # Get the original lines from the .py file try: lines = open(filename, 'r').readlines() except IOError, err: sys.stderr.write("trace: Could not open %s for reading because: %s - skipping\n" % (`filename`, err)) continue try: outfile = open(listfilename, 'w') except IOError, err: sys.stderr.write( '%s: Could not open %s for writing because: %s" \ "- skipping\n' % ("trace", `listfilename`, err)) continue # If desired, get a list of the line numbers which represent # executable content (returned as a dict for better lookup speed) if show_missing: executable_linenos = find_executable_linenos(filename) else: executable_linenos = {} n_lines = 0 n_hits = 0 lines_hit = per_file[key] for i in range(len(lines)): line = lines[i] # do the blank/comment match to try to mark more lines # (help the reader find stuff that hasn't been covered) if lines_hit.has_key(i+1): # count precedes the lines that we captured outfile.write('%5d: ' % lines_hit[i+1]) n_hits = n_hits + 1 n_lines = n_lines + 1 elif blank.match(line): # blank lines and comments are preceded by dots outfile.write(' . ') else: # lines preceded by no marks weren't hit # Highlight them if so indicated, unless the line contains # '#pragma: NO COVER' (it is possible to embed this into # the text as a non-comment; no easy fix) if executable_linenos.has_key(i+1) and \ string.find(lines[i], string.join(['#pragma', 'NO COVER'])) == -1: outfile.write('>>>>>> ') else: outfile.write(' '*7) n_lines = n_lines + 1 outfile.write(string.expandtabs(lines[i], 8)) outfile.close() if summary and n_lines: percent = int(100 * n_hits / n_lines) sums[modulename] = n_lines, percent, modulename, filename if summary and sums: mods = sums.keys() mods.sort() print "lines cov% module (path)" for m in mods: n_lines, percent, modulename, filename = sums[m] print "%5d %3d%% %s (%s)" % sums[m] if self.outfile: # try and store counts and module info into self.outfile try: pickle.dump((self.counts, self.calledfuncs,), open(self.outfile, 'w'), 1) except IOError, err: sys.stderr.write("cannot save counts files because %s" % err)# Given a code string, return the SET_LINENO informationdef _find_LINENO_from_string(co_code): """return all of the SET_LINENO information from a code string""" import dis linenos = {} # This code was filched from the `dis' module then modified n = len(co_code) i = 0 prev_op = None prev_lineno = 0 while i < n: c = co_code[i] op = ord(c) if op == dis.SET_LINENO: if prev_op == op: # two SET_LINENO in a row, so the previous didn't # indicate anything. This occurs with triple # quoted strings (?). Remove the old one. del linenos[prev_lineno] prev_lineno = ord(co_code[i+1]) + ord(co_code[i+2])*256 linenos[prev_lineno] = 1 if op >= dis.HAVE_ARGUMENT: i = i + 3 else: i = i + 1 prev_op = op return linenosdef _find_LINENO(code): """return all of the SET_LINENO information from a code object""" import types # get all of the lineno information from the code of this scope level linenos = _find_LINENO_from_string(code.co_code) # and check the constants for references to other code objects for c in code.co_consts: if type(c) == types.CodeType: # find another code object, so recurse into it linenos.update(_find_LINENO(c)) return linenosdef find_executable_linenos(filename): """return a dict of the line numbers from executable statements in a file Works by finding all of the code-like objects in the module then searching the byte code for 'SET_LINENO' terms (so this won't work one -O files). """ import parser assert filename.endswith('.py') prog = open(filename).read() ast = parser.suite(prog) code = parser.compileast(ast, filename) # The only way I know to find line numbers is to look for the # SET_LINENO instructions. Isn't there some way to get it from # the AST? return _find_LINENO(code)### XXX because os.path.commonprefix seems broken by my way of thinking...def commonprefix(dirs): "Given a list of pathnames, returns the longest common leading component" if not dirs: return '' n = copy.copy(dirs) for i in range(len(n)): n[i] = n[i].split(os.sep) prefix = n[0] for item in n: for i in range(len(prefix)): if prefix[:i+1] <> item[:i+1]: prefix = prefix[:i] if i == 0: return '' break return os.sep.join(prefix)class Trace: def __init__(self, count=1, trace=1, countfuncs=0, ignoremods=(), ignoredirs=(), infile=None, outfile=None): """ @param count true iff it should count number of times each line is executed @param trace true iff it should print out each line that is being counted @param countfuncs true iff it should just output a list of (filename, modulename, funcname,) for functions that were called at least once; This overrides `count' and `trace' @param ignoremods a list of the names of modules to ignore @param ignoredirs a list of the names of directories to ignore all of the (recursive) contents of @param infile file from which to read stored counts to be added into the results @param outfile file in which to write the results """ self.pid = os.getpid() self.infile = infile self.outfile = outfile self.ignore = Ignore(ignoremods, ignoredirs) self.counts = {} # keys are (filename, linenumber) self.blabbed = {} # for debugging self.pathtobasename = {} # for memoizing os.path.basename self.donothing = 0 self.trace = trace self._calledfuncs = {} if countfuncs: self.globaltrace = self.globaltrace_countfuncs elif trace and count: self.globaltrace = self.globaltrace_lt self.localtrace = self.localtrace_trace_and_count elif trace: self.globaltrace = self.globaltrace_lt self.localtrace = self.localtrace_trace elif count: self.globaltrace = self.globaltrace_lt self.localtrace = self.localtrace_count else: # Ahem -- do nothing? Okay. self.donothing = 1 def run(self, cmd): import __main__ dict = __main__.__dict__ if not self.donothing: sys.settrace(self.globaltrace) try: exec cmd in dict, dict finally: if not self.donothing: sys.settrace(None) def runctx(self, cmd, globals=None, locals=None): if globals is None: globals = {} if locals is None: locals = {} if not self.donothing: sys.settrace(self.globaltrace)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -