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/tests/package/version/version_selector_test.rs | |
| 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/tests/package/version/version_selector_test.rs')
| -rw-r--r-- | crates/shirabe/tests/package/version/version_selector_test.rs | 521 |
1 files changed, 495 insertions, 26 deletions
diff --git a/crates/shirabe/tests/package/version/version_selector_test.rs b/crates/shirabe/tests/package/version/version_selector_test.rs index fa288cd..f48a6ce 100644 --- a/crates/shirabe/tests/package/version/version_selector_test.rs +++ b/crates/shirabe/tests/package/version/version_selector_test.rs @@ -1,77 +1,546 @@ //! ref: composer/tests/Composer/Test/Package/Version/VersionSelectorTest.php -// VersionSelector ranks candidate packages whose versions/constraints are parsed through a -// look-around regex the regex crate cannot compile; the setup also mocks a repository. +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::filter::platform_requirement_filter::PlatformRequirementFilterFactory; +use shirabe::io::BufferIO; +use shirabe::io::IOInterface; +use shirabe::package::BasePackageHandle; +use shirabe::package::CompleteAliasPackageHandle; +use shirabe::package::CompletePackageHandle; +use shirabe::package::Link; +use shirabe::package::PackageInterfaceHandle; +use shirabe::package::version::VersionSelector; +use shirabe::package::version::version_parser::VersionParser; +use shirabe::repository::PlatformRepository; +use shirabe::repository::RepositorySetInterface; +use shirabe_php_shim::PhpMixed; +use shirabe_semver::constraint::AnyConstraint; + +use shirabe_external_packages::symfony::console::output::output_interface; + +use crate::test_case::get_package; + +mockall::mock! { + RepositorySet {} + impl RepositorySetInterface for RepositorySet { + fn find_packages( + &self, + name: &str, + constraint: Option<AnyConstraint>, + flags: i64, + ) -> anyhow::Result<Vec<BasePackageHandle>>; + } +} + +// `RepositorySetInterface` requires `Debug`; mockall does not generate it. +impl std::fmt::Debug for MockRepositorySet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("MockRepositorySet") + } +} + +fn into_seam(mock: MockRepositorySet) -> Rc<RefCell<dyn RepositorySetInterface>> { + Rc::new(RefCell::new(mock)) +} + +/// Mirrors PHPUnit `assertSame($expected, $best)`: object identity, not value equality. +fn assert_same( + best: &Option<PackageInterfaceHandle>, + expected: &PackageInterfaceHandle, + msg: &str, +) { + let best = best + .as_ref() + .unwrap_or_else(|| panic!("{msg}: expected Some(_), got None")); + assert!(best.ptr_eq(expected), "{msg}"); +} + +fn require_link(package_name: &str, target: &str, pretty_constraint: &str) -> Link { + let parser = VersionParser::new(); + Link::new( + package_name.to_string(), + target.to_string(), + parser.parse_constraints(pretty_constraint).unwrap(), + Some(Link::TYPE_REQUIRE.to_string()), + pretty_constraint.to_string(), + ) +} + +fn find_best( + version_selector: &mut VersionSelector, + package_name: &str, + preferred_stability: &str, + platform_requirement_filter: Option< + std::rc::Rc< + dyn shirabe::filter::platform_requirement_filter::PlatformRequirementFilterInterface, + >, + >, + io: Option<Rc<RefCell<dyn IOInterface>>>, +) -> Option<PackageInterfaceHandle> { + version_selector + .find_best_candidate( + package_name, + None, + preferred_stability, + platform_requirement_filter, + 0, + io, + PhpMixed::Bool(true), + ) + .unwrap() +} + #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_latest_version_is_returned() { - todo!() + let package_name = "foo/bar"; + + let package1 = get_package("foo/bar", "1.2.1"); + let package2 = get_package("foo/bar", "1.2.2"); + let package3 = get_package("foo/bar", "1.2.0"); + let packages = vec![package1.clone(), package2.clone(), package3.clone()]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(1) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = VersionSelector::new(into_seam(repository_set), None).unwrap(); + let best = find_best(&mut version_selector, package_name, "stable", None, None); + + // 1.2.2 should be returned because it's the latest of the returned versions + assert_same(&best, &package2, "Latest version should be 1.2.2"); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] +#[ignore = "PlatformRepository initialization calls shirabe_php_shim::runtime::constant() which is still todo!(); unrelated to the RepositorySet seam"] fn test_latest_version_is_returned_that_matches_php_requirements() { - todo!() + let package_name = "foo/bar"; + + let mut overrides: IndexMap<String, PhpMixed> = IndexMap::new(); + overrides.insert("php".to_string(), PhpMixed::String("5.5.0".to_string())); + let mut platform = PlatformRepository::new(vec![], overrides).unwrap(); + + let package0 = get_package("foo/bar", "0.9.0"); + package0.__set_requires(IndexMap::from([( + "php".to_string(), + require_link(package_name, "php", ">=5.6"), + )])); + let package1 = get_package("foo/bar", "1.0.0"); + package1.__set_requires(IndexMap::from([( + "php".to_string(), + require_link(package_name, "php", ">=5.4"), + )])); + let package2 = get_package("foo/bar", "2.0.0"); + package2.__set_requires(IndexMap::from([( + "php".to_string(), + require_link(package_name, "php", ">=5.6"), + )])); + let package3 = get_package("foo/bar", "2.1.0"); + package3.__set_requires(IndexMap::from([( + "php".to_string(), + require_link(package_name, "php", ">=5.6"), + )])); + let packages = vec![ + package0.clone(), + package1.clone(), + package2.clone(), + package3.clone(), + ]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(3) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = + VersionSelector::new(into_seam(repository_set), Some(&mut platform)).unwrap(); + + let io = Rc::new(RefCell::new( + BufferIO::new(String::new(), output_interface::VERBOSITY_NORMAL, None).unwrap(), + )); + let io_dyn: Rc<RefCell<dyn IOInterface>> = io.clone(); + let best = find_best( + &mut version_selector, + package_name, + "stable", + None, + Some(io_dyn), + ); + assert_same( + &best, + &package1, + "Latest version supporting php 5.5 should be returned (1.0.0)", + ); + assert_eq!( + "<warning>Cannot use foo/bar's latest version 2.1.0 as it requires php >=5.6 which is not satisfied by your platform.\n", + io.borrow().get_output() + ); + + let io = Rc::new(RefCell::new( + BufferIO::new(String::new(), output_interface::VERBOSITY_VERBOSE, None).unwrap(), + )); + let io_dyn: Rc<RefCell<dyn IOInterface>> = io.clone(); + let best = find_best( + &mut version_selector, + package_name, + "stable", + None, + Some(io_dyn), + ); + assert_same( + &best, + &package1, + "Latest version supporting php 5.5 should be returned (1.0.0)", + ); + assert_eq!( + "<warning>Cannot use foo/bar's latest version 2.1.0 as it requires php >=5.6 which is not satisfied by your platform.\n\ + <warning>Cannot use foo/bar 2.0.0 as it requires php >=5.6 which is not satisfied by your platform.\n", + io.borrow().get_output() + ); + + let best = find_best( + &mut version_selector, + package_name, + "stable", + Some(PlatformRequirementFilterFactory::ignore_all()), + None, + ); + assert_same( + &best, + &package3, + "Latest version should be returned when ignoring platform reqs (2.1.0)", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] +#[ignore = "PlatformRepository initialization calls shirabe_php_shim::runtime::constant() which is still todo!(); unrelated to the RepositorySet seam"] fn test_latest_version_is_returned_that_matches_ext_requirements() { - todo!() + let package_name = "foo/bar"; + + let mut overrides: IndexMap<String, PhpMixed> = IndexMap::new(); + overrides.insert("ext-zip".to_string(), PhpMixed::String("5.3.0".to_string())); + let mut platform = PlatformRepository::new(vec![], overrides).unwrap(); + + let package1 = get_package("foo/bar", "1.0.0"); + package1.__set_requires(IndexMap::from([( + "ext-zip".to_string(), + require_link(package_name, "ext-zip", "^5.2"), + )])); + let package2 = get_package("foo/bar", "2.0.0"); + package2.__set_requires(IndexMap::from([( + "ext-zip".to_string(), + require_link(package_name, "ext-zip", "^5.4"), + )])); + let packages = vec![package1.clone(), package2.clone()]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(2) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = + VersionSelector::new(into_seam(repository_set), Some(&mut platform)).unwrap(); + + let best = find_best(&mut version_selector, package_name, "stable", None, None); + assert_same( + &best, + &package1, + "Latest version supporting ext-zip 5.3.0 should be returned (1.0.0)", + ); + let best = find_best( + &mut version_selector, + package_name, + "stable", + Some(PlatformRequirementFilterFactory::ignore_all()), + None, + ); + assert_same( + &best, + &package2, + "Latest version should be returned when ignoring platform reqs (2.0.0)", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] +#[ignore = "PlatformRepository initialization calls shirabe_php_shim::runtime::constant() which is still todo!(); unrelated to the RepositorySet seam"] fn test_latest_version_is_returned_that_matches_platform_ext() { - todo!() + let package_name = "foo/bar"; + + let mut platform = PlatformRepository::new(vec![], IndexMap::new()).unwrap(); + + let package1 = get_package("foo/bar", "1.0.0"); + let package2 = get_package("foo/bar", "2.0.0"); + package2.__set_requires(IndexMap::from([( + "ext-barfoo".to_string(), + require_link(package_name, "ext-barfoo", "*"), + )])); + let packages = vec![package1.clone(), package2.clone()]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(2) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = + VersionSelector::new(into_seam(repository_set), Some(&mut platform)).unwrap(); + + let best = find_best(&mut version_selector, package_name, "stable", None, None); + assert_same( + &best, + &package1, + "Latest version not requiring ext-barfoo should be returned (1.0.0)", + ); + let best = find_best( + &mut version_selector, + package_name, + "stable", + Some(PlatformRequirementFilterFactory::ignore_all()), + None, + ); + assert_same( + &best, + &package2, + "Latest version should be returned when ignoring platform reqs (2.0.0)", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] +#[ignore = "PlatformRepository initialization calls shirabe_php_shim::runtime::constant() which is still todo!(); unrelated to the RepositorySet seam"] fn test_latest_version_is_returned_that_matches_composer_requirements() { - todo!() + let package_name = "foo/bar"; + + let mut overrides: IndexMap<String, PhpMixed> = IndexMap::new(); + overrides.insert( + "composer-runtime-api".to_string(), + PhpMixed::String("1.0.0".to_string()), + ); + let mut platform = PlatformRepository::new(vec![], overrides).unwrap(); + + let package1 = get_package("foo/bar", "1.0.0"); + package1.__set_requires(IndexMap::from([( + "composer-runtime-api".to_string(), + require_link(package_name, "composer-runtime-api", "^1.0"), + )])); + let package2 = get_package("foo/bar", "1.1.0"); + package2.__set_requires(IndexMap::from([( + "composer-runtime-api".to_string(), + require_link(package_name, "composer-runtime-api", "^2.0"), + )])); + let packages = vec![package1.clone(), package2.clone()]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(2) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = + VersionSelector::new(into_seam(repository_set), Some(&mut platform)).unwrap(); + + let best = find_best(&mut version_selector, package_name, "stable", None, None); + assert_same( + &best, + &package1, + "Latest version supporting composer 1 should be returned (1.0.0)", + ); + let best = find_best( + &mut version_selector, + package_name, + "stable", + Some(PlatformRequirementFilterFactory::ignore_all()), + None, + ); + assert_same( + &best, + &package2, + "Latest version should be returned when ignoring platform reqs (1.1.0)", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_most_stable_version_is_returned() { - todo!() + let package_name = "foo/bar"; + + let package1 = get_package("foo/bar", "1.0.0"); + let package2 = get_package("foo/bar", "1.1.0-beta"); + let packages = vec![package1.clone(), package2.clone()]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(1) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = VersionSelector::new(into_seam(repository_set), None).unwrap(); + let best = find_best(&mut version_selector, package_name, "stable", None, None); + + assert_same( + &best, + &package1, + "Latest most stable version should be returned (1.0.0)", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages with willReturnOnConsecutiveCalls; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_most_stable_version_is_returned_regardless_of_order() { - todo!() + let package_name = "foo/bar"; + + let package1 = get_package("foo/bar", "2.x-dev"); + let package2 = get_package("foo/bar", "2.0.0-beta3"); + let packages = vec![package1.clone(), package2.clone()]; + let reversed: Vec<PackageInterfaceHandle> = packages.iter().rev().cloned().collect(); + + let mut repository_set = MockRepositorySet::new(); + let mut seq = mockall::Sequence::new(); + repository_set + .expect_find_packages() + .times(1) + .in_sequence(&mut seq) + .returning_st(move |_, _, _| Ok(packages.clone())); + repository_set + .expect_find_packages() + .times(1) + .in_sequence(&mut seq) + .returning_st(move |_, _, _| Ok(reversed.clone())); + + let mut version_selector = VersionSelector::new(into_seam(repository_set), None).unwrap(); + let best = find_best(&mut version_selector, package_name, "stable", None, None); + assert_same( + &best, + &package2, + "Expecting 2.0.0-beta3, cause beta is more stable than dev", + ); + + let best = find_best(&mut version_selector, package_name, "stable", None, None); + assert_same( + &best, + &package2, + "Expecting 2.0.0-beta3, cause beta is more stable than dev", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_highest_version_is_returned() { - todo!() + let package_name = "foo/bar"; + + let package1 = get_package("foo/bar", "1.0.0"); + let package2 = get_package("foo/bar", "1.1.0-beta"); + let packages = vec![package1.clone(), package2.clone()]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(1) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = VersionSelector::new(into_seam(repository_set), None).unwrap(); + let best = find_best(&mut version_selector, package_name, "dev", None, None); + + assert_same( + &best, + &package2, + "Latest version should be returned (1.1.0-beta)", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_highest_version_matching_stability_is_returned() { - todo!() + let package_name = "foo/bar"; + + let package1 = get_package("foo/bar", "1.0.0"); + let package2 = get_package("foo/bar", "1.1.0-beta"); + let package3 = get_package("foo/bar", "1.2.0-alpha"); + let packages = vec![package1.clone(), package2.clone(), package3.clone()]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(1) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = VersionSelector::new(into_seam(repository_set), None).unwrap(); + let best = find_best(&mut version_selector, package_name, "beta", None, None); + + assert_same( + &best, + &package2, + "Latest version should be returned (1.1.0-beta)", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_most_stable_unstable_version_is_returned() { - todo!() + let package_name = "foo/bar"; + + let package2 = get_package("foo/bar", "1.1.0-beta"); + let package3 = get_package("foo/bar", "1.2.0-alpha"); + let packages = vec![package2.clone(), package3.clone()]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(1) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = VersionSelector::new(into_seam(repository_set), None).unwrap(); + let best = find_best(&mut version_selector, package_name, "stable", None, None); + + assert_same( + &best, + &package2, + "Latest version should be returned (1.1.0-beta)", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return fixed package objects; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_default_branch_alias_is_never_returned() { - todo!() + let package_name = "foo/bar"; + + let package = get_package("foo/bar", "1.1.0-beta"); + let package2 = get_package("foo/bar", "dev-main"); + let package2_complete = CompletePackageHandle::from_rc_unchecked(package2.as_rc().clone()); + let package2_alias: PackageInterfaceHandle = CompleteAliasPackageHandle::new( + package2_complete, + VersionParser::DEFAULT_BRANCH_ALIAS.to_string(), + VersionParser::DEFAULT_BRANCH_ALIAS.to_string(), + ) + .into(); + let packages = vec![package.clone(), package2_alias.clone()]; + + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(1) + .returning_st(move |_, _, _| Ok(packages.clone())); + + let mut version_selector = VersionSelector::new(into_seam(repository_set), None).unwrap(); + let best = find_best(&mut version_selector, package_name, "dev", None, None); + + assert_same( + &best, + &package2, + "Latest version should be returned (dev-main)", + ); } #[test] -#[ignore = "requires mocking RepositorySet::find_packages to return an empty list; RepositorySet is a concrete struct with no injectable/overridable find_packages"] fn test_false_returned_on_no_packages() { - todo!() + let mut repository_set = MockRepositorySet::new(); + repository_set + .expect_find_packages() + .times(1) + .returning_st(|_, _, _| Ok(vec![])); + + let mut version_selector = VersionSelector::new(into_seam(repository_set), None).unwrap(); + let best = find_best(&mut version_selector, "foobaz", "stable", None, None); + assert!(best.is_none(), "No versions are available returns false"); } #[test] |
