|
7 | 7 |
|
8 | 8 | importcollections |
9 | 9 | importdataclasses |
10 | | -fromcollections.abcimportContainer,Iterable |
| 10 | +fromcollections.abcimportIterable |
11 | 11 | fromtypingimportTYPE_CHECKING |
12 | 12 |
|
13 | 13 | fromcoverage.exceptionsimportConfigError |
@@ -113,45 +113,6 @@ def __post_init__(self) -> None: |
113 | 113 | n_missing_branches=n_missing_branches, |
114 | 114 | ) |
115 | 115 |
|
116 | | -defnarrow(self,lines:Container[TLineNo])->Analysis: |
117 | | -"""Create a narrowed Analysis. |
118 | | -
|
119 | | - The current analysis is copied to make a new one that only considers |
120 | | - the lines in `lines`. |
121 | | - """ |
122 | | - |
123 | | -statements= {lnoforlnoinself.statementsiflnoinlines} |
124 | | -excluded= {lnoforlnoinself.excludediflnoinlines} |
125 | | -executed= {lnoforlnoinself.executediflnoinlines} |
126 | | - |
127 | | -ifself.has_arcs: |
128 | | -arc_possibilities_set= { |
129 | | - (a,b)fora,binself.arc_possibilities_setifainlinesorbinlines |
130 | | - } |
131 | | -arcs_executed_set= { |
132 | | - (a,b)fora,binself.arcs_executed_setifainlinesorbinlines |
133 | | - } |
134 | | -exit_counts= {lno:numforlno,numinself.exit_counts.items()iflnoinlines} |
135 | | -no_branch= {lnoforlnoinself.no_branchiflnoinlines} |
136 | | -else: |
137 | | -arc_possibilities_set=set() |
138 | | -arcs_executed_set=set() |
139 | | -exit_counts= {} |
140 | | -no_branch=set() |
141 | | - |
142 | | -returnAnalysis( |
143 | | -precision=self.precision, |
144 | | -filename=self.filename, |
145 | | -has_arcs=self.has_arcs, |
146 | | -statements=statements, |
147 | | -excluded=excluded, |
148 | | -executed=executed, |
149 | | -arc_possibilities_set=arc_possibilities_set, |
150 | | -arcs_executed_set=arcs_executed_set, |
151 | | -exit_counts=exit_counts, |
152 | | -no_branch=no_branch, |
153 | | - ) |
154 | | - |
155 | 116 | defmissing_formatted(self,branches:bool=False)->str: |
156 | 117 | """The missing line numbers, formatted nicely. |
157 | 118 |
|
@@ -236,6 +197,104 @@ def branch_stats(self) -> dict[TLineNo, tuple[int, int]]: |
236 | 197 | returnstats |
237 | 198 |
|
238 | 199 |
|
| 200 | +TRegionLines=frozenset[TLineNo] |
| 201 | + |
| 202 | + |
| 203 | +classAnalysisNarrower: |
| 204 | +""" |
| 205 | + For reducing an `Analysis` to a subset of its lines. |
| 206 | +
|
| 207 | + Originally this was a simpler method on Analysis, but that led to quadratic |
| 208 | + behavior. This class does the bulk of the work up-front to provide the |
| 209 | + same results in linear time. |
| 210 | +
|
| 211 | + Create an AnalysisNarrower from an Analysis, bulk-add region lines to it |
| 212 | + with `add_regions`, then individually request new narrowed Analysis objects |
| 213 | + for each region with `narrow`. Doing most of the work in limited calls to |
| 214 | + `add_regions` lets us avoid poor performance. |
| 215 | + """ |
| 216 | + |
| 217 | +# In this class, regions are represented by a frozenset of their lines. |
| 218 | + |
| 219 | +def__init__(self,analysis:Analysis)->None: |
| 220 | +self.analysis=analysis |
| 221 | +self.region2arc_possibilities:dict[TRegionLines,set[TArc]]=collections.defaultdict(set) |
| 222 | +self.region2arc_executed:dict[TRegionLines,set[TArc]]=collections.defaultdict(set) |
| 223 | +self.region2exit_counts:dict[TRegionLines,dict[TLineNo,int]]=collections.defaultdict( |
| 224 | +dict |
| 225 | + ) |
| 226 | + |
| 227 | +defadd_regions(self,liness:Iterable[set[TLineNo]])->None: |
| 228 | +""" |
| 229 | + Pre-process a number of sets of line numbers. Later calls to `narrow` |
| 230 | + with one of these sets will provide a narrowed Analysis. |
| 231 | + """ |
| 232 | +ifself.analysis.has_arcs: |
| 233 | +line2region:dict[TLineNo,TRegionLines]= {} |
| 234 | + |
| 235 | +forlinesinliness: |
| 236 | +fzlines=frozenset(lines) |
| 237 | +forlineinlines: |
| 238 | +line2region[line]=fzlines |
| 239 | + |
| 240 | +defcollect_arcs( |
| 241 | +arc_set:set[TArc], |
| 242 | +region2arcs:dict[TRegionLines,set[TArc]], |
| 243 | + )->None: |
| 244 | +fora,binarc_set: |
| 245 | +ifr:=line2region.get(a): |
| 246 | +region2arcs[r].add((a,b)) |
| 247 | +ifr:=line2region.get(b): |
| 248 | +region2arcs[r].add((a,b)) |
| 249 | + |
| 250 | +collect_arcs(self.analysis.arc_possibilities_set,self.region2arc_possibilities) |
| 251 | +collect_arcs(self.analysis.arcs_executed_set,self.region2arc_executed) |
| 252 | + |
| 253 | +forlno,numinself.analysis.exit_counts.items(): |
| 254 | +ifr:=line2region.get(lno): |
| 255 | +self.region2exit_counts[r][lno]=num |
| 256 | + |
| 257 | +defnarrow(self,lines:set[TLineNo])->Analysis: |
| 258 | +"""Create a narrowed Analysis. |
| 259 | +
|
| 260 | + The current analysis is copied to make a new one that only considers |
| 261 | + the lines in `lines`. |
| 262 | + """ |
| 263 | + |
| 264 | +# Technically, the set intersections in this method are still O(N**2) |
| 265 | +# since this method is called N times, but they're very fast and moving |
| 266 | +# them to `add_regions` won't avoid the quadratic time. |
| 267 | + |
| 268 | +statements=self.analysis.statements&lines |
| 269 | +excluded=self.analysis.excluded&lines |
| 270 | +executed=self.analysis.executed&lines |
| 271 | + |
| 272 | +ifself.analysis.has_arcs: |
| 273 | +fzlines=frozenset(lines) |
| 274 | +arc_possibilities_set=self.region2arc_possibilities[fzlines] |
| 275 | +arcs_executed_set=self.region2arc_executed[fzlines] |
| 276 | +exit_counts=self.region2exit_counts[fzlines] |
| 277 | +no_branch=self.analysis.no_branch&lines |
| 278 | +else: |
| 279 | +arc_possibilities_set=set() |
| 280 | +arcs_executed_set=set() |
| 281 | +exit_counts= {} |
| 282 | +no_branch=set() |
| 283 | + |
| 284 | +returnAnalysis( |
| 285 | +precision=self.analysis.precision, |
| 286 | +filename=self.analysis.filename, |
| 287 | +has_arcs=self.analysis.has_arcs, |
| 288 | +statements=statements, |
| 289 | +excluded=excluded, |
| 290 | +executed=executed, |
| 291 | +arc_possibilities_set=arc_possibilities_set, |
| 292 | +arcs_executed_set=arcs_executed_set, |
| 293 | +exit_counts=exit_counts, |
| 294 | +no_branch=no_branch, |
| 295 | + ) |
| 296 | + |
| 297 | + |
239 | 298 | @dataclasses.dataclass |
240 | 299 | classNumbers: |
241 | 300 | """The numerical results of measuring coverage. |
|