1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 4"""Execute files of Python code.""" 5 6import inspect 7import marshal 8import os 9import struct 10import sys 11import types 12 13from coverage import env 14from coverage.backward import BUILTINS 15from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec 16from coverage.files import canonical_filename, python_reported_file 17from coverage.misc import CoverageException, ExceptionDuringRun, NoCode, NoSource, isolate_module 18from coverage.phystokens import compile_unicode 19from coverage.python import get_python_source 20 21os = isolate_module(os) 22 23 24class DummyLoader(object): 25 """A shim for the pep302 __loader__, emulating pkgutil.ImpLoader. 26 27 Currently only implements the .fullname attribute 28 """ 29 def __init__(self, fullname, *_args): 30 self.fullname = fullname 31 32 33if importlib_util_find_spec: 34 def find_module(modulename): 35 """Find the module named `modulename`. 36 37 Returns the file path of the module, the name of the enclosing 38 package, and the spec. 39 """ 40 try: 41 spec = importlib_util_find_spec(modulename) 42 except ImportError as err: 43 raise NoSource(str(err)) 44 if not spec: 45 raise NoSource("No module named %r" % (modulename,)) 46 pathname = spec.origin 47 packagename = spec.name 48 if spec.submodule_search_locations: 49 mod_main = modulename + ".__main__" 50 spec = importlib_util_find_spec(mod_main) 51 if not spec: 52 raise NoSource( 53 "No module named %s; " 54 "%r is a package and cannot be directly executed" 55 % (mod_main, modulename) 56 ) 57 pathname = spec.origin 58 packagename = spec.name 59 packagename = packagename.rpartition(".")[0] 60 return pathname, packagename, spec 61else: 62 def find_module(modulename): 63 """Find the module named `modulename`. 64 65 Returns the file path of the module, the name of the enclosing 66 package, and None (where a spec would have been). 67 """ 68 openfile = None 69 glo, loc = globals(), locals() 70 try: 71 # Search for the module - inside its parent package, if any - using 72 # standard import mechanics. 73 if '.' in modulename: 74 packagename, name = modulename.rsplit('.', 1) 75 package = __import__(packagename, glo, loc, ['__path__']) 76 searchpath = package.__path__ 77 else: 78 packagename, name = None, modulename 79 searchpath = None # "top-level search" in imp.find_module() 80 openfile, pathname, _ = imp.find_module(name, searchpath) 81 82 # Complain if this is a magic non-file module. 83 if openfile is None and pathname is None: 84 raise NoSource( 85 "module does not live in a file: %r" % modulename 86 ) 87 88 # If `modulename` is actually a package, not a mere module, then we 89 # pretend to be Python 2.7 and try running its __main__.py script. 90 if openfile is None: 91 packagename = modulename 92 name = '__main__' 93 package = __import__(packagename, glo, loc, ['__path__']) 94 searchpath = package.__path__ 95 openfile, pathname, _ = imp.find_module(name, searchpath) 96 except ImportError as err: 97 raise NoSource(str(err)) 98 finally: 99 if openfile: 100 openfile.close() 101 102 return pathname, packagename, None 103 104 105class PyRunner(object): 106 """Multi-stage execution of Python code. 107 108 This is meant to emulate real Python execution as closely as possible. 109 110 """ 111 def __init__(self, args, as_module=False): 112 self.args = args 113 self.as_module = as_module 114 115 self.arg0 = args[0] 116 self.package = self.modulename = self.pathname = self.loader = self.spec = None 117 118 def prepare(self): 119 """Set sys.path properly. 120 121 This needs to happen before any importing, and without importing anything. 122 """ 123 if self.as_module: 124 if env.PYBEHAVIOR.actual_syspath0_dash_m: 125 path0 = os.getcwd() 126 else: 127 path0 = "" 128 elif os.path.isdir(self.arg0): 129 # Running a directory means running the __main__.py file in that 130 # directory. 131 path0 = self.arg0 132 else: 133 path0 = os.path.abspath(os.path.dirname(self.arg0)) 134 135 if os.path.isdir(sys.path[0]): 136 # sys.path fakery. If we are being run as a command, then sys.path[0] 137 # is the directory of the "coverage" script. If this is so, replace 138 # sys.path[0] with the directory of the file we're running, or the 139 # current directory when running modules. If it isn't so, then we 140 # don't know what's going on, and just leave it alone. 141 top_file = inspect.stack()[-1][0].f_code.co_filename 142 sys_path_0_abs = os.path.abspath(sys.path[0]) 143 top_file_dir_abs = os.path.abspath(os.path.dirname(top_file)) 144 sys_path_0_abs = canonical_filename(sys_path_0_abs) 145 top_file_dir_abs = canonical_filename(top_file_dir_abs) 146 if sys_path_0_abs != top_file_dir_abs: 147 path0 = None 148 149 else: 150 # sys.path[0] is a file. Is the next entry the directory containing 151 # that file? 152 if sys.path[1] == os.path.dirname(sys.path[0]): 153 # Can it be right to always remove that? 154 del sys.path[1] 155 156 if path0 is not None: 157 sys.path[0] = python_reported_file(path0) 158 159 def _prepare2(self): 160 """Do more preparation to run Python code. 161 162 Includes finding the module to run and adjusting sys.argv[0]. 163 This method is allowed to import code. 164 165 """ 166 if self.as_module: 167 self.modulename = self.arg0 168 pathname, self.package, self.spec = find_module(self.modulename) 169 if self.spec is not None: 170 self.modulename = self.spec.name 171 self.loader = DummyLoader(self.modulename) 172 self.pathname = os.path.abspath(pathname) 173 self.args[0] = self.arg0 = self.pathname 174 elif os.path.isdir(self.arg0): 175 # Running a directory means running the __main__.py file in that 176 # directory. 177 for ext in [".py", ".pyc", ".pyo"]: 178 try_filename = os.path.join(self.arg0, "__main__" + ext) 179 if os.path.exists(try_filename): 180 self.arg0 = try_filename 181 break 182 else: 183 raise NoSource("Can't find '__main__' module in '%s'" % self.arg0) 184 185 if env.PY2: 186 self.arg0 = os.path.abspath(self.arg0) 187 188 # Make a spec. I don't know if this is the right way to do it. 189 try: 190 import importlib.machinery 191 except ImportError: 192 pass 193 else: 194 try_filename = python_reported_file(try_filename) 195 self.spec = importlib.machinery.ModuleSpec("__main__", None, origin=try_filename) 196 self.spec.has_location = True 197 self.package = "" 198 self.loader = DummyLoader("__main__") 199 else: 200 if env.PY3: 201 self.loader = DummyLoader("__main__") 202 203 self.arg0 = python_reported_file(self.arg0) 204 205 def run(self): 206 """Run the Python code!""" 207 208 self._prepare2() 209 210 # Create a module to serve as __main__ 211 main_mod = types.ModuleType('__main__') 212 213 from_pyc = self.arg0.endswith((".pyc", ".pyo")) 214 main_mod.__file__ = self.arg0 215 if from_pyc: 216 main_mod.__file__ = main_mod.__file__[:-1] 217 if self.package is not None: 218 main_mod.__package__ = self.package 219 main_mod.__loader__ = self.loader 220 if self.spec is not None: 221 main_mod.__spec__ = self.spec 222 223 main_mod.__builtins__ = BUILTINS 224 225 sys.modules['__main__'] = main_mod 226 227 # Set sys.argv properly. 228 sys.argv = self.args 229 230 try: 231 # Make a code object somehow. 232 if from_pyc: 233 code = make_code_from_pyc(self.arg0) 234 else: 235 code = make_code_from_py(self.arg0) 236 except CoverageException: 237 raise 238 except Exception as exc: 239 msg = "Couldn't run '{filename}' as Python code: {exc.__class__.__name__}: {exc}" 240 raise CoverageException(msg.format(filename=self.arg0, exc=exc)) 241 242 # Execute the code object. 243 # Return to the original directory in case the test code exits in 244 # a non-existent directory. 245 cwd = os.getcwd() 246 try: 247 exec(code, main_mod.__dict__) 248 except SystemExit: # pylint: disable=try-except-raise 249 # The user called sys.exit(). Just pass it along to the upper 250 # layers, where it will be handled. 251 raise 252 except Exception: 253 # Something went wrong while executing the user code. 254 # Get the exc_info, and pack them into an exception that we can 255 # throw up to the outer loop. We peel one layer off the traceback 256 # so that the coverage.py code doesn't appear in the final printed 257 # traceback. 258 typ, err, tb = sys.exc_info() 259 260 # PyPy3 weirdness. If I don't access __context__, then somehow it 261 # is non-None when the exception is reported at the upper layer, 262 # and a nested exception is shown to the user. This getattr fixes 263 # it somehow? https://bitbucket.org/pypy/pypy/issue/1903 264 getattr(err, '__context__', None) 265 266 # Call the excepthook. 267 try: 268 if hasattr(err, "__traceback__"): 269 err.__traceback__ = err.__traceback__.tb_next 270 sys.excepthook(typ, err, tb.tb_next) 271 except SystemExit: # pylint: disable=try-except-raise 272 raise 273 except Exception: 274 # Getting the output right in the case of excepthook 275 # shenanigans is kind of involved. 276 sys.stderr.write("Error in sys.excepthook:\n") 277 typ2, err2, tb2 = sys.exc_info() 278 err2.__suppress_context__ = True 279 if hasattr(err2, "__traceback__"): 280 err2.__traceback__ = err2.__traceback__.tb_next 281 sys.__excepthook__(typ2, err2, tb2.tb_next) 282 sys.stderr.write("\nOriginal exception was:\n") 283 raise ExceptionDuringRun(typ, err, tb.tb_next) 284 else: 285 sys.exit(1) 286 finally: 287 os.chdir(cwd) 288 289 290def run_python_module(args): 291 """Run a Python module, as though with ``python -m name args...``. 292 293 `args` is the argument array to present as sys.argv, including the first 294 element naming the module being executed. 295 296 This is a helper for tests, to encapsulate how to use PyRunner. 297 298 """ 299 runner = PyRunner(args, as_module=True) 300 runner.prepare() 301 runner.run() 302 303 304def run_python_file(args): 305 """Run a Python file as if it were the main program on the command line. 306 307 `args` is the argument array to present as sys.argv, including the first 308 element naming the file being executed. `package` is the name of the 309 enclosing package, if any. 310 311 This is a helper for tests, to encapsulate how to use PyRunner. 312 313 """ 314 runner = PyRunner(args, as_module=False) 315 runner.prepare() 316 runner.run() 317 318 319def make_code_from_py(filename): 320 """Get source from `filename` and make a code object of it.""" 321 # Open the source file. 322 try: 323 source = get_python_source(filename) 324 except (IOError, NoSource): 325 raise NoSource("No file to run: '%s'" % filename) 326 327 code = compile_unicode(source, filename, "exec") 328 return code 329 330 331def make_code_from_pyc(filename): 332 """Get a code object from a .pyc file.""" 333 try: 334 fpyc = open(filename, "rb") 335 except IOError: 336 raise NoCode("No file to run: '%s'" % filename) 337 338 with fpyc: 339 # First four bytes are a version-specific magic number. It has to 340 # match or we won't run the file. 341 magic = fpyc.read(4) 342 if magic != PYC_MAGIC_NUMBER: 343 raise NoCode("Bad magic number in .pyc file: {} != {}".format(magic, PYC_MAGIC_NUMBER)) 344 345 date_based = True 346 if env.PYBEHAVIOR.hashed_pyc_pep552: 347 flags = struct.unpack('<L', fpyc.read(4))[0] 348 hash_based = flags & 0x01 349 if hash_based: 350 fpyc.read(8) # Skip the hash. 351 date_based = False 352 if date_based: 353 # Skip the junk in the header that we don't need. 354 fpyc.read(4) # Skip the moddate. 355 if env.PYBEHAVIOR.size_in_pyc: 356 # 3.3 added another long to the header (size), skip it. 357 fpyc.read(4) 358 359 # The rest of the file is the code object we want. 360 code = marshal.load(fpyc) 361 362 return code 363