]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/trace.py
add hg and python
[plan9front.git] / sys / lib / python / trace.py
1 #!/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
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
36
37 Sample use, programmatically
38   import sys
39
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 """
50
51 import linecache
52 import os
53 import re
54 import sys
55 import threading
56 import token
57 import tokenize
58 import types
59 import gc
60
61 try:
62     import cPickle
63     pickle = cPickle
64 except ImportError:
65     import pickle
66
67 def usage(outfile):
68     outfile.write("""Usage: %s [OPTIONS] <file> [ARGS]
69
70 Meta-options:
71 --help                Display this help then exit.
72 --version             Output version information then exit.
73
74 Otherwise, exactly one of the following three options must be given:
75 -t, --trace           Print each line to sys.stdout before it is executed.
76 -c, --count           Count the number of times each line is executed
77                       and write the counts to <module>.cover for each
78                       module executed, in the module's directory.
79                       See also `--coverdir', `--file', `--no-report' below.
80 -l, --listfuncs       Keep track of which functions are executed at least
81                       once and write the results to sys.stdout after the
82                       program exits.
83 -T, --trackcalls      Keep track of caller/called pairs and write the
84                       results to sys.stdout after the program exits.
85 -r, --report          Generate a report from a counts file; do not execute
86                       any code.  `--file' must specify the results file to
87                       read, which must have been created in a previous run
88                       with `--count --file=FILE'.
89
90 Modifiers:
91 -f, --file=<file>     File to accumulate counts over several runs.
92 -R, --no-report       Do not generate the coverage report files.
93                       Useful if you want to accumulate over several runs.
94 -C, --coverdir=<dir>  Directory where the report files.  The coverage
95                       report for <package>.<module> is written to file
96                       <dir>/<package>/<module>.cover.
97 -m, --missing         Annotate executable lines that were not executed
98                       with '>>>>>> '.
99 -s, --summary         Write a brief summary on stdout for each file.
100                       (Can only be used with --count or --report.)
101
102 Filters, may be repeated multiple times:
103 --ignore-module=<mod> Ignore the given module and its submodules
104                       (if it is a package).
105 --ignore-dir=<dir>    Ignore files in the given directory (multiple
106                       directories can be joined by os.pathsep).
107 """ % sys.argv[0])
108
109 PRAGMA_NOCOVER = "#pragma NO COVER"
110
111 # Simple rx to find lines with no code.
112 rx_blank = re.compile(r'^\s*(#.*)?$')
113
114 class Ignore:
115     def __init__(self, modules = None, dirs = None):
116         self._mods = modules or []
117         self._dirs = dirs or []
118
119         self._dirs = map(os.path.normpath, self._dirs)
120         self._ignore = { '<string>': 1 }
121
122     def names(self, filename, modulename):
123         if self._ignore.has_key(modulename):
124             return self._ignore[modulename]
125
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
143
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
149
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
162
163         # Tried the different ways, so we don't ignore this module
164         self._ignore[modulename] = 0
165         return 0
166
167 def modname(path):
168     """Return a plausible module name for the patch."""
169
170     base = os.path.basename(path)
171     filename, ext = os.path.splitext(base)
172     return filename
173
174 def fullmodname(path):
175     """Return a plausible module name for the path."""
176
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.
181
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
189
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
199
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), err:
224                 print >> sys.stderr, ("Skipping counts file %r: %s"
225                                       % (self.infile, err))
226
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
235
236         for key in other_counts.keys():
237             counts[key] = counts.get(key, 0) + other_counts[key]
238
239         for key in other_calledfuncs.keys():
240             calledfuncs[key] = 1
241
242         for key in other_callers.keys():
243             callers[key] = 1
244
245     def write_results(self, show_missing=True, summary=False, coverdir=None):
246         """
247         @param coverdir
248         """
249         if self.calledfuncs:
250             print
251             print "functions called:"
252             calls = self.calledfuncs.keys()
253             calls.sort()
254             for filename, modulename, funcname in calls:
255                 print ("filename: %s, modulename: %s, funcname: %s"
256                        % (filename, modulename, funcname))
257
258         if self.callers:
259             print
260             print "calling relationships:"
261             calls = self.callers.keys()
262             calls.sort()
263             lastfile = lastcfile = ""
264             for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) in calls:
265                 if pfile != lastfile:
266                     print
267                     print "***", pfile, "***"
268                     lastfile = pfile
269                     lastcfile = ""
270                 if cfile != pfile and lastcfile != cfile:
271                     print "  -->", cfile
272                     lastcfile = cfile
273                 print "    %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc)
274
275         # turn the counts data ("(filename, lineno) = count") into something
276         # accessible on a per-file basis
277         per_file = {}
278         for filename, lineno in self.counts.keys():
279             lines_hit = per_file[filename] = per_file.get(filename, {})
280             lines_hit[lineno] = self.counts[(filename, lineno)]
281
282         # accumulate summary info, if needed
283         sums = {}
284
285         for filename, count in per_file.iteritems():
286             # skip some "files" we don't care about...
287             if filename == "<string>":
288                 continue
289
290             if filename.endswith((".pyc", ".pyo")):
291                 filename = filename[:-1]
292
293             if coverdir is None:
294                 dir = os.path.dirname(os.path.abspath(filename))
295                 modulename = modname(filename)
296             else:
297                 dir = coverdir
298                 if not os.path.exists(dir):
299                     os.makedirs(dir)
300                 modulename = fullmodname(filename)
301
302             # If desired, get a list of the line numbers which represent
303             # executable content (returned as a dict for better lookup speed)
304             if show_missing:
305                 lnotab = find_executable_linenos(filename)
306             else:
307                 lnotab = {}
308
309             source = linecache.getlines(filename)
310             coverpath = os.path.join(dir, modulename + ".cover")
311             n_hits, n_lines = self.write_results_file(coverpath, source,
312                                                       lnotab, count)
313
314             if summary and n_lines:
315                 percent = int(100 * n_hits / n_lines)
316                 sums[modulename] = n_lines, percent, modulename, filename
317
318         if summary and sums:
319             mods = sums.keys()
320             mods.sort()
321             print "lines   cov%   module   (path)"
322             for m in mods:
323                 n_lines, percent, modulename, filename = sums[m]
324                 print "%5d   %3d%%   %s   (%s)" % sums[m]
325
326         if self.outfile:
327             # try and store counts and module info into self.outfile
328             try:
329                 pickle.dump((self.counts, self.calledfuncs, self.callers),
330                             open(self.outfile, 'wb'), 1)
331             except IOError, err:
332                 print >> sys.stderr, "Can't save counts files because %s" % err
333
334     def write_results_file(self, path, lines, lnotab, lines_hit):
335         """Return a coverage results file in path."""
336
337         try:
338             outfile = open(path, "w")
339         except IOError, err:
340             print >> sys.stderr, ("trace: Could not open %r for writing: %s"
341                                   "- skipping" % (path, err))
342             return 0, 0
343
344         n_lines = 0
345         n_hits = 0
346         for i, line in enumerate(lines):
347             lineno = i + 1
348             # do the blank/comment match to try to mark more lines
349             # (help the reader find stuff that hasn't been covered)
350             if lineno in lines_hit:
351                 outfile.write("%5d: " % lines_hit[lineno])
352                 n_hits += 1
353                 n_lines += 1
354             elif rx_blank.match(line):
355                 outfile.write("       ")
356             else:
357                 # lines preceded by no marks weren't hit
358                 # Highlight them if so indicated, unless the line contains
359                 # #pragma: NO COVER
360                 if lineno in lnotab and not PRAGMA_NOCOVER in lines[i]:
361                     outfile.write(">>>>>> ")
362                     n_lines += 1
363                 else:
364                     outfile.write("       ")
365             outfile.write(lines[i].expandtabs(8))
366         outfile.close()
367
368         return n_hits, n_lines
369
370 def find_lines_from_code(code, strs):
371     """Return dict where keys are lines in the line number table."""
372     linenos = {}
373
374     line_increments = [ord(c) for c in code.co_lnotab[1::2]]
375     table_length = len(line_increments)
376     docstring = False
377
378     lineno = code.co_firstlineno
379     for li in line_increments:
380         lineno += li
381         if lineno not in strs:
382             linenos[lineno] = 1
383
384     return linenos
385
386 def find_lines(code, strs):
387     """Return lineno dict for all code objects reachable from code."""
388     # get all of the lineno information from the code of this scope level
389     linenos = find_lines_from_code(code, strs)
390
391     # and check the constants for references to other code objects
392     for c in code.co_consts:
393         if isinstance(c, types.CodeType):
394             # find another code object, so recurse into it
395             linenos.update(find_lines(c, strs))
396     return linenos
397
398 def find_strings(filename):
399     """Return a dict of possible docstring positions.
400
401     The dict maps line numbers to strings.  There is an entry for
402     line that contains only a string or a part of a triple-quoted
403     string.
404     """
405     d = {}
406     # If the first token is a string, then it's the module docstring.
407     # Add this special case so that the test in the loop passes.
408     prev_ttype = token.INDENT
409     f = open(filename)
410     for ttype, tstr, start, end, line in tokenize.generate_tokens(f.readline):
411         if ttype == token.STRING:
412             if prev_ttype == token.INDENT:
413                 sline, scol = start
414                 eline, ecol = end
415                 for i in range(sline, eline + 1):
416                     d[i] = 1
417         prev_ttype = ttype
418     f.close()
419     return d
420
421 def find_executable_linenos(filename):
422     """Return dict where keys are line numbers in the line number table."""
423     try:
424         prog = open(filename, "rU").read()
425     except IOError, err:
426         print >> sys.stderr, ("Not printing coverage data for %r: %s"
427                               % (filename, err))
428         return {}
429     code = compile(prog, filename, "exec")
430     strs = find_strings(filename)
431     return find_lines(code, strs)
432
433 class Trace:
434     def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0,
435                  ignoremods=(), ignoredirs=(), infile=None, outfile=None):
436         """
437         @param count true iff it should count number of times each
438                      line is executed
439         @param trace true iff it should print out each line that is
440                      being counted
441         @param countfuncs true iff it should just output a list of
442                      (filename, modulename, funcname,) for functions
443                      that were called at least once;  This overrides
444                      `count' and `trace'
445         @param ignoremods a list of the names of modules to ignore
446         @param ignoredirs a list of the names of directories to ignore
447                      all of the (recursive) contents of
448         @param infile file from which to read stored counts to be
449                      added into the results
450         @param outfile file in which to write the results
451         """
452         self.infile = infile
453         self.outfile = outfile
454         self.ignore = Ignore(ignoremods, ignoredirs)
455         self.counts = {}   # keys are (filename, linenumber)
456         self.blabbed = {} # for debugging
457         self.pathtobasename = {} # for memoizing os.path.basename
458         self.donothing = 0
459         self.trace = trace
460         self._calledfuncs = {}
461         self._callers = {}
462         self._caller_cache = {}
463         if countcallers:
464             self.globaltrace = self.globaltrace_trackcallers
465         elif countfuncs:
466             self.globaltrace = self.globaltrace_countfuncs
467         elif trace and count:
468             self.globaltrace = self.globaltrace_lt
469             self.localtrace = self.localtrace_trace_and_count
470         elif trace:
471             self.globaltrace = self.globaltrace_lt
472             self.localtrace = self.localtrace_trace
473         elif count:
474             self.globaltrace = self.globaltrace_lt
475             self.localtrace = self.localtrace_count
476         else:
477             # Ahem -- do nothing?  Okay.
478             self.donothing = 1
479
480     def run(self, cmd):
481         import __main__
482         dict = __main__.__dict__
483         if not self.donothing:
484             sys.settrace(self.globaltrace)
485             threading.settrace(self.globaltrace)
486         try:
487             exec cmd in dict, dict
488         finally:
489             if not self.donothing:
490                 sys.settrace(None)
491                 threading.settrace(None)
492
493     def runctx(self, cmd, globals=None, locals=None):
494         if globals is None: globals = {}
495         if locals is None: locals = {}
496         if not self.donothing:
497             sys.settrace(self.globaltrace)
498             threading.settrace(self.globaltrace)
499         try:
500             exec cmd in globals, locals
501         finally:
502             if not self.donothing:
503                 sys.settrace(None)
504                 threading.settrace(None)
505
506     def runfunc(self, func, *args, **kw):
507         result = None
508         if not self.donothing:
509             sys.settrace(self.globaltrace)
510         try:
511             result = func(*args, **kw)
512         finally:
513             if not self.donothing:
514                 sys.settrace(None)
515         return result
516
517     def file_module_function_of(self, frame):
518         code = frame.f_code
519         filename = code.co_filename
520         if filename:
521             modulename = modname(filename)
522         else:
523             modulename = None
524
525         funcname = code.co_name
526         clsname = None
527         if code in self._caller_cache:
528             if self._caller_cache[code] is not None:
529                 clsname = self._caller_cache[code]
530         else:
531             self._caller_cache[code] = None
532             ## use of gc.get_referrers() was suggested by Michael Hudson
533             # all functions which refer to this code object
534             funcs = [f for f in gc.get_referrers(code)
535                          if hasattr(f, "func_doc")]
536             # require len(func) == 1 to avoid ambiguity caused by calls to
537             # new.function(): "In the face of ambiguity, refuse the
538             # temptation to guess."
539             if len(funcs) == 1:
540                 dicts = [d for d in gc.get_referrers(funcs[0])
541                              if isinstance(d, dict)]
542                 if len(dicts) == 1:
543                     classes = [c for c in gc.get_referrers(dicts[0])
544                                    if hasattr(c, "__bases__")]
545                     if len(classes) == 1:
546                         # ditto for new.classobj()
547                         clsname = str(classes[0])
548                         # cache the result - assumption is that new.* is
549                         # not called later to disturb this relationship
550                         # _caller_cache could be flushed if functions in
551                         # the new module get called.
552                         self._caller_cache[code] = clsname
553         if clsname is not None:
554             # final hack - module name shows up in str(cls), but we've already
555             # computed module name, so remove it
556             clsname = clsname.split(".")[1:]
557             clsname = ".".join(clsname)
558             funcname = "%s.%s" % (clsname, funcname)
559
560         return filename, modulename, funcname
561
562     def globaltrace_trackcallers(self, frame, why, arg):
563         """Handler for call events.
564
565         Adds information about who called who to the self._callers dict.
566         """
567         if why == 'call':
568             # XXX Should do a better job of identifying methods
569             this_func = self.file_module_function_of(frame)
570             parent_func = self.file_module_function_of(frame.f_back)
571             self._callers[(parent_func, this_func)] = 1
572
573     def globaltrace_countfuncs(self, frame, why, arg):
574         """Handler for call events.
575
576         Adds (filename, modulename, funcname) to the self._calledfuncs dict.
577         """
578         if why == 'call':
579             this_func = self.file_module_function_of(frame)
580             self._calledfuncs[this_func] = 1
581
582     def globaltrace_lt(self, frame, why, arg):
583         """Handler for call events.
584
585         If the code block being entered is to be ignored, returns `None',
586         else returns self.localtrace.
587         """
588         if why == 'call':
589             code = frame.f_code
590             filename = frame.f_globals.get('__file__', None)
591             if filename:
592                 # XXX modname() doesn't work right for packages, so
593                 # the ignore support won't work right for packages
594                 modulename = modname(filename)
595                 if modulename is not None:
596                     ignore_it = self.ignore.names(filename, modulename)
597                     if not ignore_it:
598                         if self.trace:
599                             print (" --- modulename: %s, funcname: %s"
600                                    % (modulename, code.co_name))
601                         return self.localtrace
602             else:
603                 return None
604
605     def localtrace_trace_and_count(self, frame, why, arg):
606         if why == "line":
607             # record the file name and line number of every trace
608             filename = frame.f_code.co_filename
609             lineno = frame.f_lineno
610             key = filename, lineno
611             self.counts[key] = self.counts.get(key, 0) + 1
612
613             bname = os.path.basename(filename)
614             print "%s(%d): %s" % (bname, lineno,
615                                   linecache.getline(filename, lineno)),
616         return self.localtrace
617
618     def localtrace_trace(self, frame, why, arg):
619         if why == "line":
620             # record the file name and line number of every trace
621             filename = frame.f_code.co_filename
622             lineno = frame.f_lineno
623
624             bname = os.path.basename(filename)
625             print "%s(%d): %s" % (bname, lineno,
626                                   linecache.getline(filename, lineno)),
627         return self.localtrace
628
629     def localtrace_count(self, frame, why, arg):
630         if why == "line":
631             filename = frame.f_code.co_filename
632             lineno = frame.f_lineno
633             key = filename, lineno
634             self.counts[key] = self.counts.get(key, 0) + 1
635         return self.localtrace
636
637     def results(self):
638         return CoverageResults(self.counts, infile=self.infile,
639                                outfile=self.outfile,
640                                calledfuncs=self._calledfuncs,
641                                callers=self._callers)
642
643 def _err_exit(msg):
644     sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
645     sys.exit(1)
646
647 def main(argv=None):
648     import getopt
649
650     if argv is None:
651         argv = sys.argv
652     try:
653         opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:lT",
654                                         ["help", "version", "trace", "count",
655                                          "report", "no-report", "summary",
656                                          "file=", "missing",
657                                          "ignore-module=", "ignore-dir=",
658                                          "coverdir=", "listfuncs",
659                                          "trackcalls"])
660
661     except getopt.error, msg:
662         sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
663         sys.stderr.write("Try `%s --help' for more information\n"
664                          % sys.argv[0])
665         sys.exit(1)
666
667     trace = 0
668     count = 0
669     report = 0
670     no_report = 0
671     counts_file = None
672     missing = 0
673     ignore_modules = []
674     ignore_dirs = []
675     coverdir = None
676     summary = 0
677     listfuncs = False
678     countcallers = False
679
680     for opt, val in opts:
681         if opt == "--help":
682             usage(sys.stdout)
683             sys.exit(0)
684
685         if opt == "--version":
686             sys.stdout.write("trace 2.0\n")
687             sys.exit(0)
688
689         if opt == "-T" or opt == "--trackcalls":
690             countcallers = True
691             continue
692
693         if opt == "-l" or opt == "--listfuncs":
694             listfuncs = True
695             continue
696
697         if opt == "-t" or opt == "--trace":
698             trace = 1
699             continue
700
701         if opt == "-c" or opt == "--count":
702             count = 1
703             continue
704
705         if opt == "-r" or opt == "--report":
706             report = 1
707             continue
708
709         if opt == "-R" or opt == "--no-report":
710             no_report = 1
711             continue
712
713         if opt == "-f" or opt == "--file":
714             counts_file = val
715             continue
716
717         if opt == "-m" or opt == "--missing":
718             missing = 1
719             continue
720
721         if opt == "-C" or opt == "--coverdir":
722             coverdir = val
723             continue
724
725         if opt == "-s" or opt == "--summary":
726             summary = 1
727             continue
728
729         if opt == "--ignore-module":
730             ignore_modules.append(val)
731             continue
732
733         if opt == "--ignore-dir":
734             for s in val.split(os.pathsep):
735                 s = os.path.expandvars(s)
736                 # should I also call expanduser? (after all, could use $HOME)
737
738                 s = s.replace("$prefix",
739                               os.path.join(sys.prefix, "lib",
740                                            "python" + sys.version[:3]))
741                 s = s.replace("$exec_prefix",
742                               os.path.join(sys.exec_prefix, "lib",
743                                            "python" + sys.version[:3]))
744                 s = os.path.normpath(s)
745                 ignore_dirs.append(s)
746             continue
747
748         assert 0, "Should never get here"
749
750     if listfuncs and (count or trace):
751         _err_exit("cannot specify both --listfuncs and (--trace or --count)")
752
753     if not (count or trace or report or listfuncs or countcallers):
754         _err_exit("must specify one of --trace, --count, --report, "
755                   "--listfuncs, or --trackcalls")
756
757     if report and no_report:
758         _err_exit("cannot specify both --report and --no-report")
759
760     if report and not counts_file:
761         _err_exit("--report requires a --file")
762
763     if no_report and len(prog_argv) == 0:
764         _err_exit("missing name of file to run")
765
766     # everything is ready
767     if report:
768         results = CoverageResults(infile=counts_file, outfile=counts_file)
769         results.write_results(missing, summary=summary, coverdir=coverdir)
770     else:
771         sys.argv = prog_argv
772         progname = prog_argv[0]
773         sys.path[0] = os.path.split(progname)[0]
774
775         t = Trace(count, trace, countfuncs=listfuncs,
776                   countcallers=countcallers, ignoremods=ignore_modules,
777                   ignoredirs=ignore_dirs, infile=counts_file,
778                   outfile=counts_file)
779         try:
780             t.run('execfile(%r)' % (progname,))
781         except IOError, err:
782             _err_exit("Cannot run file %r because: %s" % (sys.argv[0], err))
783         except SystemExit:
784             pass
785
786         results = t.results()
787
788         if not no_report:
789             results.write_results(missing, summary=summary, coverdir=coverdir)
790
791 if __name__=='__main__':
792     main()