aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/installer/installation_manager_test.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-28 14:30:41 +0900
committernsfisis <nsfisis@gmail.com>2026-06-28 14:30:41 +0900
commite97dc6e64c1be4bf78c420b11cec2a18dfd506d4 (patch)
tree6e397f696631ca23e931a1873803e0b1f85af915 /crates/shirabe/tests/installer/installation_manager_test.rs
parentf408ff1ff95ce6e512810ccb10695f773fe5a8b9 (diff)
downloadphp-shirabe-e97dc6e64c1be4bf78c420b11cec2a18dfd506d4.tar.gz
php-shirabe-e97dc6e64c1be4bf78c420b11cec2a18dfd506d4.tar.zst
php-shirabe-e97dc6e64c1be4bf78c420b11cec2a18dfd506d4.zip
test(tests): use mockall for hand-written interface mocks
Replace hand-written mock/stub structs that re-implemented PHPUnit mock-builder behavior (record-and-verify, manual call counters, unreachable!() guards) with mockall::mock! locals across: - package/loader: MockLoader, VersionGuesserMock - command: ArchiveManager/RepositoryManager/EventDispatcher mocks - util: ConfigSource/AuthJson mocks (auth_helper, bitbucket, github, forgejo, gitlab) - repository/vcs: github_driver NullConfigSource - installer: CountingInstaller, RecordingBinaryInstaller, and the DownloadManager mock (formerly common/downloader_stub.rs, now deleted) - downloader: download_manager create_downloader_mock Verification (counts/args) now lives in mockall expectations checked on drop. installation_manager BinaryInstaller is left hand-written because its as_binary_presence_interface seam returns Some(&mut self), which mockall cannot express; io_stub and io_mock are left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/installer/installation_manager_test.rs')
-rw-r--r--crates/shirabe/tests/installer/installation_manager_test.rs195
1 files changed, 114 insertions, 81 deletions
diff --git a/crates/shirabe/tests/installer/installation_manager_test.rs b/crates/shirabe/tests/installer/installation_manager_test.rs
index 01a7513..7480a02 100644
--- a/crates/shirabe/tests/installer/installation_manager_test.rs
+++ b/crates/shirabe/tests/installer/installation_manager_test.rs
@@ -43,48 +43,38 @@ fn set_up() -> SetUp {
SetUp { loop_, io }
}
-/// Records of the calls a `CountingInstaller` received, shared so the test can inspect them
-/// after the installer has been moved into the InstallationManager. This reproduces PHPUnit's
-/// `expects($this->exactly(n))->method(...)->with(...)` assertions with explicit counters.
-#[derive(Debug, Default)]
-struct InstallerCalls {
- supports_args: Vec<String>,
- install: Vec<PackageInterfaceHandle>,
- uninstall: Vec<PackageInterfaceHandle>,
- update: Vec<(PackageInterfaceHandle, PackageInterfaceHandle)>,
-}
-
-/// Configurable `InstallerInterface` stub, equivalent to
-/// `getMockBuilder(InstallerInterface::class)->getMock()`. `supports` returns true only when the
-/// requested type equals `supported_type` (PHP uses a returnCallback comparing `$arg === 'vendor'`),
-/// and every method records its arguments into the shared `calls`.
-#[derive(Debug)]
-struct CountingInstaller {
- supported_type: String,
- calls: Rc<RefCell<InstallerCalls>>,
-}
-
-impl CountingInstaller {
- fn new(supported_type: &str) -> (Self, Rc<RefCell<InstallerCalls>>) {
- let calls = Rc::new(RefCell::new(InstallerCalls::default()));
- (
- Self {
- supported_type: supported_type.to_string(),
- calls: calls.clone(),
- },
- calls,
- )
+// Equivalent to `getMockBuilder(InstallerInterface::class)->getMock()`. mockall cannot generate an
+// `#[async_trait]` impl for the async methods that take `&mut dyn InstalledRepositoryInterface`
+// (the object lifetime async_trait inserts clashes with mockall's generated lifetimes), so the
+// expectations live on inherent methods and a thin hand-written InstallerInterface impl forwards to
+// them, dropping the unused `repo` argument exactly as the PHPUnit mock ignores it. The methods not
+// configured by any test (is_installed/download/prepare/cleanup/get_install_path, and the defaulted
+// as_binary_presence_interface/as_plugin_installer_mut) return the same defaults as an unconfigured
+// PHPUnit mock.
+mockall::mock! {
+ #[derive(Debug)]
+ pub Installer {
+ fn supports(&self, package_type: &str) -> bool;
+ fn install(
+ &mut self,
+ package: PackageInterfaceHandle,
+ ) -> anyhow::Result<Option<PhpMixed>>;
+ fn update(
+ &mut self,
+ initial: PackageInterfaceHandle,
+ target: PackageInterfaceHandle,
+ ) -> anyhow::Result<Option<PhpMixed>>;
+ fn uninstall(
+ &mut self,
+ package: PackageInterfaceHandle,
+ ) -> anyhow::Result<Option<PhpMixed>>;
}
}
#[async_trait::async_trait(?Send)]
-impl InstallerInterface for CountingInstaller {
+impl InstallerInterface for MockInstaller {
fn supports(&self, package_type: &str) -> bool {
- self.calls
- .borrow_mut()
- .supports_args
- .push(package_type.to_string());
- package_type == self.supported_type
+ MockInstaller::supports(self, package_type)
}
fn is_installed(
@@ -117,8 +107,7 @@ impl InstallerInterface for CountingInstaller {
_repo: &mut dyn InstalledRepositoryInterface,
package: PackageInterfaceHandle,
) -> anyhow::Result<Option<PhpMixed>> {
- self.calls.borrow_mut().install.push(package);
- Ok(None)
+ MockInstaller::install(self, package)
}
async fn update(
@@ -127,8 +116,7 @@ impl InstallerInterface for CountingInstaller {
initial: PackageInterfaceHandle,
target: PackageInterfaceHandle,
) -> anyhow::Result<Option<PhpMixed>> {
- self.calls.borrow_mut().update.push((initial, target));
- Ok(None)
+ MockInstaller::update(self, initial, target)
}
async fn uninstall(
@@ -136,8 +124,7 @@ impl InstallerInterface for CountingInstaller {
_repo: &mut dyn InstalledRepositoryInterface,
package: PackageInterfaceHandle,
) -> anyhow::Result<Option<PhpMixed>> {
- self.calls.borrow_mut().uninstall.push(package);
- Ok(None)
+ MockInstaller::uninstall(self, package)
}
async fn cleanup(
@@ -283,7 +270,13 @@ fn same_handle(a: &PackageInterfaceHandle, b: &PackageInterfaceHandle) -> bool {
#[test]
fn test_add_get_installer() {
let set_up = set_up();
- let (installer, calls) = CountingInstaller::new("vendor");
+ let mut installer = MockInstaller::new();
+ // PHP expects supports() to be called exactly twice (once for the cached 'vendor' lookup,
+ // once for the failing 'unregistered' lookup), returning true only for 'vendor'.
+ installer
+ .expect_supports()
+ .times(2)
+ .returning(|arg| arg == "vendor");
let mut manager =
shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None);
@@ -292,10 +285,6 @@ fn test_add_get_installer() {
assert!(manager.get_installer("vendor").is_ok());
assert!(manager.get_installer("unregistered").is_err());
-
- // PHP expects supports() to be called exactly twice (once for the cached 'vendor' lookup,
- // once for the failing 'unregistered' lookup).
- assert_eq!(calls.borrow().supports_args.len(), 2);
}
#[ignore = "removeInstaller compares installers by object identity, but add_installer moves the Box<dyn InstallerInterface> into the manager, leaving no &dyn reference to pass back to remove_installer; faithful reproduction needs a shared-ownership installer registry"]
@@ -313,87 +302,131 @@ fn test_execute() {
#[test]
fn test_install() {
let set_up = set_up();
- let (installer, calls) = CountingInstaller::new("library");
+ let package = get_package("test/pkg", "1.0.0");
+
+ let mut installer = MockInstaller::new();
+ installer
+ .expect_supports()
+ .times(1)
+ .withf(|package_type| package_type == "library")
+ .returning(|_| true);
+ let expected = package.clone();
+ installer
+ .expect_install()
+ .times(1)
+ .withf_st(move |package| same_handle(package, &expected))
+ .returning(|_| Ok(None));
+
let mut manager =
shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None);
manager.add_installer(Box::new(installer));
- let package = get_package("test/pkg", "1.0.0");
let operation = InstallOperation::new(package.clone());
let mut repository = InstalledArrayRepository::new().unwrap();
- // install() returns the (empty) installer promise; the call is observed via the recorded args.
run(manager.install(&mut repository, &operation));
-
- assert_eq!(calls.borrow().supports_args, vec!["library".to_string()]);
- assert_eq!(calls.borrow().install.len(), 1);
- assert!(same_handle(&calls.borrow().install[0], &package));
}
#[test]
fn test_update_with_equal_types() {
let set_up = set_up();
- let (installer, calls) = CountingInstaller::new("library");
+ let initial = get_package("test/initial", "1.0.0");
+ let target = get_package("test/target", "1.0.1");
+
+ let mut installer = MockInstaller::new();
+ installer
+ .expect_supports()
+ .times(1)
+ .withf(|package_type| package_type == "library")
+ .returning(|_| true);
+ let expected_initial = initial.clone();
+ let expected_target = target.clone();
+ installer
+ .expect_update()
+ .times(1)
+ .withf_st(move |initial, target| {
+ same_handle(initial, &expected_initial) && same_handle(target, &expected_target)
+ })
+ .returning(|_, _| Ok(None));
+
let mut manager =
shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None);
manager.add_installer(Box::new(installer));
- let initial = get_package("test/initial", "1.0.0");
- let target = get_package("test/target", "1.0.1");
let operation = UpdateOperation::new(initial.clone(), target.clone());
let mut repository = InstalledArrayRepository::new().unwrap();
run(manager.update(&mut repository, &operation));
-
- assert_eq!(calls.borrow().supports_args, vec!["library".to_string()]);
- assert_eq!(calls.borrow().update.len(), 1);
- assert!(same_handle(&calls.borrow().update[0].0, &initial));
- assert!(same_handle(&calls.borrow().update[0].1, &target));
}
#[test]
fn test_update_with_not_equal_types() {
let set_up = set_up();
- let (lib_installer, lib_calls) = CountingInstaller::new("library");
- let (bundle_installer, bundle_calls) = CountingInstaller::new("bundles");
+ let initial = typed_package("test/initial", "1.0.0", "library");
+ let target = typed_package("test/target", "1.0.1", "bundles");
+
+ let mut lib_installer = MockInstaller::new();
+ lib_installer
+ .expect_supports()
+ .times(1)
+ .withf(|package_type| package_type == "library")
+ .returning(|_| true);
+ let expected_initial = initial.clone();
+ lib_installer
+ .expect_uninstall()
+ .times(1)
+ .withf_st(move |package| same_handle(package, &expected_initial))
+ .returning(|_| Ok(None));
+
+ let mut bundle_installer = MockInstaller::new();
+ bundle_installer
+ .expect_supports()
+ .times(2)
+ .returning(|arg| arg == "bundles");
+ let expected_target = target.clone();
+ bundle_installer
+ .expect_install()
+ .times(1)
+ .withf_st(move |package| same_handle(package, &expected_target))
+ .returning(|_| Ok(None));
+
let mut manager =
shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None);
manager.add_installer(Box::new(lib_installer));
manager.add_installer(Box::new(bundle_installer));
- let initial = typed_package("test/initial", "1.0.0", "library");
- let target = typed_package("test/target", "1.0.1", "bundles");
let operation = UpdateOperation::new(initial.clone(), target.clone());
let mut repository = InstalledArrayRepository::new().unwrap();
run(manager.update(&mut repository, &operation));
-
- // The lib installer uninstalls the initial package once.
- assert_eq!(lib_calls.borrow().uninstall.len(), 1);
- assert!(same_handle(&lib_calls.borrow().uninstall[0], &initial));
-
- // The bundle installer installs the target package once.
- assert_eq!(bundle_calls.borrow().install.len(), 1);
- assert!(same_handle(&bundle_calls.borrow().install[0], &target));
}
#[test]
fn test_uninstall() {
let set_up = set_up();
- let (installer, calls) = CountingInstaller::new("library");
+ let package = get_package("test/pkg", "1.0.0");
+
+ let mut installer = MockInstaller::new();
+ installer
+ .expect_supports()
+ .times(1)
+ .withf(|package_type| package_type == "library")
+ .returning(|_| true);
+ let expected = package.clone();
+ installer
+ .expect_uninstall()
+ .times(1)
+ .withf_st(move |package| same_handle(package, &expected))
+ .returning(|_| Ok(None));
+
let mut manager =
shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None);
manager.add_installer(Box::new(installer));
- let package = get_package("test/pkg", "1.0.0");
let operation = UninstallOperation::new(package.clone());
let mut repository = InstalledArrayRepository::new().unwrap();
run(manager.uninstall(&mut repository, &operation));
-
- assert_eq!(calls.borrow().supports_args, vec!["library".to_string()]);
- assert_eq!(calls.borrow().uninstall.len(), 1);
- assert!(same_handle(&calls.borrow().uninstall[0], &package));
}
#[test]