summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/control.py34
-rw-r--r--tests/test_summary.py57
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."""