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
32Sample use, command line:
33  trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs
34  trace.py -t --ignore-dir '$prefix' spam.py eggs
35  trace.py --trackcalls spam.py eggs
36
37Sample 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
51import linecache
52import os
53import re
54import sys
55import threading
56import token
57import tokenize
58import types
59import gc
60
61try:
62    import cPickle
63    pickle = cPickle
64except ImportError:
65    import pickle
66
67def usage(outfile):
68    outfile.write("""Usage: %s [OPTIONS] <file> [ARGS]
69
70Meta-options:
71--help                Display this help then exit.
72--version             Output version information then exit.
73
74Otherwise, 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
90Modifiers:
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
102Filters, 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
109PRAGMA_NOCOVER = "#pragma NO COVER"
110
111# Simple rx to find lines with no code.
112rx_blank = re.compile(r'^\s*(#.*)?$')
113
114class 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
167def 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
174def 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    longest = ""
183    for dir in sys.path:
184        if path.startswith(dir) and path[len(dir)] == os.path.sep:
185            if len(dir) > len(longest):
186                longest = dir
187
188    if longest:
189        base = path[len(longest) + 1:]
190    else:
191        base = path
192    base = base.replace(os.sep, ".")
193    if os.altsep:
194        base = base.replace(os.altsep, ".")
195    filename, ext = os.path.splitext(base)
196    return filename
197
198class CoverageResults:
199    def __init__(self, counts=None, calledfuncs=None, infile=None,
200                 callers=None, outfile=None):
201        self.counts = counts
202        if self.counts is None:
203            self.counts = {}
204        self.counter = self.counts.copy() # map (filename, lineno) to count
205        self.calledfuncs = calledfuncs
206        if self.calledfuncs is None:
207            self.calledfuncs = {}
208        self.calledfuncs = self.calledfuncs.copy()
209        self.callers = callers
210        if self.callers is None:
211            self.callers = {}
212        self.callers = self.callers.copy()
213        self.infile = infile
214        self.outfile = outfile
215        if self.infile:
216            # Try to merge existing counts file.
217            try:
218                counts, calledfuncs, callers = \
219                        pickle.load(open(self.infile, 'rb'))
220                self.update(self.__class__(counts, calledfuncs, callers))
221            except (IOError, EOFError, ValueError), err:
222                print >> sys.stderr, ("Skipping counts file %r: %s"
223                                      % (self.infile, err))
224
225    def update(self, other):
226        """Merge in the data from another CoverageResults"""
227        counts = self.counts
228        calledfuncs = self.calledfuncs
229        callers = self.callers
230        other_counts = other.counts
231        other_calledfuncs = other.calledfuncs
232        other_callers = other.callers
233
234        for key in other_counts.keys():
235            counts[key] = counts.get(key, 0) + other_counts[key]
236
237        for key in other_calledfuncs.keys():
238            calledfuncs[key] = 1
239
240        for key in other_callers.keys():
241            callers[key] = 1
242
243    def write_results(self, show_missing=True, summary=False, coverdir=None):
244        """
245        @param coverdir
246        """
247        if self.calledfuncs:
248            print
249            print "functions called:"
250            calls = self.calledfuncs.keys()
251            calls.sort()
252            for filename, modulename, funcname in calls:
253                print ("filename: %s, modulename: %s, funcname: %s"
254                       % (filename, modulename, funcname))
255
256        if self.callers:
257            print
258            print "calling relationships:"
259            calls = self.callers.keys()
260            calls.sort()
261            lastfile = lastcfile = ""
262            for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) in calls:
263                if pfile != lastfile:
264                    print
265                    print "***", pfile, "***"
266                    lastfile = pfile
267                    lastcfile = ""
268                if cfile != pfile and lastcfile != cfile:
269                    print "  -->", cfile
270                    lastcfile = cfile
271                print "    %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc)
272
273        # turn the counts data ("(filename, lineno) = count") into something
274        # accessible on a per-file basis
275        per_file = {}
276        for filename, lineno in self.counts.keys():
277            lines_hit = per_file[filename] = per_file.get(filename, {})
278            lines_hit[lineno] = self.counts[(filename, lineno)]
279
280        # accumulate summary info, if needed
281        sums = {}
282
283        for filename, count in per_file.iteritems():
284            # skip some "files" we don't care about...
285            if filename == "<string>":
286                continue
287
288            if filename.endswith(".pyc") or filename.endswith(".pyo"):
289                filename = filename[:-1]
290
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)
299
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 = {}
306
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)
311
312            if summary and n_lines:
313                percent = int(100 * n_hits / n_lines)
314                sums[modulename] = n_lines, percent, modulename, filename
315
316        if summary and sums:
317            mods = sums.keys()
318            mods.sort()
319            print "lines   cov%   module   (path)"
320            for m in mods:
321                n_lines, percent, modulename, filename = sums[m]
322                print "%5d   %3d%%   %s   (%s)" % sums[m]
323
324        if self.outfile:
325            # try and store counts and module info into self.outfile
326            try:
327                pickle.dump((self.counts, self.calledfuncs, self.callers),
328                            open(self.outfile, 'wb'), 1)
329            except IOError, err:
330                print >> sys.stderr, "Can't save counts files because %s" % err
331
332    def write_results_file(self, path, lines, lnotab, lines_hit):
333        """Return a coverage results file in path."""
334
335        try:
336            outfile = open(path, "w")
337        except IOError, err:
338            print >> sys.stderr, ("trace: Could not open %r for writing: %s"
339                                  "- skipping" % (path, err))
340            return 0, 0
341
342        n_lines = 0
343        n_hits = 0
344        for i, line in enumerate(lines):
345            lineno = i + 1
346            # do the blank/comment match to try to mark more lines
347            # (help the reader find stuff that hasn't been covered)
348            if lineno in lines_hit:
349                outfile.write("%5d: " % lines_hit[lineno])
350                n_hits += 1
351                n_lines += 1
352            elif rx_blank.match(line):
353                outfile.write("       ")
354            else:
355                # lines preceded by no marks weren't hit
356                # Highlight them if so indicated, unless the line contains
357                # #pragma: NO COVER
358                if lineno in lnotab and not PRAGMA_NOCOVER in lines[i]:
359                    outfile.write(">>>>>> ")
360                    n_lines += 1
361                else:
362                    outfile.write("       ")
363            outfile.write(lines[i].expandtabs(8))
364        outfile.close()
365
366        return n_hits, n_lines
367
368def find_lines_from_code(code, strs):
369    """Return dict where keys are lines in the line number table."""
370    linenos = {}
371
372    #line_increments = [ord(c) for c in code.co_lnotab[1::2]]
373    # XXX Replaced above line with Python 2.2-compatible line (orip)
374    def odd_indexed_items(seq): return [seq[i] for i in xrange(1, len(seq), 2)]
375    line_increments = [ord(c) for c in odd_indexed_items(code.co_lnotab)]
376
377    table_length = len(line_increments)
378    docstring = False
379
380    lineno = code.co_firstlineno
381    for li in line_increments:
382        lineno += li
383        if lineno not in strs:
384            linenos[lineno] = 1
385
386    return linenos
387
388def 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)
392
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
399
400def find_strings(filename):
401    """Return a dict of possible docstring positions.
402
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.
406    """
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)
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
422
423def find_executable_linenos(filename):
424    """Return dict where keys are line numbers in the line number table."""
425    try:
426        prog = open(filename, "rU").read()
427    except IOError, err:
428        print >> sys.stderr, ("Not printing coverage data for %r: %s"
429                              % (filename, err))
430        return {}
431    code = compile(prog, filename, "exec")
432    strs = find_strings(filename)
433    return find_lines(code, strs)
434
435class Trace:
436    def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0,
437                 ignoremods=(), ignoredirs=(), infile=None, outfile=None):
438        """
439        @param count true iff it should count number of times each
440                     line is executed
441        @param trace true iff it should print out each line that is
442                     being counted
443        @param countfuncs true iff it should just output a list of
444                     (filename, modulename, funcname,) for functions
445                     that were called at least once;  This overrides
446                     `count' and `trace'
447        @param ignoremods a list of the names of modules to ignore
448        @param ignoredirs a list of the names of directories to ignore
449                     all of the (recursive) contents of
450        @param infile file from which to read stored counts to be
451                     added into the results
452        @param outfile file in which to write the results
453        """
454        self.infile = infile
455        self.outfile = outfile
456        self.ignore = Ignore(ignoremods, ignoredirs)
457        self.counts = {}   # keys are (filename, linenumber)
458        self.blabbed = {} # for debugging
459        self.pathtobasename = {} # for memoizing os.path.basename
460        self.donothing = 0
461        self.trace = trace
462        self._calledfuncs = {}
463        self._callers = {}
464        self._caller_cache = {}
465        if countcallers:
466            self.globaltrace = self.globaltrace_trackcallers
467        elif countfuncs:
468            self.globaltrace = self.globaltrace_countfuncs
469        elif trace and count:
470            self.globaltrace = self.globaltrace_lt
471            self.localtrace = self.localtrace_trace_and_count
472        elif trace:
473            self.globaltrace = self.globaltrace_lt
474            self.localtrace = self.localtrace_trace
475        elif count:
476            self.globaltrace = self.globaltrace_lt
477            self.localtrace = self.localtrace_count
478        else:
479            # Ahem -- do nothing?  Okay.
480            self.donothing = 1
481
482    def run(self, cmd):
483        import __main__
484        dict = __main__.__dict__
485        if not self.donothing:
486            sys.settrace(self.globaltrace)
487            threading.settrace(self.globaltrace)
488        try:
489            exec cmd in dict, dict
490        finally:
491            if not self.donothing:
492                sys.settrace(None)
493                threading.settrace(None)
494
495    def runctx(self, cmd, globals=None, locals=None):
496        if globals is None: globals = {}
497        if locals is None: locals = {}
498        if not self.donothing:
499            sys.settrace(self.globaltrace)
500            threading.settrace(self.globaltrace)
501        try:
502            exec cmd in globals, locals
503        finally:
504            if not self.donothing:
505                sys.settrace(None)
506                threading.settrace(None)
507
508    def runfunc(self, func, *args, **kw):
509        result = None
510        if not self.donothing:
511            sys.settrace(self.globaltrace)
512        try:
513            result = func(*args, **kw)
514        finally:
515            if not self.donothing:
516                sys.settrace(None)
517        return result
518
519    def file_module_function_of(self, frame):
520        code = frame.f_code
521        filename = code.co_filename
522        if filename:
523            modulename = modname(filename)
524        else:
525            modulename = None
526
527        funcname = code.co_name
528        clsname = None
529        if code in self._caller_cache:
530            if self._caller_cache[code] is not None:
531                clsname = self._caller_cache[code]
532        else:
533            self._caller_cache[code] = None
534            ## use of gc.get_referrers() was suggested by Michael Hudson
535            # all functions which refer to this code object
536            funcs = [f for f in gc.get_referrers(code)
537                         if hasattr(f, "func_doc")]
538            # require len(func) == 1 to avoid ambiguity caused by calls to
539            # new.function(): "In the face of ambiguity, refuse the
540            # temptation to guess."
541            if len(funcs) == 1:
542                dicts = [d for d in gc.get_referrers(funcs[0])
543                             if isinstance(d, dict)]
544                if len(dicts) == 1:
545                    classes = [c for c in gc.get_referrers(dicts[0])
546                                   if hasattr(c, "__bases__")]
547                    if len(classes) == 1:
548                        # ditto for new.classobj()
549                        clsname = str(classes[0])
550                        # cache the result - assumption is that new.* is
551                        # not called later to disturb this relationship
552                        # _caller_cache could be flushed if functions in
553                        # the new module get called.
554                        self._caller_cache[code] = clsname
555        if clsname is not None:
556            # final hack - module name shows up in str(cls), but we've already
557            # computed module name, so remove it
558            clsname = clsname.split(".")[1:]
559            clsname = ".".join(clsname)
560            funcname = "%s.%s" % (clsname, funcname)
561
562        return filename, modulename, funcname
563
564    def globaltrace_trackcallers(self, frame, why, arg):
565        """Handler for call events.
566
567        Adds information about who called who to the self._callers dict.
568        """
569        if why == 'call':
570            # XXX Should do a better job of identifying methods
571            this_func = self.file_module_function_of(frame)
572            parent_func = self.file_module_function_of(frame.f_back)
573            self._callers[(parent_func, this_func)] = 1
574
575    def globaltrace_countfuncs(self, frame, why, arg):
576        """Handler for call events.
577
578        Adds (filename, modulename, funcname) to the self._calledfuncs dict.
579        """
580        if why == 'call':
581            this_func = self.file_module_function_of(frame)
582            self._calledfuncs[this_func] = 1
583
584    def globaltrace_lt(self, frame, why, arg):
585        """Handler for call events.
586
587        If the code block being entered is to be ignored, returns `None',
588        else returns self.localtrace.
589        """
590        if why == 'call':
591            code = frame.f_code
592            filename = code.co_filename
593            if filename:
594                # XXX modname() doesn't work right for packages, so
595                # the ignore support won't work right for packages
596                modulename = modname(filename)
597                if modulename is not None:
598                    ignore_it = self.ignore.names(filename, modulename)
599                    if not ignore_it:
600                        if self.trace:
601                            print (" --- modulename: %s, funcname: %s"
602                                   % (modulename, code.co_name))
603                        return self.localtrace
604            else:
605                return None
606
607    def localtrace_trace_and_count(self, frame, why, arg):
608        if why == "line":
609            # record the file name and line number of every trace
610            filename = frame.f_code.co_filename
611            lineno = frame.f_lineno
612            key = filename, lineno
613            self.counts[key] = self.counts.get(key, 0) + 1
614
615            bname = os.path.basename(filename)
616            print "%s(%d): %s" % (bname, lineno,
617                                  linecache.getline(filename, lineno)),
618        return self.localtrace
619
620    def localtrace_trace(self, frame, why, arg):
621        if why == "line":
622            # record the file name and line number of every trace
623            filename = frame.f_code.co_filename
624            lineno = frame.f_lineno
625
626            bname = os.path.basename(filename)
627            print "%s(%d): %s" % (bname, lineno,
628                                  linecache.getline(filename, lineno)),
629        return self.localtrace
630
631    def localtrace_count(self, frame, why, arg):
632        if why == "line":
633            filename = frame.f_code.co_filename
634            lineno = frame.f_lineno
635            key = filename, lineno
636            self.counts[key] = self.counts.get(key, 0) + 1
637        return self.localtrace
638
639    def results(self):
640        return CoverageResults(self.counts, infile=self.infile,
641                               outfile=self.outfile,
642                               calledfuncs=self._calledfuncs,
643                               callers=self._callers)
644
645def _err_exit(msg):
646    sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
647    sys.exit(1)
648
649def main(argv=None):
650    import getopt
651
652    if argv is None:
653        argv = sys.argv
654    try:
655        opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:lT",
656                                        ["help", "version", "trace", "count",
657                                         "report", "no-report", "summary",
658                                         "file=", "missing",
659                                         "ignore-module=", "ignore-dir=",
660                                         "coverdir=", "listfuncs",
661                                         "trackcalls"])
662
663    except getopt.error, msg:
664        sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
665        sys.stderr.write("Try `%s --help' for more information\n"
666                         % sys.argv[0])
667        sys.exit(1)
668
669    trace = 0
670    count = 0
671    report = 0
672    no_report = 0
673    counts_file = None
674    missing = 0
675    ignore_modules = []
676    ignore_dirs = []
677    coverdir = None
678    summary = 0
679    listfuncs = False
680    countcallers = False
681
682    for opt, val in opts:
683        if opt == "--help":
684            usage(sys.stdout)
685            sys.exit(0)
686
687        if opt == "--version":
688            sys.stdout.write("trace 2.0\n")
689            sys.exit(0)
690
691        if opt == "-T" or opt == "--trackcalls":
692            countcallers = True
693            continue
694
695        if opt == "-l" or opt == "--listfuncs":
696            listfuncs = True
697            continue
698
699        if opt == "-t" or opt == "--trace":
700            trace = 1
701            continue
702
703        if opt == "-c" or opt == "--count":
704            count = 1
705            continue
706
707        if opt == "-r" or opt == "--report":
708            report = 1
709            continue
710
711        if opt == "-R" or opt == "--no-report":
712            no_report = 1
713            continue
714
715        if opt == "-f" or opt == "--file":
716            counts_file = val
717            continue
718
719        if opt == "-m" or opt == "--missing":
720            missing = 1
721            continue
722
723        if opt == "-C" or opt == "--coverdir":
724            coverdir = val
725            continue
726
727        if opt == "-s" or opt == "--summary":
728            summary = 1
729            continue
730
731        if opt == "--ignore-module":
732            ignore_modules.append(val)
733            continue
734
735        if opt == "--ignore-dir":
736            for s in val.split(os.pathsep):
737                s = os.path.expandvars(s)
738                # should I also call expanduser? (after all, could use $HOME)
739
740                s = s.replace("$prefix",
741                              os.path.join(sys.prefix, "lib",
742                                           "python" + sys.version[:3]))
743                s = s.replace("$exec_prefix",
744                              os.path.join(sys.exec_prefix, "lib",
745                                           "python" + sys.version[:3]))
746                s = os.path.normpath(s)
747                ignore_dirs.append(s)
748            continue
749
750        assert 0, "Should never get here"
751
752    if listfuncs and (count or trace):
753        _err_exit("cannot specify both --listfuncs and (--trace or --count)")
754
755    if not (count or trace or report or listfuncs or countcallers):
756        _err_exit("must specify one of --trace, --count, --report, "
757                  "--listfuncs, or --trackcalls")
758
759    if report and no_report:
760        _err_exit("cannot specify both --report and --no-report")
761
762    if report and not counts_file:
763        _err_exit("--report requires a --file")
764
765    if no_report and len(prog_argv) == 0:
766        _err_exit("missing name of file to run")
767
768    # everything is ready
769    if report:
770        results = CoverageResults(infile=counts_file, outfile=counts_file)
771        results.write_results(missing, summary=summary, coverdir=coverdir)
772    else:
773        sys.argv = prog_argv
774        progname = prog_argv[0]
775        sys.path[0] = os.path.split(progname)[0]
776
777        t = Trace(count, trace, countfuncs=listfuncs,
778                  countcallers=countcallers, ignoremods=ignore_modules,
779                  ignoredirs=ignore_dirs, infile=counts_file,
780                  outfile=counts_file)
781        try:
782            t.run('execfile(%r)' % (progname,))
783        except IOError, err:
784            _err_exit("Cannot run file %r because: %s" % (sys.argv[0], err))
785        except SystemExit:
786            pass
787
788        results = t.results()
789
790        if not no_report:
791            results.write_results(missing, summary=summary, coverdir=coverdir)
792
793if __name__=='__main__':
794    main()
795