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