diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2015-01-18 11:54:03 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2015-01-18 11:54:03 -0500 |
commit | 0d0836309b6354a07fb09cd8d28b309b6c8dba6c (patch) | |
tree | 53e7f22f8c83f09a6b00a54c9e0bb862d73dc68f | |
parent | b90a9db23aa99e58e20f48e53218fdb7fbd3c965 (diff) | |
download | python-coveragepy-git-0d0836309b6354a07fb09cd8d28b309b6c8dba6c.tar.gz |
Properly deal with .pyc files missing their sources.
-rw-r--r-- | coverage/control.py | 34 | ||||
-rw-r--r-- | tests/test_summary.py | 57 |
2 files changed, 81 insertions, 10 deletions
diff --git a/coverage/control.py b/coverage/control.py index 1044fad0..16a92971 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -307,19 +307,35 @@ class Coverage(object): return os.path.split(morf_filename)[0] def _source_for_file(self, filename): - """Return the source file for `filename`.""" + """Return the source file for `filename`. + + Given a filename being traced, return the best guess as to the source + file to attribute it to. + + """ if filename.endswith(".py"): + # .py files are themselves source files. return filename + elif filename.endswith((".pyc", ".pyo")): - try_filename = filename[:-1] - if os.path.exists(try_filename): - return try_filename + # Bytecode files probably have source files near them. + py_filename = filename[:-1] + if os.path.exists(py_filename): + # Found a .py file, use that. + return py_filename if sys.platform == "win32": - try_filename += "w" - if os.path.exists(try_filename): - return try_filename - elif filename.endswith("$py.class"): # Jython - filename = filename[:-9] + ".py" + # On Windows, it could be a .pyw file. + pyw_filename = py_filename + "w" + if os.path.exists(pyw_filename): + return pyw_filename + # Didn't find source, but it's probably the .py file we want. + return py_filename + + elif filename.endswith("$py.class"): + # Jython is easy to guess. + return filename[:-9] + ".py" + + # No idea, just use the filename as-is. return filename def _name_for_module(self, module_globals, filename): diff --git a/tests/test_summary.py b/tests/test_summary.py index fa5b4461..77381186 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -1,6 +1,12 @@ """Test text-based summary reporting for coverage.py""" -import os, re, sys +import glob +import os +import py_compile +import re +import sys + +from nose.plugins.skip import SkipTest import coverage from coverage.backward import StringIO @@ -438,6 +444,55 @@ class SummaryTest(CoverageTest): self.assertIn("start.pyw 2 0 100%", report) self.assertIn("mod.pyw 1 0 100%", report) + def test_tracing_pyc_file(self): + # Create two Python files. + self.make_file("mod.py", "a = 1\n") + self.make_file("main.py", "import mod\n") + + # Make one into a .pyc. + py_compile.compile("mod.py") + + # Run the program. + cov = coverage.coverage() + cov.start() + import main # pragma: nested # pylint: disable=import-error,unused-variable + cov.stop() # pragma: nested + + report = self.get_report(cov).splitlines() + self.assertIn("mod.py 1 0 100%", report) + + def test_missing_py_file_during_run(self): + # PyPy2 doesn't run bare .pyc files. + if '__pypy__' in sys.builtin_module_names and sys.version_info < (3,): + raise SkipTest("PyPy2 doesn't run bare .pyc files") + + # Create two Python files. + self.make_file("mod.py", "a = 1\n") + self.make_file("main.py", "import mod\n") + + # Make one into a .pyc, and remove the .py. + py_compile.compile("mod.py") + os.remove("mod.py") + + # Python 3 puts the .pyc files in a __pycache__ directory, and will + # not import from there without source. It will import a .pyc from + # the source location though. + if not os.path.exists("mod.pyc"): + pycs = glob.glob("__pycache__/mod.*.pyc") + self.assertEqual(len(pycs), 1) + os.rename(pycs[0], "mod.pyc") + + # Run the program. + cov = coverage.coverage() + cov.start() + import main # pragma: nested # pylint: disable=import-error,unused-variable + cov.stop() # pragma: nested + + # Put back the missing Python file. + self.make_file("mod.py", "a = 1\n") + report = self.get_report(cov).splitlines() + self.assertIn("mod.py 1 0 100%", report) + class SummaryTest2(CoverageTest): """Another bunch of summary tests.""" |