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