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 (at] 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 time 56 import token 57 import tokenize 58 import inspect 59 import gc 60 import dis 61 try: 62 import cPickle 63 pickle = cPickle 64 except ImportError: 65 import pickle 66 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 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. 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 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.) 117 -g, --timing Prefix each line with the time since the program started. 118 Only used while tracing. 119 120 Filters, 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 128 PRAGMA_NOCOVER = "#pragma NO COVER" 129 130 # Simple rx to find lines with no code. 131 rx_blank = re.compile(r'^\s*(#.*)?$') 132 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): 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 186 def 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 193 def 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 221 class 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 393 def 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 403 def 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 415 def 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 438 def 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 450 class 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 655 def _err_exit(msg): 656 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg)) 657 sys.exit(1) 658 659 def 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 818 if __name__=='__main__': 819 main() 820