Commit graph

15 commits

Author SHA1 Message Date
3569962ff3
test(cache): rely on the compute_key auto default
format='auto' is now the default, so tests no longer pass it explicitly.
2026-08-19 17:45:30 +05:30
f700666757
refactor(cache): default the analysis format in compute_key
compute_key(sha256, version, format='auto') so callers that only need the
default variant don't have to pass it explicitly.
2026-08-19 15:06:51 +05:30
47445d6a6e
fix(cache): include the analysis format in the cache key
The key was sha256+version only, so interpreting the same bytes under a
different --format (sc32 vs sc64) could serve the other variant's
stack/tight/decoded results. The CLI already disables caching for
non-auto formats, but API callers passing cache_dir were unprotected.
Format is now part of the key, so variants never collide.
2026-08-19 04:44:01 +05:30
0db76156ad
fix(cache): guard is_file() in load() against OSError
load() probed the cache entry with path.is_file() unguarded, but on
Python 3.8+ PermissionError from stat() through a locked cache directory
is not swallowed (only ENOENT-like errnos are), so it crashed the
analysis instead of treating the entry as a miss.
2026-08-18 17:33:36 +05:30
408302d3d4
fix(cache): parse boolean envars case-insensitively
FLOSS_CACHE_ENABLE=False or FLOSS_CACHE_REFRESH=True were evaluated
verbatim, so the uppercase variants silently did nothing. Normalize the
value with .lower() so 0/false/no/n and 1/true/yes/y work regardless of
capitalization.
2026-08-18 16:46:54 +05:30
82293a3131
fix(cache): record the requested min_length on cache hits
materialize() filtered the document to the requested -n but left
metadata.min_length at the cached extraction threshold, so --json on a
hit (e.g. -n 6 against a -n 4 entry) advertised min_length: 4. Set it to
the requested value, matching what load() does for user-supplied
documents and what a fresh run reports.
2026-08-18 16:46:28 +05:30
638f1f50a5
fix(cache): never crash on cache init failures
cache_dir.mkdir() in store() and tempfile.mkstemp() in _write_atomic()
ran outside the OSError guard: an unwritable cache directory or a full
disk raised straight through and aborted the analysis. Both now log a
warning and skip caching.
2026-08-18 16:46:10 +05:30
a5e7aa0097
feat(cache): add FLOSS_CACHE_REFRESH to force a re-analysis
Deleting a cache entry manually is the only forced-refresh path today, and
FLOSS_CACHE_ENABLE=0 disables reads and writes entirely. FLOSS_CACHE_REFRESH=1
bypasses the cache on the current run and overwrites the entry with the
fresh document, documented in --help alongside the other envars.
2026-08-18 14:15:28 +05:30
7a9b529100
test(cache): narrow optional layout before access for mypy
the new materialize tag tests asserted layout.strings without proving the
layout is present, failing the CI mypy check. assert layout is not None
first so the union narrows.
2026-08-18 01:45:38 +05:30
1571c403e0
fix(cache): let a tags-enabled cache serve a no-tags request
covers() treated surplus tag data as a miss: if not wanted.enable_tags and
cached.analysis.enable_tags was a miss, forcing a full re-analysis just
because the cache holds more than requested. Tag false-positive cleanup
(remove_false_positive_lib_strings) only redacts tags, never strings, so
the cached data is a faithful superset.

covers() now accepts it, and materialize() redacts the tags across the
static, language, and layout-tree strings when tags are not wanted.
2026-08-18 01:31:00 +05:30
f516fe22b8
fix(cache): never crash analysis on cache file I/O errors
load() removed stale entries with path.unlink() and store() replaced the
entry with os.replace(), neither guarded. On Windows a cache file held
open by another process or an antivirus scanner raises PermissionError,
which propagated and aborted the analysis.

Writes and removals are now best-effort: _write_atomic() logs and skips
on any OSError during mkstemp/write/replace and cleans up the temporary
file, and _drop_cache_entry() logs and continues when an entry cannot be
removed.
2026-08-18 01:28:48 +05:30
82e544668b
fix(cache): clear disabled string types on cache materialize
A cache hit for a document that holds more string types than the user
requested previously left those arrays populated: covers() treats a
superset as a hit, but materialize() only flipped the analysis flags.
With --json the output then contained data a fresh run would omit.

materialize() now mirrors a fresh run: disabled types are cleared from
the document (static also drops the layout that holds them) and the
enable_layout/enable_tags flags are synced to the requested analysis.
2026-08-18 01:28:14 +05:30
bc94b6cb27
fix(cache): allow layout-less cache hits and clarify miss comment
covers() no longer treats a cached layout as a miss for a no-layout
request: materialize() now drops the layout when it is not wanted. Tags
remain a miss because a tags-enabled document has already had
false-positive strings removed. Move the --analyze-functions comment
inline with the caching guard it documents.
2026-08-18 01:06:59 +05:30
fba96b0fb6
fix(cache): make lock and cache-dir tests Windows-compatible
msvcrt.locking cannot lock a byte range past EOF, so the lock on an
empty lock file always failed on Windows and store() never wrote a cache
entry. Ensure the lock file has at least one byte before locking.

test_get_cache_dir_default asserted the default path ends with 'floss',
but the Windows default is %LOCALAPPDATA%\floss\Cache. Assert it
equals platformdirs' user_cache_dir('floss') instead.
2026-08-18 00:19:12 +05:30
d8a4fe8bd9
feat(cache): add result-document cache module
Add floss/cache.py with cache directory resolution, content-addressed
key computation, validated load, and an atomic lock-guarded store. The
module also decides whether a cached document satisfies the requested
analysis (covers) and applies post-load filtering (materialize).

Unit tests in tests/test_cache.py cover enable switch, directory
override, key/path layout, store/load roundtrip, invalid-entry dropping,
lock contention, coverage checks, and materialize filtering.
2026-08-17 18:45:48 +05:30