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