diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-27 17:21:00 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-27 17:26:28 +0900 |
| commit | e98823e599eb375b30037cc714710e3309d927d1 (patch) | |
| tree | e1689ec164086798fc46aa6a58d1e914cf4873b2 /crates/shirabe/src/repository | |
| parent | 20f620bdd0b5764ed2e9812dc0772f907b0d6f29 (diff) | |
| download | php-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/repository')
4 files changed, 68 insertions, 46 deletions
diff --git a/crates/shirabe/src/repository/filesystem_repository.rs b/crates/shirabe/src/repository/filesystem_repository.rs index 0dae316..14365f2 100644 --- a/crates/shirabe/src/repository/filesystem_repository.rs +++ b/crates/shirabe/src/repository/filesystem_repository.rs @@ -14,7 +14,7 @@ use shirabe_php_shim::{ use crate::config::is_php_integer_key; use crate::installed_versions::InstalledVersions; -use crate::installer::InstallationManager; +use crate::installer::InstallationManagerInterface; use crate::json::JsonFile; use crate::package::BasePackageHandle; use crate::package::PackageInterfaceHandle; @@ -209,7 +209,7 @@ impl FilesystemRepository { pub fn write( &mut self, dev_mode: bool, - installation_manager: &mut InstallationManager, + installation_manager: &mut dyn InstallationManagerInterface, ) -> Result<()> { let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); data.insert("packages".to_string(), PhpMixed::List(vec![])); @@ -239,11 +239,7 @@ impl FilesystemRepository { if let Some(path_str) = &path && !path_str.is_empty() { - let normalized_path = self.filesystem.borrow_mut().normalize_path(&if self - .filesystem - .borrow() - .is_absolute_path(path_str) - { + let path_to_normalize = if self.filesystem.borrow().is_absolute_path(path_str) { path_str.clone() } else { format!( @@ -251,7 +247,11 @@ impl FilesystemRepository { Platform::get_cwd(false).unwrap_or_default(), path_str ) - }); + }; + let normalized_path = self + .filesystem + .borrow_mut() + .normalize_path(&path_to_normalize); install_path = Some(self.filesystem.borrow_mut().find_shortest_path( &repo_dir, &normalized_path, @@ -453,7 +453,7 @@ impl FilesystemRepository { /// @param array<string, string> $installPaths fn generate_installed_versions( &mut self, - installation_manager: &InstallationManager, + installation_manager: &dyn InstallationManagerInterface, install_paths: &IndexMap<String, Option<String>>, dev_mode: bool, repo_dir: &str, diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index 981ad8d..ee1d26e 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -23,7 +23,9 @@ use crate::package::PackageInterface; use crate::package::PackageInterfaceHandle; use crate::package::version::VersionParser; use crate::platform::HhvmDetector; +use crate::platform::HhvmDetectorInterface; use crate::platform::Runtime; +use crate::platform::RuntimeInterface; use crate::platform::Version; use crate::plugin::plugin_interface::{self}; use crate::repository::ArrayRepository; @@ -48,8 +50,8 @@ pub struct PlatformRepository { pub(crate) version_parser: Option<VersionParser>, pub(crate) overrides: IndexMap<String, PlatformOverride>, pub(crate) disabled_packages: IndexMap<String, CompletePackageInterfaceHandle>, - pub(crate) runtime: Runtime, - pub(crate) hhvm_detector: HhvmDetector, + pub(crate) runtime: Box<dyn RuntimeInterface>, + pub(crate) hhvm_detector: Box<dyn HhvmDetectorInterface>, } impl PlatformRepository { @@ -65,11 +67,12 @@ impl PlatformRepository { pub fn new4( packages: Vec<PackageInterfaceHandle>, overrides: IndexMap<String, PhpMixed>, - runtime: Option<Runtime>, - hhvm_detector: Option<HhvmDetector>, + runtime: Option<Box<dyn RuntimeInterface>>, + hhvm_detector: Option<Box<dyn HhvmDetectorInterface>>, ) -> anyhow::Result<Self> { - let runtime = runtime.unwrap_or(Runtime); - let hhvm_detector = hhvm_detector.unwrap_or_else(|| HhvmDetector::new(None, None)); + let runtime: Box<dyn RuntimeInterface> = runtime.unwrap_or_else(|| Box::new(Runtime)); + let hhvm_detector: Box<dyn HhvmDetectorInterface> = + hhvm_detector.unwrap_or_else(|| Box::new(HhvmDetector::new(None, None))); let mut overrides_map: IndexMap<String, PlatformOverride> = IndexMap::new(); for (name, version) in overrides { if !is_string(&version) && !matches!(version, PhpMixed::Bool(false)) { @@ -281,17 +284,10 @@ impl PlatformRepository { // IPv6 support might still be available. let has_inet6 = self.runtime.has_constant("AF_INET6", None); // PHP: Silencer::call([$this->runtime, 'invoke'], 'inet_pton', ['::']) - // TODO(phase-c): Composer's Platform\Runtime class is entirely a todo!() stub (invoke, - // has_constant, ...), so the inet_pton invocation cannot run. The placeholder callable - // returns false; resolving this requires implementing the Runtime wrapper (separate Phase C - // work) and a closure that forwards to the inet_pton shim. let inet_pton_check = Silencer::call(|| { Ok::<PhpMixed, anyhow::Error>(self.runtime.invoke( - Box::new(|_args| PhpMixed::Bool(false)), - vec![ - PhpMixed::String("inet_pton".to_string()), - PhpMixed::String("::".to_string()), - ], + PhpMixed::String("inet_pton".to_string()), + vec![PhpMixed::String("::".to_string())], )) }) .unwrap_or(PhpMixed::Bool(false)); @@ -406,10 +402,9 @@ impl PlatformRepository { } "curl" => { - let curl_version = self.runtime.invoke( - Box::new(|_args| PhpMixed::Null), - vec![PhpMixed::String("curl_version".to_string())], - ); + let curl_version = self + .runtime + .invoke(PhpMixed::String("curl_version".to_string()), vec![]); let curl_version_str = curl_version .as_array() .and_then(|m| m.get("version")) @@ -819,17 +814,14 @@ impl PlatformRepository { // Add a separate version for the CLDR library version if self.runtime.has_class("ResourceBundle") { let resource_bundle = self.runtime.invoke( - Box::new(|_args| PhpMixed::Null), + PhpMixed::List(vec![ + PhpMixed::String("ResourceBundle".to_string()), + PhpMixed::String("create".to_string()), + ]), vec![ - PhpMixed::List(vec![ - PhpMixed::String("ResourceBundle".to_string()), - PhpMixed::String("create".to_string()), - ]), - PhpMixed::List(vec![ - PhpMixed::String("root".to_string()), - PhpMixed::String("ICUDATA".to_string()), - PhpMixed::Bool(false), - ]), + PhpMixed::String("root".to_string()), + PhpMixed::String("ICUDATA".to_string()), + PhpMixed::Bool(false), ], ); if !matches!(resource_bundle, PhpMixed::Null) { @@ -853,11 +845,11 @@ impl PlatformRepository { if self.runtime.has_class("IntlChar") { let intl_char_versions = self.runtime.invoke( - Box::new(|_args| PhpMixed::Null), - vec![PhpMixed::List(vec![ + PhpMixed::List(vec![ PhpMixed::String("IntlChar".to_string()), PhpMixed::String("getUnicodeVersion".to_string()), - ])], + ]), + vec![], ); let sliced = shirabe_php_shim::array_slice_mixed(&intl_char_versions, 0, Some(3)); @@ -1421,11 +1413,11 @@ impl PlatformRepository { "zip" => { if self .runtime - .has_constant("LIBZIP_VERSION", Some("ZipArchive")) + .has_constant("LIBZIP_VERSION", Some("ZipArchive".to_string())) { let libzip = self .runtime - .get_constant("LIBZIP_VERSION", Some("ZipArchive")); + .get_constant("LIBZIP_VERSION", Some("ZipArchive".to_string())); let libzip_str = match &libzip { PhpMixed::String(s) => Some(s.clone()), _ => None, diff --git a/crates/shirabe/src/repository/repository_set.rs b/crates/shirabe/src/repository/repository_set.rs index 393639f..f8514cb 100644 --- a/crates/shirabe/src/repository/repository_set.rs +++ b/crates/shirabe/src/repository/repository_set.rs @@ -648,6 +648,29 @@ impl RepositorySet { } } +/// Seam over `RepositorySet` so consumers (e.g. `VersionSelector`) can receive a mockable +/// repository set in tests. The concrete `RepositorySet` implements it by delegating to its +/// inherent methods. New consumers may extend this trait additively. +pub trait RepositorySetInterface: std::fmt::Debug { + fn find_packages( + &self, + name: &str, + constraint: Option<AnyConstraint>, + flags: i64, + ) -> anyhow::Result<Vec<BasePackageHandle>>; +} + +impl RepositorySetInterface for RepositorySet { + fn find_packages( + &self, + name: &str, + constraint: Option<AnyConstraint>, + flags: i64, + ) -> anyhow::Result<Vec<BasePackageHandle>> { + RepositorySet::find_packages(self, name, constraint, flags) + } +} + #[derive(Debug)] pub struct SecurityAdvisoriesResult { pub advisories: IndexMap<String, Vec<AnySecurityAdvisory>>, diff --git a/crates/shirabe/src/repository/vcs/perforce_driver.rs b/crates/shirabe/src/repository/vcs/perforce_driver.rs index 50d5eb9..cb280e5 100644 --- a/crates/shirabe/src/repository/vcs/perforce_driver.rs +++ b/crates/shirabe/src/repository/vcs/perforce_driver.rs @@ -9,6 +9,7 @@ use crate::config::Config; use crate::io::IOInterface; use crate::repository::vcs::VcsDriverBase; use crate::util::Perforce; +use crate::util::PerforceInterface; use crate::util::ProcessExecutor; use crate::util::http::Response; @@ -17,7 +18,7 @@ pub struct PerforceDriver { inner: VcsDriverBase, pub(crate) depot: String, pub(crate) branch: String, - pub(crate) perforce: Option<Perforce>, + pub(crate) perforce: Option<Box<dyn PerforceInterface>>, } impl PerforceDriver { @@ -86,17 +87,23 @@ impl PerforceDriver { } let repo_dir = format!("{}/{}", cache_vcs_dir, self.depot); - self.perforce = Some(Perforce::create( + self.perforce = Some(Box::new(Perforce::create( repo_config.clone(), self.inner.url.clone(), repo_dir, self.inner.process.clone(), self.inner.io.clone(), - )); + ))); Ok(()) } + /// For testing only. Rust equivalent of the reflection-based + /// `overrideDriverInternalPerforce` helper in PerforceDriverTest. + pub fn __override_perforce(&mut self, perforce: Box<dyn PerforceInterface>) { + self.perforce = Some(perforce); + } + pub fn get_file_content( &mut self, file: &str, |
