1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt 3 4"""Execute files of Python code.""" 5 6import marshal 7import os 8import sys 9import types 10 11from coverage.backward import BUILTINS 12from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec 13from coverage.misc import ExceptionDuringRun, NoCode, NoSource, isolate_module 14from coverage.phystokens import compile_unicode 15from coverage.python import get_python_source 16 17os = isolate_module(os) 18 19 20class DummyLoader(object): 21 """A shim for the pep302 __loader__, emulating pkgutil.ImpLoader. 22 23 Currently only implements the .fullname attribute 24 """ 25 def __init__(self, fullname, *_args): 26 self.fullname = fullname 27 28 29if importlib_util_find_spec: 30 def find_module(modulename): 31 """Find the module named `modulename`. 32 33 Returns the file path of the module, and the name of the enclosing 34 package. 35 """ 36 try: 37 spec = importlib_util_find_spec(modulename) 38 except ImportError as err: 39 raise NoSource(str(err)) 40 if not spec: 41 raise NoSource("No module named %r" % (modulename,)) 42 pathname = spec.origin 43 packagename = spec.name 44 if pathname.endswith("__init__.py") and not modulename.endswith("__init__"): 45 mod_main = modulename + ".__main__" 46 spec = importlib_util_find_spec(mod_main) 47 if not spec: 48 raise NoSource( 49 "No module named %s; " 50 "%r is a package and cannot be directly executed" 51 % (mod_main, modulename) 52 ) 53 pathname = spec.origin 54 packagename = spec.name 55 packagename = packagename.rpartition(".")[0] 56 return pathname, packagename 57else: 58 def find_module(modulename): 59 """Find the module named `modulename`. 60 61 Returns the file path of the module, and the name of the enclosing 62 package. 63 """ 64 openfile = None 65 glo, loc = globals(), locals() 66 try: 67 # Search for the module - inside its parent package, if any - using 68 # standard import mechanics. 69 if '.' in modulename: 70 packagename, name = modulename.rsplit('.', 1) 71 package = __import__(packagename, glo, loc, ['__path__']) 72 searchpath = package.__path__ 73 else: 74 packagename, name = None, modulename 75 searchpath = None # "top-level search" in imp.find_module() 76 openfile, pathname, _ = imp.find_module(name, searchpath) 77 78 # Complain if this is a magic non-file module. 79 if openfile is None and pathname is None: 80 raise NoSource( 81 "module does not live in a file: %r" % modulename 82 ) 83 84 # If `modulename` is actually a package, not a mere module, then we 85 # pretend to be Python 2.7 and try running its __main__.py script. 86 if openfile is None: 87 packagename = modulename 88 name = '__main__' 89 package = __import__(packagename, glo, loc, ['__path__']) 90 searchpath = package.__path__ 91 openfile, pathname, _ = imp.find_module(name, searchpath) 92 except ImportError as err: 93 raise NoSource(str(err)) 94 finally: 95 if openfile: 96 openfile.close() 97 98 return pathname, packagename 99 100 101def run_python_module(modulename, args): 102 """Run a Python module, as though with ``python -m name args...``. 103 104 `modulename` is the name of the module, possibly a dot-separated name. 105 `args` is the argument array to present as sys.argv, including the first 106 element naming the module being executed. 107 108 """ 109 pathname, packagename = find_module(modulename) 110 111 pathname = os.path.abspath(pathname) 112 args[0] = pathname 113 run_python_file(pathname, args, package=packagename, modulename=modulename, path0="") 114 115 116def run_python_file(filename, args, package=None, modulename=None, path0=None): 117 """Run a Python file as if it were the main program on the command line. 118 119 `filename` is the path to the file to execute, it need not be a .py file. 120 `args` is the argument array to present as sys.argv, including the first 121 element naming the file being executed. `package` is the name of the 122 enclosing package, if any. 123 124 `modulename` is the name of the module the file was run as. 125 126 `path0` is the value to put into sys.path[0]. If it's None, then this 127 function will decide on a value. 128 129 """ 130 if modulename is None and sys.version_info >= (3, 3): 131 modulename = '__main__' 132 133 # Create a module to serve as __main__ 134 old_main_mod = sys.modules['__main__'] 135 main_mod = types.ModuleType('__main__') 136 sys.modules['__main__'] = main_mod 137 main_mod.__file__ = filename 138 if package: 139 main_mod.__package__ = package 140 if modulename: 141 main_mod.__loader__ = DummyLoader(modulename) 142 143 main_mod.__builtins__ = BUILTINS 144 145 # Set sys.argv properly. 146 old_argv = sys.argv 147 sys.argv = args 148 149 if os.path.isdir(filename): 150 # Running a directory means running the __main__.py file in that 151 # directory. 152 my_path0 = filename 153 154 for ext in [".py", ".pyc", ".pyo"]: 155 try_filename = os.path.join(filename, "__main__" + ext) 156 if os.path.exists(try_filename): 157 filename = try_filename 158 break 159 else: 160 raise NoSource("Can't find '__main__' module in '%s'" % filename) 161 else: 162 my_path0 = os.path.abspath(os.path.dirname(filename)) 163 164 # Set sys.path correctly. 165 old_path0 = sys.path[0] 166 sys.path[0] = path0 if path0 is not None else my_path0 167 168 try: 169 # Make a code object somehow. 170 if filename.endswith((".pyc", ".pyo")): 171 code = make_code_from_pyc(filename) 172 else: 173 code = make_code_from_py(filename) 174 175 # Execute the code object. 176 try: 177 exec(code, main_mod.__dict__) 178 except SystemExit: 179 # The user called sys.exit(). Just pass it along to the upper 180 # layers, where it will be handled. 181 raise 182 except: 183 # Something went wrong while executing the user code. 184 # Get the exc_info, and pack them into an exception that we can 185 # throw up to the outer loop. We peel one layer off the traceback 186 # so that the coverage.py code doesn't appear in the final printed 187 # traceback. 188 typ, err, tb = sys.exc_info() 189 190 # PyPy3 weirdness. If I don't access __context__, then somehow it 191 # is non-None when the exception is reported at the upper layer, 192 # and a nested exception is shown to the user. This getattr fixes 193 # it somehow? https://bitbucket.org/pypy/pypy/issue/1903 194 getattr(err, '__context__', None) 195 196 raise ExceptionDuringRun(typ, err, tb.tb_next) 197 finally: 198 # Restore the old __main__, argv, and path. 199 sys.modules['__main__'] = old_main_mod 200 sys.argv = old_argv 201 sys.path[0] = old_path0 202 203 204def make_code_from_py(filename): 205 """Get source from `filename` and make a code object of it.""" 206 # Open the source file. 207 try: 208 source = get_python_source(filename) 209 except (IOError, NoSource): 210 raise NoSource("No file to run: '%s'" % filename) 211 212 code = compile_unicode(source, filename, "exec") 213 return code 214 215 216def make_code_from_pyc(filename): 217 """Get a code object from a .pyc file.""" 218 try: 219 fpyc = open(filename, "rb") 220 except IOError: 221 raise NoCode("No file to run: '%s'" % filename) 222 223 with fpyc: 224 # First four bytes are a version-specific magic number. It has to 225 # match or we won't run the file. 226 magic = fpyc.read(4) 227 if magic != PYC_MAGIC_NUMBER: 228 raise NoCode("Bad magic number in .pyc file") 229 230 # Skip the junk in the header that we don't need. 231 fpyc.read(4) # Skip the moddate. 232 if sys.version_info >= (3, 3): 233 # 3.3 added another long to the header (size), skip it. 234 fpyc.read(4) 235 236 # The rest of the file is the code object we want. 237 code = marshal.load(fpyc) 238 239 return code 240