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