1#!/usr/bin/env python 2 3# portions copyright 2001, Autonomous Zones Industries, Inc., all rights... 4# err... reserved and offered to the public under the terms of the 5# Python 2.2 license. 6# Author: Zooko O'Whielacronx 7# http://zooko.com/ 8# mailto:zooko@zooko.com 9# 10# Copyright 2000, Mojam Media, Inc., all rights reserved. 11# Author: Skip Montanaro 12# 13# Copyright 1999, Bioreason, Inc., all rights reserved. 14# Author: Andrew Dalke 15# 16# Copyright 1995-1997, Automatrix, Inc., all rights reserved. 17# Author: Skip Montanaro 18# 19# Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved. 20# 21# 22# Permission to use, copy, modify, and distribute this Python software and 23# its associated documentation for any purpose without fee is hereby 24# granted, provided that the above copyright notice appears in all copies, 25# and that both that copyright notice and this permission notice appear in 26# supporting documentation, and that the name of neither Automatrix, 27# Bioreason or Mojam Media be used in advertising or publicity pertaining to 28# distribution of the software without specific, written prior permission. 29# 30"""program/module to trace Python program or function execution 31 32Sample use, command line: 33 trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs 34 trace.py -t --ignore-dir '$prefix' spam.py eggs 35 trace.py --trackcalls spam.py eggs 36 37Sample use, programmatically 38 import sys 39 40 # create a Trace object, telling it what to ignore, and whether to 41 # do tracing or line-counting or both. 42 tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,], trace=0, 43 count=1) 44 # run the new command using the given tracer 45 tracer.run('main()') 46 # make a report, placing output in /tmp 47 r = tracer.results() 48 r.write_results(show_missing=True, coverdir="/tmp") 49""" 50 51import linecache 52import os 53import re 54import sys 55import time 56import token 57import tokenize 58import inspect 59import gc 60import dis 61try: 62 import cPickle 63 pickle = cPickle 64except ImportError: 65 import pickle 66 67try: 68 import threading 69except ImportError: 70 _settrace = sys.settrace 71 72 def _unsettrace(): 73 sys.settrace(None) 74else: 75 def _settrace(func): 76 threading.settrace(func) 77 sys.settrace(func) 78 79 def _unsettrace(): 80 sys.settrace(None) 81 threading.settrace(None) 82 83def usage(outfile): 84 outfile.write("""Usage: %s [OPTIONS] <file> [ARGS] 85 86Meta-options: 87--help Display this help then exit. 88--version Output version information then exit. 89 90Otherwise, exactly one of the following three options must be given: 91-t, --trace Print each line to sys.stdout before it is executed. 92-c, --count Count the number of times each line is executed 93 and write the counts to <module>.cover for each 94 module executed, in the module's directory. 95 See also `--coverdir', `--file', `--no-report' below. 96-l, --listfuncs Keep track of which functions are executed at least 97 once and write the results to sys.stdout after the 98 program exits. 99-T, --trackcalls Keep track of caller/called pairs and write the 100 results to sys.stdout after the program exits. 101-r, --report Generate a report from a counts file; do not execute 102 any code. `--file' must specify the results file to 103 read, which must have been created in a previous run 104 with `--count --file=FILE'. 105 106Modifiers: 107-f, --file=<file> File to accumulate counts over several runs. 108-R, --no-report Do not generate the coverage report files. 109 Useful if you want to accumulate over several runs. 110-C, --coverdir=<dir> Directory where the report files. The coverage 111 report for <package>.<module> is written to file 112 <dir>/<package>/<module>.cover. 113-m, --missing Annotate executable lines that were not executed 114 with '>>>>>> '. 115-s, --summary Write a brief summary on stdout for each file. 116 (Can only be used with --count or --report.) 117-g, --timing Prefix each line with the time since the program started. 118 Only used while tracing. 119 120Filters, may be repeated multiple times: 121--ignore-module=<mod> Ignore the given module(s) and its submodules 122 (if it is a package). Accepts comma separated 123 list of module names 124--ignore-dir=<dir> Ignore files in the given directory (multiple 125 directories can be joined by os.pathsep). 126""" % sys.argv[0]) 127 128PRAGMA_NOCOVER = "#pragma NO COVER" 129 130# Simple rx to find lines with no code. 131rx_blank = re.compile(r'^\s*(#.*)?$') 132 133class Ignore: 134 def __init__(self, modules = None, dirs = None): 135 self._mods = modules or [] 136 self._dirs = dirs or [] 137 138 self._dirs = map(os.path.normpath, self._dirs) 139 self._ignore = { '<string>': 1 } 140 141 def names(self, filename, modulename): 142 if modulename in self._ignore: 143 return self._ignore[modulename] 144 145 # haven't seen this one before, so see if the module name is 146 # on the ignore list. Need to take some care since ignoring 147 # "cmp" musn't mean ignoring "cmpcache" but ignoring 148 # "Spam" must also mean ignoring "Spam.Eggs". 149 for mod in self._mods: 150 if mod == modulename: # Identical names, so ignore 151 self._ignore[modulename] = 1 152 return 1 153 # check if the module is a proper submodule of something on 154 # the ignore list 155 n = len(mod) 156 # (will not overflow since if the first n characters are the 157 # same and the name has not already occurred, then the size 158 # of "name" is greater than that of "mod") 159 if mod == modulename[:n] and modulename[n] == '.': 160 self._ignore[modulename] = 1 161 return 1 162 163 # Now check that __file__ isn't in one of the directories 164 if filename is None: 165 # must be a built-in, so we must ignore 166 self._ignore[modulename] = 1 167 return 1 168 169 # Ignore a file when it contains one of the ignorable paths 170 for d in self._dirs: 171 # The '+ os.sep' is to ensure that d is a parent directory, 172 # as compared to cases like: 173 # d = "/usr/local" 174 # filename = "/usr/local.py" 175 # or 176 # d = "/usr/local.py" 177 # filename = "/usr/local.py" 178 if filename.startswith(d + os.sep): 179 self._ignore[modulename] = 1 180 return 1 181 182 # Tried the different ways, so we don't ignore this module 183 self._ignore[modulename] = 0 184 return 0 185 186def modname(path): 187 """Return a plausible module name for the patch.""" 188 189 base = os.path.basename(path) 190 filename, ext = os.path.splitext(base) 191 return filename 192 193def fullmodname(path): 194 """Return a plausible module name for the path.""" 195 196 # If the file 'path' is part of a package, then the filename isn't 197 # enough to uniquely identify it. Try to do the right thing by 198 # looking in sys.path for the longest matching prefix. We'll 199 # assume that the rest is the package name. 200 201 comparepath = os.path.normcase(path) 202 longest = "" 203 for dir in sys.path: 204 dir = os.path.normcase(dir) 205 if comparepath.startswith(dir) and comparepath[len(dir)] == os.sep: 206 if len(dir) > len(longest): 207 longest = dir 208 209 if longest: 210 base = path[len(longest) + 1:] 211 else: 212 base = path 213 # the drive letter is never part of the module name 214 drive, base = os.path.splitdrive(base) 215 base = base.replace(os.sep, ".") 216 if os.altsep: 217 base = base.replace(os.altsep, ".") 218 filename, ext = os.path.splitext(base) 219 return filename.lstrip(".") 220 221class CoverageResults: 222 def __init__(self, counts=None, calledfuncs=None, infile=None, 223 callers=None, outfile=None): 224 self.counts = counts 225 if self.counts is None: 226 self.counts = {} 227 self.counter = self.counts.copy() # map (filename, lineno) to count 228 self.calledfuncs = calledfuncs 229 if self.calledfuncs is None: 230 self.calledfuncs = {} 231 self.calledfuncs = self.calledfuncs.copy() 232 self.callers = callers 233 if self.callers is None: 234 self.callers = {} 235 self.callers = self.callers.copy() 236 self.infile = infile 237 self.outfile = outfile 238 if self.infile: 239 # Try to merge existing counts file. 240 try: 241 counts, calledfuncs, callers = \ 242 pickle.load(open(self.infile, 'rb')) 243 self.update(self.__class__(counts, calledfuncs, callers)) 244 except (IOError, EOFError, ValueError), err: 245 print >> sys.stderr, ("Skipping counts file %r: %s" 246 % (self.infile, err)) 247 248 def update(self, other): 249 """Merge in the data from another CoverageResults""" 250 counts = self.counts 251 calledfuncs = self.calledfuncs 252 callers = self.callers 253 other_counts = other.counts 254 other_calledfuncs = other.calledfuncs 255 other_callers = other.callers 256 257 for key in other_counts.keys(): 258 counts[key] = counts.get(key, 0) + other_counts[key] 259 260 for key in other_calledfuncs.keys(): 261 calledfuncs[key] = 1 262 263 for key in other_callers.keys(): 264 callers[key] = 1 265 266 def write_results(self, show_missing=True, summary=False, coverdir=None): 267 """ 268 @param coverdir 269 """ 270 if self.calledfuncs: 271 print 272 print "functions called:" 273 calls = self.calledfuncs.keys() 274 calls.sort() 275 for filename, modulename, funcname in calls: 276 print ("filename: %s, modulename: %s, funcname: %s" 277 % (filename, modulename, funcname)) 278 279 if self.callers: 280 print 281 print "calling relationships:" 282 calls = self.callers.keys() 283 calls.sort() 284 lastfile = lastcfile = "" 285 for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) in calls: 286 if pfile != lastfile: 287 print 288 print "***", pfile, "***" 289 lastfile = pfile 290 lastcfile = "" 291 if cfile != pfile and lastcfile != cfile: 292 print " -->", cfile 293 lastcfile = cfile 294 print " %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc) 295 296 # turn the counts data ("(filename, lineno) = count") into something 297 # accessible on a per-file basis 298 per_file = {} 299 for filename, lineno in self.counts.keys(): 300 lines_hit = per_file[filename] = per_file.get(filename, {}) 301 lines_hit[lineno] = self.counts[(filename, lineno)] 302 303 # accumulate summary info, if needed 304 sums = {} 305 306 for filename, count in per_file.iteritems(): 307 # skip some "files" we don't care about... 308 if filename == "<string>": 309 continue 310 if filename.startswith("<doctest "): 311 continue 312 313 if filename.endswith((".pyc", ".pyo")): 314 filename = filename[:-1] 315 316 if coverdir is None: 317 dir = os.path.dirname(os.path.abspath(filename)) 318 modulename = modname(filename) 319 else: 320 dir = coverdir 321 if not os.path.exists(dir): 322 os.makedirs(dir) 323 modulename = fullmodname(filename) 324 325 # If desired, get a list of the line numbers which represent 326 # executable content (returned as a dict for better lookup speed) 327 if show_missing: 328 lnotab = find_executable_linenos(filename) 329 else: 330 lnotab = {} 331 332 source = linecache.getlines(filename) 333 coverpath = os.path.join(dir, modulename + ".cover") 334 n_hits, n_lines = self.write_results_file(coverpath, source, 335 lnotab, count) 336 337 if summary and n_lines: 338 percent = 100 * n_hits // n_lines 339 sums[modulename] = n_lines, percent, modulename, filename 340 341 if summary and sums: 342 mods = sums.keys() 343 mods.sort() 344 print "lines cov% module (path)" 345 for m in mods: 346 n_lines, percent, modulename, filename = sums[m] 347 print "%5d %3d%% %s (%s)" % sums[m] 348 349 if self.outfile: 350 # try and store counts and module info into self.outfile 351 try: 352 pickle.dump((self.counts, self.calledfuncs, self.callers), 353 open(self.outfile, 'wb'), 1) 354 except IOError, err: 355 print >> sys.stderr, "Can't save counts files because %s" % err 356 357 def write_results_file(self, path, lines, lnotab, lines_hit): 358 """Return a coverage results file in path.""" 359 360 try: 361 outfile = open(path, "w") 362 except IOError, err: 363 print >> sys.stderr, ("trace: Could not open %r for writing: %s" 364 "- skipping" % (path, err)) 365 return 0, 0 366 367 n_lines = 0 368 n_hits = 0 369 for i, line in enumerate(lines): 370 lineno = i + 1 371 # do the blank/comment match to try to mark more lines 372 # (help the reader find stuff that hasn't been covered) 373 if lineno in lines_hit: 374 outfile.write("%5d: " % lines_hit[lineno]) 375 n_hits += 1 376 n_lines += 1 377 elif rx_blank.match(line): 378 outfile.write(" ") 379 else: 380 # lines preceded by no marks weren't hit 381 # Highlight them if so indicated, unless the line contains 382 # #pragma: NO COVER 383 if lineno in lnotab and not PRAGMA_NOCOVER in lines[i]: 384 outfile.write(">>>>>> ") 385 n_lines += 1 386 else: 387 outfile.write(" ") 388 outfile.write(lines[i].expandtabs(8)) 389 outfile.close() 390 391 return n_hits, n_lines 392 393def find_lines_from_code(code, strs): 394 """Return dict where keys are lines in the line number table.""" 395 linenos = {} 396 397 for _, lineno in dis.findlinestarts(code): 398 if lineno not in strs: 399 linenos[lineno] = 1 400 401 return linenos 402 403def find_lines(code, strs): 404 """Return lineno dict for all code objects reachable from code.""" 405 # get all of the lineno information from the code of this scope level 406 linenos = find_lines_from_code(code, strs) 407 408 # and check the constants for references to other code objects 409 for c in code.co_consts: 410 if inspect.iscode(c): 411 # find another code object, so recurse into it 412 linenos.update(find_lines(c, strs)) 413 return linenos 414 415def find_strings(filename): 416 """Return a dict of possible docstring positions. 417 418 The dict maps line numbers to strings. There is an entry for 419 line that contains only a string or a part of a triple-quoted 420 string. 421 """ 422 d = {} 423 # If the first token is a string, then it's the module docstring. 424 # Add this special case so that the test in the loop passes. 425 prev_ttype = token.INDENT 426 f = open(filename) 427 for ttype, tstr, start, end, line in tokenize.generate_tokens(f.readline): 428 if ttype == token.STRING: 429 if prev_ttype == token.INDENT: 430 sline, scol = start 431 eline, ecol = end 432 for i in range(sline, eline + 1): 433 d[i] = 1 434 prev_ttype = ttype 435 f.close() 436 return d 437 438def find_executable_linenos(filename): 439 """Return dict where keys are line numbers in the line number table.""" 440 try: 441 prog = open(filename, "rU").read() 442 except IOError, err: 443 print >> sys.stderr, ("Not printing coverage data for %r: %s" 444 % (filename, err)) 445 return {} 446 code = compile(prog, filename, "exec") 447 strs = find_strings(filename) 448 return find_lines(code, strs) 449 450class Trace: 451 def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0, 452 ignoremods=(), ignoredirs=(), infile=None, outfile=None, 453 timing=False): 454 """ 455 @param count true iff it should count number of times each 456 line is executed 457 @param trace true iff it should print out each line that is 458 being counted 459 @param countfuncs true iff it should just output a list of 460 (filename, modulename, funcname,) for functions 461 that were called at least once; This overrides 462 `count' and `trace' 463 @param ignoremods a list of the names of modules to ignore 464 @param ignoredirs a list of the names of directories to ignore 465 all of the (recursive) contents of 466 @param infile file from which to read stored counts to be 467 added into the results 468 @param outfile file in which to write the results 469 @param timing true iff timing information be displayed 470 """ 471 self.infile = infile 472 self.outfile = outfile 473 self.ignore = Ignore(ignoremods, ignoredirs) 474 self.counts = {} # keys are (filename, linenumber) 475 self.blabbed = {} # for debugging 476 self.pathtobasename = {} # for memoizing os.path.basename 477 self.donothing = 0 478 self.trace = trace 479 self._calledfuncs = {} 480 self._callers = {} 481 self._caller_cache = {} 482 self.start_time = None 483 if timing: 484 self.start_time = time.time() 485 if countcallers: 486 self.globaltrace = self.globaltrace_trackcallers 487 elif countfuncs: 488 self.globaltrace = self.globaltrace_countfuncs 489 elif trace and count: 490 self.globaltrace = self.globaltrace_lt 491 self.localtrace = self.localtrace_trace_and_count 492 elif trace: 493 self.globaltrace = self.globaltrace_lt 494 self.localtrace = self.localtrace_trace 495 elif count: 496 self.globaltrace = self.globaltrace_lt 497 self.localtrace = self.localtrace_count 498 else: 499 # Ahem -- do nothing? Okay. 500 self.donothing = 1 501 502 def run(self, cmd): 503 import __main__ 504 dict = __main__.__dict__ 505 if not self.donothing: 506 threading.settrace(self.globaltrace) 507 sys.settrace(self.globaltrace) 508 try: 509 exec cmd in dict, dict 510 finally: 511 if not self.donothing: 512 sys.settrace(None) 513 threading.settrace(None) 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: 519 _settrace(self.globaltrace) 520 try: 521 exec cmd in globals, locals 522 finally: 523 if not self.donothing: 524 _unsettrace() 525 526 def runfunc(self, func, *args, **kw): 527 result = None 528 if not self.donothing: 529 sys.settrace(self.globaltrace) 530 try: 531 result = func(*args, **kw) 532 finally: 533 if not self.donothing: 534 sys.settrace(None) 535 return result 536 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) 555 if inspect.isfunction(f)] 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() 567 clsname = classes[0].__name__ 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: 574 funcname = "%s.%s" % (clsname, funcname) 575 576 return filename, modulename, funcname 577 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 585 this_func = self.file_module_function_of(frame) 586 parent_func = self.file_module_function_of(frame.f_back) 587 self._callers[(parent_func, this_func)] = 1 588 589 def globaltrace_countfuncs(self, frame, why, arg): 590 """Handler for call events. 591 592 Adds (filename, modulename, funcname) to the self._calledfuncs dict. 593 """ 594 if why == 'call': 595 this_func = self.file_module_function_of(frame) 596 self._calledfuncs[this_func] = 1 597 598 def globaltrace_lt(self, frame, why, arg): 599 """Handler for call events. 600 601 If the code block being entered is to be ignored, returns `None', 602 else returns self.localtrace. 603 """ 604 if why == 'call': 605 code = frame.f_code 606 filename = frame.f_globals.get('__file__', None) 607 if filename: 608 # XXX modname() doesn't work right for packages, so 609 # the ignore support won't work right for packages 610 modulename = modname(filename) 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" 616 % (modulename, code.co_name)) 617 return self.localtrace 618 else: 619 return None 620 621 def localtrace_trace_and_count(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 key = filename, lineno 627 self.counts[key] = self.counts.get(key, 0) + 1 628 629 if self.start_time: 630 print '%.2f' % (time.time() - self.start_time), 631 bname = os.path.basename(filename) 632 print "%s(%d): %s" % (bname, lineno, 633 linecache.getline(filename, lineno)), 634 return self.localtrace 635 636 def localtrace_trace(self, frame, why, arg): 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 641 642 if self.start_time: 643 print '%.2f' % (time.time() - self.start_time), 644 bname = os.path.basename(filename) 645 print "%s(%d): %s" % (bname, lineno, 646 linecache.getline(filename, lineno)), 647 return self.localtrace 648 649 def localtrace_count(self, frame, why, arg): 650 if why == "line": 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, 660 calledfuncs=self._calledfuncs, 661 callers=self._callers) 662 663def _err_exit(msg): 664 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg)) 665 sys.exit(1) 666 667def main(argv=None): 668 import getopt 669 670 if argv is None: 671 argv = sys.argv 672 try: 673 opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:lTg", 674 ["help", "version", "trace", "count", 675 "report", "no-report", "summary", 676 "file=", "missing", 677 "ignore-module=", "ignore-dir=", 678 "coverdir=", "listfuncs", 679 "trackcalls", "timing"]) 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 698 countcallers = False 699 timing = False 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 710 if opt == "-T" or opt == "--trackcalls": 711 countcallers = True 712 continue 713 714 if opt == "-l" or opt == "--listfuncs": 715 listfuncs = True 716 continue 717 718 if opt == "-g" or opt == "--timing": 719 timing = True 720 continue 721 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": 755 for mod in val.split(","): 756 ignore_modules.append(mod.strip()) 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 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") 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, 802 countcallers=countcallers, ignoremods=ignore_modules, 803 ignoredirs=ignore_dirs, infile=counts_file, 804 outfile=counts_file, timing=timing) 805 try: 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) 816 except IOError, err: 817 _err_exit("Cannot run file %r because: %s" % (sys.argv[0], err)) 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 826if __name__=='__main__': 827 main() 828