@@ -51,6 +51,54 @@ def _uses_low_level_thread(path: str) -> bool:
5151 return False
5252
5353
54+ # --------------------------------------------------------------------------- #
55+ # C extension dependency tracking #
56+ # --------------------------------------------------------------------------- #
57+
58+ def _add_extension_deps (
59+ file_deps : Dict [str , Tuple [list , str ]],
60+ source_root : str ,
61+ ) -> None :
62+ """
63+ Scan ``sys.modules`` for loaded C extension modules (``.so``/``.pyd``)
64+ whose top-level package has source files under *source_root*. For each
65+ match, add all C/Cython source files in that package directory to
66+ *file_deps* with whole-file fingerprints.
67+
68+ This is used as a fallback when coverage.py finds no Python files inside
69+ *source_root* (typical for C extension packages installed non-editable).
70+ """
71+ # Collect packages that have extension modules, deduplicate scan dirs
72+ seen_dirs : Set [str ] = set ()
73+ for mod in sys .modules .values ():
74+ if mod is None :
75+ continue
76+ mod_file = getattr (mod , "__file__" , None ) or ""
77+ if not (mod_file .endswith (".so" ) or mod_file .endswith (".pyd" )):
78+ continue
79+ mod_name = getattr (mod , "__name__" , "" )
80+ if not mod_name :
81+ continue
82+ pkg_root = mod_name .split ("." )[0 ]
83+ ext_source_dir = os .path .join (source_root , pkg_root )
84+ if os .path .isdir (ext_source_dir ):
85+ scan_dir = ext_source_dir
86+ elif os .path .isdir (source_root ):
87+ scan_dir = source_root
88+ else :
89+ continue
90+ if scan_dir in seen_dirs :
91+ continue
92+ seen_dirs .add (scan_dir )
93+ for root , _ , files in os .walk (scan_dir ):
94+ for f in files :
95+ if f .endswith ((".c" , ".cpp" , ".pyx" , ".pxd" , ".h" )):
96+ fpath = os .path .join (root , f )
97+ fp , sha = file_method_checksums (fpath )
98+ if sha is not None :
99+ file_deps [fpath ] = (fp , sha )
100+
101+
54102# --------------------------------------------------------------------------- #
55103# Single-benchmark survey #
56104# --------------------------------------------------------------------------- #
@@ -107,7 +155,6 @@ def survey_one(
107155 if skip :
108156 return False , "benchmark_skipped" , {}
109157
110- modules_before = set (sys .modules .keys ())
111158 tracer_before = sys .gettrace ()
112159 profiler_before = sys .getprofile ()
113160
@@ -141,56 +188,32 @@ def survey_one(
141188 except Exception as exc :
142189 return False , f"coverage_read_error: { exc } " , {}
143190
144- if not measured :
145- return False , "no_coverage_data" , {}
146-
147191 file_deps : Dict [str , Tuple [list , str ]] = {}
148- for fname in measured :
149- # Only track files inside the source root we care about.
150- try :
151- rel = os .path .relpath (fname , source_root )
152- except ValueError :
153- continue
154- if rel .startswith (".." ):
155- continue
192+ if measured :
193+ for fname in measured :
194+ # Only track files inside the source root we care about.
195+ try :
196+ rel = os .path .relpath (fname , source_root )
197+ except ValueError :
198+ continue
199+ if rel .startswith (".." ):
200+ continue
156201
157- lines : Set [int ] = set (cov_data .lines (fname ) or [])
158- if not lines :
159- continue
202+ lines : Set [int ] = set (cov_data .lines (fname ) or [])
203+ if not lines :
204+ continue
160205
161- fp , sha = coverage_fingerprint (fname , lines )
162- if sha is None :
163- continue
164- file_deps [fname ] = (fp , sha )
165-
166- # Track C extension modules loaded during benchmark execution.
167- # When coverage finds no Python files in source_root (e.g. C extension
168- # packages installed non-editable), map loaded .so/.pyd modules back to
169- # their C/Cython source files in source_root.
170- modules_after = set (sys .modules .keys ())
171- new_modules = modules_after - modules_before
172- for mod_name in new_modules :
173- mod = sys .modules .get (mod_name )
174- if mod is None :
175- continue
176- mod_file = getattr (mod , "__file__" , None ) or ""
177- if not (mod_file .endswith (".so" ) or mod_file .endswith (".pyd" )):
178- continue
179- # Check if this extension belongs to a package under source_root
180- pkg_root = mod_name .split ("." )[0 ]
181- ext_source_dir = os .path .join (source_root , pkg_root )
182- if not os .path .isdir (ext_source_dir ):
183- # Also check if source_root itself is the package directory
184- if not os .path .isdir (source_root ):
206+ fp , sha = coverage_fingerprint (fname , lines )
207+ if sha is None :
185208 continue
186- scan_dir = ext_source_dir if os . path . isdir ( ext_source_dir ) else source_root
187- for root , _ , files in os . walk ( scan_dir ):
188- for f in files :
189- if f . endswith (( ".c" , ".cpp" , ".pyx" , ".pxd" , ".h" )):
190- fpath = os . path . join ( root , f )
191- fp , sha = file_method_checksums ( fpath )
192- if sha is not None :
193- file_deps [ fpath ] = ( fp , sha )
209+ file_deps [ fname ] = ( fp , sha )
210+
211+ # Fallback: if coverage found no Python files in source_root (common for
212+ # C extension packages installed non-editable from site-packages), scan
213+ # all loaded modules for .so/.pyd extensions whose package has source
214+ # files under source_root, and add those C/Cython sources as deps.
215+ if not file_deps :
216+ _add_extension_deps ( file_deps , source_root )
194217
195218 if not file_deps :
196219 return False , "no_source_root_coverage" , {}
0 commit comments