fix: tell an offline cache miss from absence

Offline, fetch_file answered None for a path the cache never
stored, the same answer as a path known to be absent, and
profile_sync reported existing files as gone. A miss now raises
NotCachedError and the profile is reported unverified.
This commit is contained in:
Abdessamad Derraz committed 2026-10-18 02:46:57 +02:00
1 parent b91449590d
commit 9802804a9a
6 files changed
+59 -9

No files matched your search

+7 -3
View File
@@ -124,9 +124,13 @@ def main() -> None:
total = 0
for name in args.emulators:
findings, agreed, unjudged = audit(
name, args.emulators_dir, args.cache_dir, args.offline
)
try:
findings, agreed, unjudged = audit(
name, args.emulators_dir, args.cache_dir, args.offline
)
except upstream.UpstreamError as exc:
print(f"{name}: unreachable, {exc}", file=sys.stderr)
continue
total += len(findings)
print(
f"{name}: {agreed} agree, {len(findings)} point elsewhere, "
+8 -1
View File
@@ -1803,7 +1803,7 @@ def build_report(
return None
try:
return upstream.fetch_file(repo, sha, wanted, cache_dir, offline)
except upstream.RateLimitError:
except (upstream.RateLimitError, upstream.NotCachedError):
raise
except upstream.UpstreamError as exc:
mute.add(key)
@@ -3677,6 +3677,13 @@ def main() -> None:
)
except upstream.RateLimitError:
raise
except upstream.NotCachedError as exc:
# Offline with a cache that never stored this file: the profile
# is unverified, not wrong. Reporting GONE sent readers after
# files that exist.
report = ProfileReport(
name=name, entries=[], counts={}, skipped=f"offline: {exc}"
)
except upstream.GoneError as exc:
# The forge is not coming back: a takedown, or a host that no
# longer resolves. Saying so once in the summary beats repeating
+14 -2
View File
@@ -80,6 +80,14 @@ class RateLimitError(UpstreamError):
"""The forge refused the request for quota reasons."""
class NotCachedError(UpstreamError):
"""An offline read of something the cache never stored.
Distinct from absence: returning None made profile_sync report a file
that exists as GONE, "absent at HEAD, no rename found".
"""
class GoneError(UpstreamError):
"""The upstream is not coming back.
@@ -317,13 +325,17 @@ def fetch_file(
cache_dir: str,
offline: bool = False,
) -> list[str] | None:
"""Lines of one path at one revision, or None when absent."""
"""Lines of one path at one revision, or None when absent.
Offline, a path the cache never stored raises NotCachedError: nothing
is known about it, which is not the same as knowing it is absent.
"""
target = cache_path(cache_dir, repo, sha, path)
if target.is_file():
text = target.read_text(encoding="utf-8")
return None if text == ABSENT else text.splitlines()
if offline:
return None
raise NotCachedError(f"{repo.slug}@{sha[:8]}:{path} is not in the offline cache")
text = _http_text(raw_url(repo, sha, path))
write_cache(target, ABSENT if text is None else text)
return None if text is None else text.splitlines()
+5 -1
View File
@@ -115,7 +115,11 @@ class TestCorpusIsAnswered(unittest.TestCase):
read = []
def counted(*args, **kwargs):
lines = fetch(*args, **kwargs)
# A cold path is unread here, not absent.
try:
lines = fetch(*args, **kwargs)
except fileless_audit.upstream.NotCachedError:
return None
if lines is not None:
read.append(args[2])
return lines
+14
View File
@@ -3932,5 +3932,19 @@ class UpstreamVendoredAsASubmodule(unittest.TestCase):
self.assertEqual(views[1].head, "nesbox-tip")
class OfflineCacheMissIsNotGone(TestBuildReport):
"""Offline, a file the cache never stored came back as None and the
report read "GONE (absent at HEAD, no rename found)" for files that
exist. The miss now stops the profile as unverified."""
def test_an_uncached_file_is_not_reported_gone(self):
def cold(repo, sha, path, cache_dir, offline=False):
raise profile_sync.upstream.NotCachedError(f"{path} not cached")
profile_sync.upstream.fetch_file = cold
with self.assertRaises(profile_sync.upstream.NotCachedError):
build_report("test", self._profile(["a.c:2"]), self.dir, offline=True)
if __name__ == "__main__":
unittest.main()
+11 -2
View File
@@ -379,9 +379,18 @@ class TestCache(unittest.TestCase):
self.assertEqual(second, first)
self.assertEqual(len(self.calls), 1)
def test_offline_miss_returns_none_without_request(self):
self.assertIsNone(
def test_offline_miss_is_not_absence(self):
# Returning None made profile_sync report an existing file as GONE.
with self.assertRaises(upstream.NotCachedError):
upstream.fetch_file(self.repo, "abc", "a.c", self.dir, offline=True)
self.assertEqual(self.calls, [])
def test_offline_cached_absence_stays_absence(self):
upstream.write_cache(
upstream.cache_path(self.dir, self.repo, "abc", "gone.c"), upstream.ABSENT
)
self.assertIsNone(
upstream.fetch_file(self.repo, "abc", "gone.c", self.dir, offline=True)
)
self.assertEqual(self.calls, [])