~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Lib/trace.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

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