1 #!/usr/bin/env python3 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.base_prefix, sys.base_exec_prefix,], 43 trace=0, 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 __all__ = ['Trace', 'CoverageResults'] 51 import argparse 52 import linecache 53 import os 54 import re 55 import sys 56 import token 57 import tokenize 58 import inspect 59 import gc 60 import dis 61 import pickle 62 from time import monotonic as _time 63 64 try: 65 import threading 66 except ImportError: 67 _settrace = sys.settrace 68 69 def _unsettrace(): 70 sys.settrace(None) 71 else: 72 def _settrace(func): 73 threading.settrace(func) 74 sys.settrace(func) 75 76 def _unsettrace(): 77 sys.settrace(None) 78 threading.settrace(None) 79 80 PRAGMA_NOCOVER = "#pragma NO COVER" 81 82 # Simple rx to find lines with no code. 83 rx_blank = re.compile(r'^\s*(#.*)?$') 84 85 class _Ignore: 86 def __init__(self, modules=None, dirs=None): 87 self._mods = set() if not modules else set(modules) 88 self._dirs = [] if not dirs else [os.path.normpath(d) 89 for d in dirs] 90 self._ignore = { '<string>': 1 } 91 92 def names(self, filename, modulename): 93 if modulename in self._ignore: 94 return self._ignore[modulename] 95 96 # haven't seen this one before, so see if the module name is 97 # on the ignore list. 98 if modulename in self._mods: # Identical names, so ignore 99 self._ignore[modulename] = 1 100 return 1 101 102 # check if the module is a proper submodule of something on 103 # the ignore list 104 for mod in self._mods: 105 # Need to take some care since ignoring 106 # "cmp" mustn't mean ignoring "cmpcache" but ignoring 107 # "Spam" must also mean ignoring "Spam.Eggs". 108 if modulename.startswith(mod + '.'): 109 self._ignore[modulename] = 1 110 return 1 111 112 # Now check that filename isn't in one of the directories 113 if filename is None: 114 # must be a built-in, so we must ignore 115 self._ignore[modulename] = 1 116 return 1 117 118 # Ignore a file when it contains one of the ignorable paths 119 for d in self._dirs: 120 # The '+ os.sep' is to ensure that d is a parent directory, 121 # as compared to cases like: 122 # d = "/usr/local" 123 # filename = "/usr/local.py" 124 # or 125 # d = "/usr/local.py" 126 # filename = "/usr/local.py" 127 if filename.startswith(d + os.sep): 128 self._ignore[modulename] = 1 129 return 1 130 131 # Tried the different ways, so we don't ignore this module 132 self._ignore[modulename] = 0 133 return 0 134 135 def _modname(path): 136 """Return a plausible module name for the patch.""" 137 138 base = os.path.basename(path) 139 filename, ext = os.path.splitext(base) 140 return filename 141 142 def _fullmodname(path): 143 """Return a plausible module name for the path.""" 144 145 # If the file 'path' is part of a package, then the filename isn't 146 # enough to uniquely identify it. Try to do the right thing by 147 # looking in sys.path for the longest matching prefix. We'll 148 # assume that the rest is the package name. 149 150 comparepath = os.path.normcase(path) 151 longest = "" 152 for dir in sys.path: 153 dir = os.path.normcase(dir) 154 if comparepath.startswith(dir) and comparepath[len(dir)] == os.sep: 155 if len(dir) > len(longest): 156 longest = dir 157 158 if longest: 159 base = path[len(longest) + 1:] 160 else: 161 base = path 162 # the drive letter is never part of the module name 163 drive, base = os.path.splitdrive(base) 164 base = base.replace(os.sep, ".") 165 if os.altsep: 166 base = base.replace(os.altsep, ".") 167 filename, ext = os.path.splitext(base) 168 return filename.lstrip(".") 169 170 class CoverageResults: 171 def __init__(self, counts=None, calledfuncs=None, infile=None, 172 callers=None, outfile=None): 173 self.counts = counts 174 if self.counts is None: 175 self.counts = {} 176 self.counter = self.counts.copy() # map (filename, lineno) to count 177 self.calledfuncs = calledfuncs 178 if self.calledfuncs is None: 179 self.calledfuncs = {} 180 self.calledfuncs = self.calledfuncs.copy() 181 self.callers = callers 182 if self.callers is None: 183 self.callers = {} 184 self.callers = self.callers.copy() 185 self.infile = infile 186 self.outfile = outfile 187 if self.infile: 188 # Try to merge existing counts file. 189 try: 190 with open(self.infile, 'rb') as f: 191 counts, calledfuncs, callers = pickle.load(f) 192 self.update(self.__class__(counts, calledfuncs, callers)) 193 except (OSError, EOFError, ValueError) as err: 194 print(("Skipping counts file %r: %s" 195 % (self.infile, err)), file=sys.stderr) 196 197 def is_ignored_filename(self, filename): 198 """Return True if the filename does not refer to a file 199 we want to have reported. 200 """ 201 return filename.startswith('<') and filename.endswith('>') 202 203 def update(self, other): 204 """Merge in the data from another CoverageResults""" 205 counts = self.counts 206 calledfuncs = self.calledfuncs 207 callers = self.callers 208 other_counts = other.counts 209 other_calledfuncs = other.calledfuncs 210 other_callers = other.callers 211 212 for key in other_counts: 213 counts[key] = counts.get(key, 0) + other_counts[key] 214 215 for key in other_calledfuncs: 216 calledfuncs[key] = 1 217 218 for key in other_callers: 219 callers[key] = 1 220 221 def write_results(self, show_missing=True, summary=False, coverdir=None): 222 """ 223 Write the coverage results. 224 225 :param show_missing: Show lines that had no hits. 226 :param summary: Include coverage summary per module. 227 :param coverdir: If None, the results of each module are placed in its 228 directory, otherwise it is included in the directory 229 specified. 230 """ 231 if self.calledfuncs: 232 print() 233 print("functions called:") 234 calls = self.calledfuncs 235 for filename, modulename, funcname in sorted(calls): 236 print(("filename: %s, modulename: %s, funcname: %s" 237 % (filename, modulename, funcname))) 238 239 if self.callers: 240 print() 241 print("calling relationships:") 242 lastfile = lastcfile = "" 243 for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) \ 244 in sorted(self.callers): 245 if pfile != lastfile: 246 print() 247 print("***", pfile, "***") 248 lastfile = pfile 249 lastcfile = "" 250 if cfile != pfile and lastcfile != cfile: 251 print(" -->", cfile) 252 lastcfile = cfile 253 print(" %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc)) 254 255 # turn the counts data ("(filename, lineno) = count") into something 256 # accessible on a per-file basis 257 per_file = {} 258 for filename, lineno in self.counts: 259 lines_hit = per_file[filename] = per_file.get(filename, {}) 260 lines_hit[lineno] = self.counts[(filename, lineno)] 261 262 # accumulate summary info, if needed 263 sums = {} 264 265 for filename, count in per_file.items(): 266 if self.is_ignored_filename(filename): 267 continue 268 269 if filename.endswith(".pyc"): 270 filename = filename[:-1] 271 272 if coverdir is None: 273 dir = os.path.dirname(os.path.abspath(filename)) 274 modulename = _modname(filename) 275 else: 276 dir = coverdir 277 if not os.path.exists(dir): 278 os.makedirs(dir) 279 modulename = _fullmodname(filename) 280 281 # If desired, get a list of the line numbers which represent 282 # executable content (returned as a dict for better lookup speed) 283 if show_missing: 284 lnotab = _find_executable_linenos(filename) 285 else: 286 lnotab = {} 287 if lnotab: 288 source = linecache.getlines(filename) 289 coverpath = os.path.join(dir, modulename + ".cover") 290 with open(filename, 'rb') as fp: 291 encoding, _ = tokenize.detect_encoding(fp.readline) 292 n_hits, n_lines = self.write_results_file(coverpath, source, 293 lnotab, count, encoding) 294 if summary and n_lines: 295 percent = int(100 * n_hits / n_lines) 296 sums[modulename] = n_lines, percent, modulename, filename 297 298 299 if summary and sums: 300 print("lines cov% module (path)") 301 for m in sorted(sums): 302 n_lines, percent, modulename, filename = sums[m] 303 print("%5d %3d%% %s (%s)" % sums[m]) 304 305 if self.outfile: 306 # try and store counts and module info into self.outfile 307 try: 308 pickle.dump((self.counts, self.calledfuncs, self.callers), 309 open(self.outfile, 'wb'), 1) 310 except OSError as err: 311 print("Can't save counts files because %s" % err, file=sys.stderr) 312 313 def write_results_file(self, path, lines, lnotab, lines_hit, encoding=None): 314 """Return a coverage results file in path.""" 315 316 try: 317 outfile = open(path, "w", encoding=encoding) 318 except OSError as err: 319 print(("trace: Could not open %r for writing: %s" 320 "- skipping" % (path, err)), file=sys.stderr) 321 return 0, 0 322 323 n_lines = 0 324 n_hits = 0 325 with outfile: 326 for lineno, line in enumerate(lines, 1): 327 # do the blank/comment match to try to mark more lines 328 # (help the reader find stuff that hasn't been covered) 329 if lineno in lines_hit: 330 outfile.write("%5d: " % lines_hit[lineno]) 331 n_hits += 1 332 n_lines += 1 333 elif rx_blank.match(line): 334 outfile.write(" ") 335 else: 336 # lines preceded by no marks weren't hit 337 # Highlight them if so indicated, unless the line contains 338 # #pragma: NO COVER 339 if lineno in lnotab and not PRAGMA_NOCOVER in line: 340 outfile.write(">>>>>> ") 341 n_lines += 1 342 else: 343 outfile.write(" ") 344 outfile.write(line.expandtabs(8)) 345 346 return n_hits, n_lines 347 348 def _find_lines_from_code(code, strs): 349 """Return dict where keys are lines in the line number table.""" 350 linenos = {} 351 352 for _, lineno in dis.findlinestarts(code): 353 if lineno not in strs: 354 linenos[lineno] = 1 355 356 return linenos 357 358 def _find_lines(code, strs): 359 """Return lineno dict for all code objects reachable from code.""" 360 # get all of the lineno information from the code of this scope level 361 linenos = _find_lines_from_code(code, strs) 362 363 # and check the constants for references to other code objects 364 for c in code.co_consts: 365 if inspect.iscode(c): 366 # find another code object, so recurse into it 367 linenos.update(_find_lines(c, strs)) 368 return linenos 369 370 def _find_strings(filename, encoding=None): 371 """Return a dict of possible docstring positions. 372 373 The dict maps line numbers to strings. There is an entry for 374 line that contains only a string or a part of a triple-quoted 375 string. 376 """ 377 d = {} 378 # If the first token is a string, then it's the module docstring. 379 # Add this special case so that the test in the loop passes. 380 prev_ttype = token.INDENT 381 with open(filename, encoding=encoding) as f: 382 tok = tokenize.generate_tokens(f.readline) 383 for ttype, tstr, start, end, line in tok: 384 if ttype == token.STRING: 385 if prev_ttype == token.INDENT: 386 sline, scol = start 387 eline, ecol = end 388 for i in range(sline, eline + 1): 389 d[i] = 1 390 prev_ttype = ttype 391 return d 392 393 def _find_executable_linenos(filename): 394 """Return dict where keys are line numbers in the line number table.""" 395 try: 396 with tokenize.open(filename) as f: 397 prog = f.read() 398 encoding = f.encoding 399 except OSError as err: 400 print(("Not printing coverage data for %r: %s" 401 % (filename, err)), file=sys.stderr) 402 return {} 403 code = compile(prog, filename, "exec") 404 strs = _find_strings(filename, encoding) 405 return _find_lines(code, strs) 406 407 class Trace: 408 def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0, 409 ignoremods=(), ignoredirs=(), infile=None, outfile=None, 410 timing=False): 411 """ 412 @param count true iff it should count number of times each 413 line is executed 414 @param trace true iff it should print out each line that is 415 being counted 416 @param countfuncs true iff it should just output a list of 417 (filename, modulename, funcname,) for functions 418 that were called at least once; This overrides 419 `count' and `trace' 420 @param ignoremods a list of the names of modules to ignore 421 @param ignoredirs a list of the names of directories to ignore 422 all of the (recursive) contents of 423 @param infile file from which to read stored counts to be 424 added into the results 425 @param outfile file in which to write the results 426 @param timing true iff timing information be displayed 427 """ 428 self.infile = infile 429 self.outfile = outfile 430 self.ignore = _Ignore(ignoremods, ignoredirs) 431 self.counts = {} # keys are (filename, linenumber) 432 self.pathtobasename = {} # for memoizing os.path.basename 433 self.donothing = 0 434 self.trace = trace 435 self._calledfuncs = {} 436 self._callers = {} 437 self._caller_cache = {} 438 self.start_time = None 439 if timing: 440 self.start_time = _time() 441 if countcallers: 442 self.globaltrace = self.globaltrace_trackcallers 443 elif countfuncs: 444 self.globaltrace = self.globaltrace_countfuncs 445 elif trace and count: 446 self.globaltrace = self.globaltrace_lt 447 self.localtrace = self.localtrace_trace_and_count 448 elif trace: 449 self.globaltrace = self.globaltrace_lt 450 self.localtrace = self.localtrace_trace 451 elif count: 452 self.globaltrace = self.globaltrace_lt 453 self.localtrace = self.localtrace_count 454 else: 455 # Ahem -- do nothing? Okay. 456 self.donothing = 1 457 458 def run(self, cmd): 459 import __main__ 460 dict = __main__.__dict__ 461 self.runctx(cmd, dict, dict) 462 463 def runctx(self, cmd, globals=None, locals=None): 464 if globals is None: globals = {} 465 if locals is None: locals = {} 466 if not self.donothing: 467 _settrace(self.globaltrace) 468 try: 469 exec(cmd, globals, locals) 470 finally: 471 if not self.donothing: 472 _unsettrace() 473 474 def runfunc(self, func, *args, **kw): 475 result = None 476 if not self.donothing: 477 sys.settrace(self.globaltrace) 478 try: 479 result = func(*args, **kw) 480 finally: 481 if not self.donothing: 482 sys.settrace(None) 483 return result 484 485 def file_module_function_of(self, frame): 486 code = frame.f_code 487 filename = code.co_filename 488 if filename: 489 modulename = _modname(filename) 490 else: 491 modulename = None 492 493 funcname = code.co_name 494 clsname = None 495 if code in self._caller_cache: 496 if self._caller_cache[code] is not None: 497 clsname = self._caller_cache[code] 498 else: 499 self._caller_cache[code] = None 500 ## use of gc.get_referrers() was suggested by Michael Hudson 501 # all functions which refer to this code object 502 funcs = [f for f in gc.get_referrers(code) 503 if inspect.isfunction(f)] 504 # require len(func) == 1 to avoid ambiguity caused by calls to 505 # new.function(): "In the face of ambiguity, refuse the 506 # temptation to guess." 507 if len(funcs) == 1: 508 dicts = [d for d in gc.get_referrers(funcs[0]) 509 if isinstance(d, dict)] 510 if len(dicts) == 1: 511 classes = [c for c in gc.get_referrers(dicts[0]) 512 if hasattr(c, "__bases__")] 513 if len(classes) == 1: 514 # ditto for new.classobj() 515 clsname = classes[0].__name__ 516 # cache the result - assumption is that new.* is 517 # not called later to disturb this relationship 518 # _caller_cache could be flushed if functions in 519 # the new module get called. 520 self._caller_cache[code] = clsname 521 if clsname is not None: 522 funcname = "%s.%s" % (clsname, funcname) 523 524 return filename, modulename, funcname 525 526 def globaltrace_trackcallers(self, frame, why, arg): 527 """Handler for call events. 528 529 Adds information about who called who to the self._callers dict. 530 """ 531 if why == 'call': 532 # XXX Should do a better job of identifying methods 533 this_func = self.file_module_function_of(frame) 534 parent_func = self.file_module_function_of(frame.f_back) 535 self._callers[(parent_func, this_func)] = 1 536 537 def globaltrace_countfuncs(self, frame, why, arg): 538 """Handler for call events. 539 540 Adds (filename, modulename, funcname) to the self._calledfuncs dict. 541 """ 542 if why == 'call': 543 this_func = self.file_module_function_of(frame) 544 self._calledfuncs[this_func] = 1 545 546 def globaltrace_lt(self, frame, why, arg): 547 """Handler for call events. 548 549 If the code block being entered is to be ignored, returns `None', 550 else returns self.localtrace. 551 """ 552 if why == 'call': 553 code = frame.f_code 554 filename = frame.f_globals.get('__file__', None) 555 if filename: 556 # XXX _modname() doesn't work right for packages, so 557 # the ignore support won't work right for packages 558 modulename = _modname(filename) 559 if modulename is not None: 560 ignore_it = self.ignore.names(filename, modulename) 561 if not ignore_it: 562 if self.trace: 563 print((" --- modulename: %s, funcname: %s" 564 % (modulename, code.co_name))) 565 return self.localtrace 566 else: 567 return None 568 569 def localtrace_trace_and_count(self, frame, why, arg): 570 if why == "line": 571 # record the file name and line number of every trace 572 filename = frame.f_code.co_filename 573 lineno = frame.f_lineno 574 key = filename, lineno 575 self.counts[key] = self.counts.get(key, 0) + 1 576 577 if self.start_time: 578 print('%.2f' % (_time() - self.start_time), end=' ') 579 bname = os.path.basename(filename) 580 print("%s(%d): %s" % (bname, lineno, 581 linecache.getline(filename, lineno)), end='') 582 return self.localtrace 583 584 def localtrace_trace(self, frame, why, arg): 585 if why == "line": 586 # record the file name and line number of every trace 587 filename = frame.f_code.co_filename 588 lineno = frame.f_lineno 589 590 if self.start_time: 591 print('%.2f' % (_time() - self.start_time), end=' ') 592 bname = os.path.basename(filename) 593 print("%s(%d): %s" % (bname, lineno, 594 linecache.getline(filename, lineno)), end='') 595 return self.localtrace 596 597 def localtrace_count(self, frame, why, arg): 598 if why == "line": 599 filename = frame.f_code.co_filename 600 lineno = frame.f_lineno 601 key = filename, lineno 602 self.counts[key] = self.counts.get(key, 0) + 1 603 return self.localtrace 604 605 def results(self): 606 return CoverageResults(self.counts, infile=self.infile, 607 outfile=self.outfile, 608 calledfuncs=self._calledfuncs, 609 callers=self._callers) 610 611 def main(): 612 613 parser = argparse.ArgumentParser() 614 parser.add_argument('--version', action='version', version='trace 2.0') 615 616 grp = parser.add_argument_group('Main options', 617 'One of these (or --report) must be given') 618 619 grp.add_argument('-c', '--count', action='store_true', 620 help='Count the number of times each line is executed and write ' 621 'the counts to <module>.cover for each module executed, in ' 622 'the module\'s directory. See also --coverdir, --file, ' 623 '--no-report below.') 624 grp.add_argument('-t', '--trace', action='store_true', 625 help='Print each line to sys.stdout before it is executed') 626 grp.add_argument('-l', '--listfuncs', action='store_true', 627 help='Keep track of which functions are executed at least once ' 628 'and write the results to sys.stdout after the program exits. ' 629 'Cannot be specified alongside --trace or --count.') 630 grp.add_argument('-T', '--trackcalls', action='store_true', 631 help='Keep track of caller/called pairs and write the results to ' 632 'sys.stdout after the program exits.') 633 634 grp = parser.add_argument_group('Modifiers') 635 636 _grp = grp.add_mutually_exclusive_group() 637 _grp.add_argument('-r', '--report', action='store_true', 638 help='Generate a report from a counts file; does not execute any ' 639 'code. --file must specify the results file to read, which ' 640 'must have been created in a previous run with --count ' 641 '--file=FILE') 642 _grp.add_argument('-R', '--no-report', action='store_true', 643 help='Do not generate the coverage report files. ' 644 'Useful if you want to accumulate over several runs.') 645 646 grp.add_argument('-f', '--file', 647 help='File to accumulate counts over several runs') 648 grp.add_argument('-C', '--coverdir', 649 help='Directory where the report files go. The coverage report ' 650 'for <package>.<module> will be written to file ' 651 '<dir>/<package>/<module>.cover') 652 grp.add_argument('-m', '--missing', action='store_true', 653 help='Annotate executable lines that were not executed with ' 654 '">>>>>> "') 655 grp.add_argument('-s', '--summary', action='store_true', 656 help='Write a brief summary for each file to sys.stdout. ' 657 'Can only be used with --count or --report') 658 grp.add_argument('-g', '--timing', action='store_true', 659 help='Prefix each line with the time since the program started. ' 660 'Only used while tracing') 661 662 grp = parser.add_argument_group('Filters', 663 'Can be specified multiple times') 664 grp.add_argument('--ignore-module', action='append', default=[], 665 help='Ignore the given module(s) and its submodules' 666 '(if it is a package). Accepts comma separated list of ' 667 'module names.') 668 grp.add_argument('--ignore-dir', action='append', default=[], 669 help='Ignore files in the given directory ' 670 '(multiple directories can be joined by os.pathsep).') 671 672 parser.add_argument('filename', nargs='?', 673 help='file to run as main program') 674 parser.add_argument('arguments', nargs=argparse.REMAINDER, 675 help='arguments to the program') 676 677 opts = parser.parse_args() 678 679 if opts.ignore_dir: 680 rel_path = 'lib', 'python{0.major}.{0.minor}'.format(sys.version_info) 681 _prefix = os.path.join(sys.base_prefix, *rel_path) 682 _exec_prefix = os.path.join(sys.base_exec_prefix, *rel_path) 683 684 def parse_ignore_dir(s): 685 s = os.path.expanduser(os.path.expandvars(s)) 686 s = s.replace('$prefix', _prefix).replace('$exec_prefix', _exec_prefix) 687 return os.path.normpath(s) 688 689 opts.ignore_module = [mod.strip() 690 for i in opts.ignore_module for mod in i.split(',')] 691 opts.ignore_dir = [parse_ignore_dir(s) 692 for i in opts.ignore_dir for s in i.split(os.pathsep)] 693 694 if opts.report: 695 if not opts.file: 696 parser.error('-r/--report requires -f/--file') 697 results = CoverageResults(infile=opts.file, outfile=opts.file) 698 return results.write_results(opts.missing, opts.summary, opts.coverdir) 699 700 if not any([opts.trace, opts.count, opts.listfuncs, opts.trackcalls]): 701 parser.error('must specify one of --trace, --count, --report, ' 702 '--listfuncs, or --trackcalls') 703 704 if opts.listfuncs and (opts.count or opts.trace): 705 parser.error('cannot specify both --listfuncs and (--trace or --count)') 706 707 if opts.summary and not opts.count: 708 parser.error('--summary can only be used with --count or --report') 709 710 if opts.filename is None: 711 parser.error('filename is missing: required with the main options') 712 713 sys.argv = opts.filename, *opts.arguments 714 sys.path[0] = os.path.dirname(opts.filename) 715 716 t = Trace(opts.count, opts.trace, countfuncs=opts.listfuncs, 717 countcallers=opts.trackcalls, ignoremods=opts.ignore_module, 718 ignoredirs=opts.ignore_dir, infile=opts.file, 719 outfile=opts.file, timing=opts.timing) 720 try: 721 with open(opts.filename) as fp: 722 code = compile(fp.read(), opts.filename, 'exec') 723 # try to emulate __main__ namespace as much as possible 724 globs = { 725 '__file__': opts.filename, 726 '__name__': '__main__', 727 '__package__': None, 728 '__cached__': None, 729 } 730 t.runctx(code, globs, globs) 731 except OSError as err: 732 sys.exit("Cannot run file %r because: %s" % (sys.argv[0], err)) 733 except SystemExit: 734 pass 735 736 results = t.results() 737 738 if not opts.no_report: 739 results.write_results(opts.missing, opts.summary, opts.coverdir) 740 741 if __name__=='__main__': 742 main() 743