8FS-510 regression coverage for the incremental lint cache.
12- Validate content_hash and rules_hash are deterministic.
13- Validate cache miss on first run and hit on second.
14- Validate invalidation on content, rule, or version change.
15- Validate corrupt/missing file handling.
16- Validate --no-cache bypasses reads.
17- Validate thread safety.
24from __future__
import annotations
28from pathlib
import Path
47 assert content_hash(
"hello") == content_hash(
"hello")
50 assert content_hash(
"hello") != content_hash(
"world")
53 rules = {
"header":
True,
"docstring":
False}
54 assert rules_hash(rules) == rules_hash(rules)
57 r1 = {
"a":
True,
"b":
False}
58 r2 = {
"b":
False,
"a":
True}
59 assert rules_hash(r1) == rules_hash(r2)
63 r2 = {
"header":
False}
64 assert rules_hash(r1) != rules_hash(r2)
73 content_hash=file_hash,
74 rule_results={
"header":
True,
"docstring":
False},
75 json_detail={
"path":
"test.py",
"checks": {}},
77 timestamp=
"2026-04-17T00:00:00",
84 cache =
LintCache(tmp_path /
"cache.json",
"0.22.0")
85 assert cache.get(
"abc",
"def")
is None
88 cache =
LintCache(tmp_path /
"cache.json",
"0.22.0")
90 cache.put(
"abc123",
"rules1", entry)
91 result = cache.get(
"abc123",
"rules1")
92 assert result
is not None
93 assert result.content_hash ==
"abc123"
94 assert result.rule_results == {
"header":
True,
"docstring":
False}
97 cache_path = tmp_path /
"cache.json"
99 cache.put(
"abc123",
"rules1",
_make_entry(
"abc123"))
103 result = cache2.get(
"abc123",
"rules1")
104 assert result
is not None
105 assert result.content_hash ==
"abc123"
108 cache_path = tmp_path /
"cache.json"
110 cache.put(
"abc123",
"rules1",
_make_entry(
"abc123"))
114 assert cache2.get(
"abc123",
"rules1")
is None
117 cache =
LintCache(tmp_path /
"cache.json",
"0.22.0")
118 cache.put(
"abc123",
"rules1",
_make_entry(
"abc123"))
119 assert cache.get(
"different_hash",
"rules1")
is None
122 cache =
LintCache(tmp_path /
"cache.json",
"0.22.0")
123 cache.put(
"abc123",
"rules1",
_make_entry(
"abc123"))
124 assert cache.get(
"abc123",
"different_rules")
is None
127 cache_path = tmp_path /
"cache.json"
128 cache_path.write_text(
"not json!!!", encoding=
"utf-8")
130 assert cache.entry_count == 0
133 cache =
LintCache(tmp_path /
"nonexistent.json",
"0.22.0")
134 assert cache.entry_count == 0
137 cache_path = tmp_path /
"deep" /
"nested" /
"cache.json"
141 assert cache_path.is_file()
144 cache =
LintCache(tmp_path /
"cache.json",
"0.22.0")
145 assert cache.entry_count == 0
147 assert cache.entry_count == 1
149 assert cache.entry_count == 2
152 cache =
LintCache(tmp_path /
"cache.json",
"0.22.0")
153 errors: list[str] = []
155 def worker(i: int) ->
None:
159 result = cache.get(h,
"rules")
161 errors.append(f
"Worker {i}: put then get returned None")
162 except Exception
as e:
163 errors.append(f
"Worker {i}: {e}")
165 threads = [threading.Thread(target=worker, args=(i,))
for i
in range(20)]
171 assert not errors, f
"Thread safety errors: {errors}"
172 assert cache.entry_count == 20
test_invalidated_on_content_change(self, Path tmp_path)
test_entry_count(self, Path tmp_path)
test_corrupt_file_starts_fresh(self, Path tmp_path)
test_save_and_reload(self, Path tmp_path)
test_save_creates_parent_dirs(self, Path tmp_path)
test_missing_file_starts_fresh(self, Path tmp_path)
test_thread_safety(self, Path tmp_path)
test_miss_on_empty_cache(self, Path tmp_path)
test_put_and_get(self, Path tmp_path)
test_invalidated_on_rules_change(self, Path tmp_path)
test_invalidated_on_version_change(self, Path tmp_path)
test_rules_hash_order_independent(self)
test_rules_hash_changes_on_different_rules(self)
test_rules_hash_deterministic(self)
test_content_hash_changes_on_different_input(self)
test_content_hash_deterministic(self)
LintCacheEntry _make_entry(str file_hash="abc123")