eric6/DebugClients/Python/coverage/results.py

changeset 7427
362cd1b6f81a
parent 6942
2602857055c5
equal deleted inserted replaced
7426:dc171b1d8261 7427:362cd1b6f81a
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt 2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3 3
4 """Results of coverage measurement.""" 4 """Results of coverage measurement."""
5 5
6 import collections 6 import collections
7 7
8 from coverage.backward import iitems 8 from coverage.backward import iitems
9 from coverage.misc import contract, format_lines, SimpleRepr 9 from coverage.debug import SimpleReprMixin
10 from coverage.misc import contract, CoverageException, nice_pair
10 11
11 12
12 class Analysis(object): 13 class Analysis(object):
13 """The results of analyzing a FileReporter.""" 14 """The results of analyzing a FileReporter."""
14 15
15 def __init__(self, data, file_reporter): 16 def __init__(self, data, file_reporter, file_mapper):
16 self.data = data 17 self.data = data
17 self.file_reporter = file_reporter 18 self.file_reporter = file_reporter
18 self.filename = self.file_reporter.filename 19 self.filename = file_mapper(self.file_reporter.filename)
19 self.statements = self.file_reporter.lines() 20 self.statements = self.file_reporter.lines()
20 self.excluded = self.file_reporter.excluded_lines() 21 self.excluded = self.file_reporter.excluded_lines()
21 22
22 # Identify missing statements. 23 # Identify missing statements.
23 executed = self.data.lines(self.filename) or [] 24 executed = self.data.lines(self.filename) or []
24 executed = self.file_reporter.translate_lines(executed) 25 executed = self.file_reporter.translate_lines(executed)
25 self.missing = self.statements - executed 26 self.executed = executed
27 self.missing = self.statements - self.executed
26 28
27 if self.data.has_arcs(): 29 if self.data.has_arcs():
28 self._arc_possibilities = sorted(self.file_reporter.arcs()) 30 self._arc_possibilities = sorted(self.file_reporter.arcs())
29 self.exit_counts = self.file_reporter.exit_counts() 31 self.exit_counts = self.file_reporter.exit_counts()
30 self.no_branch = self.file_reporter.no_branch_lines() 32 self.no_branch = self.file_reporter.no_branch_lines()
31 n_branches = self.total_branches() 33 n_branches = self._total_branches()
32 mba = self.missing_branch_arcs() 34 mba = self.missing_branch_arcs()
33 n_partial_branches = sum(len(v) for k,v in iitems(mba) if k not in self.missing) 35 n_partial_branches = sum(len(v) for k,v in iitems(mba) if k not in self.missing)
34 n_missing_branches = sum(len(v) for k,v in iitems(mba)) 36 n_missing_branches = sum(len(v) for k,v in iitems(mba))
35 else: 37 else:
36 self._arc_possibilities = [] 38 self._arc_possibilities = []
46 n_branches=n_branches, 48 n_branches=n_branches,
47 n_partial_branches=n_partial_branches, 49 n_partial_branches=n_partial_branches,
48 n_missing_branches=n_missing_branches, 50 n_missing_branches=n_missing_branches,
49 ) 51 )
50 52
51 def missing_formatted(self): 53 def missing_formatted(self, branches=False):
52 """The missing line numbers, formatted nicely. 54 """The missing line numbers, formatted nicely.
53 55
54 Returns a string like "1-2, 5-11, 13-14". 56 Returns a string like "1-2, 5-11, 13-14".
55 57
58 If `branches` is true, includes the missing branch arcs also.
59
56 """ 60 """
57 return format_lines(self.statements, self.missing) 61 if branches and self.has_arcs():
62 arcs = iitems(self.missing_branch_arcs())
63 else:
64 arcs = None
65
66 return format_lines(self.statements, self.missing, arcs=arcs)
58 67
59 def has_arcs(self): 68 def has_arcs(self):
60 """Were arcs measured in this result?""" 69 """Were arcs measured in this result?"""
61 return self.data.has_arcs() 70 return self.data.has_arcs()
62 71
72 @contract(returns='list(tuple(int, int))')
63 def arc_possibilities(self): 73 def arc_possibilities(self):
64 """Returns a sorted list of the arcs in the code.""" 74 """Returns a sorted list of the arcs in the code."""
65 return self._arc_possibilities 75 return self._arc_possibilities
66 76
77 @contract(returns='list(tuple(int, int))')
67 def arcs_executed(self): 78 def arcs_executed(self):
68 """Returns a sorted list of the arcs actually executed in the code.""" 79 """Returns a sorted list of the arcs actually executed in the code."""
69 executed = self.data.arcs(self.filename) or [] 80 executed = self.data.arcs(self.filename) or []
70 executed = self.file_reporter.translate_arcs(executed) 81 executed = self.file_reporter.translate_arcs(executed)
71 return sorted(executed) 82 return sorted(executed)
72 83
84 @contract(returns='list(tuple(int, int))')
73 def arcs_missing(self): 85 def arcs_missing(self):
74 """Returns a sorted list of the arcs in the code not executed.""" 86 """Returns a sorted list of the arcs in the code not executed."""
75 possible = self.arc_possibilities() 87 possible = self.arc_possibilities()
76 executed = self.arcs_executed() 88 executed = self.arcs_executed()
77 missing = ( 89 missing = (
79 if p not in executed 91 if p not in executed
80 and p[0] not in self.no_branch 92 and p[0] not in self.no_branch
81 ) 93 )
82 return sorted(missing) 94 return sorted(missing)
83 95
84 def arcs_missing_formatted(self): 96 @contract(returns='list(tuple(int, int))')
85 """The missing branch arcs, formatted nicely.
86
87 Returns a string like "1->2, 1->3, 16->20". Omits any mention of
88 branches from missing lines, so if line 17 is missing, then 17->18
89 won't be included.
90
91 """
92 arcs = self.missing_branch_arcs()
93 missing = self.missing
94 line_exits = sorted(iitems(arcs))
95 pairs = []
96 for line, exits in line_exits:
97 for ex in sorted(exits):
98 if line not in missing:
99 pairs.append("%d->%s" % (line, (ex if ex > 0 else "exit")))
100 return ', '.join(pairs)
101
102 def arcs_unpredicted(self): 97 def arcs_unpredicted(self):
103 """Returns a sorted list of the executed arcs missing from the code.""" 98 """Returns a sorted list of the executed arcs missing from the code."""
104 possible = self.arc_possibilities() 99 possible = self.arc_possibilities()
105 executed = self.arcs_executed() 100 executed = self.arcs_executed()
106 # Exclude arcs here which connect a line to itself. They can occur 101 # Exclude arcs here which connect a line to itself. They can occur
114 and e[0] != e[1] 109 and e[0] != e[1]
115 and (e[0] > 0 or e[1] > 0) 110 and (e[0] > 0 or e[1] > 0)
116 ) 111 )
117 return sorted(unpredicted) 112 return sorted(unpredicted)
118 113
119 def branch_lines(self): 114 def _branch_lines(self):
120 """Returns a list of line numbers that have more than one exit.""" 115 """Returns a list of line numbers that have more than one exit."""
121 return [l1 for l1,count in iitems(self.exit_counts) if count > 1] 116 return [l1 for l1,count in iitems(self.exit_counts) if count > 1]
122 117
123 def total_branches(self): 118 def _total_branches(self):
124 """How many total branches are there?""" 119 """How many total branches are there?"""
125 return sum(count for count in self.exit_counts.values() if count > 1) 120 return sum(count for count in self.exit_counts.values() if count > 1)
126 121
122 @contract(returns='dict(int: list(int))')
127 def missing_branch_arcs(self): 123 def missing_branch_arcs(self):
128 """Return arcs that weren't executed from branch lines. 124 """Return arcs that weren't executed from branch lines.
129 125
130 Returns {l1:[l2a,l2b,...], ...} 126 Returns {l1:[l2a,l2b,...], ...}
131 127
132 """ 128 """
133 missing = self.arcs_missing() 129 missing = self.arcs_missing()
134 branch_lines = set(self.branch_lines()) 130 branch_lines = set(self._branch_lines())
135 mba = collections.defaultdict(list) 131 mba = collections.defaultdict(list)
136 for l1, l2 in missing: 132 for l1, l2 in missing:
137 if l1 in branch_lines: 133 if l1 in branch_lines:
138 mba[l1].append(l2) 134 mba[l1].append(l2)
139 return mba 135 return mba
140 136
137 @contract(returns='dict(int: tuple(int, int))')
141 def branch_stats(self): 138 def branch_stats(self):
142 """Get stats about branches. 139 """Get stats about branches.
143 140
144 Returns a dict mapping line numbers to a tuple: 141 Returns a dict mapping line numbers to a tuple:
145 (total_exits, taken_exits). 142 (total_exits, taken_exits).
146 """ 143 """
147 144
148 missing_arcs = self.missing_branch_arcs() 145 missing_arcs = self.missing_branch_arcs()
149 stats = {} 146 stats = {}
150 for lnum in self.branch_lines(): 147 for lnum in self._branch_lines():
151 exits = self.exit_counts[lnum] 148 exits = self.exit_counts[lnum]
152 try: 149 try:
153 missing = len(missing_arcs[lnum]) 150 missing = len(missing_arcs[lnum])
154 except KeyError: 151 except KeyError:
155 missing = 0 152 missing = 0
156 stats[lnum] = (exits, exits - missing) 153 stats[lnum] = (exits, exits - missing)
157 return stats 154 return stats
158 155
159 156
160 class Numbers(SimpleRepr): 157 class Numbers(SimpleReprMixin):
161 """The numerical results of measuring coverage. 158 """The numerical results of measuring coverage.
162 159
163 This holds the basic statistics from `Analysis`, and is used to roll 160 This holds the basic statistics from `Analysis`, and is used to roll
164 up statistics across files. 161 up statistics across files.
165 162
269 if other == 0: 266 if other == 0:
270 return self 267 return self
271 return NotImplemented 268 return NotImplemented
272 269
273 270
271 def _line_ranges(statements, lines):
272 """Produce a list of ranges for `format_lines`."""
273 statements = sorted(statements)
274 lines = sorted(lines)
275
276 pairs = []
277 start = None
278 lidx = 0
279 for stmt in statements:
280 if lidx >= len(lines):
281 break
282 if stmt == lines[lidx]:
283 lidx += 1
284 if not start:
285 start = stmt
286 end = stmt
287 elif start:
288 pairs.append((start, end))
289 start = None
290 if start:
291 pairs.append((start, end))
292 return pairs
293
294
295 def format_lines(statements, lines, arcs=None):
296 """Nicely format a list of line numbers.
297
298 Format a list of line numbers for printing by coalescing groups of lines as
299 long as the lines represent consecutive statements. This will coalesce
300 even if there are gaps between statements.
301
302 For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
303 `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
304
305 Both `lines` and `statements` can be any iterable. All of the elements of
306 `lines` must be in `statements`, and all of the values must be positive
307 integers.
308
309 If `arcs` is provided, they are (start,[end,end,end]) pairs that will be
310 included in the output as long as start isn't in `lines`.
311
312 """
313 line_items = [(pair[0], nice_pair(pair)) for pair in _line_ranges(statements, lines)]
314 if arcs:
315 line_exits = sorted(arcs)
316 for line, exits in line_exits:
317 for ex in sorted(exits):
318 if line not in lines:
319 dest = (ex if ex > 0 else "exit")
320 line_items.append((line, "%d->%s" % (line, dest)))
321
322 ret = ', '.join(t[-1] for t in sorted(line_items))
323 return ret
324
325
274 @contract(total='number', fail_under='number', precision=int, returns=bool) 326 @contract(total='number', fail_under='number', precision=int, returns=bool)
275 def should_fail_under(total, fail_under, precision): 327 def should_fail_under(total, fail_under, precision):
276 """Determine if a total should fail due to fail-under. 328 """Determine if a total should fail due to fail-under.
277 329
278 `total` is a float, the coverage measurement total. `fail_under` is the 330 `total` is a float, the coverage measurement total. `fail_under` is the
280 to consider after the decimal point. 332 to consider after the decimal point.
281 333
282 Returns True if the total should fail. 334 Returns True if the total should fail.
283 335
284 """ 336 """
337 # We can never achieve higher than 100% coverage, or less than zero.
338 if not (0 <= fail_under <= 100.0):
339 msg = "fail_under={} is invalid. Must be between 0 and 100.".format(fail_under)
340 raise CoverageException(msg)
341
285 # Special case for fail_under=100, it must really be 100. 342 # Special case for fail_under=100, it must really be 100.
286 if fail_under == 100.0 and total != 100.0: 343 if fail_under == 100.0 and total != 100.0:
287 return True 344 return True
288 345
289 return round(total, precision) < fail_under 346 return round(total, precision) < fail_under

eric ide

mercurial