blob: 2f9fb046ef6f2b85af8265178ced93e6b37a0979 [file] [log] [blame]
Jeremy Hyltonda1ec462000-08-03 19:26:21 +00001#!/usr/bin/env python
2
Guido van Rossuma30eacf2001-11-28 19:41:45 +00003# portions copyright 2001, Autonomous Zones Industries, Inc., all rights...
4# err... reserved and offered to the public under the terms of the
5# Python 2.2 license.
6# Author: Zooko O'Whielacronx
7# http://zooko.com/
8# mailto:zooko@zooko.com
9#
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000010# Copyright 2000, Mojam Media, Inc., all rights reserved.
11# Author: Skip Montanaro
12#
13# Copyright 1999, Bioreason, Inc., all rights reserved.
14# Author: Andrew Dalke
15#
16# Copyright 1995-1997, Automatrix, Inc., all rights reserved.
17# Author: Skip Montanaro
18#
19# Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
20#
21#
22# Permission to use, copy, modify, and distribute this Python software and
23# its associated documentation for any purpose without fee is hereby
24# granted, provided that the above copyright notice appears in all copies,
25# and that both that copyright notice and this permission notice appear in
26# supporting documentation, and that the name of neither Automatrix,
27# Bioreason or Mojam Media be used in advertising or publicity pertaining to
28# distribution of the software without specific, written prior permission.
29#
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +000030"""program/module to trace Python program or function execution
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000031
32Sample use, command line:
33 trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs
34 trace.py -t --ignore-dir '$prefix' spam.py eggs
35
Guido van Rossuma30eacf2001-11-28 19:41:45 +000036Sample use, programmatically
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +000037 # create a Trace object, telling it what to ignore, and whether to
38 # do tracing or line-counting or both.
39 trace = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,], trace=0,
40 count=1)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000041 # run the new command using the given trace
Guido van Rossuma30eacf2001-11-28 19:41:45 +000042 trace.run(coverage.globaltrace, 'main()')
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000043 # make a report, telling it where you want output
Guido van Rossuma30eacf2001-11-28 19:41:45 +000044 trace.print_results(show_missing=1)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000045"""
46
Walter Dörwaldaaab30e2002-09-11 20:36:02 +000047import sys, os, tempfile, types, copy, operator, inspect, exceptions, marshal
Guido van Rossuma30eacf2001-11-28 19:41:45 +000048try:
49 import cPickle
50 pickle = cPickle
51except ImportError:
52 import pickle
53
54true = 1
55false = None
56
57# DEBUG_MODE=1 # make this true to get printouts which help you understand what's going on
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000058
59def usage(outfile):
60 outfile.write("""Usage: %s [OPTIONS] <file> [ARGS]
61
Guido van Rossuma30eacf2001-11-28 19:41:45 +000062Meta-options:
63--help Display this help then exit.
64--version Output version information then exit.
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000065
Guido van Rossuma30eacf2001-11-28 19:41:45 +000066Otherwise, exactly one of the following three options must be given:
67-t, --trace Print each line to sys.stdout before it is executed.
68-c, --count Count the number of times each line is executed
69 and write the counts to <module>.cover for each
70 module executed, in the module's directory.
71 See also `--coverdir', `--file', `--no-report' below.
72-r, --report Generate a report from a counts file; do not execute
73 any code. `--file' must specify the results file to
74 read, which must have been created in a previous run
75 with `--count --file=FILE'.
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000076
Guido van Rossuma30eacf2001-11-28 19:41:45 +000077Modifiers:
78-f, --file=<file> File to accumulate counts over several runs.
79-R, --no-report Do not generate the coverage report files.
80 Useful if you want to accumulate over several runs.
81-C, --coverdir=<dir> Directory where the report files. The coverage
82 report for <package>.<module> is written to file
83 <dir>/<package>/<module>.cover.
84-m, --missing Annotate executable lines that were not executed
85 with '>>>>>> '.
86-s, --summary Write a brief summary on stdout for each file.
87 (Can only be used with --count or --report.)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000088
Guido van Rossuma30eacf2001-11-28 19:41:45 +000089Filters, may be repeated multiple times:
90--ignore-module=<mod> Ignore the given module and its submodules
91 (if it is a package).
92--ignore-dir=<dir> Ignore files in the given directory (multiple
93 directories can be joined by os.pathsep).
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000094""" % sys.argv[0])
95
Jeremy Hyltonda1ec462000-08-03 19:26:21 +000096class Ignore:
97 def __init__(self, modules = None, dirs = None):
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +000098 self._mods = modules or []
99 self._dirs = dirs or []
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000100
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000101 self._dirs = map(os.path.normpath, self._dirs)
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000102 self._ignore = { '<string>': 1 }
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000103
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000104 def names(self, filename, modulename):
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000105 if self._ignore.has_key(modulename):
106 return self._ignore[modulename]
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000107
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000108 # haven't seen this one before, so see if the module name is
109 # on the ignore list. Need to take some care since ignoring
110 # "cmp" musn't mean ignoring "cmpcache" but ignoring
111 # "Spam" must also mean ignoring "Spam.Eggs".
112 for mod in self._mods:
113 if mod == modulename: # Identical names, so ignore
114 self._ignore[modulename] = 1
115 return 1
116 # check if the module is a proper submodule of something on
117 # the ignore list
118 n = len(mod)
119 # (will not overflow since if the first n characters are the
120 # same and the name has not already occured, then the size
121 # of "name" is greater than that of "mod")
122 if mod == modulename[:n] and modulename[n] == '.':
123 self._ignore[modulename] = 1
124 return 1
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000125
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000126 # Now check that __file__ isn't in one of the directories
127 if filename is None:
128 # must be a built-in, so we must ignore
129 self._ignore[modulename] = 1
130 return 1
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000131
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000132 # Ignore a file when it contains one of the ignorable paths
133 for d in self._dirs:
134 # The '+ os.sep' is to ensure that d is a parent directory,
135 # as compared to cases like:
136 # d = "/usr/local"
137 # filename = "/usr/local.py"
138 # or
139 # d = "/usr/local.py"
140 # filename = "/usr/local.py"
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000141 if filename.startswith(d + os.sep):
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000142 self._ignore[modulename] = 1
143 return 1
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000144
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000145 # Tried the different ways, so we don't ignore this module
146 self._ignore[modulename] = 0
147 return 0
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000148
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000149class CoverageResults:
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000150 def __init__(self, counts=None, calledfuncs=None, infile=None, outfile=None):
151 self.counts = counts
152 if self.counts is None:
153 self.counts = {}
154 self.counter = self.counts.copy() # map (filename, lineno) to count
155 self.calledfuncs = calledfuncs
156 if self.calledfuncs is None:
157 self.calledfuncs = {}
158 self.calledfuncs = self.calledfuncs.copy()
159 self.infile = infile
160 self.outfile = outfile
161 if self.infile:
162 # try and merge existing counts file
163 try:
164 thingie = pickle.load(open(self.infile, 'r'))
165 if type(thingie) is types.DictType:
166 # backwards compatibility for old trace.py after Zooko touched it but before calledfuncs --Zooko 2001-10-24
167 self.update(self.__class__(thingie))
168 elif type(thingie) is types.TupleType and len(thingie) == 2:
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000169 counts, calledfuncs = thingie
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000170 self.update(self.__class__(counts, calledfuncs))
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000171 except (IOError, EOFError):
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000172 pass
173 except pickle.UnpicklingError:
174 # backwards compatibility for old trace.py before Zooko touched it --Zooko 2001-10-24
175 self.update(self.__class__(marshal.load(open(self.infile))))
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000176
177 def update(self, other):
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000178 """Merge in the data from another CoverageResults"""
179 counts = self.counts
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000180 calledfuncs = self.calledfuncs
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000181 other_counts = other.counts
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000182 other_calledfuncs = other.calledfuncs
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000183
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000184 for key in other_counts.keys():
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000185 if key != 'calledfuncs': # backwards compatibility for abortive attempt to stuff calledfuncs into self.counts, by Zooko --Zooko 2001-10-24
186 counts[key] = counts.get(key, 0) + other_counts[key]
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000187
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000188 for key in other_calledfuncs.keys():
189 calledfuncs[key] = 1
190
191 def write_results(self, show_missing = 1, summary = 0, coverdir = None):
192 """
193 @param coverdir
194 """
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000195 for filename, modulename, funcname in self.calledfuncs.keys():
196 print ("filename: %s, modulename: %s, funcname: %s"
197 % (filename, modulename, funcname))
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000198
199 import re
200 # turn the counts data ("(filename, lineno) = count") into something
201 # accessible on a per-file basis
202 per_file = {}
203 for thingie in self.counts.keys():
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000204 if thingie != "calledfuncs":
205 # backwards compatibility for abortive attempt to
206 # stuff calledfuncs into self.counts, by Zooko --Zooko
207 # 2001-10-24
208 filename, lineno = thingie
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000209 lines_hit = per_file[filename] = per_file.get(filename, {})
210 lines_hit[lineno] = self.counts[(filename, lineno)]
211
212 # there are many places where this is insufficient, like a blank
213 # line embedded in a multiline string.
214 blank = re.compile(r'^\s*(#.*)?$')
215
216 # accumulate summary info, if needed
217 sums = {}
218
219 # generate file paths for the coverage files we are going to write...
220 fnlist = []
221 tfdir = tempfile.gettempdir()
222 for key in per_file.keys():
223 filename = key
224
225 # skip some "files" we don't care about...
226 if filename == "<string>":
227 continue
228 # are these caused by code compiled using exec or something?
229 if filename.startswith(tfdir):
230 continue
231
232 modulename = inspect.getmodulename(filename)
233
234 if filename.endswith(".pyc") or filename.endswith(".pyo"):
235 filename = filename[:-1]
236
237 if coverdir:
238 thiscoverdir = coverdir
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000239 else:
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000240 thiscoverdir = os.path.dirname(os.path.abspath(filename))
241
242 # the code from here to "<<<" is the contents of the `fileutil.make_dirs()' function in the Mojo Nation project. --Zooko 2001-10-14
243 # http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/mojonation/evil/common/fileutil.py?rev=HEAD&content-type=text/vnd.viewcvs-markup
244 tx = None
245 try:
246 os.makedirs(thiscoverdir)
247 except OSError, x:
248 tx = x
249
250 if not os.path.isdir(thiscoverdir):
251 if tx:
252 raise tx
253 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...
254 # <<<
255
256 # build list file name by appending a ".cover" to the module name
257 # and sticking it into the specified directory
258 if "." in modulename:
259 # A module in a package
260 finalname = modulename.split(".")[-1]
261 listfilename = os.path.join(thiscoverdir, finalname + ".cover")
262 else:
263 listfilename = os.path.join(thiscoverdir, modulename + ".cover")
264
265 # Get the original lines from the .py file
266 try:
267 lines = open(filename, 'r').readlines()
268 except IOError, err:
269 sys.stderr.write("trace: Could not open %s for reading because: %s - skipping\n" % (`filename`, err))
270 continue
271
272 try:
273 outfile = open(listfilename, 'w')
274 except IOError, err:
275 sys.stderr.write(
276 '%s: Could not open %s for writing because: %s" \
277 "- skipping\n' % ("trace", `listfilename`, err))
278 continue
279
280 # If desired, get a list of the line numbers which represent
281 # executable content (returned as a dict for better lookup speed)
282 if show_missing:
283 executable_linenos = find_executable_linenos(filename)
284 else:
285 executable_linenos = {}
286
287 n_lines = 0
288 n_hits = 0
289 lines_hit = per_file[key]
290 for i in range(len(lines)):
291 line = lines[i]
292
293 # do the blank/comment match to try to mark more lines
294 # (help the reader find stuff that hasn't been covered)
295 if lines_hit.has_key(i+1):
296 # count precedes the lines that we captured
297 outfile.write('%5d: ' % lines_hit[i+1])
298 n_hits = n_hits + 1
299 n_lines = n_lines + 1
300 elif blank.match(line):
301 # blank lines and comments are preceded by dots
302 outfile.write(' . ')
303 else:
304 # lines preceded by no marks weren't hit
305 # Highlight them if so indicated, unless the line contains
306 # '#pragma: NO COVER' (it is possible to embed this into
307 # the text as a non-comment; no easy fix)
308 if executable_linenos.has_key(i+1) and \
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000309 lines[i].find(' '.join(['#pragma', 'NO COVER'])) == -1:
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000310 outfile.write('>>>>>> ')
311 else:
312 outfile.write(' '*7)
313 n_lines = n_lines + 1
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000314 outfile.write(lines[i].expandtabs(8))
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000315
316 outfile.close()
317
318 if summary and n_lines:
319 percent = int(100 * n_hits / n_lines)
320 sums[modulename] = n_lines, percent, modulename, filename
321
322 if summary and sums:
323 mods = sums.keys()
324 mods.sort()
325 print "lines cov% module (path)"
326 for m in mods:
327 n_lines, percent, modulename, filename = sums[m]
328 print "%5d %3d%% %s (%s)" % sums[m]
329
330 if self.outfile:
331 # try and store counts and module info into self.outfile
332 try:
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000333 pickle.dump((self.counts, self.calledfuncs),
334 open(self.outfile, 'w'), 1)
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000335 except IOError, err:
336 sys.stderr.write("cannot save counts files because %s" % err)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000337
Michael W. Hudsondd32a912002-08-15 14:59:02 +0000338def _find_LINENO_from_code(code):
339 """return the numbers of the lines containing the source code that
340 was compiled into code"""
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000341 linenos = {}
342
Michael W. Hudsondd32a912002-08-15 14:59:02 +0000343 line_increments = [ord(c) for c in code.co_lnotab[1::2]]
344 table_length = len(line_increments)
345
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000346 lineno = code.co_firstlineno
Michael W. Hudsondd32a912002-08-15 14:59:02 +0000347
348 for li in line_increments:
349 linenos[lineno] = 1
350 lineno += li
351 linenos[lineno] = 1
352
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000353 return linenos
354
355def _find_LINENO(code):
Michael W. Hudsondd32a912002-08-15 14:59:02 +0000356 """return all of the lineno information from a code object"""
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000357 import types
358
359 # get all of the lineno information from the code of this scope level
Michael W. Hudsondd32a912002-08-15 14:59:02 +0000360 linenos = _find_LINENO_from_code(code)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000361
362 # and check the constants for references to other code objects
363 for c in code.co_consts:
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000364 if type(c) == types.CodeType:
365 # find another code object, so recurse into it
366 linenos.update(_find_LINENO(c))
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000367 return linenos
368
369def find_executable_linenos(filename):
370 """return a dict of the line numbers from executable statements in a file
371
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000372 """
373 import parser
374
Jeremy Hylton66a7e572001-05-08 04:20:52 +0000375 assert filename.endswith('.py')
376
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000377 prog = open(filename).read()
378 ast = parser.suite(prog)
379 code = parser.compileast(ast, filename)
380
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000381 return _find_LINENO(code)
382
383### XXX because os.path.commonprefix seems broken by my way of thinking...
384def commonprefix(dirs):
385 "Given a list of pathnames, returns the longest common leading component"
386 if not dirs: return ''
387 n = copy.copy(dirs)
388 for i in range(len(n)):
389 n[i] = n[i].split(os.sep)
390 prefix = n[0]
391 for item in n:
392 for i in range(len(prefix)):
393 if prefix[:i+1] <> item[:i+1]:
394 prefix = prefix[:i]
395 if i == 0: return ''
396 break
397 return os.sep.join(prefix)
Tim Peters70c43782001-01-17 08:48:39 +0000398
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000399class Trace:
400 def __init__(self, count=1, trace=1, countfuncs=0, ignoremods=(), ignoredirs=(), infile=None, outfile=None):
401 """
402 @param count true iff it should count number of times each line is executed
403 @param trace true iff it should print out each line that is being counted
404 @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'
405 @param ignoremods a list of the names of modules to ignore
406 @param ignoredirs a list of the names of directories to ignore all of the (recursive) contents of
407 @param infile file from which to read stored counts to be added into the results
408 @param outfile file in which to write the results
409 """
410 self.infile = infile
411 self.outfile = outfile
412 self.ignore = Ignore(ignoremods, ignoredirs)
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000413 self.counts = {} # keys are (filename, linenumber)
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000414 self.blabbed = {} # for debugging
415 self.pathtobasename = {} # for memoizing os.path.basename
416 self.donothing = 0
417 self.trace = trace
418 self._calledfuncs = {}
419 if countfuncs:
420 self.globaltrace = self.globaltrace_countfuncs
421 elif trace and count:
422 self.globaltrace = self.globaltrace_lt
423 self.localtrace = self.localtrace_trace_and_count
424 elif trace:
425 self.globaltrace = self.globaltrace_lt
426 self.localtrace = self.localtrace_trace
427 elif count:
428 self.globaltrace = self.globaltrace_lt
429 self.localtrace = self.localtrace_count
430 else:
431 # Ahem -- do nothing? Okay.
432 self.donothing = 1
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000433
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000434 def run(self, cmd):
435 import __main__
436 dict = __main__.__dict__
437 if not self.donothing:
438 sys.settrace(self.globaltrace)
439 try:
440 exec cmd in dict, dict
441 finally:
442 if not self.donothing:
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000443 sys.settrace(None)
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000444
445 def runctx(self, cmd, globals=None, locals=None):
446 if globals is None: globals = {}
447 if locals is None: locals = {}
448 if not self.donothing:
Skip Montanaro3a48ed92002-07-25 16:09:35 +0000449 sys.settrace(self.globaltrace)
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000450 try:
Skip Montanaro3a48ed92002-07-25 16:09:35 +0000451 exec cmd in globals, locals
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000452 finally:
453 if not self.donothing:
454 sys.settrace(None)
455
456 def runfunc(self, func, *args, **kw):
457 result = None
458 if not self.donothing:
459 sys.settrace(self.globaltrace)
460 try:
461 result = apply(func, args, kw)
462 finally:
463 if not self.donothing:
464 sys.settrace(None)
465 return result
466
467 def globaltrace_countfuncs(self, frame, why, arg):
468 """
469 Handles `call' events (why == 'call') and adds the (filename, modulename, funcname,) to the self._calledfuncs dict.
470 """
471 if why == 'call':
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000472 filename, lineno, funcname, context, lineindex = \
473 inspect.getframeinfo(frame, 0)
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000474 if filename:
475 modulename = inspect.getmodulename(filename)
476 else:
477 modulename = None
478 self._calledfuncs[(filename, modulename, funcname,)] = 1
479
480 def globaltrace_lt(self, frame, why, arg):
481 """
482 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'.
483 """
484 if why == 'call':
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000485 filename, lineno, funcname, context, lineindex = \
486 inspect.getframeinfo(frame, 0)
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000487 if filename:
488 modulename = inspect.getmodulename(filename)
Skip Montanaro3a48ed92002-07-25 16:09:35 +0000489 if modulename is not None:
490 ignore_it = self.ignore.names(filename, modulename)
Skip Montanaro3a48ed92002-07-25 16:09:35 +0000491 if not ignore_it:
492 if self.trace:
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000493 print (" --- modulename: %s, funcname: %s"
494 % (modulename, funcname))
Skip Montanaro3a48ed92002-07-25 16:09:35 +0000495 return self.localtrace
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000496 else:
497 # XXX why no filename?
498 return None
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000499
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000500 def localtrace_trace_and_count(self, frame, why, arg):
501 if why == 'line':
502 # record the file name and line number of every trace
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000503 # XXX I wish inspect offered me an optimized
504 # `getfilename(frame)' to use in place of the presumably
505 # heavier `getframeinfo()'. --Zooko 2001-10-14
506
507 filename, lineno, funcname, context, lineindex = \
508 inspect.getframeinfo(frame, 1)
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000509 key = (filename, lineno,)
510 self.counts[key] = self.counts.get(key, 0) + 1
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000511
512 # XXX not convinced that this memoizing is a performance
513 # win -- I don't know enough about Python guts to tell.
514 # --Zooko 2001-10-14
515
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000516 bname = self.pathtobasename.get(filename)
517 if bname is None:
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000518
519 # Using setdefault faster than two separate lines?
520 # --Zooko 2001-10-14
521 bname = self.pathtobasename.setdefault(filename,
522 os.path.basename(filename))
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000523 try:
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000524 print "%s(%d): %s" % (bname, lineno, context[lineindex]),
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000525 except IndexError:
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000526 # Uh.. sometimes getframeinfo gives me a context of
527 # length 1 and a lineindex of -2. Oh well.
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000528 pass
529 return self.localtrace
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000530
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000531 def localtrace_trace(self, frame, why, arg):
532 if why == 'line':
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000533 # XXX shouldn't do the count increment when arg is
534 # exception? But be careful to return self.localtrace
535 # when arg is exception! ? --Zooko 2001-10-14
536
537 # record the file name and line number of every trace XXX
538 # I wish inspect offered me an optimized
539 # `getfilename(frame)' to use in place of the presumably
540 # heavier `getframeinfo()'. --Zooko 2001-10-14
541 filename, lineno, funcname, context, lineindex = \
542 inspect.getframeinfo(frame)
543
544 # XXX not convinced that this memoizing is a performance
545 # win -- I don't know enough about Python guts to tell.
546 # --Zooko 2001-10-14
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000547 bname = self.pathtobasename.get(filename)
548 if bname is None:
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000549 # Using setdefault faster than two separate lines?
550 # --Zooko 2001-10-14
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000551 bname = self.pathtobasename.setdefault(filename, os.path.basename(filename))
Skip Montanaro3a48ed92002-07-25 16:09:35 +0000552 if context is not None:
553 try:
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000554 print "%s(%d): %s" % (bname, lineno, context[lineindex]),
Skip Montanaro3a48ed92002-07-25 16:09:35 +0000555 except IndexError:
556 # Uh.. sometimes getframeinfo gives me a context of length 1 and a lineindex of -2. Oh well.
557 pass
558 else:
559 print "%s(???): ???" % bname
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000560 return self.localtrace
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000561
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000562 def localtrace_count(self, frame, why, arg):
563 if why == 'line':
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000564 filename = frame.f_code.co_filename
565 lineno = frame.f_lineno
566 key = filename, lineno
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000567 self.counts[key] = self.counts.get(key, 0) + 1
568 return self.localtrace
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000569
570 def results(self):
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000571 return CoverageResults(self.counts, infile=self.infile,
572 outfile=self.outfile,
573 calledfuncs=self._calledfuncs)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000574
575def _err_exit(msg):
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000576 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000577 sys.exit(1)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000578
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000579def main(argv=None):
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000580 import getopt
581
582 if argv is None:
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000583 argv = sys.argv
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000584 try:
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000585 opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:l",
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000586 ["help", "version", "trace", "count",
587 "report", "no-report",
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000588 "file=", "missing",
Jeremy Hylton66a7e572001-05-08 04:20:52 +0000589 "ignore-module=", "ignore-dir=",
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000590 "coverdir=", "listfuncs",])
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000591
592 except getopt.error, msg:
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000593 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000594 sys.stderr.write("Try `%s --help' for more information\n"
595 % sys.argv[0])
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000596 sys.exit(1)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000597
598 trace = 0
599 count = 0
600 report = 0
601 no_report = 0
602 counts_file = None
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000603 missing = 0
604 ignore_modules = []
605 ignore_dirs = []
Jeremy Hylton66a7e572001-05-08 04:20:52 +0000606 coverdir = None
607 summary = 0
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000608 listfuncs = false
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000609
610 for opt, val in opts:
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000611 if opt == "--help":
612 usage(sys.stdout)
613 sys.exit(0)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000614
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000615 if opt == "--version":
616 sys.stdout.write("trace 2.0\n")
617 sys.exit(0)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000618
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000619 if opt == "-l" or opt == "--listfuncs":
620 listfuncs = true
621 continue
622
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000623 if opt == "-t" or opt == "--trace":
624 trace = 1
625 continue
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000626
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000627 if opt == "-c" or opt == "--count":
628 count = 1
629 continue
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000630
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000631 if opt == "-r" or opt == "--report":
632 report = 1
633 continue
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000634
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000635 if opt == "-R" or opt == "--no-report":
636 no_report = 1
637 continue
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000638
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000639 if opt == "-f" or opt == "--file":
640 counts_file = val
641 continue
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000642
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000643 if opt == "-m" or opt == "--missing":
644 missing = 1
645 continue
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000646
Jeremy Hylton66a7e572001-05-08 04:20:52 +0000647 if opt == "-C" or opt == "--coverdir":
648 coverdir = val
649 continue
650
651 if opt == "-s" or opt == "--summary":
652 summary = 1
653 continue
654
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000655 if opt == "--ignore-module":
656 ignore_modules.append(val)
657 continue
658
659 if opt == "--ignore-dir":
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000660 for s in val.split(os.pathsep):
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000661 s = os.path.expandvars(s)
662 # should I also call expanduser? (after all, could use $HOME)
663
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000664 s = s.replace("$prefix",
665 os.path.join(sys.prefix, "lib",
666 "python" + sys.version[:3]))
667 s = s.replace("$exec_prefix",
668 os.path.join(sys.exec_prefix, "lib",
669 "python" + sys.version[:3]))
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000670 s = os.path.normpath(s)
671 ignore_dirs.append(s)
672 continue
673
674 assert 0, "Should never get here"
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000675
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000676 if listfuncs and (count or trace):
677 _err_exit("cannot specify both --listfuncs and (--trace or --count)")
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000678
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000679 if not count and not trace and not report and not listfuncs:
680 _err_exit("must specify one of --trace, --count, --report or --listfuncs")
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000681
682 if report and no_report:
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000683 _err_exit("cannot specify both --report and --no-report")
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000684
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000685 if report and not counts_file:
686 _err_exit("--report requires a --file")
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000687
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000688 if no_report and len(prog_argv) == 0:
689 _err_exit("missing name of file to run")
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000690
691 # everything is ready
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000692 if report:
693 results = CoverageResults(infile=counts_file, outfile=counts_file)
694 results.write_results(missing, summary=summary, coverdir=coverdir)
695 else:
696 sys.argv = prog_argv
697 progname = prog_argv[0]
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000698 sys.path[0] = os.path.split(progname)[0]
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000699
Jeremy Hylton89f1d6c2002-12-11 21:28:32 +0000700 t = Trace(count, trace, countfuncs=listfuncs,
701 ignoremods=ignore_modules, ignoredirs=ignore_dirs,
702 infile=counts_file, outfile=counts_file)
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000703 try:
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000704 t.run('execfile(' + `progname` + ')')
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000705 except IOError, err:
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000706 _err_exit("Cannot run file %s because: %s" % (`sys.argv[0]`, err))
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000707 except SystemExit:
708 pass
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000709
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000710 results = t.results()
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000711
Jeremy Hylton0b7b4b82000-09-18 01:46:01 +0000712 if not no_report:
Guido van Rossuma30eacf2001-11-28 19:41:45 +0000713 results.write_results(missing, summary=summary, coverdir=coverdir)
Jeremy Hyltonda1ec462000-08-03 19:26:21 +0000714
715if __name__=='__main__':
716 main()