| # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 | |
| # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt | |
| """Execute files of Python code.""" | |
| import marshal | |
| import os | |
| import sys | |
| import types | |
| from coverage.backward import BUILTINS | |
| from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec | |
| from coverage.misc import ExceptionDuringRun, NoCode, NoSource, isolate_module | |
| from coverage.phystokens import compile_unicode | |
| from coverage.python import get_python_source | |
| os = isolate_module(os) | |
| class DummyLoader(object): | |
| """A shim for the pep302 __loader__, emulating pkgutil.ImpLoader. | |
| Currently only implements the .fullname attribute | |
| """ | |
| def __init__(self, fullname, *_args): | |
| self.fullname = fullname | |
| if importlib_util_find_spec: | |
| def find_module(modulename): | |
| """Find the module named `modulename`. | |
| Returns the file path of the module, and the name of the enclosing | |
| package. | |
| """ | |
| try: | |
| spec = importlib_util_find_spec(modulename) | |
| except ImportError as err: | |
| raise NoSource(str(err)) | |
| if not spec: | |
| raise NoSource("No module named %r" % (modulename,)) | |
| pathname = spec.origin | |
| packagename = spec.name | |
| if pathname.endswith("__init__.py") and not modulename.endswith("__init__"): | |
| mod_main = modulename + ".__main__" | |
| spec = importlib_util_find_spec(mod_main) | |
| if not spec: | |
| raise NoSource( | |
| "No module named %s; " | |
| "%r is a package and cannot be directly executed" | |
| % (mod_main, modulename) | |
| ) | |
| pathname = spec.origin | |
| packagename = spec.name | |
| packagename = packagename.rpartition(".")[0] | |
| return pathname, packagename | |
| else: | |
| def find_module(modulename): | |
| """Find the module named `modulename`. | |
| Returns the file path of the module, and the name of the enclosing | |
| package. | |
| """ | |
| openfile = None | |
| glo, loc = globals(), locals() | |
| try: | |
| # Search for the module - inside its parent package, if any - using | |
| # standard import mechanics. | |
| if '.' in modulename: | |
| packagename, name = modulename.rsplit('.', 1) | |
| package = __import__(packagename, glo, loc, ['__path__']) | |
| searchpath = package.__path__ | |
| else: | |
| packagename, name = None, modulename | |
| searchpath = None # "top-level search" in imp.find_module() | |
| openfile, pathname, _ = imp.find_module(name, searchpath) | |
| # Complain if this is a magic non-file module. | |
| if openfile is None and pathname is None: | |
| raise NoSource( | |
| "module does not live in a file: %r" % modulename | |
| ) | |
| # If `modulename` is actually a package, not a mere module, then we | |
| # pretend to be Python 2.7 and try running its __main__.py script. | |
| if openfile is None: | |
| packagename = modulename | |
| name = '__main__' | |
| package = __import__(packagename, glo, loc, ['__path__']) | |
| searchpath = package.__path__ | |
| openfile, pathname, _ = imp.find_module(name, searchpath) | |
| except ImportError as err: | |
| raise NoSource(str(err)) | |
| finally: | |
| if openfile: | |
| openfile.close() | |
| return pathname, packagename | |
| def run_python_module(modulename, args): | |
| """Run a Python module, as though with ``python -m name args...``. | |
| `modulename` is the name of the module, possibly a dot-separated name. | |
| `args` is the argument array to present as sys.argv, including the first | |
| element naming the module being executed. | |
| """ | |
| pathname, packagename = find_module(modulename) | |
| pathname = os.path.abspath(pathname) | |
| args[0] = pathname | |
| run_python_file(pathname, args, package=packagename, modulename=modulename, path0="") | |
| def run_python_file(filename, args, package=None, modulename=None, path0=None): | |
| """Run a Python file as if it were the main program on the command line. | |
| `filename` is the path to the file to execute, it need not be a .py file. | |
| `args` is the argument array to present as sys.argv, including the first | |
| element naming the file being executed. `package` is the name of the | |
| enclosing package, if any. | |
| `modulename` is the name of the module the file was run as. | |
| `path0` is the value to put into sys.path[0]. If it's None, then this | |
| function will decide on a value. | |
| """ | |
| if modulename is None and sys.version_info >= (3, 3): | |
| modulename = '__main__' | |
| # Create a module to serve as __main__ | |
| old_main_mod = sys.modules['__main__'] | |
| main_mod = types.ModuleType('__main__') | |
| sys.modules['__main__'] = main_mod | |
| main_mod.__file__ = filename | |
| if package: | |
| main_mod.__package__ = package | |
| if modulename: | |
| main_mod.__loader__ = DummyLoader(modulename) | |
| main_mod.__builtins__ = BUILTINS | |
| # Set sys.argv properly. | |
| old_argv = sys.argv | |
| sys.argv = args | |
| if os.path.isdir(filename): | |
| # Running a directory means running the __main__.py file in that | |
| # directory. | |
| my_path0 = filename | |
| for ext in [".py", ".pyc", ".pyo"]: | |
| try_filename = os.path.join(filename, "__main__" + ext) | |
| if os.path.exists(try_filename): | |
| filename = try_filename | |
| break | |
| else: | |
| raise NoSource("Can't find '__main__' module in '%s'" % filename) | |
| else: | |
| my_path0 = os.path.abspath(os.path.dirname(filename)) | |
| # Set sys.path correctly. | |
| old_path0 = sys.path[0] | |
| sys.path[0] = path0 if path0 is not None else my_path0 | |
| try: | |
| # Make a code object somehow. | |
| if filename.endswith((".pyc", ".pyo")): | |
| code = make_code_from_pyc(filename) | |
| else: | |
| code = make_code_from_py(filename) | |
| # Execute the code object. | |
| try: | |
| exec(code, main_mod.__dict__) | |
| except SystemExit: | |
| # The user called sys.exit(). Just pass it along to the upper | |
| # layers, where it will be handled. | |
| raise | |
| except: | |
| # Something went wrong while executing the user code. | |
| # Get the exc_info, and pack them into an exception that we can | |
| # throw up to the outer loop. We peel one layer off the traceback | |
| # so that the coverage.py code doesn't appear in the final printed | |
| # traceback. | |
| typ, err, tb = sys.exc_info() | |
| # PyPy3 weirdness. If I don't access __context__, then somehow it | |
| # is non-None when the exception is reported at the upper layer, | |
| # and a nested exception is shown to the user. This getattr fixes | |
| # it somehow? https://bitbucket.org/pypy/pypy/issue/1903 | |
| getattr(err, '__context__', None) | |
| raise ExceptionDuringRun(typ, err, tb.tb_next) | |
| finally: | |
| # Restore the old __main__, argv, and path. | |
| sys.modules['__main__'] = old_main_mod | |
| sys.argv = old_argv | |
| sys.path[0] = old_path0 | |
| def make_code_from_py(filename): | |
| """Get source from `filename` and make a code object of it.""" | |
| # Open the source file. | |
| try: | |
| source = get_python_source(filename) | |
| except (IOError, NoSource): | |
| raise NoSource("No file to run: '%s'" % filename) | |
| code = compile_unicode(source, filename, "exec") | |
| return code | |
| def make_code_from_pyc(filename): | |
| """Get a code object from a .pyc file.""" | |
| try: | |
| fpyc = open(filename, "rb") | |
| except IOError: | |
| raise NoCode("No file to run: '%s'" % filename) | |
| with fpyc: | |
| # First four bytes are a version-specific magic number. It has to | |
| # match or we won't run the file. | |
| magic = fpyc.read(4) | |
| if magic != PYC_MAGIC_NUMBER: | |
| raise NoCode("Bad magic number in .pyc file") | |
| # Skip the junk in the header that we don't need. | |
| fpyc.read(4) # Skip the moddate. | |
| if sys.version_info >= (3, 3): | |
| # 3.3 added another long to the header (size), skip it. | |
| fpyc.read(4) | |
| # The rest of the file is the code object we want. | |
| code = marshal.load(fpyc) | |
| return code |