mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-14 07:59:40 +00:00
extract()'s id remap called Path.resolve() on the same file paths once
per input, once per target_file-stamped edge and once per node: ~24k
calls for ~1k distinct paths on graphify's own repo. Resolve each
distinct path once per extract() call through a local memo; a hit
returns the same Path the call would, and a path that raises is never
stored.
_path_proximity_winner (the #1553 god-node tie-break) re-parsed every
candidate's parent directory with PurePosixPath on every ambiguous call
site (~227k Path objects per update on graphify's repo), although the
same candidate files come back for every site of a common name. Memoize
the parent-directory parts per path string; equal tuples are exactly
equal PurePosixPath parents, so every tier decides as before.
graphify's own repo (18,830 nodes), `graphify update --force`,
alternated: cold 28.1 -> 26.3 s, no-change update 24.8 -> 22.5 s and
20.9 -> 18.0 s. graph.json identical (all attributes) cold and warm;
old vs new tie-break identical on 200,000 random cases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d5966eda8a)
68 lines
3.0 KiB
Python
68 lines
3.0 KiB
Python
"""extract()'s id remap and the call tie-break parse each distinct path once.
|
|
|
|
The remap resolved the same file paths once per input, once per stamped edge
|
|
and once per node, and `_path_proximity_winner` re-parsed every candidate's
|
|
directory on every ambiguous call site. Both now memoize; answers are unchanged.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path, PurePosixPath
|
|
|
|
import pytest
|
|
|
|
from graphify import paths as paths_mod
|
|
from graphify.extract import extract
|
|
|
|
|
|
def _corpus(root: Path) -> list[Path]:
|
|
(root / "pkg").mkdir(parents=True)
|
|
files = []
|
|
for i in range(4):
|
|
body = "".join(f"def f{i}_{j}():\n return f{i}_{j + 1}()\n\n\n" for j in range(40))
|
|
body += "".join(f"from pkg.m{k} import f{k}_0\n" for k in range(4) if k != i)
|
|
p = root / "pkg" / f"m{i}.py"
|
|
p.write_text(body, encoding="utf-8")
|
|
files.append(p)
|
|
(root / "pkg" / "__init__.py").write_text("", encoding="utf-8")
|
|
return files + [root / "pkg" / "__init__.py"]
|
|
|
|
|
|
def test_extract_resolves_each_distinct_path_once(tmp_path: Path, monkeypatch):
|
|
files = _corpus(tmp_path / "proj")
|
|
calls: list[str] = []
|
|
real_resolve = Path.resolve
|
|
remap_frames = {"extract", "_decompose", "_resolved"} # the id-remap code in extract()
|
|
|
|
def counting_resolve(self, *args, **kwargs):
|
|
if sys._getframe(1).f_code.co_name in remap_frames:
|
|
calls.append(str(self))
|
|
return real_resolve(self, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(Path, "resolve", counting_resolve)
|
|
result = extract(files, root=tmp_path / "proj", cache_root=tmp_path / "cache", parallel=False)
|
|
assert len(result["nodes"]) > 160 # every function became a node, so the per-node remap ran
|
|
# Without the memo the remap resolved once per node and per stamped edge
|
|
# (~200 calls here); with it, once per distinct path.
|
|
assert len(calls) == len(set(calls)), f"{len(calls)} remap resolve calls for {len(set(calls))} paths"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"site, candidates, expected",
|
|
[
|
|
("pkg/a/x.py", {"1": "pkg/a/y.py", "2": "pkg/b/y.py"}, "1"), # same dir
|
|
("pkg\\a\\x.py", {"1": "pkg/a/y.py", "2": "pkg/b/y.py"}, "1"), # backslashes
|
|
("pkg/a/b/x.py", {"1": "pkg/a/y.py", "2": "other/y.py"}, "1"), # longest prefix
|
|
("pkg/a/x.py", {"1": "pkg/b/y.py", "2": "pkg/c/y.py"}, None), # tied prefix
|
|
("x.py", {"1": "y.py", "2": "z/y.py"}, "1"), # root-level file
|
|
("pkg/a/x.py", {"1": "pkg/a/x.py", "2": "pkg/a/y.py"}, "1"), # same file
|
|
],
|
|
)
|
|
def test_proximity_winner_matches_a_fresh_parse(site, candidates, expected):
|
|
paths_mod._parent_parts.cache_clear()
|
|
assert paths_mod._path_proximity_winner(site, candidates) == expected
|
|
# second call is served from the memo and must agree
|
|
assert paths_mod._path_proximity_winner(site, candidates) == expected
|
|
for f in candidates.values():
|
|
assert paths_mod._parent_parts(f) == PurePosixPath(f.replace("\\", "/")).parent.parts
|