Commits

Ned Batchelder committed cbe93a0

Branch coverage is computed more accurately, #156.

  • Participants
  • Parent commits 80cf39d

Comments (0)

Files changed (8)

 - Embarrassingly, the `[xml] output=' setting in the .coveragerc file simply
   didn't work.  Now it does.
 
+- Coverage percentage metrics are computed slightly differently under branch
+  coverage.  This means that completely unexecuted files will now correctly
+  have 0% coverage, fixing `issue 156`_.  This also means that your total
+  coverage numbers will be lower in general if you are using branch coverage.
+
 - When installing, now in addition to creating a "coverage" command, two new
   aliases are also installed.  A "coverage2" or "coverage3" command will be
   created, depending on whether you are installing in Python 2.x or 3.x.
 .. _issue 139: https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests
 .. _issue 143: https://bitbucket.org/ned/coveragepy/issue/143/omit-doesnt-seem-to-work-in-coverage
 .. _issue 153: https://bitbucket.org/ned/coveragepy/issue/153/non-existent-filename-triggers
+.. _issue 156: https://bitbucket.org/ned/coveragepy/issue/156/a-completely-unexecuted-file-shows-14
 .. _issue 163: https://bitbucket.org/ned/coveragepy/issue/163/problem-with-include-and-omit-filename
 .. _issue 171: https://bitbucket.org/ned/coveragepy/issue/171/how-to-contribute-and-run-tests
 .. _issue 193: https://bitbucket.org/ned/coveragepy/issue/193/unicodedecodeerror-on-htmlpy

File coverage/html.py

         nums = analysis.numbers
 
         missing_branch_arcs = analysis.missing_branch_arcs()
-        n_par = 0   # accumulated below.
         arcs = self.arcs
 
         # These classes determine which lines are highlighted by default.
                 line_class.append(c_mis)
             elif self.arcs and lineno in missing_branch_arcs:
                 line_class.append(c_par)
-                n_par += 1
                 annlines = []
                 for b in missing_branch_arcs[lineno]:
                     if b < 0:
         # Save this file's information for the index file.
         index_info = {
             'nums': nums,
-            'par': n_par,
             'html_filename': html_filename,
             'name': cu.name,
             }

File coverage/htmlfiles/index.html

                 <td>{{totals.n_excluded}}</td>
                 {% if arcs %}
                 <td>{{totals.n_branches}}</td>
-                <td>{{totals.n_missing_branches}}</td>
+                <td>{{totals.n_partial_branches}}</td>
                 {% endif %}
                 <td class='right'>{{totals.pc_covered_str}}%</td>
             </tr>
                 <td>{{file.nums.n_excluded}}</td>
                 {% if arcs %}
                 <td>{{file.nums.n_branches}}</td>
-                <td>{{file.nums.n_missing_branches}}</td>
+                <td>{{file.nums.n_partial_branches}}</td>
                 {% endif %}
                 <td class='right'>{{file.nums.pc_covered_str}}%</td>
             </tr>

File coverage/htmlfiles/pyfile.html

             <span class='{{c_mis}} shortkey_m button_toggle_mis'>{{nums.n_missing}} missing</span>
             <span class='{{c_exc}} shortkey_x button_toggle_exc'>{{nums.n_excluded}} excluded</span>
             {% if arcs %}
-                <span class='{{c_par}} shortkey_p button_toggle_par'>{{n_par}} partial</span>
+                <span class='{{c_par}} shortkey_p button_toggle_par'>{{nums.n_partial_branches}} partial</span>
             {% endif %}
         </h2>
     </div>

File coverage/results.py

                 )
             n_branches = self.total_branches()
             mba = self.missing_branch_arcs()
-            n_missing_branches = sum(
+            n_partial_branches = sum(
                 [len(v) for k,v in iitems(mba) if k not in self.missing]
                 )
+            n_missing_branches = sum([len(v) for k,v in iitems(mba)])
         else:
-            n_branches = n_missing_branches = 0
+            n_branches = n_partial_branches = n_missing_branches = 0
             self.no_branch = set()
 
         self.numbers = Numbers(
             n_excluded=len(self.excluded),
             n_missing=len(self.missing),
             n_branches=n_branches,
+            n_partial_branches=n_partial_branches,
             n_missing_branches=n_missing_branches,
             )
 
     _near100 = 99.0
 
     def __init__(self, n_files=0, n_statements=0, n_excluded=0, n_missing=0,
-                    n_branches=0, n_missing_branches=0
+                    n_branches=0, n_partial_branches=0, n_missing_branches=0
                     ):
         self.n_files = n_files
         self.n_statements = n_statements
         self.n_excluded = n_excluded
         self.n_missing = n_missing
         self.n_branches = n_branches
+        self.n_partial_branches = n_partial_branches
         self.n_missing_branches = n_missing_branches
 
     def set_precision(cls, precision):
         nums.n_excluded = self.n_excluded + other.n_excluded
         nums.n_missing = self.n_missing + other.n_missing
         nums.n_branches = self.n_branches + other.n_branches
-        nums.n_missing_branches = (self.n_missing_branches +
-                                                    other.n_missing_branches)
+        nums.n_partial_branches = (
+            self.n_partial_branches + other.n_partial_branches
+            )
+        nums.n_missing_branches = (
+            self.n_missing_branches + other.n_missing_branches
+            )
         return nums
 
     def __radd__(self, other):

File coverage/summary.py

         header = (fmt_name % "Name") + " Stmts   Miss"
         fmt_coverage = fmt_name + "%6d %6d"
         if self.branches:
-            header += " Branch BrPart"
+            header += " Branch BrMiss"
             fmt_coverage += " %6d %6d"
         width100 = Numbers.pc_str_width()
         header += "%*s" % (width100+4, "Cover")

File test/test_api.py

         self.assertEqual(nums.n_excluded, 1)
         self.assertEqual(nums.n_missing, 3)
         self.assertEqual(nums.n_branches, 2)
-        self.assertEqual(nums.n_missing_branches, 0)
+        self.assertEqual(nums.n_partial_branches, 0)
+        self.assertEqual(nums.n_missing_branches, 2)

File test/test_summary.py

         self.assertEqual(out, 'x\n')
         report = self.report_from_command("coverage report")
 
-        # Name       Stmts   Miss Branch BrPart  Cover
+        # Name       Stmts   Miss Branch BrMiss  Cover
         # --------------------------------------------
         # mybranch       5      0      2      1    85%
 
 
         self.assertEqual(self.line_count(report), 2)
 
+    def get_report(self, cov):
+        """Get the report from `cov`, and canonicalize it."""
+        repout = StringIO()
+        cov.report(file=repout, show_missing=False)
+        report = repout.getvalue().replace('\\', '/')
+        report = re.sub(r" +", " ", report)
+        return report
+
+    def test_bug_156_file_not_run_should_be_zero(self):
+        # https://bitbucket.org/ned/coveragepy/issue/156
+        self.make_file("mybranch.py", """\
+            def branch(x):
+                if x:
+                    print("x")
+                return x
+            branch(1)
+            """)
+        self.make_file("main.py", """\
+            print("y")
+            """)
+        cov = coverage.coverage(branch=True, source=["."])
+        cov.start()
+        import main
+        cov.stop()
+        report = self.get_report(cov).splitlines()
+        self.assertIn("mybranch 5 5 2 2 0%", report)
+
     def run_TheCode_and_report_it(self):
         """A helper for the next few tests."""
         cov = coverage.coverage()
         cov.start()
         import TheCode                          # pylint: disable=F0401,W0612
         cov.stop()
-
-        repout = StringIO()
-        cov.report(file=repout, show_missing=False)
-        report = repout.getvalue().replace('\\', '/')
-        report = re.sub(r"\s+", " ", report)
-        return report
+        return self.get_report(cov)
 
     def test_bug_203_mixed_case_listed_twice_with_rc(self):
         self.make_file("TheCode.py", "a = 1\n")