eric6/DebugClients/Python/coverage/results.py

changeset 7427
362cd1b6f81a
parent 6942
2602857055c5
diff -r dc171b1d8261 -r 362cd1b6f81a eric6/DebugClients/Python/coverage/results.py
--- a/eric6/DebugClients/Python/coverage/results.py	Wed Feb 19 19:38:36 2020 +0100
+++ b/eric6/DebugClients/Python/coverage/results.py	Sat Feb 22 14:27:42 2020 +0100
@@ -1,34 +1,36 @@
 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
-# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 
 """Results of coverage measurement."""
 
 import collections
 
 from coverage.backward import iitems
-from coverage.misc import contract, format_lines, SimpleRepr
+from coverage.debug import SimpleReprMixin
+from coverage.misc import contract, CoverageException, nice_pair
 
 
 class Analysis(object):
     """The results of analyzing a FileReporter."""
 
-    def __init__(self, data, file_reporter):
+    def __init__(self, data, file_reporter, file_mapper):
         self.data = data
         self.file_reporter = file_reporter
-        self.filename = self.file_reporter.filename
+        self.filename = file_mapper(self.file_reporter.filename)
         self.statements = self.file_reporter.lines()
         self.excluded = self.file_reporter.excluded_lines()
 
         # Identify missing statements.
         executed = self.data.lines(self.filename) or []
         executed = self.file_reporter.translate_lines(executed)
-        self.missing = self.statements - executed
+        self.executed = executed
+        self.missing = self.statements - self.executed
 
         if self.data.has_arcs():
             self._arc_possibilities = sorted(self.file_reporter.arcs())
             self.exit_counts = self.file_reporter.exit_counts()
             self.no_branch = self.file_reporter.no_branch_lines()
-            n_branches = self.total_branches()
+            n_branches = self._total_branches()
             mba = self.missing_branch_arcs()
             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))
@@ -48,28 +50,38 @@
             n_missing_branches=n_missing_branches,
         )
 
-    def missing_formatted(self):
+    def missing_formatted(self, branches=False):
         """The missing line numbers, formatted nicely.
 
         Returns a string like "1-2, 5-11, 13-14".
 
+        If `branches` is true, includes the missing branch arcs also.
+
         """
-        return format_lines(self.statements, self.missing)
+        if branches and self.has_arcs():
+            arcs = iitems(self.missing_branch_arcs())
+        else:
+            arcs = None
+
+        return format_lines(self.statements, self.missing, arcs=arcs)
 
     def has_arcs(self):
         """Were arcs measured in this result?"""
         return self.data.has_arcs()
 
+    @contract(returns='list(tuple(int, int))')
     def arc_possibilities(self):
         """Returns a sorted list of the arcs in the code."""
         return self._arc_possibilities
 
+    @contract(returns='list(tuple(int, int))')
     def arcs_executed(self):
         """Returns a sorted list of the arcs actually executed in the code."""
         executed = self.data.arcs(self.filename) or []
         executed = self.file_reporter.translate_arcs(executed)
         return sorted(executed)
 
+    @contract(returns='list(tuple(int, int))')
     def arcs_missing(self):
         """Returns a sorted list of the arcs in the code not executed."""
         possible = self.arc_possibilities()
@@ -81,24 +93,7 @@
         )
         return sorted(missing)
 
-    def arcs_missing_formatted(self):
-        """The missing branch arcs, formatted nicely.
-
-        Returns a string like "1->2, 1->3, 16->20". Omits any mention of
-        branches from missing lines, so if line 17 is missing, then 17->18
-        won't be included.
-
-        """
-        arcs = self.missing_branch_arcs()
-        missing = self.missing
-        line_exits = sorted(iitems(arcs))
-        pairs = []
-        for line, exits in line_exits:
-            for ex in sorted(exits):
-                if line not in missing:
-                    pairs.append("%d->%s" % (line, (ex if ex > 0 else "exit")))
-        return ', '.join(pairs)
-
+    @contract(returns='list(tuple(int, int))')
     def arcs_unpredicted(self):
         """Returns a sorted list of the executed arcs missing from the code."""
         possible = self.arc_possibilities()
