summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt4
-rw-r--r--coverage/parser.py5
-rw-r--r--test/modules/pkg1/__init__.py3
-rw-r--r--test/modules/pkg2/__init__.py2
-rw-r--r--test/test_summary.py43
5 files changed, 55 insertions, 2 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 69f005b6..e114dbd6 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -39,6 +39,9 @@ Version 3.4a1
it needs to be. A cache of tracing decisions was broken, but has now been
fixed.
+- Python files with no statements, for example, empty ``__init__.py`` files,
+ are now reported as having zero statements instead of one. Fixes `issue 1`_.
+
- Reports now have a column of missed line counts rather than executed line
counts, since developers should focus on reducing the missed lines to zero,
rather than increasing the executed lines to varying targets. Once
@@ -70,6 +73,7 @@ Version 3.4a1
- Asking for an HTML report with no files now shows a nice error message rather
than a cryptic failure ('int' object is unsubscriptable). Fixes `issue 59`_.
+.. _issue 1: http://bitbucket.org/ned/coveragepy/issue/1/empty-__init__py-files-are-reported-as-1-executable
.. _issue 34: http://bitbucket.org/ned/coveragepy/issue/34/enhanced-omit-globbing-handling
.. _issue 36: http://bitbucket.org/ned/coveragepy/issue/36/provide-regex-style-omit
.. _issue 46: http://bitbucket.org/ned/coveragepy/issue/46
diff --git a/coverage/parser.py b/coverage/parser.py
index d883df46..8285d433 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -85,6 +85,7 @@ class CodeParser(object):
excluding = False
prev_toktype = token.INDENT
first_line = None
+ empty = True
tokgen = tokenize.generate_tokens(StringIO(self.text).readline)
for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen:
@@ -128,6 +129,7 @@ class CodeParser(object):
if ttext.strip() and toktype != tokenize.COMMENT:
# A non-whitespace token.
+ empty = False
if first_line is None:
# The token is not whitespace, and is the first in a
# statement.
@@ -141,7 +143,8 @@ class CodeParser(object):
prev_toktype = toktype
# Find the starts of the executable statements.
- self.statement_starts.update(self.byte_parser._find_statements())
+ if not empty:
+ self.statement_starts.update(self.byte_parser._find_statements())
def first_line(self, line):
"""Return the first line number of the statement including `line`."""
diff --git a/test/modules/pkg1/__init__.py b/test/modules/pkg1/__init__.py
index e69de29b..2dfeb9c1 100644
--- a/test/modules/pkg1/__init__.py
+++ b/test/modules/pkg1/__init__.py
@@ -0,0 +1,3 @@
+# This __init__.py has a module-level docstring, which is counted as a
+# statement.
+"""A simple package for testing with."""
diff --git a/test/modules/pkg2/__init__.py b/test/modules/pkg2/__init__.py
index e69de29b..090efbf5 100644
--- a/test/modules/pkg2/__init__.py
+++ b/test/modules/pkg2/__init__.py
@@ -0,0 +1,2 @@
+# This is an __init__.py file, with no executable statements in it.
+# This comment shouldn't confuse the parser.
diff --git a/test/test_summary.py b/test/test_summary.py
index d797cc50..c6598c6d 100644
--- a/test/test_summary.py
+++ b/test/test_summary.py
@@ -1,6 +1,9 @@
"""Test text-based summary reporting for coverage.py"""
-import os, re, sys
+import os, re, sys, textwrap
+
+import coverage
+from coverage.backward import StringIO
sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
from coveragetest import CoverageTest
@@ -16,6 +19,8 @@ class SummaryTest(CoverageTest):
a = 1
print ('done')
""")
+ # Parent class saves and restores sys.path, we can just modify it.
+ sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules'))
def report_from_command(self, cmd):
"""Return the report from the `cmd`, with some convenience added."""
@@ -105,3 +110,39 @@ class SummaryTest(CoverageTest):
self.assertTrue("mybranch " in report)
self.assertEqual(self.last_line_squeezed(report),
"mybranch 5 0 2 1 85%")
+
+class SummaryTest2(CoverageTest):
+ """Another bunch of summary tests."""
+ # This class exists because tests naturally clump into classes based on the
+ # needs of their setUp and tearDown, rather than the product features they
+ # are testing. There's probably a better way to organize these.
+
+ run_in_temp_dir = False
+
+ def setUp(self):
+ super(SummaryTest2, self).setUp()
+ # Parent class saves and restores sys.path, we can just modify it.
+ sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules'))
+
+ def test_empty_files(self):
+ cov = coverage.coverage()
+ cov.start()
+ import usepkgs # pylint: disable-msg=F0401,W0612
+ cov.stop()
+
+ repout = StringIO()
+ cov.report(file=repout, show_missing=False)
+
+ self.assertMultiLineEqual(repout.getvalue(), textwrap.dedent("""\
+ Name Stmts Miss Cover
+ ------------------------------------------------
+ test\modules\pkg1\__init__ 1 0 100%
+ test\modules\pkg1\p1a 3 0 100%
+ test\modules\pkg1\p1b 3 0 100%
+ test\modules\pkg2\__init__ 0 0 100%
+ test\modules\pkg2\p2a 3 0 100%
+ test\modules\pkg2\p2b 3 0 100%
+ test\modules\usepkgs 2 0 100%
+ ------------------------------------------------
+ TOTAL 15 0 100%
+ """))