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