DebugClients/Python3/coverage/results.py

changeset 29
391dc0bc4ae5
child 3495
fac17a82b431
equal deleted inserted replaced
28:dde24fc7f7ba 29:391dc0bc4ae5
1 """Results of coverage measurement."""
2
3 import os
4
5 from .backward import set, sorted # pylint: disable-msg=W0622
6 from .misc import format_lines, NoSource
7 from .parser import CodeParser
8
9
10 class Analysis(object):
11 """The results of analyzing a code unit."""
12
13 def __init__(self, cov, code_unit):
14 self.coverage = cov
15 self.code_unit = code_unit
16
17 self.filename = self.code_unit.filename
18 ext = os.path.splitext(self.filename)[1]
19 source = None
20 if ext == '.py':
21 if not os.path.exists(self.filename):
22 source = self.coverage.file_locator.get_zip_data(self.filename)
23 if not source:
24 raise NoSource("No source for code: %r" % self.filename)
25
26 self.parser = CodeParser(
27 text=source, filename=self.filename,
28 exclude=self.coverage.exclude_re
29 )
30 self.statements, self.excluded = self.parser.parse_source()
31
32 # Identify missing statements.
33 executed = self.coverage.data.executed_lines(self.filename)
34 exec1 = self.parser.first_lines(executed)
35 self.missing = sorted(set(self.statements) - set(exec1))
36
37 if self.coverage.data.has_arcs():
38 n_branches = self.total_branches()
39 mba = self.missing_branch_arcs()
40 n_missing_branches = sum([len(v) for v in mba.values()])
41 else:
42 n_branches = n_missing_branches = 0
43
44 self.numbers = Numbers(
45 n_files=1,
46 n_statements=len(self.statements),
47 n_excluded=len(self.excluded),
48 n_missing=len(self.missing),
49 n_branches=n_branches,
50 n_missing_branches=n_missing_branches,
51 )
52
53 def missing_formatted(self):
54 """The missing line numbers, formatted nicely.
55
56 Returns a string like "1-2, 5-11, 13-14".
57
58 """
59 return format_lines(self.statements, self.missing)
60
61 def has_arcs(self):
62 """Were arcs measured in this result?"""
63 return self.coverage.data.has_arcs()
64
65 def arc_possibilities(self):
66 """Returns a sorted list of the arcs in the code."""
67 return self.parser.arcs()
68
69 def arcs_executed(self):
70 """Returns a sorted list of the arcs actually executed in the code."""
71 executed = self.coverage.data.executed_arcs(self.filename)
72 m2fl = self.parser.first_line
73 executed = [(m2fl(l1), m2fl(l2)) for (l1,l2) in executed]
74 return sorted(executed)
75
76 def arcs_missing(self):
77 """Returns a sorted list of the arcs in the code not executed."""
78 possible = self.arc_possibilities()
79 executed = self.arcs_executed()
80 missing = [p for p in possible if p not in executed]
81 return sorted(missing)
82
83 def arcs_unpredicted(self):
84 """Returns a sorted list of the executed arcs missing from the code."""
85 possible = self.arc_possibilities()
86 executed = self.arcs_executed()
87 # Exclude arcs here which connect a line to itself. They can occur
88 # in executed data in some cases. This is where they can cause
89 # trouble, and here is where it's the least burden to remove them.
90 unpredicted = [
91 e for e in executed
92 if e not in possible and e[0] != e[1]
93 ]
94 return sorted(unpredicted)
95
96 def branch_lines(self):
97 """Returns lines that have more than one exit."""
98 exit_counts = self.parser.exit_counts()
99 return [l1 for l1,count in exit_counts.items() if count > 1]
100
101 def total_branches(self):
102 """How many total branches are there?"""
103 exit_counts = self.parser.exit_counts()
104 return sum([count for count in exit_counts.values() if count > 1])
105
106 def missing_branch_arcs(self):
107 """Return arcs that weren't executed from branch lines.
108
109 Returns {l1:[l2a,l2b,...], ...}
110
111 """
112 missing = self.arcs_missing()
113 branch_lines = set(self.branch_lines())
114 mba = {}
115 for l1, l2 in missing:
116 if l1 in branch_lines:
117 if l1 not in mba:
118 mba[l1] = []
119 mba[l1].append(l2)
120 return mba
121
122
123 class Numbers(object):
124 """The numerical results of measuring coverage.
125
126 This holds the basic statistics from `Analysis`, and is used to roll
127 up statistics across files.
128
129 """
130 def __init__(self, n_files=0, n_statements=0, n_excluded=0, n_missing=0,
131 n_branches=0, n_missing_branches=0
132 ):
133 self.n_files = n_files
134 self.n_statements = n_statements
135 self.n_excluded = n_excluded
136 self.n_missing = n_missing
137 self.n_branches = n_branches
138 self.n_missing_branches = n_missing_branches
139
140 def _get_n_executed(self):
141 """Returns the number of executed statements."""
142 return self.n_statements - self.n_missing
143 n_executed = property(_get_n_executed)
144
145 def _get_n_executed_branches(self):
146 """Returns the number of executed branches."""
147 return self.n_branches - self.n_missing_branches
148 n_executed_branches = property(_get_n_executed_branches)
149
150 def _get_pc_covered(self):
151 """Returns a single percentage value for coverage."""
152 if self.n_statements > 0:
153 pc_cov = (100.0 * (self.n_executed + self.n_executed_branches) /
154 (self.n_statements + self.n_branches))
155 else:
156 pc_cov = 100.0
157 return pc_cov
158 pc_covered = property(_get_pc_covered)
159
160 def __add__(self, other):
161 nums = Numbers()
162 nums.n_files = self.n_files + other.n_files
163 nums.n_statements = self.n_statements + other.n_statements
164 nums.n_excluded = self.n_excluded + other.n_excluded
165 nums.n_missing = self.n_missing + other.n_missing
166 nums.n_branches = self.n_branches + other.n_branches
167 nums.n_missing_branches = (self.n_missing_branches +
168 other.n_missing_branches)
169 return nums
170
171 def __radd__(self, other):
172 # Implementing 0+Numbers allows us to sum() a list of Numbers.
173 if other == 0:
174 return self
175 raise NotImplemented

eric ide

mercurial