diff --git a/crates/fparkan-resource/src/lib.rs b/crates/fparkan-resource/src/lib.rs index 666245f..e60b3ba 100644 --- a/crates/fparkan-resource/src/lib.rs +++ b/crates/fparkan-resource/src/lib.rs @@ -1476,6 +1476,58 @@ mod tests { ); } + #[test] + fn archive_cache_evicts_by_byte_budget() { + let first_path = archive_path(b"cache/first-bytes.lib").expect("first path"); + let second_path = archive_path(b"cache/second-bytes.lib").expect("second path"); + let first_bytes = build_nres(&[("a.bin", b"first".as_slice())]); + let second_bytes = build_nres(&[("b.bin", b"second".as_slice())]); + let second_budget = second_bytes.len(); + let mut vfs = MemoryVfs::default(); + vfs.insert( + first_path.clone(), + Arc::from(first_bytes.into_boxed_slice()), + ); + vfs.insert( + second_path.clone(), + Arc::from(second_bytes.into_boxed_slice()), + ); + let repo = CachedResourceRepository::with_limits( + Arc::new(vfs), + RepositoryLimits { + max_open_archives: 2, + max_archive_bytes: second_budget, + max_decoded_payload_entries: 64, + max_decoded_payload_bytes: 64 * 1024 * 1024, + }, + ); + + let first_archive = repo.open_archive(&first_path).expect("open first"); + let first_handle = repo + .find(first_archive, &resource_name(b"a.bin")) + .expect("find first") + .expect("first handle"); + assert_eq!( + repo.read(first_handle).expect("read first").as_slice(), + b"first" + ); + + let second_archive = repo.open_archive(&second_path).expect("open second"); + let second_handle = repo + .find(second_archive, &resource_name(b"b.bin")) + .expect("find second") + .expect("second handle"); + assert_eq!( + repo.read(second_handle).expect("read second").as_slice(), + b"second" + ); + + assert!(matches!( + repo.read(first_handle), + Err(ResourceError::StaleHandle) + )); + } + #[test] fn resource_error_display_is_actionable() { let path = archive_path(b"bad/rsli.lib").expect("path"); diff --git a/fixtures/acceptance/coverage.tsv b/fixtures/acceptance/coverage.tsv index 991e174..2917c62 100644 --- a/fixtures/acceptance/coverage.tsv +++ b/fixtures/acceptance/coverage.tsv @@ -151,7 +151,7 @@ S1-RES-001 covered cargo test -p fparkan-resource --offline cached_repository_re S1-RES-002 covered cargo test -p fparkan-resource --offline entry_handles_are_archive_qualified S1-RES-003 covered cargo test -p fparkan-resource --offline archive_cache_and_decoded_payload_cache_evict_independently S1-RES-004 covered cargo test -p fparkan-resource --offline entry_read_error_carries_archive_path_and_entry_name -S1-RES-005 partial cargo test -p fparkan-resource --offline archive_cache_and_decoded_payload_cache_evict_independently; explicit max_archive_bytes eviction test is still missing +S1-RES-005 covered cargo test -p fparkan-resource --offline archive_cache_evicts_by_byte_budget S1-RES-006 covered cargo test -p fparkan-resource --offline archive_cache_eviction_makes_old_handles_stale S1-RES-007 covered cargo test -p fparkan-resource --offline lossy_equivalent_archive_paths_remain_distinct S1-DIAG-001 partial cargo test -p fparkan-inspection --offline archive_diagnostic_preserves_source_path; entry/span coverage is still missing