@@ -149,10 +149,37 @@ def code_mask(source: str) -> str:
149149 return '' .join (chars )
150150
151151
152+ def cfg_requires_test (expression : str ) -> bool :
153+ expression = expression .strip ()
154+ if expression == 'test' :
155+ return True
156+ group = re .fullmatch (r'(all|any)\s*\((.*)\)' , expression , re .DOTALL )
157+ if not group :
158+ return False
159+ arguments = []
160+ depth = start = 0
161+ for index , char in enumerate (group [2 ]):
162+ depth += (char == '(' ) - (char == ')' )
163+ if depth < 0 :
164+ raise SnapshotError ('Unbalanced Rust cfg predicate' )
165+ if char == ',' and depth == 0 :
166+ arguments .append (group [2 ][start :index ])
167+ start = index + 1
168+ if depth :
169+ raise SnapshotError ('Unbalanced Rust cfg predicate' )
170+ final = group [2 ][start :]
171+ if final .strip ():
172+ arguments .append (final )
173+ required = [cfg_requires_test (argument ) for argument in arguments ]
174+ return any (required ) if group [1 ] == 'all' else bool (required ) and all (required )
175+
176+
152177def test_lines (source : str ) -> set [int ]:
153178 masked = code_mask (source )
154179 excluded : set [int ] = set ()
155- for match in re .finditer (r'#\s*\[\s*(?:cfg\s*\(\s*test\s*\)|test)\s*\]' , masked ):
180+ for match in re .finditer (r'#\s*\[\s*(?:cfg\s*\((?P<cfg>[^]]*)\)|test)\s*\]' , masked ):
181+ if match ['cfg' ] is not None and not cfg_requires_test (match ['cfg' ]):
182+ continue
156183 start = match .start ()
157184 end = match .end ()
158185 # Other attributes belong to this same item, not its body.
@@ -226,20 +253,21 @@ def summarize(root: Path, records: dict[str, SourceCoverage], changed: dict[str,
226253 if any (n > len (source .splitlines ()) for n in lines ):
227254 raise SnapshotError (f'LCOV source revision mismatch: { name } ' )
228255 parts = Path (name ).parts
229- excluded = set (lines ) if 'tests' in parts or 'benches' in parts else test_lines (source )
256+ test_only = 'tests' in parts or 'benches' in parts
257+ excluded = set (lines ) if test_only else test_lines (source )
230258 production = {n : count for n , count in lines .items () if n not in excluded }
231259 tests = {n : count for n , count in lines .items () if n in excluded }
232260 edits = production .keys () & changed .get (name , set ())
233261 uncertainty = record .unmapped + record .summary_hit_shortfall - record .unmapped_hits
234262 files .append ({
235- 'path' : name , 'production_found' : len (production ) + record .unmapped ,
263+ 'path' : name , 'production_found' : len (production ) + ( 0 if test_only else record .unmapped ) ,
236264 'unmapped_summary_lines' : record .unmapped ,
237265 'unmapped_summary_hits' : record .unmapped_hits ,
238266 'summary_hit_shortfall' : record .summary_hit_shortfall ,
239267 'mapped_hit_uncertainty' : uncertainty ,
240268 'changed_lines_without_line_records' : sorted (changed .get (name , set ()) - lines .keys () - excluded ),
241269 'production_hit' : max (0 , sum (n > 0 for n in production .values ()) - uncertainty ),
242- 'test_found' : len (tests ),
270+ 'test_found' : len (tests ) + ( record . unmapped if test_only else 0 ) ,
243271 'test_hit' : max (0 , sum (n > 0 for n in tests .values ()) - uncertainty ),
244272 'uncovered_production' : sorted (n for n , hits in production .items () if not hits ),
245273 'changed_production_found' : len (edits ),
0 commit comments