blob: 89f3162979485a4431bccbb1c6c403bda0e8b968 [file] [log] [blame]
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +00001#!/usr/bin/env python
2
3# 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#
10# 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#
30"""program/module to trace Python program or function execution
31
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
36Sample use, programmatically
37 # 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)
41 # run the new command using the given trace
42 trace.run(coverage.globaltrace, 'main()')
43 # make a report, telling it where you want output
44 r = trace.results()
Jeremy Hylton38732e12003-04-21 22:04:46 +000045 r.write_results(show_missing=True)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +000046"""
47
Jeremy Hylton38732e12003-04-21 22:04:46 +000048import linecache
49import marshal
50import os
51import re
52import sys
Jeremy Hylton546e34b2003-06-26 14:56:17 +000053import threading
Jeremy Hylton38732e12003-04-21 22:04:46 +000054import token
55import tokenize
56import types
57
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +000058try:
59 import cPickle
60 pickle = cPickle
61except ImportError:
62 import pickle
63
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +000064def usage(outfile):
65 outfile.write("""Usage: %s [OPTIONS] <file> [ARGS]
66
67Meta-options:
68--help Display this help then exit.
69--version Output version information then exit.
70
71Otherwise, exactly one of the following three options must be given:
72-t, --trace Print each line to sys.stdout before it is executed.
73-c, --count Count the number of times each line is executed
74 and write the counts to <module>.cover for each
75 module executed, in the module's directory.
76 See also `--coverdir', `--file', `--no-report' below.
77-r, --report Generate a report from a counts file; do not execute
78 any code. `--file' must specify the results file to
79 read, which must have been created in a previous run
80 with `--count --file=FILE'.
81
82Modifiers:
83-f, --file=<file> File to accumulate counts over several runs.
84-R, --no-report Do not generate the coverage report files.
85 Useful if you want to accumulate over several runs.
86-C, --coverdir=<dir> Directory where the report files. The coverage
87 report for <package>.<module> is written to file
88 <dir>/<package>/<module>.cover.
89-m, --missing Annotate executable lines that were not executed
90 with '>>>>>> '.
91-s, --summary Write a brief summary on stdout for each file.
92 (Can only be used with --count or --report.)
93
94Filters, may be repeated multiple times:
95--ignore-module=<mod> Ignore the given module and its submodules
96 (if it is a package).
97--ignore-dir=<dir> Ignore files in the given directory (multiple
98 directories can be joined by os.pathsep).
99""" % sys.argv[0])
100
Jeremy Hylton38732e12003-04-21 22:04:46 +0000101PRAGMA_NOCOVER = "#pragma NO COVER"
102
103# Simple rx to find lines with no code.
104rx_blank = re.compile(r'^\s*(#.*)?$')
105
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000106class Ignore:
107 def __init__(self, modules = None, dirs = None):
108 self._mods = modules or []
109 self._dirs = dirs or []
110
111 self._dirs = map(os.path.normpath, self._dirs)
112 self._ignore = { '<string>': 1 }
113
114 def names(self, filename, modulename):
115 if self._ignore.has_key(modulename):
116 return self._ignore[modulename]
117
118 # haven't seen this one before, so see if the module name is
119 # on the ignore list. Need to take some care since ignoring
120 # "cmp" musn't mean ignoring "cmpcache" but ignoring
121 # "Spam" must also mean ignoring "Spam.Eggs".
122 for mod in self._mods:
123 if mod == modulename: # Identical names, so ignore
124 self._ignore[modulename] = 1
125 return 1
126 # check if the module is a proper submodule of something on
127 # the ignore list
128 n = len(mod)
129 # (will not overflow since if the first n characters are the
130 # same and the name has not already occured, then the size
131 # of "name" is greater than that of "mod")
132 if mod == modulename[:n] and modulename[n] == '.':
133 self._ignore[modulename] = 1
134 return 1
135
136 # Now check that __file__ isn't in one of the directories
137 if filename is None:
138 # must be a built-in, so we must ignore
139 self._ignore[modulename] = 1
140 return 1
141
142 # Ignore a file when it contains one of the ignorable paths
143 for d in self._dirs:
144 # The '+ os.sep' is to ensure that d is a parent directory,
145 # as compared to cases like:
146 # d = "/usr/local"
147 # filename = "/usr/local.py"
148 # or
149 # d = "/usr/local.py"
150 # filename = "/usr/local.py"
151 if filename.startswith(d + os.sep):
152 self._ignore[modulename] = 1
153 return 1
154
155 # Tried the different ways, so we don't ignore this module
156 self._ignore[modulename] = 0
157 return 0
158
Jeremy Hylton38732e12003-04-21 22:04:46 +0000159def modname(path):
160 """Return a plausible module name for the patch."""
Jeremy Hyltondfbfe732003-04-21 22:49:17 +0000161
Jeremy Hylton38732e12003-04-21 22:04:46 +0000162 base = os.path.basename(path)
163 filename, ext = os.path.splitext(base)
164 return filename
165
Jeremy Hyltondfbfe732003-04-21 22:49:17 +0000166def fullmodname(path):
Jeremy Hyltonc8c8b942003-04-22 15:35:51 +0000167 """Return a plausible module name for the path."""
Jeremy Hyltondfbfe732003-04-21 22:49:17 +0000168
169 # If the file 'path' is part of a package, then the filename isn't
170 # enough to uniquely identify it. Try to do the right thing by
171 # looking in sys.path for the longest matching prefix. We'll
172 # assume that the rest is the package name.
173
174 longest = ""
175 for dir in sys.path:
176 if path.startswith(dir) and path[len(dir)] == os.path.sep:
177 if len(dir) > len(longest):
178 longest = dir
179
180 base = path[len(longest) + 1:].replace("/", ".")
181 filename, ext = os.path.splitext(base)
182 return filename
183
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000184class CoverageResults:
185 def __init__(self, counts=None, calledfuncs=None, infile=None,
Tim Petersf2715e02003-02-19 02:35:07 +0000186 outfile=None):
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000187 self.counts = counts
188 if self.counts is None:
189 self.counts = {}
190 self.counter = self.counts.copy() # map (filename, lineno) to count
191 self.calledfuncs = calledfuncs
192 if self.calledfuncs is None:
193 self.calledfuncs = {}
194 self.calledfuncs = self.calledfuncs.copy()
195 self.infile = infile
196 self.outfile = outfile
197 if self.infile:
Jeremy Hylton38732e12003-04-21 22:04:46 +0000198 # Try and merge existing counts file.
199 # This code understand a couple of old trace.py formats.
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000200 try:
201 thingie = pickle.load(open(self.infile, 'r'))
Jeremy Hylton38732e12003-04-21 22:04:46 +0000202 if isinstance(thingie, dict):
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000203 self.update(self.__class__(thingie))
Jeremy Hylton38732e12003-04-21 22:04:46 +0000204 elif isinstance(thingie, tuple) and len(thingie) == 2:
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000205 counts, calledfuncs = thingie
206 self.update(self.__class__(counts, calledfuncs))
Jeremy Hylton38732e12003-04-21 22:04:46 +0000207 except (IOError, EOFError), err:
208 print >> sys.stderr, ("Skipping counts file %r: %s"
209 % (self.infile, err))
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000210 except pickle.UnpicklingError:
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000211 self.update(self.__class__(marshal.load(open(self.infile))))
212
213 def update(self, other):
214 """Merge in the data from another CoverageResults"""
215 counts = self.counts
216 calledfuncs = self.calledfuncs
217 other_counts = other.counts
218 other_calledfuncs = other.calledfuncs
219
220 for key in other_counts.keys():
Jeremy Hylton38732e12003-04-21 22:04:46 +0000221 counts[key] = counts.get(key, 0) + other_counts[key]
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000222
223 for key in other_calledfuncs.keys():
224 calledfuncs[key] = 1
225
Jeremy Hylton38732e12003-04-21 22:04:46 +0000226 def write_results(self, show_missing=True, summary=False, coverdir=None):
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000227 """
228 @param coverdir
229 """
230 for filename, modulename, funcname in self.calledfuncs.keys():
231 print ("filename: %s, modulename: %s, funcname: %s"
232 % (filename, modulename, funcname))
233
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000234 # turn the counts data ("(filename, lineno) = count") into something
235 # accessible on a per-file basis
236 per_file = {}
Jeremy Hylton38732e12003-04-21 22:04:46 +0000237 for filename, lineno in self.counts.keys():
238 lines_hit = per_file[filename] = per_file.get(filename, {})
239 lines_hit[lineno] = self.counts[(filename, lineno)]
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000240
241 # accumulate summary info, if needed
242 sums = {}
243
Jeremy Hylton38732e12003-04-21 22:04:46 +0000244 for filename, count in per_file.iteritems():
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000245 # skip some "files" we don't care about...
246 if filename == "<string>":
247 continue
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000248
249 if filename.endswith(".pyc") or filename.endswith(".pyo"):
250 filename = filename[:-1]
251
Jeremy Hylton38732e12003-04-21 22:04:46 +0000252 if coverdir is None:
253 dir = os.path.dirname(os.path.abspath(filename))
Jeremy Hyltonc8c8b942003-04-22 15:35:51 +0000254 modulename = modname(filename)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000255 else:
Jeremy Hylton38732e12003-04-21 22:04:46 +0000256 dir = coverdir
257 if not os.path.exists(dir):
258 os.makedirs(dir)
Jeremy Hyltonc8c8b942003-04-22 15:35:51 +0000259 modulename = fullmodname(filename)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000260
261 # If desired, get a list of the line numbers which represent
262 # executable content (returned as a dict for better lookup speed)
263 if show_missing:
Jeremy Hylton38732e12003-04-21 22:04:46 +0000264 lnotab = find_executable_linenos(filename)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000265 else:
Jeremy Hylton38732e12003-04-21 22:04:46 +0000266 lnotab = {}
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000267
Jeremy Hylton38732e12003-04-21 22:04:46 +0000268 source = linecache.getlines(filename)
269 coverpath = os.path.join(dir, modulename + ".cover")
270 n_hits, n_lines = self.write_results_file(coverpath, source,
271 lnotab, count)
Tim Peters0eadaac2003-04-24 16:02:54 +0000272
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000273 if summary and n_lines:
274 percent = int(100 * n_hits / n_lines)
275 sums[modulename] = n_lines, percent, modulename, filename
276
277 if summary and sums:
278 mods = sums.keys()
279 mods.sort()
280 print "lines cov% module (path)"
281 for m in mods:
282 n_lines, percent, modulename, filename = sums[m]
283 print "%5d %3d%% %s (%s)" % sums[m]
284
285 if self.outfile:
286 # try and store counts and module info into self.outfile
287 try:
288 pickle.dump((self.counts, self.calledfuncs),
289 open(self.outfile, 'w'), 1)
290 except IOError, err:
Jeremy Hylton38732e12003-04-21 22:04:46 +0000291 print >> sys.stderr, "Can't save counts files because %s" % err
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000292
Jeremy Hylton38732e12003-04-21 22:04:46 +0000293 def write_results_file(self, path, lines, lnotab, lines_hit):
294 """Return a coverage results file in path."""
295
296 try:
297 outfile = open(path, "w")
298 except IOError, err:
299 print >> sys.stderr, ("trace: Could not open %r for writing: %s"
300 "- skipping" % (path, err))
301 return
302
303 n_lines = 0
304 n_hits = 0
305 for i, line in enumerate(lines):
306 lineno = i + 1
307 # do the blank/comment match to try to mark more lines
308 # (help the reader find stuff that hasn't been covered)
309 if lineno in lines_hit:
310 outfile.write("%5d: " % lines_hit[lineno])
311 n_hits += 1
312 n_lines += 1
313 elif rx_blank.match(line):
314 outfile.write(" ")
315 else:
316 # lines preceded by no marks weren't hit
317 # Highlight them if so indicated, unless the line contains
318 # #pragma: NO COVER
319 if lineno in lnotab and not PRAGMA_NOCOVER in lines[i]:
320 outfile.write(">>>>>> ")
Jeremy Hylton546e34b2003-06-26 14:56:17 +0000321 n_lines += 1
Jeremy Hylton38732e12003-04-21 22:04:46 +0000322 else:
323 outfile.write(" ")
Jeremy Hylton38732e12003-04-21 22:04:46 +0000324 outfile.write(lines[i].expandtabs(8))
325 outfile.close()
326
327 return n_hits, n_lines
328
329def find_lines_from_code(code, strs):
330 """Return dict where keys are lines in the line number table."""
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000331 linenos = {}
332
333 line_increments = [ord(c) for c in code.co_lnotab[1::2]]
334 table_length = len(line_increments)
Jeremy Hylton38732e12003-04-21 22:04:46 +0000335 docstring = False
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000336
337 lineno = code.co_firstlineno
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000338 for li in line_increments:
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000339 lineno += li
Jeremy Hylton38732e12003-04-21 22:04:46 +0000340 if lineno not in strs:
341 linenos[lineno] = 1
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000342
343 return linenos
344
Jeremy Hylton38732e12003-04-21 22:04:46 +0000345def find_lines(code, strs):
346 """Return lineno dict for all code objects reachable from code."""
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000347 # get all of the lineno information from the code of this scope level
Jeremy Hylton38732e12003-04-21 22:04:46 +0000348 linenos = find_lines_from_code(code, strs)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000349
350 # and check the constants for references to other code objects
351 for c in code.co_consts:
Jeremy Hylton38732e12003-04-21 22:04:46 +0000352 if isinstance(c, types.CodeType):
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000353 # find another code object, so recurse into it
Jeremy Hylton38732e12003-04-21 22:04:46 +0000354 linenos.update(find_lines(c, strs))
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000355 return linenos
356
Jeremy Hylton38732e12003-04-21 22:04:46 +0000357def find_strings(filename):
358 """Return a dict of possible docstring positions.
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000359
Jeremy Hylton38732e12003-04-21 22:04:46 +0000360 The dict maps line numbers to strings. There is an entry for
361 line that contains only a string or a part of a triple-quoted
362 string.
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000363 """
Jeremy Hylton38732e12003-04-21 22:04:46 +0000364 d = {}
365 # If the first token is a string, then it's the module docstring.
366 # Add this special case so that the test in the loop passes.
367 prev_ttype = token.INDENT
368 f = open(filename)
369 for ttype, tstr, start, end, line in tokenize.generate_tokens(f.readline):
370 if ttype == token.STRING:
371 if prev_ttype == token.INDENT:
372 sline, scol = start
373 eline, ecol = end
374 for i in range(sline, eline + 1):
375 d[i] = 1
376 prev_ttype = ttype
377 f.close()
378 return d
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000379
Jeremy Hylton38732e12003-04-21 22:04:46 +0000380def find_executable_linenos(filename):
381 """Return dict where keys are line numbers in the line number table."""
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000382 assert filename.endswith('.py')
Jeremy Hylton38732e12003-04-21 22:04:46 +0000383 try:
384 prog = open(filename).read()
385 except IOError, err:
386 print >> sys.stderr, ("Not printing coverage data for %r: %s"
387 % (filename, err))
388 return {}
389 code = compile(prog, filename, "exec")
390 strs = find_strings(filename)
391 return find_lines(code, strs)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000392
393class Trace:
394 def __init__(self, count=1, trace=1, countfuncs=0, ignoremods=(),
395 ignoredirs=(), infile=None, outfile=None):
396 """
397 @param count true iff it should count number of times each
Tim Petersf2715e02003-02-19 02:35:07 +0000398 line is executed
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000399 @param trace true iff it should print out each line that is
Tim Petersf2715e02003-02-19 02:35:07 +0000400 being counted
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000401 @param countfuncs true iff it should just output a list of
402 (filename, modulename, funcname,) for functions
403 that were called at least once; This overrides
Tim Petersf2715e02003-02-19 02:35:07 +0000404 `count' and `trace'
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000405 @param ignoremods a list of the names of modules to ignore
406 @param ignoredirs a list of the names of directories to ignore
Tim Petersf2715e02003-02-19 02:35:07 +0000407 all of the (recursive) contents of
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000408 @param infile file from which to read stored counts to be
Tim Petersf2715e02003-02-19 02:35:07 +0000409 added into the results
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000410 @param outfile file in which to write the results
411 """
412 self.infile = infile
413 self.outfile = outfile
414 self.ignore = Ignore(ignoremods, ignoredirs)
415 self.counts = {} # keys are (filename, linenumber)
416 self.blabbed = {} # for debugging
417 self.pathtobasename = {} # for memoizing os.path.basename
418 self.donothing = 0
419 self.trace = trace
420 self._calledfuncs = {}
421 if countfuncs:
422 self.globaltrace = self.globaltrace_countfuncs
423 elif trace and count:
424 self.globaltrace = self.globaltrace_lt
425 self.localtrace = self.localtrace_trace_and_count
426 elif trace:
427 self.globaltrace = self.globaltrace_lt
428 self.localtrace = self.localtrace_trace
429 elif count:
430 self.globaltrace = self.globaltrace_lt
431 self.localtrace = self.localtrace_count
432 else:
433 # Ahem -- do nothing? Okay.
434 self.donothing = 1
435
436 def run(self, cmd):
437 import __main__
438 dict = __main__.__dict__
439 if not self.donothing:
440 sys.settrace(self.globaltrace)
Jeremy Hylton546e34b2003-06-26 14:56:17 +0000441 threading.settrace(self.globaltrace)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000442 try:
443 exec cmd in dict, dict
444 finally:
445 if not self.donothing:
446 sys.settrace(None)
Jeremy Hylton546e34b2003-06-26 14:56:17 +0000447 threading.settrace(None)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000448
449 def runctx(self, cmd, globals=None, locals=None):
450 if globals is None: globals = {}
451 if locals is None: locals = {}
452 if not self.donothing:
453 sys.settrace(self.globaltrace)
Jeremy Hylton546e34b2003-06-26 14:56:17 +0000454 threading.settrace(self.globaltrace)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000455 try:
456 exec cmd in globals, locals
457 finally:
458 if not self.donothing:
459 sys.settrace(None)
Jeremy Hylton546e34b2003-06-26 14:56:17 +0000460 threading.settrace(None)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000461
462 def runfunc(self, func, *args, **kw):
463 result = None
464 if not self.donothing:
465 sys.settrace(self.globaltrace)
466 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000467 result = func(*args, **kw)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000468 finally:
469 if not self.donothing:
470 sys.settrace(None)
471 return result
472
473 def globaltrace_countfuncs(self, frame, why, arg):
Jeremy Hylton38732e12003-04-21 22:04:46 +0000474 """Handler for call events.
Tim Peters0eadaac2003-04-24 16:02:54 +0000475
Jeremy Hylton38732e12003-04-21 22:04:46 +0000476 Adds (filename, modulename, funcname) to the self._calledfuncs dict.
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000477 """
478 if why == 'call':
Jeremy Hylton38732e12003-04-21 22:04:46 +0000479 code = frame.f_code
480 filename = code.co_filename
481 funcname = code.co_name
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000482 if filename:
Jeremy Hylton38732e12003-04-21 22:04:46 +0000483 modulename = modname(filename)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000484 else:
485 modulename = None
Jeremy Hylton38732e12003-04-21 22:04:46 +0000486 self._calledfuncs[(filename, modulename, funcname)] = 1
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000487
488 def globaltrace_lt(self, frame, why, arg):
Jeremy Hylton38732e12003-04-21 22:04:46 +0000489 """Handler for call events.
490
491 If the code block being entered is to be ignored, returns `None',
492 else returns self.localtrace.
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000493 """
494 if why == 'call':
Jeremy Hylton38732e12003-04-21 22:04:46 +0000495 code = frame.f_code
496 filename = code.co_filename
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000497 if filename:
Jeremy Hyltondfbfe732003-04-21 22:49:17 +0000498 # XXX modname() doesn't work right for packages, so
499 # the ignore support won't work right for packages
Jeremy Hylton38732e12003-04-21 22:04:46 +0000500 modulename = modname(filename)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000501 if modulename is not None:
502 ignore_it = self.ignore.names(filename, modulename)
503 if not ignore_it:
504 if self.trace:
505 print (" --- modulename: %s, funcname: %s"
Jeremy Hylton38732e12003-04-21 22:04:46 +0000506 % (modulename, code.co_name))
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000507 return self.localtrace
508 else:
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000509 return None
510
511 def localtrace_trace_and_count(self, frame, why, arg):
Jeremy Hylton38732e12003-04-21 22:04:46 +0000512 if why == "line":
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000513 # record the file name and line number of every trace
Jeremy Hylton38732e12003-04-21 22:04:46 +0000514 filename = frame.f_code.co_filename
515 lineno = frame.f_lineno
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000516 key = filename, lineno
517 self.counts[key] = self.counts.get(key, 0) + 1
Tim Petersf2715e02003-02-19 02:35:07 +0000518
Jeremy Hylton38732e12003-04-21 22:04:46 +0000519 bname = os.path.basename(filename)
520 print "%s(%d): %s" % (bname, lineno,
521 linecache.getline(filename, lineno)),
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000522 return self.localtrace
523
524 def localtrace_trace(self, frame, why, arg):
Jeremy Hylton38732e12003-04-21 22:04:46 +0000525 if why == "line":
526 # record the file name and line number of every trace
527 filename = frame.f_code.co_filename
528 lineno = frame.f_lineno
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000529
Jeremy Hylton38732e12003-04-21 22:04:46 +0000530 bname = os.path.basename(filename)
531 print "%s(%d): %s" % (bname, lineno,
532 linecache.getline(filename, lineno)),
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000533 return self.localtrace
534
535 def localtrace_count(self, frame, why, arg):
Jeremy Hylton38732e12003-04-21 22:04:46 +0000536 if why == "line":
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000537 filename = frame.f_code.co_filename
538 lineno = frame.f_lineno
539 key = filename, lineno
540 self.counts[key] = self.counts.get(key, 0) + 1
541 return self.localtrace
542
543 def results(self):
544 return CoverageResults(self.counts, infile=self.infile,
545 outfile=self.outfile,
546 calledfuncs=self._calledfuncs)
547
548def _err_exit(msg):
549 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
550 sys.exit(1)
551
552def main(argv=None):
553 import getopt
554
555 if argv is None:
556 argv = sys.argv
557 try:
558 opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:l",
559 ["help", "version", "trace", "count",
560 "report", "no-report", "summary",
561 "file=", "missing",
562 "ignore-module=", "ignore-dir=",
563 "coverdir=", "listfuncs",])
564
565 except getopt.error, msg:
566 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
567 sys.stderr.write("Try `%s --help' for more information\n"
568 % sys.argv[0])
569 sys.exit(1)
570
571 trace = 0
572 count = 0
573 report = 0
574 no_report = 0
575 counts_file = None
576 missing = 0
577 ignore_modules = []
578 ignore_dirs = []
579 coverdir = None
580 summary = 0
581 listfuncs = False
582
583 for opt, val in opts:
584 if opt == "--help":
585 usage(sys.stdout)
586 sys.exit(0)
587
588 if opt == "--version":
589 sys.stdout.write("trace 2.0\n")
590 sys.exit(0)
591
592 if opt == "-l" or opt == "--listfuncs":
593 listfuncs = True
594 continue
595
596 if opt == "-t" or opt == "--trace":
597 trace = 1
598 continue
599
600 if opt == "-c" or opt == "--count":
601 count = 1
602 continue
603
604 if opt == "-r" or opt == "--report":
605 report = 1
606 continue
607
608 if opt == "-R" or opt == "--no-report":
609 no_report = 1
610 continue
611
612 if opt == "-f" or opt == "--file":
613 counts_file = val
614 continue
615
616 if opt == "-m" or opt == "--missing":
617 missing = 1
618 continue
619
620 if opt == "-C" or opt == "--coverdir":
621 coverdir = val
622 continue
623
624 if opt == "-s" or opt == "--summary":
625 summary = 1
626 continue
627
628 if opt == "--ignore-module":
629 ignore_modules.append(val)
630 continue
631
632 if opt == "--ignore-dir":
633 for s in val.split(os.pathsep):
634 s = os.path.expandvars(s)
635 # should I also call expanduser? (after all, could use $HOME)
636
637 s = s.replace("$prefix",
638 os.path.join(sys.prefix, "lib",
639 "python" + sys.version[:3]))
640 s = s.replace("$exec_prefix",
641 os.path.join(sys.exec_prefix, "lib",
642 "python" + sys.version[:3]))
643 s = os.path.normpath(s)
644 ignore_dirs.append(s)
645 continue
646
647 assert 0, "Should never get here"
648
649 if listfuncs and (count or trace):
650 _err_exit("cannot specify both --listfuncs and (--trace or --count)")
651
652 if not count and not trace and not report and not listfuncs:
Jeremy Hylton38732e12003-04-21 22:04:46 +0000653 _err_exit("must specify one of --trace, --count, --report or "
654 "--listfuncs")
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000655
656 if report and no_report:
657 _err_exit("cannot specify both --report and --no-report")
658
659 if report and not counts_file:
660 _err_exit("--report requires a --file")
661
662 if no_report and len(prog_argv) == 0:
663 _err_exit("missing name of file to run")
664
665 # everything is ready
666 if report:
667 results = CoverageResults(infile=counts_file, outfile=counts_file)
668 results.write_results(missing, summary=summary, coverdir=coverdir)
669 else:
670 sys.argv = prog_argv
671 progname = prog_argv[0]
672 sys.path[0] = os.path.split(progname)[0]
673
674 t = Trace(count, trace, countfuncs=listfuncs,
675 ignoremods=ignore_modules, ignoredirs=ignore_dirs,
676 infile=counts_file, outfile=counts_file)
677 try:
678 t.run('execfile(' + `progname` + ')')
679 except IOError, err:
Jeremy Hylton38732e12003-04-21 22:04:46 +0000680 _err_exit("Cannot run file %r because: %s" % (sys.argv[0], err))
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000681 except SystemExit:
682 pass
683
684 results = t.results()
685
686 if not no_report:
687 results.write_results(missing, summary=summary, coverdir=coverdir)
688
689if __name__=='__main__':
690 main()