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