blob: 8b49a2ece28e563b106a1821ba3748a4153b5f6a [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()
45 r.write_results(show_missing=1)
46"""
47
48import sys, os, tempfile, types, copy, operator, inspect, exceptions, marshal
49try:
50 import cPickle
51 pickle = cPickle
52except ImportError:
53 import pickle
54
55# DEBUG_MODE=1 # make this true to get printouts which help you understand what's going on
56
57def usage(outfile):
58 outfile.write("""Usage: %s [OPTIONS] <file> [ARGS]
59
60Meta-options:
61--help Display this help then exit.
62--version Output version information then exit.
63
64Otherwise, exactly one of the following three options must be given:
65-t, --trace Print each line to sys.stdout before it is executed.
66-c, --count Count the number of times each line is executed
67 and write the counts to <module>.cover for each
68 module executed, in the module's directory.
69 See also `--coverdir', `--file', `--no-report' below.
70-r, --report Generate a report from a counts file; do not execute
71 any code. `--file' must specify the results file to
72 read, which must have been created in a previous run
73 with `--count --file=FILE'.
74
75Modifiers:
76-f, --file=<file> File to accumulate counts over several runs.
77-R, --no-report Do not generate the coverage report files.
78 Useful if you want to accumulate over several runs.
79-C, --coverdir=<dir> Directory where the report files. The coverage
80 report for <package>.<module> is written to file
81 <dir>/<package>/<module>.cover.
82-m, --missing Annotate executable lines that were not executed
83 with '>>>>>> '.
84-s, --summary Write a brief summary on stdout for each file.
85 (Can only be used with --count or --report.)
86
87Filters, may be repeated multiple times:
88--ignore-module=<mod> Ignore the given module and its submodules
89 (if it is a package).
90--ignore-dir=<dir> Ignore files in the given directory (multiple
91 directories can be joined by os.pathsep).
92""" % sys.argv[0])
93
94class Ignore:
95 def __init__(self, modules = None, dirs = None):
96 self._mods = modules or []
97 self._dirs = dirs or []
98
99 self._dirs = map(os.path.normpath, self._dirs)
100 self._ignore = { '<string>': 1 }
101
102 def names(self, filename, modulename):
103 if self._ignore.has_key(modulename):
104 return self._ignore[modulename]
105
106 # haven't seen this one before, so see if the module name is
107 # on the ignore list. Need to take some care since ignoring
108 # "cmp" musn't mean ignoring "cmpcache" but ignoring
109 # "Spam" must also mean ignoring "Spam.Eggs".
110 for mod in self._mods:
111 if mod == modulename: # Identical names, so ignore
112 self._ignore[modulename] = 1
113 return 1
114 # check if the module is a proper submodule of something on
115 # the ignore list
116 n = len(mod)
117 # (will not overflow since if the first n characters are the
118 # same and the name has not already occured, then the size
119 # of "name" is greater than that of "mod")
120 if mod == modulename[:n] and modulename[n] == '.':
121 self._ignore[modulename] = 1
122 return 1
123
124 # Now check that __file__ isn't in one of the directories
125 if filename is None:
126 # must be a built-in, so we must ignore
127 self._ignore[modulename] = 1
128 return 1
129
130 # Ignore a file when it contains one of the ignorable paths
131 for d in self._dirs:
132 # The '+ os.sep' is to ensure that d is a parent directory,
133 # as compared to cases like:
134 # d = "/usr/local"
135 # filename = "/usr/local.py"
136 # or
137 # d = "/usr/local.py"
138 # filename = "/usr/local.py"
139 if filename.startswith(d + os.sep):
140 self._ignore[modulename] = 1
141 return 1
142
143 # Tried the different ways, so we don't ignore this module
144 self._ignore[modulename] = 0
145 return 0
146
147class CoverageResults:
148 def __init__(self, counts=None, calledfuncs=None, infile=None,
Tim Petersf2715e02003-02-19 02:35:07 +0000149 outfile=None):
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000150 self.counts = counts
151 if self.counts is None:
152 self.counts = {}
153 self.counter = self.counts.copy() # map (filename, lineno) to count
154 self.calledfuncs = calledfuncs
155 if self.calledfuncs is None:
156 self.calledfuncs = {}
157 self.calledfuncs = self.calledfuncs.copy()
158 self.infile = infile
159 self.outfile = outfile
160 if self.infile:
161 # try and merge existing counts file
162 try:
163 thingie = pickle.load(open(self.infile, 'r'))
164 if type(thingie) is types.DictType:
165 # backwards compatibility for old trace.py after
166 # Zooko touched it but before calledfuncs --Zooko
Tim Petersf2715e02003-02-19 02:35:07 +0000167 # 2001-10-24
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000168 self.update(self.__class__(thingie))
169 elif type(thingie) is types.TupleType and len(thingie) == 2:
170 counts, calledfuncs = thingie
171 self.update(self.__class__(counts, calledfuncs))
172 except (IOError, EOFError):
173 pass
174 except pickle.UnpicklingError:
175 # backwards compatibility for old trace.py before
Tim Petersf2715e02003-02-19 02:35:07 +0000176 # Zooko touched it --Zooko 2001-10-24
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000177 self.update(self.__class__(marshal.load(open(self.infile))))
178
179 def update(self, other):
180 """Merge in the data from another CoverageResults"""
181 counts = self.counts
182 calledfuncs = self.calledfuncs
183 other_counts = other.counts
184 other_calledfuncs = other.calledfuncs
185
186 for key in other_counts.keys():
187 if key != 'calledfuncs':
188 # backwards compatibility for abortive attempt to
189 # stuff calledfuncs into self.counts, by Zooko
Tim Petersf2715e02003-02-19 02:35:07 +0000190 # --Zooko 2001-10-24
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000191 counts[key] = counts.get(key, 0) + other_counts[key]
192
193 for key in other_calledfuncs.keys():
194 calledfuncs[key] = 1
195
196 def write_results(self, show_missing = 1, summary = 0, coverdir = None):
197 """
198 @param coverdir
199 """
200 for filename, modulename, funcname in self.calledfuncs.keys():
201 print ("filename: %s, modulename: %s, funcname: %s"
202 % (filename, modulename, funcname))
203
204 import re
205 # turn the counts data ("(filename, lineno) = count") into something
206 # accessible on a per-file basis
207 per_file = {}
208 for thingie in self.counts.keys():
209 if thingie != "calledfuncs":
210 # backwards compatibility for abortive attempt to
211 # stuff calledfuncs into self.counts, by Zooko --Zooko
212 # 2001-10-24
213 filename, lineno = thingie
214 lines_hit = per_file[filename] = per_file.get(filename, {})
215 lines_hit[lineno] = self.counts[(filename, lineno)]
216
217 # there are many places where this is insufficient, like a blank
218 # line embedded in a multiline string.
219 blank = re.compile(r'^\s*(#.*)?$')
220
221 # accumulate summary info, if needed
222 sums = {}
223
224 # generate file paths for the coverage files we are going to write...
225 fnlist = []
226 tfdir = tempfile.gettempdir()
227 for key in per_file.keys():
228 filename = key
229
230 # skip some "files" we don't care about...
231 if filename == "<string>":
232 continue
233 # are these caused by code compiled using exec or something?
234 if filename.startswith(tfdir):
235 continue
236
237 modulename = inspect.getmodulename(filename)
238
239 if filename.endswith(".pyc") or filename.endswith(".pyo"):
240 filename = filename[:-1]
241
242 if coverdir:
243 thiscoverdir = coverdir
244 else:
245 thiscoverdir = os.path.dirname(os.path.abspath(filename))
246
247 # the code from here to "<<<" is the contents of the `fileutil.make_dirs()' function in the Mojo Nation project. --Zooko 2001-10-14
248 # http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/mojonation/evil/common/fileutil.py?rev=HEAD&content-type=text/vnd.viewcvs-markup
249 tx = None
250 try:
251 os.makedirs(thiscoverdir)
252 except OSError, x:
253 tx = x
254
255 if not os.path.isdir(thiscoverdir):
256 if tx:
257 raise tx
258 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...
259 # <<<
260
261 # build list file name by appending a ".cover" to the module name
262 # and sticking it into the specified directory
263 if "." in modulename:
264 # A module in a package
265 finalname = modulename.split(".")[-1]
266 listfilename = os.path.join(thiscoverdir, finalname + ".cover")
267 else:
268 listfilename = os.path.join(thiscoverdir, modulename + ".cover")
269
270 # Get the original lines from the .py file
271 try:
272 lines = open(filename, 'r').readlines()
273 except IOError, err:
274 sys.stderr.write("trace: Could not open %s for reading because: %s - skipping\n" % (`filename`, err))
275 continue
276
277 try:
278 outfile = open(listfilename, 'w')
279 except IOError, err:
280 sys.stderr.write(
281 '%s: Could not open %s for writing because: %s" \
282 "- skipping\n' % ("trace", `listfilename`, err))
283 continue
284
285 # If desired, get a list of the line numbers which represent
286 # executable content (returned as a dict for better lookup speed)
287 if show_missing:
288 executable_linenos = find_executable_linenos(filename)
289 else:
290 executable_linenos = {}
291
292 n_lines = 0
293 n_hits = 0
294 lines_hit = per_file[key]
295 for i in range(len(lines)):
296 line = lines[i]
297
298 # do the blank/comment match to try to mark more lines
299 # (help the reader find stuff that hasn't been covered)
300 if lines_hit.has_key(i+1):
301 # count precedes the lines that we captured
302 outfile.write('%5d: ' % lines_hit[i+1])
303 n_hits = n_hits + 1
304 n_lines = n_lines + 1
305 elif blank.match(line):
306 # blank lines and comments are preceded by dots
307 outfile.write(' . ')
308 else:
309 # lines preceded by no marks weren't hit
310 # Highlight them if so indicated, unless the line contains
311 # '#pragma: NO COVER' (it is possible to embed this into
312 # the text as a non-comment; no easy fix)
313 if executable_linenos.has_key(i+1) and \
314 lines[i].find(' '.join(['#pragma', 'NO COVER'])) == -1:
315 outfile.write('>>>>>> ')
316 else:
317 outfile.write(' '*7)
318 n_lines = n_lines + 1
319 outfile.write(lines[i].expandtabs(8))
320
321 outfile.close()
322
323 if summary and n_lines:
324 percent = int(100 * n_hits / n_lines)
325 sums[modulename] = n_lines, percent, modulename, filename
326
327 if summary and sums:
328 mods = sums.keys()
329 mods.sort()
330 print "lines cov% module (path)"
331 for m in mods:
332 n_lines, percent, modulename, filename = sums[m]
333 print "%5d %3d%% %s (%s)" % sums[m]
334
335 if self.outfile:
336 # try and store counts and module info into self.outfile
337 try:
338 pickle.dump((self.counts, self.calledfuncs),
339 open(self.outfile, 'w'), 1)
340 except IOError, err:
341 sys.stderr.write("cannot save counts files because %s" % err)
342
343def _find_LINENO_from_code(code):
344 """return the numbers of the lines containing the source code that
345 was compiled into code"""
346 linenos = {}
347
348 line_increments = [ord(c) for c in code.co_lnotab[1::2]]
349 table_length = len(line_increments)
350
351 lineno = code.co_firstlineno
352
353 for li in line_increments:
354 linenos[lineno] = 1
355 lineno += li
356 linenos[lineno] = 1
357
358 return linenos
359
360def _find_LINENO(code):
361 """return all of the lineno information from a code object"""
362 import types
363
364 # get all of the lineno information from the code of this scope level
365 linenos = _find_LINENO_from_code(code)
366
367 # and check the constants for references to other code objects
368 for c in code.co_consts:
369 if type(c) == types.CodeType:
370 # find another code object, so recurse into it
371 linenos.update(_find_LINENO(c))
372 return linenos
373
374def find_executable_linenos(filename):
375 """return a dict of the line numbers from executable statements in a file
376
377 """
378 import parser
379
380 assert filename.endswith('.py')
381
382 prog = open(filename).read()
383 ast = parser.suite(prog)
384 code = parser.compileast(ast, filename)
385
386 return _find_LINENO(code)
387
388### XXX because os.path.commonprefix seems broken by my way of thinking...
389def commonprefix(dirs):
390 "Given a list of pathnames, returns the longest common leading component"
391 if not dirs: return ''
392 n = copy.copy(dirs)
393 for i in range(len(n)):
394 n[i] = n[i].split(os.sep)
395 prefix = n[0]
396 for item in n:
397 for i in range(len(prefix)):
398 if prefix[:i+1] <> item[:i+1]:
399 prefix = prefix[:i]
400 if i == 0: return ''
401 break
402 return os.sep.join(prefix)
403
404class Trace:
405 def __init__(self, count=1, trace=1, countfuncs=0, ignoremods=(),
406 ignoredirs=(), infile=None, outfile=None):
407 """
408 @param count true iff it should count number of times each
Tim Petersf2715e02003-02-19 02:35:07 +0000409 line is executed
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000410 @param trace true iff it should print out each line that is
Tim Petersf2715e02003-02-19 02:35:07 +0000411 being counted
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000412 @param countfuncs true iff it should just output a list of
413 (filename, modulename, funcname,) for functions
414 that were called at least once; This overrides
Tim Petersf2715e02003-02-19 02:35:07 +0000415 `count' and `trace'
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000416 @param ignoremods a list of the names of modules to ignore
417 @param ignoredirs a list of the names of directories to ignore
Tim Petersf2715e02003-02-19 02:35:07 +0000418 all of the (recursive) contents of
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000419 @param infile file from which to read stored counts to be
Tim Petersf2715e02003-02-19 02:35:07 +0000420 added into the results
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000421 @param outfile file in which to write the results
422 """
423 self.infile = infile
424 self.outfile = outfile
425 self.ignore = Ignore(ignoremods, ignoredirs)
426 self.counts = {} # keys are (filename, linenumber)
427 self.blabbed = {} # for debugging
428 self.pathtobasename = {} # for memoizing os.path.basename
429 self.donothing = 0
430 self.trace = trace
431 self._calledfuncs = {}
432 if countfuncs:
433 self.globaltrace = self.globaltrace_countfuncs
434 elif trace and count:
435 self.globaltrace = self.globaltrace_lt
436 self.localtrace = self.localtrace_trace_and_count
437 elif trace:
438 self.globaltrace = self.globaltrace_lt
439 self.localtrace = self.localtrace_trace
440 elif count:
441 self.globaltrace = self.globaltrace_lt
442 self.localtrace = self.localtrace_count
443 else:
444 # Ahem -- do nothing? Okay.
445 self.donothing = 1
446
447 def run(self, cmd):
448 import __main__
449 dict = __main__.__dict__
450 if not self.donothing:
451 sys.settrace(self.globaltrace)
452 try:
453 exec cmd in dict, dict
454 finally:
455 if not self.donothing:
456 sys.settrace(None)
457
458 def runctx(self, cmd, globals=None, locals=None):
459 if globals is None: globals = {}
460 if locals is None: locals = {}
461 if not self.donothing:
462 sys.settrace(self.globaltrace)
463 try:
464 exec cmd in globals, locals
465 finally:
466 if not self.donothing:
467 sys.settrace(None)
468
469 def runfunc(self, func, *args, **kw):
470 result = None
471 if not self.donothing:
472 sys.settrace(self.globaltrace)
473 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000474 result = func(*args, **kw)
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000475 finally:
476 if not self.donothing:
477 sys.settrace(None)
478 return result
479
480 def globaltrace_countfuncs(self, frame, why, arg):
481 """
482 Handles `call' events (why == 'call') and adds the (filename, modulename, funcname,) to the self._calledfuncs dict.
483 """
484 if why == 'call':
485 filename, lineno, funcname, context, lineindex = \
486 inspect.getframeinfo(frame, 0)
487 if filename:
488 modulename = inspect.getmodulename(filename)
489 else:
490 modulename = None
491 self._calledfuncs[(filename, modulename, funcname,)] = 1
492
493 def globaltrace_lt(self, frame, why, arg):
494 """
495 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'.
496 """
497 if why == 'call':
498 filename, lineno, funcname, context, lineindex = \
499 inspect.getframeinfo(frame, 0)
500 if filename:
501 modulename = inspect.getmodulename(filename)
502 if modulename is not None:
503 ignore_it = self.ignore.names(filename, modulename)
504 if not ignore_it:
505 if self.trace:
506 print (" --- modulename: %s, funcname: %s"
507 % (modulename, funcname))
508 return self.localtrace
509 else:
510 # XXX why no filename?
511 return None
512
513 def localtrace_trace_and_count(self, frame, why, arg):
514 if why == 'line':
515 # record the file name and line number of every trace
516 # XXX I wish inspect offered me an optimized
517 # `getfilename(frame)' to use in place of the presumably
518 # heavier `getframeinfo()'. --Zooko 2001-10-14
Tim Petersf2715e02003-02-19 02:35:07 +0000519
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000520 filename, lineno, funcname, context, lineindex = \
521 inspect.getframeinfo(frame, 1)
522 key = filename, lineno
523 self.counts[key] = self.counts.get(key, 0) + 1
Tim Petersf2715e02003-02-19 02:35:07 +0000524
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000525 # XXX not convinced that this memoizing is a performance
526 # win -- I don't know enough about Python guts to tell.
527 # --Zooko 2001-10-14
Tim Petersf2715e02003-02-19 02:35:07 +0000528
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000529 bname = self.pathtobasename.get(filename)
530 if bname is None:
Tim Petersf2715e02003-02-19 02:35:07 +0000531
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000532 # Using setdefault faster than two separate lines?
533 # --Zooko 2001-10-14
534 bname = self.pathtobasename.setdefault(filename,
535 os.path.basename(filename))
536 try:
537 print "%s(%d): %s" % (bname, lineno, context[lineindex]),
538 except IndexError:
539 # Uh.. sometimes getframeinfo gives me a context of
540 # length 1 and a lineindex of -2. Oh well.
541 pass
542 return self.localtrace
543
544 def localtrace_trace(self, frame, why, arg):
545 if why == 'line':
546 # XXX shouldn't do the count increment when arg is
547 # exception? But be careful to return self.localtrace
548 # when arg is exception! ? --Zooko 2001-10-14
549
550 # record the file name and line number of every trace XXX
551 # I wish inspect offered me an optimized
552 # `getfilename(frame)' to use in place of the presumably
553 # heavier `getframeinfo()'. --Zooko 2001-10-14
554 filename, lineno, funcname, context, lineindex = \
555 inspect.getframeinfo(frame)
Tim Petersf2715e02003-02-19 02:35:07 +0000556
Jeremy Hylton4edaa0d2003-02-18 15:06:17 +0000557 # XXX not convinced that this memoizing is a performance
558 # win -- I don't know enough about Python guts to tell.
559 # --Zooko 2001-10-14
560 bname = self.pathtobasename.get(filename)
561 if bname is None:
562 # Using setdefault faster than two separate lines?
563 # --Zooko 2001-10-14
564 bname = self.pathtobasename.setdefault(filename, os.path.basename(filename))
565 if context is not None:
566 try:
567 print "%s(%d): %s" % (bname, lineno, context[lineindex]),
568 except IndexError:
569 # Uh.. sometimes getframeinfo gives me a context of length 1 and a lineindex of -2. Oh well.
570 pass
571 else:
572 print "%s(???): ???" % bname
573 return self.localtrace
574
575 def localtrace_count(self, frame, why, arg):
576 if why == 'line':
577 filename = frame.f_code.co_filename
578 lineno = frame.f_lineno
579 key = filename, lineno
580 self.counts[key] = self.counts.get(key, 0) + 1
581 return self.localtrace
582
583 def results(self):
584 return CoverageResults(self.counts, infile=self.infile,
585 outfile=self.outfile,
586 calledfuncs=self._calledfuncs)
587
588def _err_exit(msg):
589 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
590 sys.exit(1)
591
592def main(argv=None):
593 import getopt
594
595 if argv is None:
596 argv = sys.argv
597 try:
598 opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:l",
599 ["help", "version", "trace", "count",
600 "report", "no-report", "summary",
601 "file=", "missing",
602 "ignore-module=", "ignore-dir=",
603 "coverdir=", "listfuncs",])
604
605 except getopt.error, msg:
606 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
607 sys.stderr.write("Try `%s --help' for more information\n"
608 % sys.argv[0])
609 sys.exit(1)
610
611 trace = 0
612 count = 0
613 report = 0
614 no_report = 0
615 counts_file = None
616 missing = 0
617 ignore_modules = []
618 ignore_dirs = []
619 coverdir = None
620 summary = 0
621 listfuncs = False
622
623 for opt, val in opts:
624 if opt == "--help":
625 usage(sys.stdout)
626 sys.exit(0)
627
628 if opt == "--version":
629 sys.stdout.write("trace 2.0\n")
630 sys.exit(0)
631
632 if opt == "-l" or opt == "--listfuncs":
633 listfuncs = True
634 continue
635
636 if opt == "-t" or opt == "--trace":
637 trace = 1
638 continue
639
640 if opt == "-c" or opt == "--count":
641 count = 1
642 continue
643
644 if opt == "-r" or opt == "--report":
645 report = 1
646 continue
647
648 if opt == "-R" or opt == "--no-report":
649 no_report = 1
650 continue
651
652 if opt == "-f" or opt == "--file":
653 counts_file = val
654 continue
655
656 if opt == "-m" or opt == "--missing":
657 missing = 1
658 continue
659
660 if opt == "-C" or opt == "--coverdir":
661 coverdir = val
662 continue
663
664 if opt == "-s" or opt == "--summary":
665 summary = 1
666 continue
667
668 if opt == "--ignore-module":
669 ignore_modules.append(val)
670 continue
671
672 if opt == "--ignore-dir":
673 for s in val.split(os.pathsep):
674 s = os.path.expandvars(s)
675 # should I also call expanduser? (after all, could use $HOME)
676
677 s = s.replace("$prefix",
678 os.path.join(sys.prefix, "lib",
679 "python" + sys.version[:3]))
680 s = s.replace("$exec_prefix",
681 os.path.join(sys.exec_prefix, "lib",
682 "python" + sys.version[:3]))
683 s = os.path.normpath(s)
684 ignore_dirs.append(s)
685 continue
686
687 assert 0, "Should never get here"
688
689 if listfuncs and (count or trace):
690 _err_exit("cannot specify both --listfuncs and (--trace or --count)")
691
692 if not count and not trace and not report and not listfuncs:
693 _err_exit("must specify one of --trace, --count, --report or --listfuncs")
694
695 if report and no_report:
696 _err_exit("cannot specify both --report and --no-report")
697
698 if report and not counts_file:
699 _err_exit("--report requires a --file")
700
701 if no_report and len(prog_argv) == 0:
702 _err_exit("missing name of file to run")
703
704 # everything is ready
705 if report:
706 results = CoverageResults(infile=counts_file, outfile=counts_file)
707 results.write_results(missing, summary=summary, coverdir=coverdir)
708 else:
709 sys.argv = prog_argv
710 progname = prog_argv[0]
711 sys.path[0] = os.path.split(progname)[0]
712
713 t = Trace(count, trace, countfuncs=listfuncs,
714 ignoremods=ignore_modules, ignoredirs=ignore_dirs,
715 infile=counts_file, outfile=counts_file)
716 try:
717 t.run('execfile(' + `progname` + ')')
718 except IOError, err:
719 _err_exit("Cannot run file %s because: %s" % (`sys.argv[0]`, err))
720 except SystemExit:
721 pass
722
723 results = t.results()
724
725 if not no_report:
726 results.write_results(missing, summary=summary, coverdir=coverdir)
727
728if __name__=='__main__':
729 main()