Skip to content
9 changes: 8 additions & 1 deletion scripts/compare_scan_accuracy.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import platform
import sys
import urllib.parse
import urllib.request
from pathlib import Path

MAX_DEPENDENCY_FILES = 200_000
Expand Down Expand Up @@ -178,7 +179,13 @@ def hash_file(digest, label, path):
parsed = urllib.parse.urlsplit(raw_url)
if parsed.scheme != "file" or parsed.netloc not in {"", "localhost"}:
raise RuntimeError(f"editable dependency is not a local file target: {normalized_name}")
editable_root = Path(urllib.parse.unquote(parsed.path)).resolve(strict=True)
# url2pathname needs the empty-authority delimiter for paths beginning
# with "//". Build it explicitly because urlunsplit() normalizes this
# form differently across Python patch releases.
converter_input = parsed.path
if not parsed.netloc and converter_input.startswith("//"):
converter_input = f"//{converter_input}"
editable_root = Path(urllib.request.url2pathname(converter_input)).resolve(strict=True)
if not editable_root.is_dir():
raise RuntimeError(f"editable dependency target is not a directory: {normalized_name}")
for editable_path in sorted(editable_root.rglob("*")):
Expand Down
58 changes: 58 additions & 0 deletions tests/unit/test_compare_scan_accuracy.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import subprocess
import sys
import tarfile
import urllib.request
from collections.abc import Iterator
from contextlib import contextmanager, redirect_stdout
from pathlib import Path
Expand Down Expand Up @@ -1116,6 +1117,63 @@ def probe() -> dict[str, object]:
assert editable_changed["dependencies"] != original["dependencies"]


@pytest.mark.parametrize(
("editable_url", "expected_converter_input"),
[
("file:///C:/PortableRegressionProbe", "/C:/PortableRegressionProbe"),
("file:////portable-regression-probe", "////portable-regression-probe"),
],
ids=["windows_drive", "empty_authority"],
)
def test_runtime_probe_preserves_file_url_structure_for_path_conversion(
tmp_path: Path,
monkeypatch,
editable_url: str,
expected_converter_input: str,
) -> None:
installed_root = tmp_path / "site-packages"
installed_root.mkdir()
(installed_root / "dependency.py").write_text("VALUE = 1\n", encoding="utf-8")
editable_root = tmp_path / "editable-dependency"
editable_root.mkdir()
(editable_root / "source.py").write_text("VALUE = 2\n", encoding="utf-8")

class FakeDistribution:
metadata = {"Name": "example-dependency"}
version = "1.0"
files = ["dependency.py"]

def read_text(self, name: str) -> str | None:
if name == "RECORD":
return "dependency.py,,\n"
if name == "METADATA":
return "Name: example-dependency\nVersion: 1.0\n"
if name == "direct_url.json":
return json.dumps({"url": editable_url, "dir_info": {"editable": True}})
return None

def locate_file(self, package_path: object) -> Path:
return installed_root / str(package_path)

monkeypatch.setattr(importlib.metadata, "distributions", lambda: [FakeDistribution()])
converter_inputs: list[str] = []

def fake_url2pathname(value: str) -> str:
converter_inputs.append(value)
return str(editable_root)

monkeypatch.setattr(urllib.request, "url2pathname", fake_url2pathname)
rendered = io.StringIO()
with redirect_stdout(rendered):
exec(compare_scan_accuracy._RUNTIME_IDENTITY_PROBE, {})

payload = json.loads(rendered.getvalue())
dependency = payload["dependencies"][0]
assert converter_inputs == [expected_converter_input]
assert dependency["editable"] is True
assert dependency["editable_file_count"] == 1


@pytest.mark.parametrize(
("mutation", "message"),
[
Expand Down