aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/cache.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-27 17:21:00 +0900
committernsfisis <nsfisis@gmail.com>2026-06-27 17:26:28 +0900
commite98823e599eb375b30037cc714710e3309d927d1 (patch)
treee1689ec164086798fc46aa6a58d1e914cf4873b2 /crates/shirabe/src/cache.rs
parent20f620bdd0b5764ed2e9812dc0772f907b0d6f29 (diff)
downloadphp-shirabe-e98823e599eb375b30037cc714710e3309d927d1.tar.gz
php-shirabe-e98823e599eb375b30037cc714710e3309d927d1.tar.zst
php-shirabe-e98823e599eb375b30037cc714710e3309d927d1.zip
test: port Composer tests unblocked by mockall, add seams
Port 11 categories of previously-ignored Composer tests now reachable with the mockall crate: DownloadManager, VCS/Perforce/File downloaders, VersionSelector, PlatformRepository, Auditor, installer/FilesystemRepository, RootPackageLoader, util auth/http, commands, and Cache. Extract test seams additively on concrete structs as *Interface traits (Runtime, HhvmDetector, VersionGuesser, RepositorySet, Perforce, BinaryInstaller) plus mock-field seams (Cache, Filesystem); consumers take trait objects. Mocks are defined locally in the test crates via mockall::mock!, since automock-generated mocks are cfg(test)-gated and invisible across the integration-test boundary. dataProviders are ported in full; tests blocked by unported shims stay #[ignore] with documented reasons rather than reduced or weakened. Fix product bugs surfaced by the ports: - util/github: use the exception code, not the HTTP status, for 401/403 - advisory: serialize empty audit maps as [] to match PHP json_encode - repository/filesystem and downloader/file: fix RefCell double-borrow panics Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/cache.rs')
-rw-r--r--crates/shirabe/src/cache.rs94
1 files changed, 85 insertions, 9 deletions
diff --git a/crates/shirabe/src/cache.rs b/crates/shirabe/src/cache.rs
index 696378a..20197cd 100644
--- a/crates/shirabe/src/cache.rs
+++ b/crates/shirabe/src/cache.rs
@@ -27,6 +27,29 @@ pub struct Cache {
allowlist: String,
filesystem: std::rc::Rc<std::cell::RefCell<Filesystem>>,
read_only: bool,
+ /// Test-only seam. Always `None` in production; configured via [`Cache::__set_mock`].
+ mock: Option<CacheMock>,
+}
+
+/// Test-only seam mirroring the PHP CacheTest/FileDownloaderTest mocks of `Cache`.
+#[derive(Debug, Default)]
+pub struct CacheMock {
+ /// Overrides the file lists `gc()` derives from `get_finder()`, mirroring the PHP CacheTest
+ /// which mocks `getFinder` to feed `gc()` a controlled iterator.
+ pub finder: Option<GcFinderMock>,
+ /// When `Some`, `gc_is_necessary` returns it verbatim.
+ pub gc_is_necessary: Option<bool>,
+ /// When `Some`, `gc` records each `(ttl, max_size)` call here and skips its real body.
+ pub gc_calls: Option<Vec<(i64, i64)>>,
+}
+
+/// The controlled file lists used by a mocked `gc()` pass.
+#[derive(Debug, Default)]
+pub struct GcFinderMock {
+ /// Entries yielded by `get_finder().date(...)`, i.e. the outdated files.
+ pub outdated: Vec<std::path::PathBuf>,
+ /// Entries yielded by `get_finder().sort_by_accessed_time().get_iterator()`.
+ pub by_accessed_time: Vec<std::path::PathBuf>,
}
/// @var bool|null
@@ -55,6 +78,7 @@ impl Cache {
filesystem,
read_only,
enabled: None,
+ mock: None,
};
if !Self::is_usable(cache_dir) {
@@ -253,6 +277,12 @@ impl Cache {
}
pub fn gc_is_necessary(&self) -> bool {
+ if let Some(mock) = &self.mock
+ && let Some(necessary) = mock.gc_is_necessary
+ {
+ return necessary;
+ }
+
let mut cache_collected = CACHE_COLLECTED.lock().unwrap();
if cache_collected.unwrap_or(false) {
return false;
@@ -316,26 +346,32 @@ impl Cache {
}
pub fn gc(&mut self, ttl: i64, max_size: i64) -> bool {
+ if let Some(mock) = self.mock.as_mut()
+ && let Some(calls) = mock.gc_calls.as_mut()
+ {
+ calls.push((ttl, max_size));
+ return true;
+ }
+
if self.is_enabled() && !self.read_only {
let expire = Utc::now() - chrono::Duration::seconds(ttl);
-
- let mut finder = self.get_finder();
- finder.date(&format!(
+ let expire_str = format!(
"until {}",
expire.format(date_format_to_strftime("Y-m-d H:i:s"))
- ));
- for file in &mut finder {
+ );
+
+ for file in self.gc_outdated_files(&expire_str) {
let _ = self.filesystem.borrow_mut().unlink(&file);
}
let mut total_size = self.filesystem.borrow_mut().size(&self.root).unwrap_or(0);
if total_size > max_size {
- let mut iterator = self.get_finder().sort_by_accessed_time().get_iterator();
- while total_size > max_size && iterator.valid() {
- let filepath = iterator.current();
+ for filepath in self.gc_files_by_accessed_time() {
+ if total_size <= max_size {
+ break;
+ }
total_size -= self.filesystem.borrow_mut().size(&filepath).unwrap_or(0);
let _ = self.filesystem.borrow_mut().unlink(&filepath);
- iterator.next();
}
}
@@ -347,6 +383,46 @@ impl Cache {
false
}
+ /// Files matching `get_finder().date(...)`. Honours the [`CacheMock`] seam when set.
+ fn gc_outdated_files(&self, expire_str: &str) -> Vec<std::path::PathBuf> {
+ if let Some(mock) = &self.mock
+ && let Some(finder) = &mock.finder
+ {
+ return finder.outdated.clone();
+ }
+
+ let mut finder = self.get_finder();
+ finder.date(expire_str);
+ finder.into_iter().collect()
+ }
+
+ /// Files from `get_finder().sort_by_accessed_time()`. Honours the [`CacheMock`] seam when set.
+ fn gc_files_by_accessed_time(&self) -> Vec<std::path::PathBuf> {
+ if let Some(mock) = &self.mock
+ && let Some(finder) = &mock.finder
+ {
+ return finder.by_accessed_time.clone();
+ }
+
+ self.get_finder()
+ .sort_by_accessed_time()
+ .get_iterator()
+ .collect()
+ }
+
+ /// For testing only: install the [`CacheMock`] seam used by CacheTest/FileDownloaderTest.
+ pub fn __set_mock(&mut self, mock: CacheMock) {
+ self.mock = Some(mock);
+ }
+
+ /// For testing only: read back the `(ttl, max_size)` calls recorded by the [`CacheMock`] seam.
+ pub fn __gc_calls(&self) -> Vec<(i64, i64)> {
+ self.mock
+ .as_ref()
+ .and_then(|m| m.gc_calls.clone())
+ .unwrap_or_default()
+ }
+
pub fn gc_vcs_cache(&mut self, ttl: i64) -> bool {
if self.is_enabled() {
let expire = Utc::now() - chrono::Duration::seconds(ttl);