From e97dc6e64c1be4bf78c420b11cec2a18dfd506d4 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 28 Jun 2026 14:30:41 +0900 Subject: 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) --- .../tests/installer/installation_manager_test.rs | 195 ++++++++------- .../tests/installer/library_installer_test.rs | 265 ++++++++++++--------- crates/shirabe/tests/installer/main.rs | 2 - 3 files changed, 272 insertions(+), 190 deletions(-) (limited to 'crates/shirabe/tests/installer') 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, - install: Vec, - uninstall: Vec, - 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>, -} - -impl CountingInstaller { - fn new(supported_type: &str) -> (Self, Rc>) { - 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>; + fn update( + &mut self, + initial: PackageInterfaceHandle, + target: PackageInterfaceHandle, + ) -> anyhow::Result>; + fn uninstall( + &mut self, + package: PackageInterfaceHandle, + ) -> anyhow::Result>; } } #[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> { - 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> { - 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> { - 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 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] diff --git a/crates/shirabe/tests/installer/library_installer_test.rs b/crates/shirabe/tests/installer/library_installer_test.rs index f519764..b91ec51 100644 --- a/crates/shirabe/tests/installer/library_installer_test.rs +++ b/crates/shirabe/tests/installer/library_installer_test.rs @@ -11,19 +11,87 @@ use shirabe::composer::{ ComposerHandle, PartialComposerHandle, PartialComposerWeakHandle, PartialOrFullComposer, }; use shirabe::config::Config; -use shirabe::downloader::DownloadManager; -use shirabe::installer::{InstallerInterface, LibraryInstaller}; +use shirabe::downloader::{DownloadManagerInterface, DownloaderInterface}; +use shirabe::installer::{BinaryInstallerInterface, InstallerInterface, LibraryInstaller}; use shirabe::io::IOInterface; use shirabe::io::null_io::NullIO; +use shirabe::package::PackageInterfaceHandle; use shirabe::repository::InstalledArrayRepository; use shirabe::repository::RepositoryInterface; use shirabe::repository::WritableRepositoryInterface; use shirabe::util::filesystem::Filesystem; use shirabe_php_shim::PhpMixed; -use crate::downloader_stub::{DownloaderCall, DownloaderStub}; use crate::test_case::get_package; +// PHP mocks `Composer\Downloader\DownloadManager` with getMockBuilder and asserts its +// install/update/remove calls; here the equivalent mock is injected into the Composer via +// setDownloadManager. +mockall::mock! { + #[derive(Debug)] + pub DownloadManager {} + #[async_trait::async_trait(?Send)] + impl DownloadManagerInterface for DownloadManager { + fn set_prefer_source(&mut self, prefer_source: bool); + fn set_prefer_dist(&mut self, prefer_dist: bool); + fn get_downloader_for_package( + &self, + package: PackageInterfaceHandle, + ) -> anyhow::Result>>>; + async fn download( + &self, + package: PackageInterfaceHandle, + target_dir: &str, + prev_package: Option, + ) -> anyhow::Result>; + async fn prepare( + &self, + r#type: &str, + package: PackageInterfaceHandle, + target_dir: &str, + prev_package: Option, + ) -> anyhow::Result>; + async fn install( + &self, + package: PackageInterfaceHandle, + target_dir: &str, + ) -> anyhow::Result>; + async fn update( + &self, + initial: PackageInterfaceHandle, + target: PackageInterfaceHandle, + target_dir: &str, + ) -> anyhow::Result>; + async fn remove( + &self, + package: PackageInterfaceHandle, + target_dir: &str, + ) -> anyhow::Result>; + async fn cleanup( + &self, + r#type: &str, + package: PackageInterfaceHandle, + target_dir: &str, + prev_package: Option, + ) -> anyhow::Result>; + } +} + +// PHP mocks `Composer\Installer\BinaryInstaller`; the Rust seam is `BinaryInstallerInterface`. +mockall::mock! { + #[derive(Debug)] + pub BinaryInstaller {} + impl BinaryInstallerInterface for BinaryInstaller { + fn install_binaries( + &mut self, + package: PackageInterfaceHandle, + install_path: &str, + warn_on_overwrite: bool, + ); + fn remove_binaries(&mut self, package: PackageInterfaceHandle); + } +} + fn run(future: F) -> F::Output { tokio::runtime::Builder::new_current_thread() .build() @@ -31,10 +99,10 @@ fn run(future: F) -> F::Output { .block_on(future) } -/// Mirror of setUp(): builds the Composer/Config over temp root/vendor/bin dirs -/// plus a real DownloadManager and a NullIO. The `_composer_rc` keeps the inner -/// Rc alive for the duration of the test since LibraryInstaller only holds a weak -/// handle. +/// Mirror of setUp(): builds the Composer/Config over temp root/vendor/bin dirs plus a NullIO and a +/// DownloadManager mock. `composer_full` keeps the inner Rc alive for the duration of the test since +/// LibraryInstaller only holds a weak handle, and lets tests swap in a configured DownloadManager +/// mock before constructing the installer. struct SetUp { root: TempDir, vendor_dir: String, @@ -42,10 +110,16 @@ struct SetUp { io: Rc>, composer: PartialComposerWeakHandle, fs: Filesystem, - /// Recorded downloader operations forwarded by the LibraryInstaller, mirroring the - /// PHP DownloadManager mock's expectations on install/update/remove. - downloader_calls: Rc>>, - _composer_rc: ComposerHandle, + composer_full: ComposerHandle, +} + +/// Replaces the Composer's DownloadManager with the given mock. Must be called before +/// `LibraryInstaller::new`, which captures the manager at construction time. +fn set_download_manager(setup: &SetUp, dm: MockDownloadManager) { + setup + .composer_full + .borrow_mut() + .set_download_manager(Rc::new(RefCell::new(dm))); } fn set_up() -> SetUp { @@ -77,16 +151,15 @@ fn set_up() -> SetUp { let io: Rc> = Rc::new(RefCell::new(NullIO::new())); - let mut dm = DownloadManager::new(io.clone(), false, None); - let downloader = DownloaderStub::new(); - let downloader_calls = downloader.calls(); - dm.set_downloader("fake", Rc::new(RefCell::new(downloader))); - let dm_rc = Rc::new(RefCell::new(dm)); - let composer_rc = Rc::new(RefCell::new(PartialOrFullComposer::new_full())); let composer = ComposerHandle::from_rc_unchecked(composer_rc.clone()); composer.borrow_mut().set_config(config_rc); - composer.borrow_mut().set_download_manager(dm_rc); + // Default unconfigured mock so LibraryInstaller::new can resolve a DownloadManager even in + // tests that exercise no downloader call; tests that assert calls install their own via + // set_download_manager. + composer + .borrow_mut() + .set_download_manager(Rc::new(RefCell::new(MockDownloadManager::new()))); let weak = PartialComposerHandle::from_rc(composer_rc).downgrade(); @@ -97,21 +170,10 @@ fn set_up() -> SetUp { io, composer: weak, fs, - downloader_calls, - _composer_rc: composer, + composer_full: composer, } } -/// Builds a `dist`-installable test package backed by the `fake` downloader stub, so the -/// real DownloadManager dispatches install/update/remove to the recording stub instead of -/// erroring on a package with no installation source. -fn get_installable_package(name: &str, version: &str) -> shirabe::package::PackageInterfaceHandle { - let package = get_package(name, version); - package.set_installation_source(Some("dist".to_string())); - package.set_dist_type(Some("fake".to_string())); - package -} - fn tear_down(setup: &mut SetUp) { let root = setup.root.path().to_path_buf(); setup.fs.remove_directory(&root).ok(); @@ -167,24 +229,29 @@ fn test_is_installed() { #[test] fn test_install() { let mut setup = set_up(); + let package = get_package("some/package", "1.0.0"); + + // PHP asserts the DownloadManager mock's install() is called once with + // ($package, vendorDir/some/package). + let mut dm = MockDownloadManager::new(); + let expected_package = package.clone(); + let expected_path = format!("{}/some/package", setup.vendor_dir); + dm.expect_install() + .times(1) + .withf_st(move |package, target_dir| { + Rc::ptr_eq(package.as_rc(), expected_package.as_rc()) + && target_dir == expected_path.as_str() + }) + .returning(|_, _| Ok(None)); + set_download_manager(&setup, dm); + let mut library = LibraryInstaller::new(setup.io.clone(), setup.composer.clone(), None, None, None); - let package = get_installable_package("some/package", "1.0.0"); - // PHP asserts the DownloadManager mock's install() is called once with - // ($package, vendorDir/some/package); here the recording downloader stub - // captures the forwarded call instead. let mut repository = InstalledArrayRepository::new().unwrap(); run(library.install(&mut repository, package.clone())).unwrap(); - assert_eq!( - vec![DownloaderCall::Install { - package: "some/package".to_string(), - path: format!("{}/some/package", setup.vendor_dir), - }], - *setup.downloader_calls.borrow() - ); // PHP asserts repository->addPackage was called once with $package. assert!(repository.has_package(package)); @@ -204,8 +271,8 @@ fn test_install() { fn test_update() { let mut setup = set_up(); - let initial = get_installable_package("vendor/package1", "1.0.0"); - let target = get_installable_package("vendor/package1", "2.0.0"); + let initial = get_package("vendor/package1", "1.0.0"); + let target = get_package("vendor/package1", "2.0.0"); initial.__set_target_dir(Some("oldtarget".to_string())); target.__set_target_dir(Some("newtarget".to_string())); @@ -217,6 +284,22 @@ fn test_update() { let new_target_dir = format!("{}/vendor/package1/newtarget", setup.vendor_dir); fs::create_dir_all(&old_target_dir).unwrap(); + // PHP asserts the DownloadManager mock's update() is called once with + // ($initial, $target, vendorDir/vendor/package1/newtarget). + let mut dm = MockDownloadManager::new(); + let expected_initial = initial.clone(); + let expected_target = target.clone(); + let expected_path = new_target_dir.clone(); + dm.expect_update() + .times(1) + .withf_st(move |initial, target, target_dir| { + Rc::ptr_eq(initial.as_rc(), expected_initial.as_rc()) + && Rc::ptr_eq(target.as_rc(), expected_target.as_rc()) + && target_dir == expected_path.as_str() + }) + .returning(|_, _, _| Ok(None)); + set_download_manager(&setup, dm); + let mut repository = InstalledArrayRepository::new().unwrap(); repository.add_package(initial.clone()).unwrap(); @@ -232,14 +315,6 @@ fn test_update() { ); assert!(!std::path::Path::new(&old_target_dir).exists()); - assert_eq!( - vec![DownloaderCall::Update { - initial: "vendor/package1".to_string(), - target: "vendor/package1".to_string(), - path: new_target_dir.clone(), - }], - *setup.downloader_calls.borrow() - ); assert!(!repository.has_package(initial.clone())); assert!(repository.has_package(target.clone())); @@ -261,9 +336,24 @@ fn test_update() { #[test] fn test_uninstall() { let mut setup = set_up(); + let package = get_package("vendor/pkg", "1.0.0"); + + // PHP asserts the DownloadManager mock's remove() is called once with + // ($package, vendorDir/vendor/pkg). + let mut dm = MockDownloadManager::new(); + let expected_package = package.clone(); + let expected_path = format!("{}/vendor/pkg", setup.vendor_dir); + dm.expect_remove() + .times(1) + .withf_st(move |package, target_dir| { + Rc::ptr_eq(package.as_rc(), expected_package.as_rc()) + && target_dir == expected_path.as_str() + }) + .returning(|_, _| Ok(None)); + set_download_manager(&setup, dm); + let mut library = LibraryInstaller::new(setup.io.clone(), setup.composer.clone(), None, None, None); - let package = get_installable_package("vendor/pkg", "1.0.0"); // PHP mocks hasPackage to return (true, false) over two calls; a real repository // seeded with the package reproduces this naturally: present, then absent after @@ -273,13 +363,6 @@ fn test_uninstall() { run(library.uninstall(&mut repository, package.clone())).unwrap(); - assert_eq!( - vec![DownloaderCall::Remove { - package: "vendor/pkg".to_string(), - path: format!("{}/vendor/pkg", setup.vendor_dir), - }], - *setup.downloader_calls.borrow() - ); assert!(!repository.has_package(package.clone())); // Uninstalling again, with the package no longer installed, fails. @@ -323,42 +406,9 @@ fn test_get_install_path_with_target_dir() { tear_down(&mut setup); } -/// Records the calls a `BinaryInstaller` double receives, standing in for the PHPUnit mock that -/// asserts `removeBinaries` is never called and `installBinaries` is called once. -#[derive(Debug, Default)] -struct BinaryInstallerCalls { - install_binaries: Vec<(shirabe::package::PackageInterfaceHandle, String, bool)>, - remove_binaries: Vec, -} - -#[derive(Debug)] -struct RecordingBinaryInstaller { - calls: Rc>, -} - -impl shirabe::installer::BinaryInstallerInterface for RecordingBinaryInstaller { - fn install_binaries( - &mut self, - package: shirabe::package::PackageInterfaceHandle, - install_path: &str, - warn_on_overwrite: bool, - ) { - self.calls.borrow_mut().install_binaries.push(( - package, - install_path.to_string(), - warn_on_overwrite, - )); - } - - fn remove_binaries(&mut self, package: shirabe::package::PackageInterfaceHandle) { - self.calls.borrow_mut().remove_binaries.push(package); - } -} - #[test] fn test_ensure_binaries_installed() { let mut setup = set_up(); - let calls = Rc::new(RefCell::new(BinaryInstallerCalls::default())); let mut library = LibraryInstaller::new( setup.io.clone(), setup.composer.clone(), @@ -366,26 +416,27 @@ fn test_ensure_binaries_installed() { None, None, ); - library.__set_binary_installer(std::rc::Rc::new(std::cell::RefCell::new( - RecordingBinaryInstaller { - calls: calls.clone(), - }, - ))); let package = get_package("foo/bar", "1.0.0"); let expected_path = library.get_install_path(package.clone()).unwrap(); - library.ensure_binaries_presence(package.clone()); - - let recorded = calls.borrow(); + let mut binary_installer = MockBinaryInstaller::new(); // PHP asserts removeBinaries is never called. - assert!(recorded.remove_binaries.is_empty()); + binary_installer.expect_remove_binaries().times(0); // PHP asserts installBinaries is called once with ($package, getInstallPath, false). - assert_eq!(recorded.install_binaries.len(), 1); - let (recorded_package, recorded_path, warn_on_overwrite) = &recorded.install_binaries[0]; - assert!(Rc::ptr_eq(recorded_package.as_rc(), package.as_rc())); - assert_eq!(recorded_path, &expected_path); - assert!(!warn_on_overwrite); - drop(recorded); + let expected_package = package.clone(); + let expected_install_path = expected_path.clone(); + binary_installer + .expect_install_binaries() + .times(1) + .withf_st(move |package, install_path, warn_on_overwrite| { + Rc::ptr_eq(package.as_rc(), expected_package.as_rc()) + && install_path == expected_install_path.as_str() + && !*warn_on_overwrite + }) + .returning(|_, _, _| ()); + library.__set_binary_installer(Rc::new(RefCell::new(binary_installer))); + + library.ensure_binaries_presence(package.clone()); tear_down(&mut setup); } diff --git a/crates/shirabe/tests/installer/main.rs b/crates/shirabe/tests/installer/main.rs index a5ca671..4b42278 100644 --- a/crates/shirabe/tests/installer/main.rs +++ b/crates/shirabe/tests/installer/main.rs @@ -1,5 +1,3 @@ -#[path = "../common/downloader_stub.rs"] -mod downloader_stub; #[path = "../common/io_mock.rs"] mod io_mock; #[path = "../common/test_case.rs"] -- cgit v1.3.1