diff --git a/CHANGES.rst b/CHANGES.rst index 226e3701b..281551c44 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -30,6 +30,11 @@ Unreleased information about the config files read now shows absolute paths to the files. +- When running programs as modules (with ``-m``), some measured modules were + imported before coverage starts. This resulted in unwanted warnings + ("Already imported a file that will be measured") and a reduction in coverage + totals, described in `issue 909`_. This is now fixed. + - The handling of source files with non-encodable file names has changed. Previously, if a file name could not be encoded as UTF-8, an error occurred, as described in `issue 891`_. Now, those files will not be measured, since @@ -45,6 +50,7 @@ Unreleased .. _issue 891: https://github.com/nedbat/coveragepy/issues/891 .. _issue 901: https://github.com/nedbat/coveragepy/issues/901 .. _issue 907: https://github.com/nedbat/coveragepy/issues/907 +.. _issue 909: https://github.com/nedbat/coveragepy/issues/909 .. _changes_501: diff --git a/coverage/execfile.py b/coverage/execfile.py index 628b5c94e..928f9d354 100644 --- a/coverage/execfile.py +++ b/coverage/execfile.py @@ -116,14 +116,11 @@ def __init__(self, args, as_module=False): self.package = self.modulename = self.pathname = self.loader = self.spec = None def prepare(self): - """Do initial preparation to run Python code. - - Includes finding the module to run, adjusting sys.argv[0], and changing - sys.path to match what Python does. + """Set sys.path properly. + This needs to happen before any importing, and without importing anything. """ should_update_sys_path = True - if self.as_module: if env.PYBEHAVIOR.actual_syspath0_dash_m: path0 = os.getcwd() @@ -131,6 +128,33 @@ def prepare(self): path0 = "" sys.path[0] = path0 should_update_sys_path = False + 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)) + + + if should_update_sys_path: + # 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 + if os.path.abspath(sys.path[0]) == os.path.abspath(os.path.dirname(top_file)): + # Set sys.path correctly. + 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 self.as_module: self.modulename = self.arg0 pathname, self.package, self.spec = find_module(self.modulename) if self.spec is not None: @@ -141,7 +165,6 @@ def prepare(self): elif os.path.isdir(self.arg0): # Running a directory means running the __main__.py file in that # directory. - path0 = self.arg0 for ext in [".py", ".pyc", ".pyo"]: try_filename = os.path.join(self.arg0, "__main__" + ext) if os.path.exists(try_filename): @@ -165,29 +188,16 @@ def prepare(self): self.package = "" self.loader = DummyLoader("__main__") else: - path0 = os.path.abspath(os.path.dirname(self.arg0)) if env.PY3: self.loader = DummyLoader("__main__") self.arg0 = python_reported_file(self.arg0) - if self.modulename is None: - self.modulename = '__main__' - - if should_update_sys_path: - # 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 - if os.path.abspath(sys.path[0]) == os.path.abspath(os.path.dirname(top_file)): - # Set sys.path correctly. - sys.path[0] = python_reported_file(path0) - def run(self): """Run the Python code!""" + self._prepare2() + # Create a module to serve as __main__ main_mod = types.ModuleType('__main__')