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@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.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 52import linecache 53import os 54import re 55import sys 56import token 57import tokenize 58import inspect 59import gc 60import dis 61import pickle 62from time import monotonic as _time 63 64import threading 65 66PRAGMA_NOCOVER = "#pragma NO COVER" 67 68class _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 118def _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 125def _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 153class 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 327def _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 337def _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 349def _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 372def _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 386class 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 592def 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 723if __name__=='__main__': 724 main() 725