diff -r dc171b1d8261 -r 362cd1b6f81a eric6/DebugClients/Python/coverage/execfile.py --- a/eric6/DebugClients/Python/coverage/execfile.py Wed Feb 19 19:38:36 2020 +0100 +++ b/eric6/DebugClients/Python/coverage/execfile.py Sat Feb 22 14:27:42 2020 +0100 @@ -1,16 +1,19 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Execute files of Python code.""" +import inspect import marshal import os import struct import sys import types +from coverage import env from coverage.backward import BUILTINS from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec +from coverage.files import canonical_filename, python_reported_file from coverage.misc import CoverageException, ExceptionDuringRun, NoCode, NoSource, isolate_module from coverage.phystokens import compile_unicode from coverage.python import get_python_source @@ -31,8 +34,8 @@ def find_module(modulename): """Find the module named `modulename`. - Returns the file path of the module, and the name of the enclosing - package. + Returns the file path of the module, the name of the enclosing + package, and the spec. """ try: spec = importlib_util_find_spec(modulename) @@ -42,7 +45,7 @@ raise NoSource("No module named %r" % (modulename,)) pathname = spec.origin packagename = spec.name - if pathname.endswith("__init__.py") and not modulename.endswith("__init__"): + if spec.submodule_search_locations: mod_main = modulename + ".__main__" spec = importlib_util_find_spec(mod_main) if not spec: @@ -54,13 +57,13 @@ pathname = spec.origin packagename = spec.name packagename = packagename.rpartition(".")[0] - return pathname, packagename + return pathname, packagename, spec else: def find_module(modulename): """Find the module named `modulename`. - Returns the file path of the module, and the name of the enclosing - package. + Returns the file path of the module, the name of the enclosing + package, and None (where a spec would have been). """ openfile = None glo, loc = globals(), locals() @@ -96,101 +99,153 @@ if openfile: openfile.close() - return pathname, packagename + return pathname, packagename, None -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) +class PyRunner(object): + """Multi-stage execution of Python code. - pathname = os.path.abspath(pathname) - args[0] = pathname - # Python 3.7.0b3 changed the behavior of the sys.path[0] entry for -m. It - # used to be an empty string (meaning the current directory). It changed - # to be the actual path to the current directory, so that os.chdir wouldn't - # affect the outcome. - if sys.version_info >= (3, 7, 0, 'beta', 3): - path0 = os.getcwd() - else: - path0 = "" - run_python_file(pathname, args, package=packagename, modulename=modulename, path0=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. + This is meant to emulate real Python execution as closely as possible. """ - if modulename is None and sys.version_info >= (3, 3): - modulename = '__main__' + def __init__(self, args, as_module=False): + self.args = args + self.as_module = as_module + + self.arg0 = args[0] + self.package = self.modulename = self.pathname = self.loader = self.spec = None + + def prepare(self): + """Set sys.path properly. + + This needs to happen before any importing, and without importing anything. + """ + if self.as_module: + if env.PYBEHAVIOR.actual_syspath0_dash_m: + path0 = os.getcwd() + else: + path0 = "" + elif os.path.isdir(self.arg0): + # Running a directory means running the __main__.py file in that + # directory. + path0 = self.arg0 + else: + path0 = os.path.abspath(os.path.dirname(self.arg0)) - # 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) + if os.path.isdir(sys.path[0]): + # sys.path fakery. If we are being run as a command, then sys.path[0] + # is the directory of the "coverage" script. If this is so, replace + # sys.path[0] with the directory of the file we're running, or the + # current directory when running modules. If it isn't so, then we + # don't know what's going on, and just leave it alone. + top_file = inspect.stack()[-1][0].f_code.co_filename + sys_path_0_abs = os.path.abspath(sys.path[0]) + top_file_dir_abs = os.path.abspath(os.path.dirname(top_file)) + sys_path_0_abs = canonical_filename(sys_path_0_abs) + top_file_dir_abs = canonical_filename(top_file_dir_abs) + if sys_path_0_abs != top_file_dir_abs: + path0 = None - main_mod.__builtins__ = BUILTINS + else: + # sys.path[0] is a file. Is the next entry the directory containing + # that file? + if sys.path[1] == os.path.dirname(sys.path[0]): + # Can it be right to always remove that? + del sys.path[1] - # Set sys.argv properly. - old_argv = sys.argv - sys.argv = args + if path0 is not None: + sys.path[0] = python_reported_file(path0) + + def _prepare2(self): + """Do more preparation to run Python code. + + Includes finding the module to run and adjusting sys.argv[0]. + This method is allowed to import code. - if os.path.isdir(filename): - # Running a directory means running the __main__.py file in that - # directory. - my_path0 = filename + """ + if self.as_module: + self.modulename = self.arg0 + pathname, self.package, self.spec = find_module(self.modulename) + if self.spec is not None: + self.modulename = self.spec.name + self.loader = DummyLoader(self.modulename) + self.pathname = os.path.abspath(pathname) + self.args[0] = self.arg0 = self.pathname + elif os.path.isdir(self.arg0): + # Running a directory means running the __main__.py file in that + # directory. + for ext in [".py", ".pyc", ".pyo"]: + try_filename = os.path.join(self.arg0, "__main__" + ext) + if os.path.exists(try_filename): + self.arg0 = try_filename + break + else: + raise NoSource("Can't find '__main__' module in '%s'" % self.arg0) + + if env.PY2: + self.arg0 = os.path.abspath(self.arg0) - for ext in [".py", ".pyc", ".pyo"]: - try_filename = os.path.join(filename, "__main__" + ext) - if os.path.exists(try_filename): - filename = try_filename - break + # Make a spec. I don't know if this is the right way to do it. + try: + import importlib.machinery + except ImportError: + pass + else: + try_filename = python_reported_file(try_filename) + self.spec = importlib.machinery.ModuleSpec("__main__", None, origin=try_filename) + self.spec.has_location = True + self.package = "" + self.loader = DummyLoader("__main__") else: - raise NoSource("Can't find '__main__' module in '%s'" % filename) - else: - my_path0 = os.path.abspath(os.path.dirname(filename)) + if env.PY3: + self.loader = DummyLoader("__main__") + + self.arg0 = python_reported_file(self.arg0) + + def run(self): + """Run the Python code!""" + + self._prepare2() + + # Create a module to serve as __main__ + main_mod = types.ModuleType('__main__') - # Set sys.path correctly. - old_path0 = sys.path[0] - sys.path[0] = path0 if path0 is not None else my_path0 + from_pyc = self.arg0.endswith((".pyc", ".pyo")) + main_mod.__file__ = self.arg0 + if from_pyc: + main_mod.__file__ = main_mod.__file__[:-1] + if self.package is not None: + main_mod.__package__ = self.package + main_mod.__loader__ = self.loader + if self.spec is not None: + main_mod.__spec__ = self.spec - try: + main_mod.__builtins__ = BUILTINS + + sys.modules['__main__'] = main_mod + + # Set sys.argv properly. + sys.argv = self.args + try: # Make a code object somehow. - if filename.endswith((".pyc", ".pyo")): - code = make_code_from_pyc(filename) + if from_pyc: + code = make_code_from_pyc(self.arg0) else: - code = make_code_from_py(filename) + code = make_code_from_py(self.arg0) except CoverageException: raise except Exception as exc: - msg = "Couldn't run {filename!r} as Python code: {exc.__class__.__name__}: {exc}" - raise CoverageException(msg.format(filename=filename, exc=exc)) + msg = "Couldn't run '{filename}' as Python code: {exc.__class__.__name__}: {exc}" + raise CoverageException(msg.format(filename=self.arg0, exc=exc)) # Execute the code object. + # Return to the original directory in case the test code exits in + # a non-existent directory. + cwd = os.getcwd() try: exec(code, main_mod.__dict__) - except SystemExit: + except SystemExit: # pylint: disable=try-except-raise # The user called sys.exit(). Just pass it along to the upper # layers, where it will be handled. raise @@ -213,7 +268,7 @@ if hasattr(err, "__traceback__"): err.__traceback__ = err.__traceback__.tb_next sys.excepthook(typ, err, tb.tb_next) - except SystemExit: + except SystemExit: # pylint: disable=try-except-raise raise except Exception: # Getting the output right in the case of excepthook @@ -228,12 +283,37 @@ raise ExceptionDuringRun(typ, err, tb.tb_next) else: sys.exit(1) + finally: + os.chdir(cwd) - finally: - # Restore the old __main__, argv, and path. - sys.modules['__main__'] = old_main_mod - sys.argv = old_argv - sys.path[0] = old_path0 + +def run_python_module(args): + """Run a Python module, as though with ``python -m name args...``. + + `args` is the argument array to present as sys.argv, including the first + element naming the module being executed. + + This is a helper for tests, to encapsulate how to use PyRunner. + + """ + runner = PyRunner(args, as_module=True) + runner.prepare() + runner.run() + + +def run_python_file(args): + """Run a Python file as if it were the main program on the command line. + + `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. + + This is a helper for tests, to encapsulate how to use PyRunner. + + """ + runner = PyRunner(args, as_module=False) + runner.prepare() + runner.run() def make_code_from_py(filename): @@ -260,10 +340,10 @@ # 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") + raise NoCode("Bad magic number in .pyc file: {} != {}".format(magic, PYC_MAGIC_NUMBER)) date_based = True - if sys.version_info >= (3, 7, 0, 'alpha', 4): + if env.PYBEHAVIOR.hashed_pyc_pep552: flags = struct.unpack('<L', fpyc.read(4))[0] hash_based = flags & 0x01 if hash_based: @@ -272,7 +352,7 @@ if date_based: # Skip the junk in the header that we don't need. fpyc.read(4) # Skip the moddate. - if sys.version_info >= (3, 3): + if env.PYBEHAVIOR.size_in_pyc: # 3.3 added another long to the header (size), skip it. fpyc.read(4)