aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/repository
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/repository
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/repository')
-rw-r--r--crates/shirabe/src/repository/filesystem_repository.rs18
-rw-r--r--crates/shirabe/src/repository/platform_repository.rs60
-rw-r--r--crates/shirabe/src/repository/repository_set.rs23
-rw-r--r--crates/shirabe/src/repository/vcs/perforce_driver.rs13
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,