| 210 | |
| 211 | |
| 212 | class TestIncrementalAstCache(unittest.TestCase): |
| 213 | def setUp(self): |
| 214 | self._tmpdir = tempfile.TemporaryDirectory() |
| 215 | self.tmp = Path(self._tmpdir.name) |
| 216 | |
| 217 | def tearDown(self): |
| 218 | self._tmpdir.cleanup() |
| 219 | |
| 220 | def _write(self, name: str, content: str) -> Path: |
| 221 | p = self.tmp / name |
| 222 | p.write_text(content, encoding="utf-8") |
| 223 | return p |
| 224 | |
| 225 | def _l1_key(self, p: Path) -> str: |
| 226 | """Return the L1 dict key for a path (always the resolved form).""" |
| 227 | return str(p.resolve()) |
| 228 | |
| 229 | # ── L1 mtime hit ────────────────────────────────────────────────────── |
| 230 | |
| 231 | def test_l1_mtime_hit_skips_hash(self): |
| 232 | cache = _make_cache(self.tmp) |
| 233 | src = "def foo(): pass\n" |
| 234 | p = self._write("a.py", src) |
| 235 | |
| 236 | cache.get_ast_json(p, src) # populate L1 |
| 237 | |
| 238 | with patch("pyspector.ast_cache.hashlib") as mock_hash: |
| 239 | cache.get_ast_json(p, src) # same mtime → must not hash |
| 240 | mock_hash.sha256.assert_not_called() |
| 241 | |
| 242 | # ── L1 hash hit ─────────────────────────────────────────────────────── |
| 243 | |
| 244 | def test_l1_hash_hit_updates_mtime(self): |
| 245 | cache = _make_cache(self.tmp) |
| 246 | src = "x = 1\n" |
| 247 | p = self._write("b.py", src) |
| 248 | |
| 249 | cache.get_ast_json(p, src) |
| 250 | entry_before = cache._l1[self._l1_key(p)] |
| 251 | old_mtime = entry_before.mtime |
| 252 | |
| 253 | # Touch the file (change mtime without changing content) |
| 254 | os.utime(p, (old_mtime + 1, old_mtime + 1)) |
| 255 | cache.get_ast_json(p, src) |
| 256 | |
| 257 | entry_after = cache._l1[self._l1_key(p)] |
| 258 | self.assertNotEqual(entry_after.mtime, old_mtime) |
| 259 | # Same bytes object (shallow copy via dataclasses.replace — no rebuild) |
| 260 | self.assertIs(entry_before.full_ast_json_z, entry_after.full_ast_json_z) |
| 261 | |
| 262 | # ── L2 disk hit ─────────────────────────────────────────────────────── |
| 263 | |
| 264 | def test_l2_disk_survives_l1_eviction(self): |
| 265 | cache = _make_cache(self.tmp) |
| 266 | src = "def saved(): pass\n" |
| 267 | p = self._write("c.py", src) |
| 268 | |
| 269 | cache.get_ast_json(p, src) # write to disk |
nothing calls this directly
no outgoing calls
no test coverage detected