2000-08-03 16:26:21 -03:00
#!/usr/bin/env python
2001-11-28 15:41:45 -04:00
# portions copyright 2001, Autonomous Zones Industries, Inc., all rights...
# err... reserved and offered to the public under the terms of the
# Python 2.2 license.
# Author: Zooko O'Whielacronx
# http://zooko.com/
# mailto:zooko@zooko.com
#
2000-08-03 16:26:21 -03:00
# Copyright 2000, Mojam Media, Inc., all rights reserved.
# Author: Skip Montanaro
#
# Copyright 1999, Bioreason, Inc., all rights reserved.
# Author: Andrew Dalke
#
# Copyright 1995-1997, Automatrix, Inc., all rights reserved.
# Author: Skip Montanaro
#
# Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
#
#
# Permission to use, copy, modify, and distribute this Python software and
# its associated documentation for any purpose without fee is hereby
# granted, provided that the above copyright notice appears in all copies,
# and that both that copyright notice and this permission notice appear in
# supporting documentation, and that the name of neither Automatrix,
# Bioreason or Mojam Media be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior permission.
#
#
2001-11-28 15:41:45 -04:00
# Cleaned up the usage message --GvR 11/28/01
#
# Summary of even more recent changes, --Zooko 2001-10-14
# Used new `inspect' module for better (?) determination of file<->module
# mappings, line numbers, and source code.
# Used new local trace function for faster (and better?) operation.
# Removed "speed hack", which, as far as I can tell, meant that it would
# ignore all files ??? (When I tried it, it would ignore only *most* of my
# files. In any case with the speed hack removed in favor of actually
# calling `Ignore.names()', it ignores only those files that I told it to
# ignore, so I am happy.)
# Rolled the `Coverage' class into `Trace', which now does either tracing or
# counting or both according to constructor flags.
# Moved the construction of the `Ignore' object inside the constructor of
# `Trace', simplifying usage.
# Changed function `create_results_log()' into method
# `CoverageResults.write_results()'.
# Add new mode "countfuncs" which is faster and which just reports which
# functions were invoked.
# Made `write_results' create `coverdir' if it doesn't already exist.
# Moved the `run' funcs into `Trace' for simpler usage.
# Use pickle instead of marshal for persistence.
#
2000-08-03 16:26:21 -03:00
# Summary of recent changes:
# Support for files with the same basename (submodules in packages)
# Expanded the idea of how to ignore files or modules
# Split tracing and counting into different classes
# Extracted count information and reporting from the count class
# Added some ability to detect which missing lines could be executed
# Added pseudo-pragma to prohibit complaining about unexecuted lines
# Rewrote the main program
# Summary of older changes:
# Added run-time display of statements being executed
# Incorporated portability and performance fixes from Greg Stein
# Incorporated main program from Michael Scharf
"""
program / module to trace Python program or function execution
Sample use , command line :
trace . py - c - f counts - - ignore - dir ' $prefix ' spam . py eggs
trace . py - t - - ignore - dir ' $prefix ' spam . py eggs
2001-11-28 15:41:45 -04:00
Sample use , programmatically
# create a Trace object, telling it what to ignore, and whether to do tracing
# or line-counting or both.
trace = trace . Trace ( ignoredirs = [ sys . prefix , sys . exec_prefix , ] , trace = 0 , count = 1 )
2000-08-03 16:26:21 -03:00
# run the new command using the given trace
2001-11-28 15:41:45 -04:00
trace . run ( coverage . globaltrace , ' main() ' )
2000-08-03 16:26:21 -03:00
# make a report, telling it where you want output
2001-11-28 15:41:45 -04:00
trace . print_results ( show_missing = 1 )
2000-08-03 16:26:21 -03:00
"""
2001-11-28 15:41:45 -04:00
import sys , os , string , tempfile , types , copy , operator , inspect , exceptions , marshal
try :
import cPickle
pickle = cPickle
except ImportError :
import pickle
true = 1
false = None
# DEBUG_MODE=1 # make this true to get printouts which help you understand what's going on
2000-08-03 16:26:21 -03:00
def usage ( outfile ) :
outfile . write ( """ Usage: %s [OPTIONS] <file> [ARGS]
2001-11-28 15:41:45 -04:00
Meta - options :
- - help Display this help then exit .
- - version Output version information then exit .
Otherwise , exactly one of the following three options must be given :
- t , - - trace Print each line to sys . stdout before it is executed .
- c , - - count Count the number of times each line is executed
and write the counts to < module > . cover for each
module executed , in the module ' s directory.
See also ` - - coverdir ' , `--file ' , ` - - no - report ' below.
- r , - - report Generate a report from a counts file ; do not execute
any code . ` - - file ' must specify the results file to
read , which must have been created in a previous run
with ` - - count - - file = FILE ' .
Modifiers :
- f , - - file = < file > File to accumulate counts over several runs .
- R , - - no - report Do not generate the coverage report files .
Useful if you want to accumulate over several runs .
- C , - - coverdir = < dir > Directory where the report files . The coverage
report for < package > . < module > is written to file
< dir > / < package > / < module > . cover .
- m , - - missing Annotate executable lines that were not executed
with ' >>>>>> ' .
- s , - - summary Write a brief summary on stdout for each file .
( Can only be used with - - count or - - report . )
Filters , may be repeated multiple times :
- - ignore - module = < mod > Ignore the given module and its submodules
( if it is a package ) .
- - ignore - dir = < dir > Ignore files in the given directory ( multiple
directories can be joined by os . pathsep ) .
2000-08-03 16:26:21 -03:00
""" % s ys.argv[0])
class Ignore :
def __init__ ( self , modules = None , dirs = None ) :
2000-09-17 22:46:01 -03:00
self . _mods = modules or [ ]
self . _dirs = dirs or [ ]
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
self . _dirs = map ( os . path . normpath , self . _dirs )
2000-09-17 22:46:01 -03:00
self . _ignore = { ' <string> ' : 1 }
2000-08-03 16:26:21 -03:00
def names ( self , filename , modulename ) :
2000-09-17 22:46:01 -03:00
if self . _ignore . has_key ( modulename ) :
return self . _ignore [ modulename ]
# haven't seen this one before, so see if the module name is
# on the ignore list. Need to take some care since ignoring
# "cmp" musn't mean ignoring "cmpcache" but ignoring
# "Spam" must also mean ignoring "Spam.Eggs".
for mod in self . _mods :
if mod == modulename : # Identical names, so ignore
self . _ignore [ modulename ] = 1
return 1
# check if the module is a proper submodule of something on
# the ignore list
n = len ( mod )
# (will not overflow since if the first n characters are the
# same and the name has not already occured, then the size
# of "name" is greater than that of "mod")
if mod == modulename [ : n ] and modulename [ n ] == ' . ' :
self . _ignore [ modulename ] = 1
return 1
# Now check that __file__ isn't in one of the directories
if filename is None :
# must be a built-in, so we must ignore
self . _ignore [ modulename ] = 1
return 1
# Ignore a file when it contains one of the ignorable paths
for d in self . _dirs :
# The '+ os.sep' is to ensure that d is a parent directory,
# as compared to cases like:
# d = "/usr/local"
# filename = "/usr/local.py"
# or
# d = "/usr/local.py"
# filename = "/usr/local.py"
if string . find ( filename , d + os . sep ) == 0 :
self . _ignore [ modulename ] = 1
return 1
# Tried the different ways, so we don't ignore this module
self . _ignore [ modulename ] = 0
return 0
2000-08-03 16:26:21 -03:00
class CoverageResults :
2001-11-28 15:41:45 -04:00
def __init__ ( self , counts = None , calledfuncs = None , infile = None , outfile = None ) :
self . counts = counts
if self . counts is None :
self . counts = { }
self . counter = self . counts . copy ( ) # map (filename, lineno) to count
self . calledfuncs = calledfuncs
if self . calledfuncs is None :
self . calledfuncs = { }
self . calledfuncs = self . calledfuncs . copy ( )
self . infile = infile
self . outfile = outfile
if self . infile :
# try and merge existing counts file
try :
thingie = pickle . load ( open ( self . infile , ' r ' ) )
if type ( thingie ) is types . DictType :
# backwards compatibility for old trace.py after Zooko touched it but before calledfuncs --Zooko 2001-10-24
self . update ( self . __class__ ( thingie ) )
elif type ( thingie ) is types . TupleType and len ( thingie ) == 2 :
( counts , calledfuncs , ) = thingie
self . update ( self . __class__ ( counts , calledfuncs ) )
except ( IOError , EOFError , ) :
pass
except pickle . UnpicklingError :
# backwards compatibility for old trace.py before Zooko touched it --Zooko 2001-10-24
self . update ( self . __class__ ( marshal . load ( open ( self . infile ) ) ) )
2000-08-03 16:26:21 -03:00
def update ( self , other ) :
2000-09-17 22:46:01 -03:00
""" Merge in the data from another CoverageResults """
counts = self . counts
2001-11-28 15:41:45 -04:00
calledfuncs = self . calledfuncs
2000-09-17 22:46:01 -03:00
other_counts = other . counts
2001-11-28 15:41:45 -04:00
other_calledfuncs = other . calledfuncs
2000-09-17 22:46:01 -03:00
for key in other_counts . keys ( ) :
2001-11-28 15:41:45 -04:00
if key != ' calledfuncs ' : # backwards compatibility for abortive attempt to stuff calledfuncs into self.counts, by Zooko --Zooko 2001-10-24
counts [ key ] = counts . get ( key , 0 ) + other_counts [ key ]
for key in other_calledfuncs . keys ( ) :
calledfuncs [ key ] = 1
def write_results ( self , show_missing = 1 , summary = 0 , coverdir = None ) :
"""
@param coverdir
"""
for ( filename , modulename , funcname , ) in self . calledfuncs . keys ( ) :
print " filename: %s , modulename: %s , funcname: %s " % ( filename , modulename , funcname , )
import re
# turn the counts data ("(filename, lineno) = count") into something
# accessible on a per-file basis
per_file = { }
for thingie in self . counts . keys ( ) :
if thingie != " calledfuncs " : # backwards compatibility for abortive attempt to stuff calledfuncs into self.counts, by Zooko --Zooko 2001-10-24
( filename , lineno , ) = thingie
lines_hit = per_file [ filename ] = per_file . get ( filename , { } )
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 )
2000-09-17 22:46:01 -03:00
else :
2001-11-28 15:41:45 -04:00
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 )
2000-08-03 16:26:21 -03:00
# Given a code string, return the SET_LINENO information
def _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 :
2000-09-17 22:46:01 -03:00
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
2000-08-03 16:26:21 -03:00
return linenos
def _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 :
2000-09-17 22:46:01 -03:00
if type ( c ) == types . CodeType :
# find another code object, so recurse into it
linenos . update ( _find_LINENO ( c ) )
2000-08-03 16:26:21 -03:00
return linenos
def 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
2001-05-08 01:20:52 -03:00
assert filename . endswith ( ' .py ' )
2000-08-03 16:26:21 -03:00
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?
2001-01-17 04:48:39 -04:00
2000-08-03 16:26:21 -03:00
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 )
2001-01-17 04:48:39 -04:00
2001-11-28 15:41:45 -04:00
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 . 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
2001-05-08 01:20:52 -03:00
else :
2001-11-28 15:41:45 -04:00
# Ahem -- do nothing? Okay.
self . donothing = 1
def run ( self , cmd ) :
import __main__
dict = __main__ . __dict__
if not self . donothing :
sys . settrace ( self . globaltrace )
2000-09-17 22:46:01 -03:00
try :
2001-11-28 15:41:45 -04:00
exec cmd in dict , dict
finally :
if not self . donothing :
sys . settrace ( None )
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
def runctx ( self , cmd , globals = None , locals = None ) :
if globals is None : globals = { }
if locals is None : locals = { }
if not self . donothing :
2002-07-25 13:09:35 -03:00
sys . settrace ( self . globaltrace )
2000-09-17 22:46:01 -03:00
try :
2002-07-25 13:09:35 -03:00
exec cmd in globals , locals
2001-11-28 15:41:45 -04:00
finally :
if not self . donothing :
sys . settrace ( None )
2000-09-17 22:46:01 -03:00
2001-11-28 15:41:45 -04:00
def runfunc ( self , func , * args , * * kw ) :
result = None
if not self . donothing :
sys . settrace ( self . globaltrace )
try :
result = apply ( func , args , kw )
finally :
if not self . donothing :
sys . settrace ( None )
return result
def globaltrace_countfuncs ( self , frame , why , arg ) :
"""
Handles ` call ' events (why == ' call ' ) and adds the (filename, modulename, funcname,) to the self._calledfuncs dict.
"""
if why == ' call ' :
( filename , lineno , funcname , context , lineindex , ) = inspect . getframeinfo ( frame , 0 )
if filename :
modulename = inspect . getmodulename ( filename )
2000-09-17 22:46:01 -03:00
else :
2001-05-08 01:20:52 -03:00
modulename = None
2001-11-28 15:41:45 -04:00
self . _calledfuncs [ ( filename , modulename , funcname , ) ] = 1
def globaltrace_lt ( self , frame , why , arg ) :
"""
Handles ` call ' events (why == ' call ' ) and if the code block being entered is to be ignored then it returns `None ' , else it returns ` self . localtrace ' .
"""
if why == ' call ' :
( filename , lineno , funcname , context , lineindex , ) = inspect . getframeinfo ( frame , 0 )
# if DEBUG_MODE and not filename:
# print "%s.globaltrace(frame: %s, why: %s, arg: %s): filename: %s, lineno: %s, funcname: %s, context: %s, lineindex: %s\n" % (self, frame, why, arg, filename, lineno, funcname, context, lineindex,)
if filename :
modulename = inspect . getmodulename ( filename )
2002-07-25 13:09:35 -03:00
if modulename is not None :
ignore_it = self . ignore . names ( filename , modulename )
# if DEBUG_MODE and not self.blabbed.has_key((filename, modulename,)):
# self.blabbed[(filename, modulename,)] = None
# print "%s.globaltrace(frame: %s, why: %s, arg: %s, filename: %s, modulename: %s, ignore_it: %s\n" % (self, frame, why, arg, filename, modulename, ignore_it,)
if not ignore_it :
if self . trace :
print " --- modulename: %s , funcname: %s " % ( modulename , funcname , )
# if DEBUG_MODE:
# print "%s.globaltrace(frame: %s, why: %s, arg: %s, filename: %s, modulename: %s, ignore_it: %s -- about to localtrace\n" % (self, frame, why, arg, filename, modulename, ignore_it,)
return self . localtrace
2001-11-28 15:41:45 -04:00
else :
# XXX why no filename?
return None
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
def localtrace_trace_and_count ( self , frame , why , arg ) :
2000-09-17 22:46:01 -03:00
if why == ' line ' :
2001-11-28 15:41:45 -04:00
# record the file name and line number of every trace
# XXX I wish inspect offered me an optimized `getfilename(frame)' to use in place of the presumably heavier `getframeinfo()'. --Zooko 2001-10-14
( filename , lineno , funcname , context , lineindex , ) = inspect . getframeinfo ( frame , 1 )
key = ( filename , lineno , )
self . counts [ key ] = self . counts . get ( key , 0 ) + 1
# XXX not convinced that this memoizing is a performance win -- I don't know enough about Python guts to tell. --Zooko 2001-10-14
bname = self . pathtobasename . get ( filename )
if bname is None :
# Using setdefault faster than two separate lines? --Zooko 2001-10-14
bname = self . pathtobasename . setdefault ( filename , os . path . basename ( filename ) )
2001-05-08 01:20:52 -03:00
try :
2001-11-28 15:41:45 -04:00
print " %s ( %d ): %s " % ( bname , lineno , context [ lineindex ] , ) ,
except IndexError :
# Uh.. sometimes getframeinfo gives me a context of length 1 and a lineindex of -2. Oh well.
pass
return self . localtrace
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
def localtrace_trace ( self , frame , why , arg ) :
if why == ' line ' :
# XXX shouldn't do the count increment when arg is exception? But be careful to return self.localtrace when arg is exception! ? --Zooko 2001-10-14
# record the file name and line number of every trace
# XXX I wish inspect offered me an optimized `getfilename(frame)' to use in place of the presumably heavier `getframeinfo()'. --Zooko 2001-10-14
( filename , lineno , funcname , context , lineindex , ) = inspect . getframeinfo ( frame )
# if DEBUG_MODE:
# print "%s.localtrace_trace(frame: %s, why: %s, arg: %s); filename: %s, lineno: %s, funcname: %s, context: %s, lineindex: %s\n" % (self, frame, why, arg, filename, lineno, funcname, context, lineindex,)
# XXX not convinced that this memoizing is a performance win -- I don't know enough about Python guts to tell. --Zooko 2001-10-14
bname = self . pathtobasename . get ( filename )
if bname is None :
# Using setdefault faster than two separate lines? --Zooko 2001-10-14
bname = self . pathtobasename . setdefault ( filename , os . path . basename ( filename ) )
2002-07-25 13:09:35 -03:00
if context is not None :
try :
print " %s ( %d ): %s " % ( bname , lineno , context [ lineindex ] , ) ,
except IndexError :
# Uh.. sometimes getframeinfo gives me a context of length 1 and a lineindex of -2. Oh well.
pass
else :
print " %s (???): ??? " % bname
2001-11-28 15:41:45 -04:00
return self . localtrace
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
def localtrace_count ( self , frame , why , arg ) :
if why == ' line ' :
# XXX shouldn't do the count increment when arg is exception? But be careful to return self.localtrace when arg is exception! ? --Zooko 2001-10-14
# record the file name and line number of every trace
# XXX I wish inspect offered me an optimized `getfilename(frame)' to use in place of the presumably heavier `getframeinfo()'. --Zooko 2001-10-14
( filename , lineno , funcname , context , lineindex , ) = inspect . getframeinfo ( frame )
key = ( filename , lineno , )
self . counts [ key ] = self . counts . get ( key , 0 ) + 1
return self . localtrace
2001-01-17 04:48:39 -04:00
2001-11-28 15:41:45 -04:00
def results ( self ) :
return CoverageResults ( self . counts , infile = self . infile , outfile = self . outfile , calledfuncs = self . _calledfuncs )
2000-08-03 16:26:21 -03:00
def _err_exit ( msg ) :
2001-11-28 15:41:45 -04:00
sys . stderr . write ( " %s : %s \n " % ( sys . argv [ 0 ] , msg ) )
2000-09-17 22:46:01 -03:00
sys . exit ( 1 )
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
def main ( argv = None ) :
2000-08-03 16:26:21 -03:00
import getopt
if argv is None :
2000-09-17 22:46:01 -03:00
argv = sys . argv
2000-08-03 16:26:21 -03:00
try :
2001-11-28 15:41:45 -04:00
opts , prog_argv = getopt . getopt ( argv [ 1 : ] , " tcrRf:d:msC:l " ,
2000-09-17 22:46:01 -03:00
[ " help " , " version " , " trace " , " count " ,
" report " , " no-report " ,
2001-11-28 15:41:45 -04:00
" file= " , " missing " ,
2001-05-08 01:20:52 -03:00
" ignore-module= " , " ignore-dir= " ,
2001-11-28 15:41:45 -04:00
" coverdir= " , " listfuncs " , ] )
2000-08-03 16:26:21 -03:00
except getopt . error , msg :
2001-11-28 15:41:45 -04:00
sys . stderr . write ( " %s : %s \n " % ( sys . argv [ 0 ] , msg ) )
sys . stderr . write ( " Try ` %s --help ' for more information \n " % sys . argv [ 0 ] )
2000-09-17 22:46:01 -03:00
sys . exit ( 1 )
2000-08-03 16:26:21 -03:00
trace = 0
count = 0
report = 0
no_report = 0
counts_file = None
missing = 0
ignore_modules = [ ]
ignore_dirs = [ ]
2001-05-08 01:20:52 -03:00
coverdir = None
summary = 0
2001-11-28 15:41:45 -04:00
listfuncs = false
2000-08-03 16:26:21 -03:00
for opt , val in opts :
2000-09-17 22:46:01 -03:00
if opt == " --help " :
usage ( sys . stdout )
sys . exit ( 0 )
if opt == " --version " :
sys . stdout . write ( " trace 2.0 \n " )
sys . exit ( 0 )
2001-11-28 15:41:45 -04:00
if opt == " -l " or opt == " --listfuncs " :
listfuncs = true
continue
2000-09-17 22:46:01 -03:00
if opt == " -t " or opt == " --trace " :
trace = 1
continue
if opt == " -c " or opt == " --count " :
count = 1
continue
if opt == " -r " or opt == " --report " :
report = 1
continue
if opt == " -R " or opt == " --no-report " :
no_report = 1
continue
if opt == " -f " or opt == " --file " :
counts_file = val
continue
if opt == " -m " or opt == " --missing " :
missing = 1
continue
2001-05-08 01:20:52 -03:00
if opt == " -C " or opt == " --coverdir " :
coverdir = val
continue
if opt == " -s " or opt == " --summary " :
summary = 1
continue
2000-09-17 22:46:01 -03:00
if opt == " --ignore-module " :
ignore_modules . append ( val )
continue
if opt == " --ignore-dir " :
for s in string . split ( val , os . pathsep ) :
s = os . path . expandvars ( s )
# should I also call expanduser? (after all, could use $HOME)
s = string . replace ( s , " $prefix " ,
os . path . join ( sys . prefix , " lib " ,
" python " + sys . version [ : 3 ] ) )
s = string . replace ( s , " $exec_prefix " ,
os . path . join ( sys . exec_prefix , " lib " ,
" python " + sys . version [ : 3 ] ) )
s = os . path . normpath ( s )
ignore_dirs . append ( s )
continue
assert 0 , " Should never get here "
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
if listfuncs and ( count or trace ) :
_err_exit ( " cannot specify both --listfuncs and (--trace or --count) " )
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
if not count and not trace and not report and not listfuncs :
_err_exit ( " must specify one of --trace, --count, --report or --listfuncs " )
2000-08-03 16:26:21 -03:00
if report and no_report :
2000-09-17 22:46:01 -03:00
_err_exit ( " cannot specify both --report and --no-report " )
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
if report and not counts_file :
_err_exit ( " --report requires a --file " )
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
if no_report and len ( prog_argv ) == 0 :
_err_exit ( " missing name of file to run " )
2000-08-03 16:26:21 -03:00
# everything is ready
2001-11-28 15:41:45 -04:00
if report :
results = CoverageResults ( infile = counts_file , outfile = counts_file )
results . write_results ( missing , summary = summary , coverdir = coverdir )
else :
sys . argv = prog_argv
progname = prog_argv [ 0 ]
if eval ( sys . version [ : 3 ] ) > 1.3 :
sys . path [ 0 ] = os . path . split ( progname ) [ 0 ] # ???
2000-08-03 16:26:21 -03:00
2001-11-28 15:41:45 -04:00
t = Trace ( count , trace , countfuncs = listfuncs , ignoremods = ignore_modules , ignoredirs = ignore_dirs , infile = counts_file , outfile = counts_file )
2000-09-17 22:46:01 -03:00
try :
2001-11-28 15:41:45 -04:00
t . run ( ' execfile( ' + ` progname ` + ' ) ' )
2000-09-17 22:46:01 -03:00
except IOError , err :
2001-11-28 15:41:45 -04:00
_err_exit ( " Cannot run file %s because: %s " % ( ` sys . argv [ 0 ] ` , err ) )
2000-09-17 22:46:01 -03:00
except SystemExit :
pass
results = t . results ( )
if not no_report :
2001-11-28 15:41:45 -04:00
results . write_results ( missing , summary = summary , coverdir = coverdir )
2000-08-03 16:26:21 -03:00
if __name__ == ' __main__ ' :
main ( )