| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 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:[email protected] | 
|  | 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 | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 35 | trace.py --trackcalls spam.py eggs | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 36 |  | 
|  | 37 | Sample use, programmatically | 
| Skip Montanaro | 7b1559a | 2006-04-23 19:32:14 +0000 | [diff] [blame] | 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, | 
| Tim Peters | be635cd | 2006-04-24 22:45:13 +0000 | [diff] [blame] | 43 | count=1) | 
| Skip Montanaro | 7b1559a | 2006-04-23 19:32:14 +0000 | [diff] [blame] | 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") | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 49 | """ | 
|  | 50 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 51 | import linecache | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 52 | import os | 
|  | 53 | import re | 
|  | 54 | import sys | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 55 | import time | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 56 | import token | 
|  | 57 | import tokenize | 
| Alexander Belopolsky | 9d17da3 | 2010-09-13 16:45:02 +0000 | [diff] [blame] | 58 | import inspect | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 59 | import gc | 
| Alexander Belopolsky | 517185e | 2010-09-24 18:14:18 +0000 | [diff] [blame] | 60 | import dis | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 61 | try: | 
|  | 62 | import cPickle | 
|  | 63 | pickle = cPickle | 
|  | 64 | except ImportError: | 
|  | 65 | import pickle | 
|  | 66 |  | 
| Alexander Belopolsky | 860c05d | 2010-11-06 01:38:48 +0000 | [diff] [blame^] | 67 | try: | 
|  | 68 | import threading | 
|  | 69 | except ImportError: | 
|  | 70 | _settrace = sys.settrace | 
|  | 71 |  | 
|  | 72 | def _unsettrace(): | 
|  | 73 | sys.settrace(None) | 
|  | 74 | else: | 
|  | 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 |  | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 83 | def usage(outfile): | 
|  | 84 | outfile.write("""Usage: %s [OPTIONS] <file> [ARGS] | 
|  | 85 |  | 
|  | 86 | Meta-options: | 
|  | 87 | --help                Display this help then exit. | 
|  | 88 | --version             Output version information then exit. | 
|  | 89 |  | 
|  | 90 | Otherwise, 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. | 
| Skip Montanaro | a7b8ac6 | 2003-06-27 19:09:33 +0000 | [diff] [blame] | 96 | -l, --listfuncs       Keep track of which functions are executed at least | 
| Fred Drake | 01c623b | 2003-06-27 19:22:11 +0000 | [diff] [blame] | 97 | once and write the results to sys.stdout after the | 
| Skip Montanaro | a7b8ac6 | 2003-06-27 19:09:33 +0000 | [diff] [blame] | 98 | program exits. | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 99 | -T, --trackcalls      Keep track of caller/called pairs and write the | 
|  | 100 | results to sys.stdout after the program exits. | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 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 |  | 
|  | 106 | Modifiers: | 
|  | 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.) | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 117 | -g, --timing          Prefix each line with the time since the program started. | 
|  | 118 | Only used while tracing. | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 119 |  | 
|  | 120 | Filters, may be repeated multiple times: | 
| Facundo Batista | 873c985 | 2008-01-19 18:38:19 +0000 | [diff] [blame] | 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 | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 124 | --ignore-dir=<dir>    Ignore files in the given directory (multiple | 
|  | 125 | directories can be joined by os.pathsep). | 
|  | 126 | """ % sys.argv[0]) | 
|  | 127 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 128 | PRAGMA_NOCOVER = "#pragma NO COVER" | 
|  | 129 |  | 
|  | 130 | # Simple rx to find lines with no code. | 
|  | 131 | rx_blank = re.compile(r'^\s*(#.*)?$') | 
|  | 132 |  | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 133 | class 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): | 
| Benjamin Peterson | 6e3dbbd | 2009-10-09 22:15:50 +0000 | [diff] [blame] | 142 | if modulename in self._ignore: | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 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 | 
| Fred Drake | db390c1 | 2005-10-28 14:39:47 +0000 | [diff] [blame] | 157 | # same and the name has not already occurred, then the size | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 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 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 186 | def modname(path): | 
|  | 187 | """Return a plausible module name for the patch.""" | 
| Jeremy Hylton | dfbfe73 | 2003-04-21 22:49:17 +0000 | [diff] [blame] | 188 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 189 | base = os.path.basename(path) | 
|  | 190 | filename, ext = os.path.splitext(base) | 
|  | 191 | return filename | 
|  | 192 |  | 
| Jeremy Hylton | dfbfe73 | 2003-04-21 22:49:17 +0000 | [diff] [blame] | 193 | def fullmodname(path): | 
| Jeremy Hylton | c8c8b94 | 2003-04-22 15:35:51 +0000 | [diff] [blame] | 194 | """Return a plausible module name for the path.""" | 
| Jeremy Hylton | dfbfe73 | 2003-04-21 22:49:17 +0000 | [diff] [blame] | 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 |  | 
| Georg Brandl | 7a1af77 | 2006-08-14 21:55:28 +0000 | [diff] [blame] | 201 | comparepath = os.path.normcase(path) | 
| Jeremy Hylton | dfbfe73 | 2003-04-21 22:49:17 +0000 | [diff] [blame] | 202 | longest = "" | 
|  | 203 | for dir in sys.path: | 
| Georg Brandl | 7a1af77 | 2006-08-14 21:55:28 +0000 | [diff] [blame] | 204 | dir = os.path.normcase(dir) | 
|  | 205 | if comparepath.startswith(dir) and comparepath[len(dir)] == os.sep: | 
| Jeremy Hylton | dfbfe73 | 2003-04-21 22:49:17 +0000 | [diff] [blame] | 206 | if len(dir) > len(longest): | 
|  | 207 | longest = dir | 
|  | 208 |  | 
| Guido van Rossum | b427c00 | 2003-10-10 23:02:01 +0000 | [diff] [blame] | 209 | if longest: | 
|  | 210 | base = path[len(longest) + 1:] | 
|  | 211 | else: | 
|  | 212 | base = path | 
| Georg Brandl | 78e6957 | 2010-08-01 18:52:52 +0000 | [diff] [blame] | 213 | # the drive letter is never part of the module name | 
|  | 214 | drive, base = os.path.splitdrive(base) | 
| Guido van Rossum | bbca8da | 2004-02-19 19:16:50 +0000 | [diff] [blame] | 215 | base = base.replace(os.sep, ".") | 
|  | 216 | if os.altsep: | 
|  | 217 | base = base.replace(os.altsep, ".") | 
| Jeremy Hylton | dfbfe73 | 2003-04-21 22:49:17 +0000 | [diff] [blame] | 218 | filename, ext = os.path.splitext(base) | 
| Georg Brandl | 78e6957 | 2010-08-01 18:52:52 +0000 | [diff] [blame] | 219 | return filename.lstrip(".") | 
| Jeremy Hylton | dfbfe73 | 2003-04-21 22:49:17 +0000 | [diff] [blame] | 220 |  | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 221 | class CoverageResults: | 
|  | 222 | def __init__(self, counts=None, calledfuncs=None, infile=None, | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 223 | callers=None, outfile=None): | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 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() | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 232 | self.callers = callers | 
|  | 233 | if self.callers is None: | 
|  | 234 | self.callers = {} | 
|  | 235 | self.callers = self.callers.copy() | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 236 | self.infile = infile | 
|  | 237 | self.outfile = outfile | 
|  | 238 | if self.infile: | 
| Jeremy Hylton | d7ce86d | 2003-07-07 16:08:47 +0000 | [diff] [blame] | 239 | # Try to merge existing counts file. | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 240 | try: | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 241 | counts, calledfuncs, callers = \ | 
|  | 242 | pickle.load(open(self.infile, 'rb')) | 
|  | 243 | self.update(self.__class__(counts, calledfuncs, callers)) | 
| Jeremy Hylton | d7ce86d | 2003-07-07 16:08:47 +0000 | [diff] [blame] | 244 | except (IOError, EOFError, ValueError), err: | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 245 | print >> sys.stderr, ("Skipping counts file %r: %s" | 
|  | 246 | % (self.infile, err)) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 247 |  | 
|  | 248 | def update(self, other): | 
|  | 249 | """Merge in the data from another CoverageResults""" | 
|  | 250 | counts = self.counts | 
|  | 251 | calledfuncs = self.calledfuncs | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 252 | callers = self.callers | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 253 | other_counts = other.counts | 
|  | 254 | other_calledfuncs = other.calledfuncs | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 255 | other_callers = other.callers | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 256 |  | 
|  | 257 | for key in other_counts.keys(): | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 258 | counts[key] = counts.get(key, 0) + other_counts[key] | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 259 |  | 
|  | 260 | for key in other_calledfuncs.keys(): | 
|  | 261 | calledfuncs[key] = 1 | 
|  | 262 |  | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 263 | for key in other_callers.keys(): | 
|  | 264 | callers[key] = 1 | 
|  | 265 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 266 | def write_results(self, show_missing=True, summary=False, coverdir=None): | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 267 | """ | 
|  | 268 | @param coverdir | 
|  | 269 | """ | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 270 | if self.calledfuncs: | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 271 | print | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 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: | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 280 | print | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 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: | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 287 | print | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 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) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 295 |  | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 296 | # turn the counts data ("(filename, lineno) = count") into something | 
|  | 297 | # accessible on a per-file basis | 
|  | 298 | per_file = {} | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 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)] | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 302 |  | 
|  | 303 | # accumulate summary info, if needed | 
|  | 304 | sums = {} | 
|  | 305 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 306 | for filename, count in per_file.iteritems(): | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 307 | # skip some "files" we don't care about... | 
|  | 308 | if filename == "<string>": | 
|  | 309 | continue | 
| Skip Montanaro | 58a6f44 | 2007-11-24 14:30:47 +0000 | [diff] [blame] | 310 | if filename.startswith("<doctest "): | 
|  | 311 | continue | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 312 |  | 
| Georg Brandl | b2afe85 | 2006-06-09 20:43:48 +0000 | [diff] [blame] | 313 | if filename.endswith((".pyc", ".pyo")): | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 314 | filename = filename[:-1] | 
|  | 315 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 316 | if coverdir is None: | 
|  | 317 | dir = os.path.dirname(os.path.abspath(filename)) | 
| Jeremy Hylton | c8c8b94 | 2003-04-22 15:35:51 +0000 | [diff] [blame] | 318 | modulename = modname(filename) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 319 | else: | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 320 | dir = coverdir | 
|  | 321 | if not os.path.exists(dir): | 
|  | 322 | os.makedirs(dir) | 
| Jeremy Hylton | c8c8b94 | 2003-04-22 15:35:51 +0000 | [diff] [blame] | 323 | modulename = fullmodname(filename) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 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: | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 328 | lnotab = find_executable_linenos(filename) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 329 | else: | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 330 | lnotab = {} | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 331 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 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) | 
| Tim Peters | 0eadaac | 2003-04-24 16:02:54 +0000 | [diff] [blame] | 336 |  | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 337 | if summary and n_lines: | 
|  | 338 | percent = int(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: | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 352 | pickle.dump((self.counts, self.calledfuncs, self.callers), | 
| Jeremy Hylton | d0e2705 | 2003-10-14 20:12:06 +0000 | [diff] [blame] | 353 | open(self.outfile, 'wb'), 1) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 354 | except IOError, err: | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 355 | print >> sys.stderr, "Can't save counts files because %s" % err | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 356 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 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)) | 
| Guido van Rossum | bbca8da | 2004-02-19 19:16:50 +0000 | [diff] [blame] | 365 | return 0, 0 | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 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): | 
| Walter Dörwald | c171172 | 2003-07-15 10:34:02 +0000 | [diff] [blame] | 378 | outfile.write("       ") | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 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(">>>>>> ") | 
| Jeremy Hylton | 546e34b | 2003-06-26 14:56:17 +0000 | [diff] [blame] | 385 | n_lines += 1 | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 386 | else: | 
|  | 387 | outfile.write("       ") | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 388 | outfile.write(lines[i].expandtabs(8)) | 
|  | 389 | outfile.close() | 
|  | 390 |  | 
|  | 391 | return n_hits, n_lines | 
|  | 392 |  | 
|  | 393 | def find_lines_from_code(code, strs): | 
|  | 394 | """Return dict where keys are lines in the line number table.""" | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 395 | linenos = {} | 
|  | 396 |  | 
| Alexander Belopolsky | 517185e | 2010-09-24 18:14:18 +0000 | [diff] [blame] | 397 | for _, lineno in dis.findlinestarts(code): | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 398 | if lineno not in strs: | 
|  | 399 | linenos[lineno] = 1 | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 400 |  | 
|  | 401 | return linenos | 
|  | 402 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 403 | def find_lines(code, strs): | 
|  | 404 | """Return lineno dict for all code objects reachable from code.""" | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 405 | # get all of the lineno information from the code of this scope level | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 406 | linenos = find_lines_from_code(code, strs) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 407 |  | 
|  | 408 | # and check the constants for references to other code objects | 
|  | 409 | for c in code.co_consts: | 
| Alexander Belopolsky | 9d17da3 | 2010-09-13 16:45:02 +0000 | [diff] [blame] | 410 | if inspect.iscode(c): | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 411 | # find another code object, so recurse into it | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 412 | linenos.update(find_lines(c, strs)) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 413 | return linenos | 
|  | 414 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 415 | def find_strings(filename): | 
|  | 416 | """Return a dict of possible docstring positions. | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 417 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 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. | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 421 | """ | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 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 | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 437 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 438 | def find_executable_linenos(filename): | 
|  | 439 | """Return dict where keys are line numbers in the line number table.""" | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 440 | try: | 
| Skip Montanaro | c00fc84 | 2004-04-16 03:28:19 +0000 | [diff] [blame] | 441 | prog = open(filename, "rU").read() | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 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) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 449 |  | 
|  | 450 | class Trace: | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 451 | def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0, | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 452 | ignoremods=(), ignoredirs=(), infile=None, outfile=None, | 
|  | 453 | timing=False): | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 454 | """ | 
|  | 455 | @param count true iff it should count number of times each | 
| Tim Peters | f2715e0 | 2003-02-19 02:35:07 +0000 | [diff] [blame] | 456 | line is executed | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 457 | @param trace true iff it should print out each line that is | 
| Tim Peters | f2715e0 | 2003-02-19 02:35:07 +0000 | [diff] [blame] | 458 | being counted | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 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 | 
| Tim Peters | f2715e0 | 2003-02-19 02:35:07 +0000 | [diff] [blame] | 462 | `count' and `trace' | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 463 | @param ignoremods a list of the names of modules to ignore | 
|  | 464 | @param ignoredirs a list of the names of directories to ignore | 
| Tim Peters | f2715e0 | 2003-02-19 02:35:07 +0000 | [diff] [blame] | 465 | all of the (recursive) contents of | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 466 | @param infile file from which to read stored counts to be | 
| Tim Peters | f2715e0 | 2003-02-19 02:35:07 +0000 | [diff] [blame] | 467 | added into the results | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 468 | @param outfile file in which to write the results | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 469 | @param timing true iff timing information be displayed | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 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 = {} | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 480 | self._callers = {} | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 481 | self._caller_cache = {} | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 482 | self.start_time = None | 
|  | 483 | if timing: | 
|  | 484 | self.start_time = time.time() | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 485 | if countcallers: | 
|  | 486 | self.globaltrace = self.globaltrace_trackcallers | 
|  | 487 | elif countfuncs: | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 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 | if not self.donothing: | 
| Jeremy Hylton | 546e34b | 2003-06-26 14:56:17 +0000 | [diff] [blame] | 506 | threading.settrace(self.globaltrace) | 
| Georg Brandl | 11ce4d9 | 2010-08-02 21:38:08 +0000 | [diff] [blame] | 507 | sys.settrace(self.globaltrace) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 508 | try: | 
|  | 509 | exec cmd in dict, dict | 
|  | 510 | finally: | 
|  | 511 | if not self.donothing: | 
|  | 512 | sys.settrace(None) | 
| Jeremy Hylton | 546e34b | 2003-06-26 14:56:17 +0000 | [diff] [blame] | 513 | threading.settrace(None) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 514 |  | 
|  | 515 | def runctx(self, cmd, globals=None, locals=None): | 
|  | 516 | if globals is None: globals = {} | 
|  | 517 | if locals is None: locals = {} | 
|  | 518 | if not self.donothing: | 
| Alexander Belopolsky | 860c05d | 2010-11-06 01:38:48 +0000 | [diff] [blame^] | 519 | _settrace(self.globaltrace) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 520 | try: | 
|  | 521 | exec cmd in globals, locals | 
|  | 522 | finally: | 
|  | 523 | if not self.donothing: | 
| Alexander Belopolsky | 860c05d | 2010-11-06 01:38:48 +0000 | [diff] [blame^] | 524 | _unsettrace() | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 525 |  | 
|  | 526 | def runfunc(self, func, *args, **kw): | 
|  | 527 | result = None | 
|  | 528 | if not self.donothing: | 
|  | 529 | sys.settrace(self.globaltrace) | 
|  | 530 | try: | 
| Guido van Rossum | 68468eb | 2003-02-27 20:14:51 +0000 | [diff] [blame] | 531 | result = func(*args, **kw) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 532 | finally: | 
|  | 533 | if not self.donothing: | 
|  | 534 | sys.settrace(None) | 
|  | 535 | return result | 
|  | 536 |  | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 537 | def file_module_function_of(self, frame): | 
|  | 538 | code = frame.f_code | 
|  | 539 | filename = code.co_filename | 
|  | 540 | if filename: | 
|  | 541 | modulename = modname(filename) | 
|  | 542 | else: | 
|  | 543 | modulename = None | 
|  | 544 |  | 
|  | 545 | funcname = code.co_name | 
|  | 546 | clsname = None | 
|  | 547 | if code in self._caller_cache: | 
|  | 548 | if self._caller_cache[code] is not None: | 
|  | 549 | clsname = self._caller_cache[code] | 
|  | 550 | else: | 
|  | 551 | self._caller_cache[code] = None | 
|  | 552 | ## use of gc.get_referrers() was suggested by Michael Hudson | 
|  | 553 | # all functions which refer to this code object | 
|  | 554 | funcs = [f for f in gc.get_referrers(code) | 
| Alexander Belopolsky | 9d17da3 | 2010-09-13 16:45:02 +0000 | [diff] [blame] | 555 | if inspect.isfunction(f)] | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 556 | # require len(func) == 1 to avoid ambiguity caused by calls to | 
|  | 557 | # new.function(): "In the face of ambiguity, refuse the | 
|  | 558 | # temptation to guess." | 
|  | 559 | if len(funcs) == 1: | 
|  | 560 | dicts = [d for d in gc.get_referrers(funcs[0]) | 
|  | 561 | if isinstance(d, dict)] | 
|  | 562 | if len(dicts) == 1: | 
|  | 563 | classes = [c for c in gc.get_referrers(dicts[0]) | 
|  | 564 | if hasattr(c, "__bases__")] | 
|  | 565 | if len(classes) == 1: | 
|  | 566 | # ditto for new.classobj() | 
| Alexander Belopolsky | 9d17da3 | 2010-09-13 16:45:02 +0000 | [diff] [blame] | 567 | clsname = classes[0].__name__ | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 568 | # cache the result - assumption is that new.* is | 
|  | 569 | # not called later to disturb this relationship | 
|  | 570 | # _caller_cache could be flushed if functions in | 
|  | 571 | # the new module get called. | 
|  | 572 | self._caller_cache[code] = clsname | 
|  | 573 | if clsname is not None: | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 574 | funcname = "%s.%s" % (clsname, funcname) | 
|  | 575 |  | 
|  | 576 | return filename, modulename, funcname | 
|  | 577 |  | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 578 | def globaltrace_trackcallers(self, frame, why, arg): | 
|  | 579 | """Handler for call events. | 
|  | 580 |  | 
|  | 581 | Adds information about who called who to the self._callers dict. | 
|  | 582 | """ | 
|  | 583 | if why == 'call': | 
|  | 584 | # XXX Should do a better job of identifying methods | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 585 | this_func = self.file_module_function_of(frame) | 
|  | 586 | parent_func = self.file_module_function_of(frame.f_back) | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 587 | self._callers[(parent_func, this_func)] = 1 | 
|  | 588 |  | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 589 | def globaltrace_countfuncs(self, frame, why, arg): | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 590 | """Handler for call events. | 
| Tim Peters | 0eadaac | 2003-04-24 16:02:54 +0000 | [diff] [blame] | 591 |  | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 592 | Adds (filename, modulename, funcname) to the self._calledfuncs dict. | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 593 | """ | 
|  | 594 | if why == 'call': | 
| Skip Montanaro | 5bfd984 | 2004-04-10 16:29:58 +0000 | [diff] [blame] | 595 | this_func = self.file_module_function_of(frame) | 
|  | 596 | self._calledfuncs[this_func] = 1 | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 597 |  | 
|  | 598 | def globaltrace_lt(self, frame, why, arg): | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 599 | """Handler for call events. | 
|  | 600 |  | 
|  | 601 | If the code block being entered is to be ignored, returns `None', | 
|  | 602 | else returns self.localtrace. | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 603 | """ | 
|  | 604 | if why == 'call': | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 605 | code = frame.f_code | 
| Skip Montanaro | 691acf2 | 2007-02-11 18:24:37 +0000 | [diff] [blame] | 606 | filename = frame.f_globals.get('__file__', None) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 607 | if filename: | 
| Jeremy Hylton | dfbfe73 | 2003-04-21 22:49:17 +0000 | [diff] [blame] | 608 | # XXX modname() doesn't work right for packages, so | 
|  | 609 | # the ignore support won't work right for packages | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 610 | modulename = modname(filename) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 611 | if modulename is not None: | 
|  | 612 | ignore_it = self.ignore.names(filename, modulename) | 
|  | 613 | if not ignore_it: | 
|  | 614 | if self.trace: | 
|  | 615 | print (" --- modulename: %s, funcname: %s" | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 616 | % (modulename, code.co_name)) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 617 | return self.localtrace | 
|  | 618 | else: | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 619 | return None | 
|  | 620 |  | 
|  | 621 | def localtrace_trace_and_count(self, frame, why, arg): | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 622 | if why == "line": | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 623 | # record the file name and line number of every trace | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 624 | filename = frame.f_code.co_filename | 
|  | 625 | lineno = frame.f_lineno | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 626 | key = filename, lineno | 
|  | 627 | self.counts[key] = self.counts.get(key, 0) + 1 | 
| Tim Peters | f2715e0 | 2003-02-19 02:35:07 +0000 | [diff] [blame] | 628 |  | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 629 | if self.start_time: | 
|  | 630 | print '%.2f' % (time.time() - self.start_time), | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 631 | bname = os.path.basename(filename) | 
|  | 632 | print "%s(%d): %s" % (bname, lineno, | 
|  | 633 | linecache.getline(filename, lineno)), | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 634 | return self.localtrace | 
|  | 635 |  | 
|  | 636 | def localtrace_trace(self, frame, why, arg): | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 637 | if why == "line": | 
|  | 638 | # record the file name and line number of every trace | 
|  | 639 | filename = frame.f_code.co_filename | 
|  | 640 | lineno = frame.f_lineno | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 641 |  | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 642 | if self.start_time: | 
|  | 643 | print '%.2f' % (time.time() - self.start_time), | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 644 | bname = os.path.basename(filename) | 
|  | 645 | print "%s(%d): %s" % (bname, lineno, | 
|  | 646 | linecache.getline(filename, lineno)), | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 647 | return self.localtrace | 
|  | 648 |  | 
|  | 649 | def localtrace_count(self, frame, why, arg): | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 650 | if why == "line": | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 651 | filename = frame.f_code.co_filename | 
|  | 652 | lineno = frame.f_lineno | 
|  | 653 | key = filename, lineno | 
|  | 654 | self.counts[key] = self.counts.get(key, 0) + 1 | 
|  | 655 | return self.localtrace | 
|  | 656 |  | 
|  | 657 | def results(self): | 
|  | 658 | return CoverageResults(self.counts, infile=self.infile, | 
|  | 659 | outfile=self.outfile, | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 660 | calledfuncs=self._calledfuncs, | 
|  | 661 | callers=self._callers) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 662 |  | 
|  | 663 | def _err_exit(msg): | 
|  | 664 | sys.stderr.write("%s: %s\n" % (sys.argv[0], msg)) | 
|  | 665 | sys.exit(1) | 
|  | 666 |  | 
|  | 667 | def main(argv=None): | 
|  | 668 | import getopt | 
|  | 669 |  | 
|  | 670 | if argv is None: | 
|  | 671 | argv = sys.argv | 
|  | 672 | try: | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 673 | opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:lTg", | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 674 | ["help", "version", "trace", "count", | 
|  | 675 | "report", "no-report", "summary", | 
|  | 676 | "file=", "missing", | 
|  | 677 | "ignore-module=", "ignore-dir=", | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 678 | "coverdir=", "listfuncs", | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 679 | "trackcalls", "timing"]) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 680 |  | 
|  | 681 | except getopt.error, msg: | 
|  | 682 | sys.stderr.write("%s: %s\n" % (sys.argv[0], msg)) | 
|  | 683 | sys.stderr.write("Try `%s --help' for more information\n" | 
|  | 684 | % sys.argv[0]) | 
|  | 685 | sys.exit(1) | 
|  | 686 |  | 
|  | 687 | trace = 0 | 
|  | 688 | count = 0 | 
|  | 689 | report = 0 | 
|  | 690 | no_report = 0 | 
|  | 691 | counts_file = None | 
|  | 692 | missing = 0 | 
|  | 693 | ignore_modules = [] | 
|  | 694 | ignore_dirs = [] | 
|  | 695 | coverdir = None | 
|  | 696 | summary = 0 | 
|  | 697 | listfuncs = False | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 698 | countcallers = False | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 699 | timing = False | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 700 |  | 
|  | 701 | for opt, val in opts: | 
|  | 702 | if opt == "--help": | 
|  | 703 | usage(sys.stdout) | 
|  | 704 | sys.exit(0) | 
|  | 705 |  | 
|  | 706 | if opt == "--version": | 
|  | 707 | sys.stdout.write("trace 2.0\n") | 
|  | 708 | sys.exit(0) | 
|  | 709 |  | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 710 | if opt == "-T" or opt == "--trackcalls": | 
|  | 711 | countcallers = True | 
|  | 712 | continue | 
|  | 713 |  | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 714 | if opt == "-l" or opt == "--listfuncs": | 
|  | 715 | listfuncs = True | 
|  | 716 | continue | 
|  | 717 |  | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 718 | if opt == "-g" or opt == "--timing": | 
|  | 719 | timing = True | 
|  | 720 | continue | 
|  | 721 |  | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 722 | if opt == "-t" or opt == "--trace": | 
|  | 723 | trace = 1 | 
|  | 724 | continue | 
|  | 725 |  | 
|  | 726 | if opt == "-c" or opt == "--count": | 
|  | 727 | count = 1 | 
|  | 728 | continue | 
|  | 729 |  | 
|  | 730 | if opt == "-r" or opt == "--report": | 
|  | 731 | report = 1 | 
|  | 732 | continue | 
|  | 733 |  | 
|  | 734 | if opt == "-R" or opt == "--no-report": | 
|  | 735 | no_report = 1 | 
|  | 736 | continue | 
|  | 737 |  | 
|  | 738 | if opt == "-f" or opt == "--file": | 
|  | 739 | counts_file = val | 
|  | 740 | continue | 
|  | 741 |  | 
|  | 742 | if opt == "-m" or opt == "--missing": | 
|  | 743 | missing = 1 | 
|  | 744 | continue | 
|  | 745 |  | 
|  | 746 | if opt == "-C" or opt == "--coverdir": | 
|  | 747 | coverdir = val | 
|  | 748 | continue | 
|  | 749 |  | 
|  | 750 | if opt == "-s" or opt == "--summary": | 
|  | 751 | summary = 1 | 
|  | 752 | continue | 
|  | 753 |  | 
|  | 754 | if opt == "--ignore-module": | 
| Facundo Batista | 873c985 | 2008-01-19 18:38:19 +0000 | [diff] [blame] | 755 | for mod in val.split(","): | 
|  | 756 | ignore_modules.append(mod.strip()) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 757 | continue | 
|  | 758 |  | 
|  | 759 | if opt == "--ignore-dir": | 
|  | 760 | for s in val.split(os.pathsep): | 
|  | 761 | s = os.path.expandvars(s) | 
|  | 762 | # should I also call expanduser? (after all, could use $HOME) | 
|  | 763 |  | 
|  | 764 | s = s.replace("$prefix", | 
|  | 765 | os.path.join(sys.prefix, "lib", | 
|  | 766 | "python" + sys.version[:3])) | 
|  | 767 | s = s.replace("$exec_prefix", | 
|  | 768 | os.path.join(sys.exec_prefix, "lib", | 
|  | 769 | "python" + sys.version[:3])) | 
|  | 770 | s = os.path.normpath(s) | 
|  | 771 | ignore_dirs.append(s) | 
|  | 772 | continue | 
|  | 773 |  | 
|  | 774 | assert 0, "Should never get here" | 
|  | 775 |  | 
|  | 776 | if listfuncs and (count or trace): | 
|  | 777 | _err_exit("cannot specify both --listfuncs and (--trace or --count)") | 
|  | 778 |  | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 779 | if not (count or trace or report or listfuncs or countcallers): | 
|  | 780 | _err_exit("must specify one of --trace, --count, --report, " | 
|  | 781 | "--listfuncs, or --trackcalls") | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 782 |  | 
|  | 783 | if report and no_report: | 
|  | 784 | _err_exit("cannot specify both --report and --no-report") | 
|  | 785 |  | 
|  | 786 | if report and not counts_file: | 
|  | 787 | _err_exit("--report requires a --file") | 
|  | 788 |  | 
|  | 789 | if no_report and len(prog_argv) == 0: | 
|  | 790 | _err_exit("missing name of file to run") | 
|  | 791 |  | 
|  | 792 | # everything is ready | 
|  | 793 | if report: | 
|  | 794 | results = CoverageResults(infile=counts_file, outfile=counts_file) | 
|  | 795 | results.write_results(missing, summary=summary, coverdir=coverdir) | 
|  | 796 | else: | 
|  | 797 | sys.argv = prog_argv | 
|  | 798 | progname = prog_argv[0] | 
|  | 799 | sys.path[0] = os.path.split(progname)[0] | 
|  | 800 |  | 
|  | 801 | t = Trace(count, trace, countfuncs=listfuncs, | 
| Skip Montanaro | cafc811 | 2004-04-07 15:46:05 +0000 | [diff] [blame] | 802 | countcallers=countcallers, ignoremods=ignore_modules, | 
|  | 803 | ignoredirs=ignore_dirs, infile=counts_file, | 
| Neal Norwitz | ca37661 | 2008-02-26 08:21:28 +0000 | [diff] [blame] | 804 | outfile=counts_file, timing=timing) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 805 | try: | 
| Georg Brandl | 78e6957 | 2010-08-01 18:52:52 +0000 | [diff] [blame] | 806 | with open(progname) as fp: | 
|  | 807 | code = compile(fp.read(), progname, 'exec') | 
|  | 808 | # try to emulate __main__ namespace as much as possible | 
|  | 809 | globs = { | 
|  | 810 | '__file__': progname, | 
|  | 811 | '__name__': '__main__', | 
|  | 812 | '__package__': None, | 
|  | 813 | '__cached__': None, | 
|  | 814 | } | 
|  | 815 | t.runctx(code, globs, globs) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 816 | except IOError, err: | 
| Jeremy Hylton | 38732e1 | 2003-04-21 22:04:46 +0000 | [diff] [blame] | 817 | _err_exit("Cannot run file %r because: %s" % (sys.argv[0], err)) | 
| Jeremy Hylton | 4edaa0d | 2003-02-18 15:06:17 +0000 | [diff] [blame] | 818 | except SystemExit: | 
|  | 819 | pass | 
|  | 820 |  | 
|  | 821 | results = t.results() | 
|  | 822 |  | 
|  | 823 | if not no_report: | 
|  | 824 | results.write_results(missing, summary=summary, coverdir=coverdir) | 
|  | 825 |  | 
|  | 826 | if __name__=='__main__': | 
|  | 827 | main() |