@@ -116,14 +111,15 @@
         )
         return sorted(unpredicted)
 
-    def branch_lines(self):
+    def _branch_lines(self):
         """Returns a list of line numbers that have more than one exit."""
         return [l1 for l1,count in iitems(self.exit_counts) if count > 1]
 
-    def total_branches(self):
+    def _total_branches(self):
         """How many total branches are there?"""
         return sum(count for count in self.exit_counts.values() if count > 1)
 
+    @contract(returns='dict(int: list(int))')
     def missing_branch_arcs(self):
         """Return arcs that weren't executed from branch lines.
 
@@ -131,13 +127,14 @@
 
         """
         missing = self.arcs_missing()
-        branch_lines = set(self.branch_lines())
+        branch_lines = set(self._branch_lines())
         mba = collections.defaultdict(list)
         for l1, l2 in missing:
             if l1 in branch_lines:
                 mba[l1].append(l2)
         return mba
 
+    @contract(returns='dict(int: tuple(int, int))')
     def branch_stats(self):
         """Get stats about branches.
 
@@ -147,7 +144,7 @@
 
         missing_arcs = self.missing_branch_arcs()
         stats = {}
-        for lnum in self.branch_lines():
+        for lnum in self._branch_lines():
             exits = self.exit_counts[lnum]
             try:
                 missing = len(missing_arcs[lnum])
@@ -157,7 +154,7 @@
         return stats
 
 
-class Numbers(SimpleRepr):
+class Numbers(SimpleReprMixin):
     """The numerical results of measuring coverage.
 
     This holds the basic statistics from `Analysis`, and is used to roll
@@ -271,6 +268,61 @@
         return NotImplemented
 
 
+def _line_ranges(statements, lines):
+    """Produce a list of ranges for `format_lines`."""
+    statements = sorted(statements)
+    lines = sorted(lines)
+
+    pairs = []
+    start = None
+    lidx = 0
+    for stmt in statements:
+        if lidx >= len(lines):
+            break
+        if stmt == lines[lidx]:
+            lidx += 1
+            if not start:
+                start = stmt
+            end = stmt
+        elif start:
+            pairs.append((start, end))
+            start = None
+    if start:
+        pairs.append((start, end))
+    return pairs
+
+
+def format_lines(statements, lines, arcs=None):
+    """Nicely format a list of line numbers.
+
+    Format a list of line numbers for printing by coalescing groups of lines as
+    long as the lines represent consecutive statements.  This will coalesce
+    even if there are gaps between statements.
+
+    For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
+    `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
+
+    Both `lines` and `statements` can be any iterable. All of the elements of
+    `lines` must be in `statements`, and all of the values must be positive
+    integers.
+
+    If `arcs` is provided, they are (start,[end,end,end]) pairs that will be
+    included in the output as long as start isn't in `lines`.
+
+    """
+    line_items = [(pair[0], nice_pair(pair)) for pair in _line_ranges(statements, lines)]
+    if arcs:
+        line_exits = sorted(arcs)
+        for line, exits in line_exits:
+            for ex in sorted(exits):
+                if line not in lines:
+                    dest = (ex if ex > 0 else "exit")
+                    line_items.append((line, "%d->%s" % (line, dest)))
+
+    ret = ', '.join(t[-1] for t in sorted(line_items))
+    return ret
+
+
 @contract(total='number', fail_under='number', precision=int, returns=bool)
 def should_fail_under(total, fail_under, precision):
     """Determine if a total should fail due to fail-under.
@@ -282,6 +334,11 @@
     Returns True if the total should fail.
 
     """
+    # We can never achieve higher than 100% coverage, or less than zero.
+    if not (0 <= fail_under <= 100.0):
+        msg = "fail_under={} is invalid. Must be between 0 and 100.".format(fail_under)
+        raise CoverageException(msg)
+
     # Special case for fail_under=100, it must really be 100.
     if fail_under == 100.0 and total != 100.0:
         return True

eric ide

mercurial