issue5063: Fixes for building RPM on CentOS plus misc .spec file enhancements.
[python.git] / Lib / trace.py
blob7d504c1e3d6d87dc921b284550845b63f7c01cdc
1 #!/usr/bin/env python
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
10 # Copyright 2000, Mojam Media, Inc., all rights reserved.
11 # Author: Skip Montanaro
13 # Copyright 1999, Bioreason, Inc., all rights reserved.
14 # Author: Andrew Dalke
16 # Copyright 1995-1997, Automatrix, Inc., all rights reserved.
17 # Author: Skip Montanaro
19 # Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
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.
30 """program/module to trace Python program or function execution
32 Sample 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 trace.py --trackcalls spam.py eggs
37 Sample use, programmatically
38 import sys
40 # create a Trace object, telling it what to ignore, and whether to
41 # do tracing or line-counting or both.
42 tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,], trace=0,
43 count=1)
44 # run the new command using the given tracer
45 tracer.run('main()')
46 # make a report, placing output in /tmp
47 r = tracer.results()
48 r.write_results(show_missing=True, coverdir="/tmp")
49 """
51 import linecache
52 import os
53 import re
54 import sys
55 import threading
56 import time
57 import token
58 import tokenize
59 import types
60 import gc
62 try:
63 import cPickle
64 pickle = cPickle
65 except ImportError:
66 import pickle
68 def usage(outfile):
69 outfile.write("""Usage: %s [OPTIONS] <file> [ARGS]
71 Meta-options:
72 --help Display this help then exit.
73 --version Output version information then exit.
75 Otherwise, exactly one of the following three options must be given:
76 -t, --trace Print each line to sys.stdout before it is executed.
77 -c, --count Count the number of times each line is executed
78 and write the counts to <module>.cover for each
79 module executed, in the module's directory.
80 See also `--coverdir', `--file', `--no-report' below.
81 -l, --listfuncs Keep track of which functions are executed at least
82 once and write the results to sys.stdout after the
83 program exits.
84 -T, --trackcalls Keep track of caller/called pairs and write the
85 results to sys.stdout after the program exits.
86 -r, --report Generate a report from a counts file; do not execute
87 any code. `--file' must specify the results file to
88 read, which must have been created in a previous run
89 with `--count --file=FILE'.
91 Modifiers:
92 -f, --file=<file> File to accumulate counts over several runs.
93 -R, --no-report Do not generate the coverage report files.
94 Useful if you want to accumulate over several runs.
95 -C, --coverdir=<dir> Directory where the report files. The coverage
96 report for <package>.<module> is written to file
97 <dir>/<package>/<module>.cover.
98 -m, --missing Annotate executable lines that were not executed
99 with '>>>>>> '.
100 -s, --summary Write a brief summary on stdout for each file.
101 (Can only be used with --count or --report.)
102 -g, --timing Prefix each line with the time since the program started.
103 Only used while tracing.
105 Filters, may be repeated multiple times:
106 --ignore-module=<mod> Ignore the given module(s) and its submodules
107 (if it is a package). Accepts comma separated
108 list of module names
109 --ignore-dir=<dir> Ignore files in the given directory (multiple
110 directories can be joined by os.pathsep).
111 """ % sys.argv[0])
113 PRAGMA_NOCOVER = "#pragma NO COVER"
115 # Simple rx to find lines with no code.
116 rx_blank = re.compile(r'^\s*(#.*)?$')
118 class Ignore:
119 def __init__(self, modules = None, dirs = None):
120 self._mods = modules or []
121 self._dirs = dirs or []
123 self._dirs = map(os.path.normpath, self._dirs)
124 self._ignore = { '<string>': 1 }
126 def names(self, filename, modulename):
127 if modulename in self._ignore:
128 return self._ignore[modulename]
130 # haven't seen this one before, so see if the module name is
131 # on the ignore list. Need to take some care since ignoring
132 # "cmp" musn't mean ignoring "cmpcache" but ignoring
133 # "Spam" must also mean ignoring "Spam.Eggs".
134 for mod in self._mods:
135 if mod == modulename: # Identical names, so ignore
136 self._ignore[modulename] = 1
137 return 1
138 # check if the module is a proper submodule of something on
139 # the ignore list
140 n = len(mod)
141 # (will not overflow since if the first n characters are the
142 # same and the name has not already occurred, then the size
143 # of "name" is greater than that of "mod")
144 if mod == modulename[:n] and modulename[n] == '.':
145 self._ignore[modulename] = 1
146 return 1
148 # Now check that __file__ isn't in one of the directories
149 if filename is None:
150 # must be a built-in, so we must ignore
151 self._ignore[modulename] = 1
152 return 1
154 # Ignore a file when it contains one of the ignorable paths
155 for d in self._dirs:
156 # The '+ os.sep' is to ensure that d is a parent directory,
157 # as compared to cases like:
158 # d = "/usr/local"
159 # filename = "/usr/local.py"
160 # or
161 # d = "/usr/local.py"
162 # filename = "/usr/local.py"
163 if filename.startswith(d + os.sep):
164 self._ignore[modulename] = 1
165 return 1
167 # Tried the different ways, so we don't ignore this module
168 self._ignore[modulename] = 0
169 return 0
171 def modname(path):
172 """Return a plausible module name for the patch."""
174 base = os.path.basename(path)
175 filename, ext = os.path.splitext(base)
176 return filename
178 def fullmodname(path):
179 """Return a plausible module name for the path."""
181 # If the file 'path' is part of a package, then the filename isn't
182 # enough to uniquely identify it. Try to do the right thing by
183 # looking in sys.path for the longest matching prefix. We'll
184 # assume that the rest is the package name.
186 comparepath = os.path.normcase(path)
187 longest = ""
188 for dir in sys.path:
189 dir = os.path.normcase(dir)
190 if comparepath.startswith(dir) and comparepath[len(dir)] == os.sep:
191 if len(dir) > len(longest):
192 longest = dir
194 if longest:
195 base = path[len(longest) + 1:]
196 else:
197 base = path
198 base = base.replace(os.sep, ".")
199 if os.altsep:
200 base = base.replace(os.altsep, ".")
201 filename, ext = os.path.splitext(base)
202 return filename
204 class CoverageResults:
205 def __init__(self, counts=None, calledfuncs=None, infile=None,
206 callers=None, outfile=None):
207 self.counts = counts
208 if self.counts is None:
209 self.counts = {}
210 self.counter = self.counts.copy() # map (filename, lineno) to count
211 self.calledfuncs = calledfuncs
212 if self.calledfuncs is None:
213 self.calledfuncs = {}
214 self.calledfuncs = self.calledfuncs.copy()
215 self.callers = callers
216 if self.callers is None:
217 self.callers = {}
218 self.callers = self.callers.copy()
219 self.infile = infile
220 self.outfile = outfile
221 if self.infile:
222 # Try to merge existing counts file.
223 try:
224 counts, calledfuncs, callers = \
225 pickle.load(open(self.infile, 'rb'))
226 self.update(self.__class__(counts, calledfuncs, callers))
227 except (IOError, EOFError, ValueError), err:
228 print >> sys.stderr, ("Skipping counts file %r: %s"
229 % (self.infile, err))
231 def update(self, other):
232 """Merge in the data from another CoverageResults"""
233 counts = self.counts
234 calledfuncs = self.calledfuncs
235 callers = self.callers
236 other_counts = other.counts
237 other_calledfuncs = other.calledfuncs
238 other_callers = other.callers
240 for key in other_counts.keys():
241 counts[key] = counts.get(key, 0) + other_counts[key]
243 for key in other_calledfuncs.keys():
244 calledfuncs[key] = 1
246 for key in other_callers.keys():
247 callers[key] = 1
249 def write_results(self, show_missing=True, summary=False, coverdir=None):
251 @param coverdir
253 if self.calledfuncs:
254 print
255 print "functions called:"
256 calls = self.calledfuncs.keys()
257 calls.sort()
258 for filename, modulename, funcname in calls:
259 print ("filename: %s, modulename: %s, funcname: %s"
260 % (filename, modulename, funcname))
262 if self.callers:
263 print
264 print "calling relationships:"
265 calls = self.callers.keys()
266 calls.sort()
267 lastfile = lastcfile = ""
268 for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) in calls:
269 if pfile != lastfile:
270 print
271 print "***", pfile, "***"
272 lastfile = pfile
273 lastcfile = ""
274 if cfile != pfile and lastcfile != cfile:
275 print " -->", cfile
276 lastcfile = cfile
277 print " %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc)
279 # turn the counts data ("(filename, lineno) = count") into something
280 # accessible on a per-file basis
281 per_file = {}
282 for filename, lineno in self.counts.keys():
283 lines_hit = per_file[filename] = per_file.get(filename, {})
284 lines_hit[lineno] = self.counts[(filename, lineno)]
286 # accumulate summary info, if needed
287 sums = {}
289 for filename, count in per_file.iteritems():
290 # skip some "files" we don't care about...
291 if filename == "<string>":
292 continue
293 if filename.startswith("<doctest "):
294 continue
296 if filename.endswith((".pyc", ".pyo")):
297 filename = filename[:-1]
299 if coverdir is None:
300 dir = os.path.dirname(os.path.abspath(filename))
301 modulename = modname(filename)
302 else:
303 dir = coverdir
304 if not os.path.exists(dir):
305 os.makedirs(dir)
306 modulename = fullmodname(filename)
308 # If desired, get a list of the line numbers which represent
309 # executable content (returned as a dict for better lookup speed)
310 if show_missing:
311 lnotab = find_executable_linenos(filename)
312 else:
313 lnotab = {}
315 source = linecache.getlines(filename)
316 coverpath = os.path.join(dir, modulename + ".cover")
317 n_hits, n_lines = self.write_results_file(coverpath, source,
318 lnotab, count)
320 if summary and n_lines:
321 percent = int(100 * n_hits / n_lines)
322 sums[modulename] = n_lines, percent, modulename, filename
324 if summary and sums:
325 mods = sums.keys()
326 mods.sort()
327 print "lines cov% module (path)"
328 for m in mods:
329 n_lines, percent, modulename, filename = sums[m]
330 print "%5d %3d%% %s (%s)" % sums[m]
332 if self.outfile:
333 # try and store counts and module info into self.outfile
334 try:
335 pickle.dump((self.counts, self.calledfuncs, self.callers),
336 open(self.outfile, 'wb'), 1)
337 except IOError, err:
338 print >> sys.stderr, "Can't save counts files because %s" % err
340 def write_results_file(self, path, lines, lnotab, lines_hit):
341 """Return a coverage results file in path."""
343 try:
344 outfile = open(path, "w")
345 except IOError, err:
346 print >> sys.stderr, ("trace: Could not open %r for writing: %s"
347 "- skipping" % (path, err))
348 return 0, 0
350 n_lines = 0
351 n_hits = 0
352 for i, line in enumerate(lines):
353 lineno = i + 1
354 # do the blank/comment match to try to mark more lines
355 # (help the reader find stuff that hasn't been covered)
356 if lineno in lines_hit:
357 outfile.write("%5d: " % lines_hit[lineno])
358 n_hits += 1
359 n_lines += 1
360 elif rx_blank.match(line):
361 outfile.write(" ")
362 else:
363 # lines preceded by no marks weren't hit
364 # Highlight them if so indicated, unless the line contains
365 # #pragma: NO COVER
366 if lineno in lnotab and not PRAGMA_NOCOVER in lines[i]:
367 outfile.write(">>>>>> ")
368 n_lines += 1
369 else:
370 outfile.write(" ")
371 outfile.write(lines[i].expandtabs(8))
372 outfile.close()
374 return n_hits, n_lines
376 def find_lines_from_code(code, strs):
377 """Return dict where keys are lines in the line number table."""
378 linenos = {}
380 line_increments = [ord(c) for c in code.co_lnotab[1::2]]
381 table_length = len(line_increments)
382 docstring = False
384 lineno = code.co_firstlineno
385 for li in line_increments:
386 lineno += li
387 if lineno not in strs:
388 linenos[lineno] = 1
390 return linenos
392 def find_lines(code, strs):
393 """Return lineno dict for all code objects reachable from code."""
394 # get all of the lineno information from the code of this scope level
395 linenos = find_lines_from_code(code, strs)
397 # and check the constants for references to other code objects
398 for c in code.co_consts:
399 if isinstance(c, types.CodeType):
400 # find another code object, so recurse into it
401 linenos.update(find_lines(c, strs))
402 return linenos
404 def find_strings(filename):
405 """Return a dict of possible docstring positions.
407 The dict maps line numbers to strings. There is an entry for
408 line that contains only a string or a part of a triple-quoted
409 string.
411 d = {}
412 # If the first token is a string, then it's the module docstring.
413 # Add this special case so that the test in the loop passes.
414 prev_ttype = token.INDENT
415 f = open(filename)
416 for ttype, tstr, start, end, line in tokenize.generate_tokens(f.readline):
417 if ttype == token.STRING:
418 if prev_ttype == token.INDENT:
419 sline, scol = start
420 eline, ecol = end
421 for i in range(sline, eline + 1):
422 d[i] = 1
423 prev_ttype = ttype
424 f.close()
425 return d
427 def find_executable_linenos(filename):
428 """Return dict where keys are line numbers in the line number table."""
429 try:
430 prog = open(filename, "rU").read()
431 except IOError, err:
432 print >> sys.stderr, ("Not printing coverage data for %r: %s"
433 % (filename, err))
434 return {}
435 code = compile(prog, filename, "exec")
436 strs = find_strings(filename)
437 return find_lines(code, strs)
439 class Trace:
440 def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0,
441 ignoremods=(), ignoredirs=(), infile=None, outfile=None,
442 timing=False):
444 @param count true iff it should count number of times each
445 line is executed
446 @param trace true iff it should print out each line that is
447 being counted
448 @param countfuncs true iff it should just output a list of
449 (filename, modulename, funcname,) for functions
450 that were called at least once; This overrides
451 `count' and `trace'
452 @param ignoremods a list of the names of modules to ignore
453 @param ignoredirs a list of the names of directories to ignore
454 all of the (recursive) contents of
455 @param infile file from which to read stored counts to be
456 added into the results
457 @param outfile file in which to write the results
458 @param timing true iff timing information be displayed
460 self.infile = infile
461 self.outfile = outfile
462 self.ignore = Ignore(ignoremods, ignoredirs)
463 self.counts = {} # keys are (filename, linenumber)
464 self.blabbed = {} # for debugging
465 self.pathtobasename = {} # for memoizing os.path.basename
466 self.donothing = 0
467 self.trace = trace
468 self._calledfuncs = {}
469 self._callers = {}
470 self._caller_cache = {}
471 self.start_time = None
472 if timing:
473 self.start_time = time.time()
474 if countcallers:
475 self.globaltrace = self.globaltrace_trackcallers
476 elif countfuncs:
477 self.globaltrace = self.globaltrace_countfuncs
478 elif trace and count:
479 self.globaltrace = self.globaltrace_lt
480 self.localtrace = self.localtrace_trace_and_count
481 elif trace:
482 self.globaltrace = self.globaltrace_lt
483 self.localtrace = self.localtrace_trace
484 elif count:
485 self.globaltrace = self.globaltrace_lt
486 self.localtrace = self.localtrace_count
487 else:
488 # Ahem -- do nothing? Okay.
489 self.donothing = 1
491 def run(self, cmd):
492 import __main__
493 dict = __main__.__dict__
494 if not self.donothing:
495 sys.settrace(self.globaltrace)
496 threading.settrace(self.globaltrace)
497 try:
498 exec cmd in dict, dict
499 finally:
500 if not self.donothing:
501 sys.settrace(None)
502 threading.settrace(None)
504 def runctx(self, cmd, globals=None, locals=None):
505 if globals is None: globals = {}
506 if locals is None: locals = {}
507 if not self.donothing:
508 sys.settrace(self.globaltrace)
509 threading.settrace(self.globaltrace)
510 try:
511 exec cmd in globals, locals
512 finally:
513 if not self.donothing:
514 sys.settrace(None)
515 threading.settrace(None)
517 def runfunc(self, func, *args, **kw):
518 result = None
519 if not self.donothing:
520 sys.settrace(self.globaltrace)
521 try:
522 result = func(*args, **kw)
523 finally:
524 if not self.donothing:
525 sys.settrace(None)
526 return result
528 def file_module_function_of(self, frame):
529 code = frame.f_code
530 filename = code.co_filename
531 if filename:
532 modulename = modname(filename)
533 else:
534 modulename = None
536 funcname = code.co_name
537 clsname = None
538 if code in self._caller_cache:
539 if self._caller_cache[code] is not None:
540 clsname = self._caller_cache[code]
541 else:
542 self._caller_cache[code] = None
543 ## use of gc.get_referrers() was suggested by Michael Hudson
544 # all functions which refer to this code object
545 funcs = [f for f in gc.get_referrers(code)
546 if hasattr(f, "func_doc")]
547 # require len(func) == 1 to avoid ambiguity caused by calls to
548 # new.function(): "In the face of ambiguity, refuse the
549 # temptation to guess."
550 if len(funcs) == 1:
551 dicts = [d for d in gc.get_referrers(funcs[0])
552 if isinstance(d, dict)]
553 if len(dicts) == 1:
554 classes = [c for c in gc.get_referrers(dicts[0])
555 if hasattr(c, "__bases__")]
556 if len(classes) == 1:
557 # ditto for new.classobj()
558 clsname = str(classes[0])
559 # cache the result - assumption is that new.* is
560 # not called later to disturb this relationship
561 # _caller_cache could be flushed if functions in
562 # the new module get called.
563 self._caller_cache[code] = clsname
564 if clsname is not None:
565 # final hack - module name shows up in str(cls), but we've already
566 # computed module name, so remove it
567 clsname = clsname.split(".")[1:]
568 clsname = ".".join(clsname)
569 funcname = "%s.%s" % (clsname, funcname)
571 return filename, modulename, funcname
573 def globaltrace_trackcallers(self, frame, why, arg):
574 """Handler for call events.
576 Adds information about who called who to the self._callers dict.
578 if why == 'call':
579 # XXX Should do a better job of identifying methods
580 this_func = self.file_module_function_of(frame)
581 parent_func = self.file_module_function_of(frame.f_back)
582 self._callers[(parent_func, this_func)] = 1
584 def globaltrace_countfuncs(self, frame, why, arg):
585 """Handler for call events.
587 Adds (filename, modulename, funcname) to the self._calledfuncs dict.
589 if why == 'call':
590 this_func = self.file_module_function_of(frame)
591 self._calledfuncs[this_func] = 1
593 def globaltrace_lt(self, frame, why, arg):
594 """Handler for call events.
596 If the code block being entered is to be ignored, returns `None',
597 else returns self.localtrace.
599 if why == 'call':
600 code = frame.f_code
601 filename = frame.f_globals.get('__file__', None)
602 if filename:
603 # XXX modname() doesn't work right for packages, so
604 # the ignore support won't work right for packages
605 modulename = modname(filename)
606 if modulename is not None:
607 ignore_it = self.ignore.names(filename, modulename)
608 if not ignore_it:
609 if self.trace:
610 print (" --- modulename: %s, funcname: %s"
611 % (modulename, code.co_name))
612 return self.localtrace
613 else:
614 return None
616 def localtrace_trace_and_count(self, frame, why, arg):
617 if why == "line":
618 # record the file name and line number of every trace
619 filename = frame.f_code.co_filename
620 lineno = frame.f_lineno
621 key = filename, lineno
622 self.counts[key] = self.counts.get(key, 0) + 1
624 if self.start_time:
625 print '%.2f' % (time.time() - self.start_time),
626 bname = os.path.basename(filename)
627 print "%s(%d): %s" % (bname, lineno,
628 linecache.getline(filename, lineno)),
629 return self.localtrace
631 def localtrace_trace(self, frame, why, arg):
632 if why == "line":
633 # record the file name and line number of every trace
634 filename = frame.f_code.co_filename
635 lineno = frame.f_lineno
637 if self.start_time:
638 print '%.2f' % (time.time() - self.start_time),
639 bname = os.path.basename(filename)
640 print "%s(%d): %s" % (bname, lineno,
641 linecache.getline(filename, lineno)),
642 return self.localtrace
644 def localtrace_count(self, frame, why, arg):
645 if why == "line":
646 filename = frame.f_code.co_filename
647 lineno = frame.f_lineno
648 key = filename, lineno
649 self.counts[key] = self.counts.get(key, 0) + 1
650 return self.localtrace
652 def results(self):
653 return CoverageResults(self.counts, infile=self.infile,
654 outfile=self.outfile,
655 calledfuncs=self._calledfuncs,
656 callers=self._callers)
658 def _err_exit(msg):
659 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
660 sys.exit(1)
662 def main(argv=None):
663 import getopt
665 if argv is None:
666 argv = sys.argv
667 try:
668 opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:lTg",
669 ["help", "version", "trace", "count",
670 "report", "no-report", "summary",
671 "file=", "missing",
672 "ignore-module=", "ignore-dir=",
673 "coverdir=", "listfuncs",
674 "trackcalls", "timing"])
676 except getopt.error, msg:
677 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
678 sys.stderr.write("Try `%s --help' for more information\n"
679 % sys.argv[0])
680 sys.exit(1)
682 trace = 0
683 count = 0
684 report = 0
685 no_report = 0
686 counts_file = None
687 missing = 0
688 ignore_modules = []
689 ignore_dirs = []
690 coverdir = None
691 summary = 0
692 listfuncs = False
693 countcallers = False
694 timing = False
696 for opt, val in opts:
697 if opt == "--help":
698 usage(sys.stdout)
699 sys.exit(0)
701 if opt == "--version":
702 sys.stdout.write("trace 2.0\n")
703 sys.exit(0)
705 if opt == "-T" or opt == "--trackcalls":
706 countcallers = True
707 continue
709 if opt == "-l" or opt == "--listfuncs":
710 listfuncs = True
711 continue
713 if opt == "-g" or opt == "--timing":
714 timing = True
715 continue
717 if opt == "-t" or opt == "--trace":
718 trace = 1
719 continue
721 if opt == "-c" or opt == "--count":
722 count = 1
723 continue
725 if opt == "-r" or opt == "--report":
726 report = 1
727 continue
729 if opt == "-R" or opt == "--no-report":
730 no_report = 1
731 continue
733 if opt == "-f" or opt == "--file":
734 counts_file = val
735 continue
737 if opt == "-m" or opt == "--missing":
738 missing = 1
739 continue
741 if opt == "-C" or opt == "--coverdir":
742 coverdir = val
743 continue
745 if opt == "-s" or opt == "--summary":
746 summary = 1
747 continue
749 if opt == "--ignore-module":
750 for mod in val.split(","):
751 ignore_modules.append(mod.strip())
752 continue
754 if opt == "--ignore-dir":
755 for s in val.split(os.pathsep):
756 s = os.path.expandvars(s)
757 # should I also call expanduser? (after all, could use $HOME)
759 s = s.replace("$prefix",
760 os.path.join(sys.prefix, "lib",
761 "python" + sys.version[:3]))
762 s = s.replace("$exec_prefix",
763 os.path.join(sys.exec_prefix, "lib",
764 "python" + sys.version[:3]))
765 s = os.path.normpath(s)
766 ignore_dirs.append(s)
767 continue
769 assert 0, "Should never get here"
771 if listfuncs and (count or trace):
772 _err_exit("cannot specify both --listfuncs and (--trace or --count)")
774 if not (count or trace or report or listfuncs or countcallers):
775 _err_exit("must specify one of --trace, --count, --report, "
776 "--listfuncs, or --trackcalls")
778 if report and no_report:
779 _err_exit("cannot specify both --report and --no-report")
781 if report and not counts_file:
782 _err_exit("--report requires a --file")
784 if no_report and len(prog_argv) == 0:
785 _err_exit("missing name of file to run")
787 # everything is ready
788 if report:
789 results = CoverageResults(infile=counts_file, outfile=counts_file)
790 results.write_results(missing, summary=summary, coverdir=coverdir)
791 else:
792 sys.argv = prog_argv
793 progname = prog_argv[0]
794 sys.path[0] = os.path.split(progname)[0]
796 t = Trace(count, trace, countfuncs=listfuncs,
797 countcallers=countcallers, ignoremods=ignore_modules,
798 ignoredirs=ignore_dirs, infile=counts_file,
799 outfile=counts_file, timing=timing)
800 try:
801 t.run('execfile(%r)' % (progname,))
802 except IOError, err:
803 _err_exit("Cannot run file %r because: %s" % (sys.argv[0], err))
804 except SystemExit:
805 pass
807 results = t.results()
809 if not no_report:
810 results.write_results(missing, summary=summary, coverdir=coverdir)
812 if __name__=='__main__':
813 main()