Skip to content

Commit bcf5445

Browse files
committed
Update survey
1 parent 61a7456 commit bcf5445

1 file changed

Lines changed: 70 additions & 47 deletions

File tree

asv/contrib/lightspeed/survey.py

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)