diff options
-rw-r--r-- | AUTHORS.txt | 1 | ||||
-rw-r--r-- | CHANGES.txt | 4 | ||||
-rw-r--r-- | coverage/cmdline.py | 9 | ||||
-rw-r--r-- | coverage/execfile.py | 23 | ||||
-rw-r--r-- | tests/test_execfile.py | 15 | ||||
-rw-r--r-- | tests/test_process.py | 27 | ||||
-rw-r--r-- | tests/with_main/__main__.py | 2 | ||||
-rw-r--r-- | tests/with_main/without/__init__.py | 1 |
8 files changed, 58 insertions, 24 deletions
diff --git a/AUTHORS.txt b/AUTHORS.txt index d484edf1..1508d45d 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -26,6 +26,7 @@ David Christian David Stanek Detlev Offenbach Devin Jeanpierre +Dmitry Trofimov Eduardo Schettino Edward Loper Geoff Bache diff --git a/CHANGES.txt b/CHANGES.txt index 492e831c..45a264a2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -27,6 +27,9 @@ Latest - In parallel mode, ``coverage erase`` will now delete all of the data files, fixing `issue 262`_. +- Coverage.py now accepts a directory name for ``coverage run`` and will run a + ``__main__.py`` found there, just like Python will. Fixes `issue 252`_. + - The XML report now includes a ``missing-branches`` attribute. Thanks, Steve Peak. This is not a part of the Cobertura DTD, so the XML report no longer references the DTD. @@ -60,6 +63,7 @@ Latest support, finishing up `issue 387`_. .. _issue 236: https://bitbucket.org/ned/coveragepy/issues/236/pickles-are-bad-and-you-should-feel-bad +.. _issue 252: https://bitbucket.org/ned/coveragepy/issues/252/coverage-wont-run-a-program-with .. _issue 262: https://bitbucket.org/ned/coveragepy/issues/262/when-parallel-true-erase-should-erase-all .. _issue 275: https://bitbucket.org/ned/coveragepy/issues/275/refer-consistently-to-project-as-coverage .. _issue 313: https://bitbucket.org/ned/coveragepy/issues/313/add-license-file-containing-2-3-or-4 diff --git a/coverage/cmdline.py b/coverage/cmdline.py index fc40e619..aeb74bd5 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -5,7 +5,6 @@ import glob import optparse -import os import sys import traceback @@ -560,19 +559,14 @@ class CoverageScript(object): if not options.append: self.coverage.erase() - # Set the first path element properly. - old_path0 = sys.path[0] - # Run the script. self.coverage.start() code_ran = True try: if options.module: - sys.path[0] = '' self.run_python_module(args[0], args) else: filename = args[0] - sys.path[0] = os.path.abspath(os.path.dirname(filename)) self.run_python_file(filename, args) except NoSource: code_ran = False @@ -584,9 +578,6 @@ class CoverageScript(object): self.coverage.combine(data_paths=[self.coverage.config.data_file]) self.coverage.save() - # Restore the old path - sys.path[0] = old_path0 - return OK def do_debug(self, args): diff --git a/coverage/execfile.py b/coverage/execfile.py index d1158b51..1845f8d7 100644 --- a/coverage/execfile.py +++ b/coverage/execfile.py @@ -108,10 +108,10 @@ def run_python_module(modulename, args): pathname = os.path.abspath(pathname) args[0] = pathname - run_python_file(pathname, args, package=packagename, modulename=modulename) + run_python_file(pathname, args, package=packagename, modulename=modulename, path0="") -def run_python_file(filename, args, package=None, modulename=None): +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. @@ -121,6 +121,9 @@ def run_python_file(filename, args, package=None, modulename=None): `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__' @@ -142,7 +145,10 @@ def run_python_file(filename, args, package=None, modulename=None): sys.argv = args if os.path.isdir(filename): - # in directory we should look for __main__ module + # 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): @@ -150,6 +156,12 @@ def run_python_file(filename, args, package=None, modulename=None): 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. @@ -181,11 +193,10 @@ def run_python_file(filename, args, package=None, modulename=None): raise ExceptionDuringRun(typ, err, tb.tb_next) finally: - # Restore the old __main__ + # Restore the old __main__, argv, and path. sys.modules['__main__'] = old_main_mod - - # Restore the old argv and path sys.argv = old_argv + sys.path[0] = old_path0 def make_code_from_py(filename): diff --git a/tests/test_execfile.py b/tests/test_execfile.py index 2533f81d..a3ea1153 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -86,14 +86,17 @@ class RunFileTest(CoverageTest): run_python_file("xyzzy.py", []) def test_directory_with_main(self): - directory_with_main = os.path.join(HERE, "with_main") - run_python_file(directory_with_main, [directory_with_main]) - self.assertEqual(self.stdout(), "1\n") + self.make_file("with_main/__main__.py", """\ + print("I am __main__") + """) + run_python_file("with_main", ["with_main"]) + self.assertEqual(self.stdout(), "I am __main__\n") def test_directory_without_main(self): - with self.assertRaises(NoSource): - directory_with_main = os.path.join(HERE, "with_main", "without") - run_python_file(directory_with_main, [directory_with_main]) + self.make_file("without_main/__init__.py", "") + with self.assertRaisesRegex(NoSource, "Can't find '__main__' module in 'without_main'"): + run_python_file("without_main", ["without_main"]) + class RunPycFileTest(CoverageTest): """Test cases for `run_python_file`.""" diff --git a/tests/test_process.py b/tests/test_process.py index 7c8b0c2d..fcd88827 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -7,6 +7,7 @@ import glob import os import os.path +import re import sys import textwrap @@ -387,6 +388,26 @@ class ProcessTest(CoverageTest): out_py = self.run_command("python -m tests.try_execfile") self.assertMultiLineEqual(out_cov, out_py) + def test_coverage_run_dir_is_like_python_dir(self): + tryfile = os.path.join(HERE, "try_execfile.py") + with open(tryfile) as f: + self.make_file("with_main/__main__.py", f.read()) + out_cov = self.run_command("coverage run with_main") + out_py = self.run_command("python with_main") + + # The coverage.py results are not identical to the Python results, and + # I don't know why. For now, ignore those failures. If someone finds + # a real problem with the discrepancies, we can work on it some more. + ignored = r"__file__|__loader__|__package__" + # PyPy includes the current directory in the path when running a + # directory, while CPython and coverage.py do not. Exclude that from + # the comparison also... + if env.PYPY: + ignored += "|"+re.escape(os.getcwd()) + out_cov = remove_matching_lines(out_cov, ignored) + out_py = remove_matching_lines(out_py, ignored) + self.assertMultiLineEqual(out_cov, out_py) + def test_coverage_run_dashm_equal_to_doubledashsource(self): """regression test for #328 @@ -995,3 +1016,9 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest): def test_script_pkg_sub(self): self.assert_pth_and_source_work_together('', 'pkg', 'sub') + + +def remove_matching_lines(text, pat): + """Return `text` with all lines matching `pat` removed.""" + lines = [l for l in text.splitlines(True) if not re.search(pat, l)] + return "".join(lines) diff --git a/tests/with_main/__main__.py b/tests/with_main/__main__.py deleted file mode 100644 index e7a4e4f9..00000000 --- a/tests/with_main/__main__.py +++ /dev/null @@ -1,2 +0,0 @@ -x = 1 -print(x)
\ No newline at end of file diff --git a/tests/with_main/without/__init__.py b/tests/with_main/without/__init__.py deleted file mode 100644 index 595e3818..00000000 --- a/tests/with_main/without/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'traff' |