From 822d9a872807a92a5337ee8b7bab96dc9845cdbb Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 22 Jun 2026 02:09:59 +0900 Subject: test: port more test cases Co-Authored-By: Claude Opus 4.8 (1M context) --- .../dependency_resolver/default_policy_test.rs | 523 +++++++++++++++++++-- .../tests/dependency_resolver/pool_builder_test.rs | 2 +- .../dependency_resolver/pool_optimizer_test.rs | 263 ++++++++++- .../tests/dependency_resolver/rule_set_test.rs | 60 ++- .../shirabe/tests/dependency_resolver/rule_test.rs | 81 +++- .../security_advisory_pool_filter_test.rs | 89 +++- .../tests/dependency_resolver/solver_test.rs | 521 +++++++++++++++++--- .../tests/dependency_resolver/transaction_test.rs | 5 +- 8 files changed, 1397 insertions(+), 147 deletions(-) (limited to 'crates/shirabe/tests/dependency_resolver') diff --git a/crates/shirabe/tests/dependency_resolver/default_policy_test.rs b/crates/shirabe/tests/dependency_resolver/default_policy_test.rs index 077dd71..776de6c 100644 --- a/crates/shirabe/tests/dependency_resolver/default_policy_test.rs +++ b/crates/shirabe/tests/dependency_resolver/default_policy_test.rs @@ -1,17 +1,23 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php use indexmap::IndexMap; +use shirabe::dependency_resolver::PolicyInterface; use shirabe::dependency_resolver::default_policy::DefaultPolicy; +use shirabe::package::handle::{CompleteAliasPackageHandle, CompletePackageHandle}; use shirabe::repository::array_repository::ArrayRepository; +use shirabe::repository::handle::{LockArrayRepositoryHandle, RepositoryInterfaceHandle}; use shirabe::repository::lock_array_repository::LockArrayRepository; use shirabe::repository::repository_set::RepositorySet; use shirabe::util::platform::Platform; +use shirabe_semver::constraint::{AnyConstraint, SimpleConstraint}; + +use crate::test_case::get_package; #[allow(dead_code)] struct Fixtures { repository_set: RepositorySet, repo: ArrayRepository, - repo_locked: LockArrayRepository, + repo_locked: LockArrayRepositoryHandle, policy: DefaultPolicy, } @@ -25,7 +31,7 @@ fn set_up() -> Fixtures { IndexMap::new(), ); let repo = ArrayRepository::new(vec![]).unwrap(); - let repo_locked = LockArrayRepository::new(vec![]).unwrap(); + let repo_locked = LockArrayRepositoryHandle::new(LockArrayRepository::new(vec![]).unwrap()); let policy = DefaultPolicy::new(false, false, None); @@ -48,141 +54,558 @@ impl Drop for TearDown { } } -// These build a Pool from packages and exercise DefaultPolicy::selectPreferredPackages. -// Constructing the packages/constraints parses versions through a look-around regex the -// regex crate cannot compile, and the setup mirrors the solver fixtures. +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_single() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a = get_package("A", "1.0"); + fixtures.repo.add_package(package_a.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a.get_id()]; + let expected = vec![package_a.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0"); + let package_a2 = get_package("A", "2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a2.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_picks_latest() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.0.1-alpha"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a2.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_picks_latest_stable_with_prefer_stable() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.0.1-alpha"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a1.get_id()]; + + let policy = DefaultPolicy::new(true, false, None); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_lowest_with_prefer_dev_over_prerelease() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + + for stability in ["alpha1", "beta1", "RC1"] { + let mut fixtures = set_up(); + + Platform::put_env("COMPOSER_PREFER_DEV_OVER_PRERELEASE", "1"); + let dev_package = get_package("A", "dev-master"); + let prerelease_package = get_package("A", &format!("1.0.0-{}", stability)); + fixtures.repo.add_package(dev_package.clone()).unwrap(); + fixtures + .repo + .add_package(prerelease_package.clone()) + .unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![dev_package.get_id(), prerelease_package.get_id()]; + let expected = vec![dev_package.get_id()]; + + let policy = DefaultPolicy::new(true, true, None); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); + } } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_lowest_prefers_prerelease_over_dev() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + + for stability in ["alpha1", "beta1", "RC1"] { + let mut fixtures = set_up(); + + let dev_package = get_package("A", "dev-master"); + let prerelease_package = get_package("A", &format!("1.0.0-{}", stability)); + fixtures.repo.add_package(dev_package.clone()).unwrap(); + fixtures + .repo + .add_package(prerelease_package.clone()) + .unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![prerelease_package.get_id(), dev_package.get_id()]; + let expected = vec![prerelease_package.get_id()]; + + let policy = DefaultPolicy::new(true, true, None); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); + } } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_lowest_with_prefer_stable_still_prefers_stable() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + Platform::put_env("COMPOSER_PREFER_DEV_OVER_PRERELEASE", "1"); + let stable_package = get_package("A", "1.0.0"); + let dev_package = get_package("A", "dev-master"); + fixtures.repo.add_package(stable_package.clone()).unwrap(); + fixtures.repo.add_package(dev_package.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![stable_package.get_id(), dev_package.get_id()]; + let expected = vec![stable_package.get_id()]; + + let policy = DefaultPolicy::new(true, true, None); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_with_dev_picks_non_dev() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "dev-foo"); + let package_a2 = get_package("A", "1.0.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a2.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_with_preferred_version_picks_preferred_version_if_available() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.1.0"); + let package_a2b = get_package("A", "1.1.0"); + let package_a3 = get_package("A", "1.2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures.repo.add_package(package_a2b.clone()).unwrap(); + fixtures.repo.add_package(package_a3.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![ + package_a1.get_id(), + package_a2.get_id(), + package_a2b.get_id(), + package_a3.get_id(), + ]; + let expected = vec![package_a2.get_id(), package_a2b.get_id()]; + + let mut preferred = IndexMap::new(); + preferred.insert("a".to_string(), "1.1.0.0".to_string()); + let policy = DefaultPolicy::new(false, false, Some(preferred)); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_with_preferred_version_picks_newest_otherwise() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a2.get_id()]; + + let mut preferred = IndexMap::new(); + preferred.insert("a".to_string(), "1.1.0.0".to_string()); + let policy = DefaultPolicy::new(false, false, Some(preferred)); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_newest_with_preferred_version_picks_lowest_if_prefer_lowest() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let package_a1 = get_package("A", "1.0.0"); + let package_a2 = get_package("A", "1.2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a1.get_id()]; + + let mut preferred = IndexMap::new(); + preferred.insert("a".to_string(), "1.1.0.0".to_string()); + let policy = DefaultPolicy::new(false, true, Some(preferred)); + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_repository_ordering_affects_priority() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let repo1 = ArrayRepository::new(vec![]).unwrap(); + let repo2 = ArrayRepository::new(vec![]).unwrap(); + + let package1 = get_package("A", "1.0"); + let package2 = get_package("A", "1.1"); + let package3 = get_package("A", "1.1"); + let package4 = get_package("A", "1.2"); + repo1.add_package(package1.clone()).unwrap(); + repo1.add_package(package2.clone()).unwrap(); + repo2.add_package(package3.clone()).unwrap(); + repo2.add_package(package4.clone()).unwrap(); + + let repo1_handle = RepositoryInterfaceHandle::new(repo1); + let repo2_handle = RepositoryInterfaceHandle::new(repo2); + + fixtures + .repository_set + .add_repository(repo1_handle.clone()) + .unwrap(); + fixtures + .repository_set + .add_repository(repo2_handle.clone()) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![ + package1.get_id(), + package2.get_id(), + package3.get_id(), + package4.get_id(), + ]; + let expected = vec![package2.get_id()]; + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals.clone(), None); + + assert_eq!(expected, selected); + + let mut repository_set = RepositorySet::new( + "dev", + IndexMap::new(), + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + repository_set.add_repository(repo2_handle).unwrap(); + repository_set.add_repository(repo1_handle).unwrap(); + + let pool = repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let expected = vec![package4.get_id()]; + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_local_repos_first() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let repo_important = ArrayRepository::new(vec![]).unwrap(); + + let package_a = get_package("A", "dev-master"); + let package_a_alias = CompleteAliasPackageHandle::new( + CompletePackageHandle::from_rc_unchecked(package_a.as_rc().clone()), + "2.1.9999999.9999999-dev".to_string(), + "2.1.x-dev".to_string(), + ); + let package_a_important = get_package("A", "dev-feature-a"); + let package_a_alias_important = CompleteAliasPackageHandle::new( + CompletePackageHandle::from_rc_unchecked(package_a_important.as_rc().clone()), + "2.1.9999999.9999999-dev".to_string(), + "2.1.x-dev".to_string(), + ); + let package_a2_important = get_package("A", "dev-master"); + let package_a2_alias_important = CompleteAliasPackageHandle::new( + CompletePackageHandle::from_rc_unchecked(package_a2_important.as_rc().clone()), + "2.1.9999999.9999999-dev".to_string(), + "2.1.x-dev".to_string(), + ); + package_a_alias_important.set_root_package_alias(true); + + fixtures.repo.add_package(package_a).unwrap(); + fixtures + .repo + .add_package(package_a_alias.clone().into()) + .unwrap(); + repo_important.add_package(package_a_important).unwrap(); + repo_important + .add_package(package_a_alias_important.clone().into()) + .unwrap(); + repo_important.add_package(package_a2_important).unwrap(); + repo_important + .add_package(package_a2_alias_important.into()) + .unwrap(); + + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(repo_important)) + .unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + fixtures + .repository_set + .add_repository(fixtures.repo_locked.clone().into()) + .unwrap(); + + let mut pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let constraint = AnyConstraint::Simple(SimpleConstraint::new( + "=".to_string(), + "2.1.9999999.9999999-dev".to_string(), + None, + )); + let packages = pool.what_provides("a", Some(&constraint)); + assert!(!packages.is_empty()); + let mut literals = vec![]; + for package in &packages { + literals.push(package.get_id()); + } + + let expected = vec![package_a_alias_important.get_id()]; + + let selected = fixtures + .policy + .select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } +#[ignore = "set_provides/set_replaces only exist on RootPackage handles; CompletePackage from get_package has no set_provides"] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_all_providers() { let _tear_down = TearDown; let _fixtures = set_up(); todo!() } +#[ignore = "set_provides/set_replaces only exist on RootPackage handles; CompletePackage from get_package has no set_replaces"] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_prefer_non_replacing_from_same_repo() { let _tear_down = TearDown; let _fixtures = set_up(); todo!() } +#[ignore = "set_replaces only exists on RootPackage handles; CompletePackage from get_package has no set_replaces"] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_prefer_replacing_package_from_same_vendor() { let _tear_down = TearDown; let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (DefaultPolicy over a Pool; constraint parsing uses a look-around regex)"] fn test_select_lowest() { let _tear_down = TearDown; - let _fixtures = set_up(); - todo!() + let mut fixtures = set_up(); + + let policy = DefaultPolicy::new(false, true, None); + + let package_a1 = get_package("A", "1.0"); + let package_a2 = get_package("A", "2.0"); + fixtures.repo.add_package(package_a1.clone()).unwrap(); + fixtures.repo.add_package(package_a2.clone()).unwrap(); + fixtures + .repository_set + .add_repository(RepositoryInterfaceHandle::new(fixtures.repo)) + .unwrap(); + + let pool = fixtures + .repository_set + .create_pool_for_package("A", Some(fixtures.repo_locked.clone())) + .unwrap(); + + let literals = vec![package_a1.get_id(), package_a2.get_id()]; + let expected = vec![package_a1.get_id()]; + + let selected = policy.select_preferred_packages(&pool, literals, None); + + assert_eq!(expected, selected); } diff --git a/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs b/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs index 617c7b9..4c72091 100644 --- a/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs +++ b/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs @@ -3,7 +3,7 @@ // testPoolBuilder is a large fixture-driven case that loads packages from test inputs and // builds a Pool; constraint parsing uses a look-around regex the regex crate cannot compile. #[test] -#[ignore = "not yet ported (fixture-driven PoolBuilder; constraint parsing uses a look-around regex)"] +#[ignore = "ArrayLoader::load (single-package, pub) and Pool::count are not exposed: the loadPackage closure calls $loader->load($data) per package and getPackageResultSet uses count($pool)"] fn test_pool_builder() { todo!() } diff --git a/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs b/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs index a25aec6..408c191 100644 --- a/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs +++ b/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs @@ -1,9 +1,264 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php -// testPoolOptimizer is a large fixture-driven case building and optimizing a Pool; -// constraint parsing uses a look-around regex the regex crate cannot compile. +use std::path::PathBuf; +use std::rc::Rc; + +use indexmap::IndexMap; +use shirabe::dependency_resolver::default_policy::DefaultPolicy; +use shirabe::dependency_resolver::pool::Pool; +use shirabe::dependency_resolver::pool_optimizer::PoolOptimizer; +use shirabe::dependency_resolver::request::Request; +use shirabe::json::JsonFile; +use shirabe::package::BasePackageHandle; +use shirabe::package::loader::{ArrayLoader, LoaderInterface}; +use shirabe::package::version::version_parser::VersionParser; +use shirabe::repository::handle::LockArrayRepositoryHandle; +use shirabe::repository::lock_array_repository::LockArrayRepository; +use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_php_shim::PREG_SPLIT_DELIM_CAPTURE; +use shirabe_php_shim::PhpMixed; + +fn load_package(package_data: &PhpMixed) -> BasePackageHandle { + let loader = ArrayLoader::new(None, false); + loader + .load(package_data.as_array().unwrap().clone(), None) + .unwrap() +} + +fn load_packages(packages_data: &[PhpMixed]) -> Vec { + let mut packages: Vec = Vec::new(); + + for package_data in packages_data { + let package = load_package(package_data); + packages.push(package.clone()); + if let Some(alias) = package.as_alias() { + packages.push(alias.get_alias_of().into()); + } + } + + packages +} + +fn reduce_packages_info_for_comparison(packages: &[BasePackageHandle]) -> Vec { + let mut packages_info: Vec = Vec::new(); + + for package in packages { + let suffix = if let Some(alias) = package.as_alias() { + format!(" (alias of {})", alias.get_alias_of().get_version()) + } else { + String::new() + }; + packages_info.push(format!( + "{}@{}{}", + package.get_name(), + package.get_version(), + suffix + )); + } + + packages_info.sort(); + + packages_info +} + +fn read_test_file(file: &str, fixtures_dir: &str) -> IndexMap { + let contents = shirabe_php_shim::file_get_contents(file).unwrap(); + let tokens = Preg::split4( + r"#(?:^|\n*)--([A-Z-]+)--\n#", + &contents, + -1, + PREG_SPLIT_DELIM_CAPTURE, + ); + + let section_info: Vec<&str> = vec!["TEST", "REQUEST", "POOL-BEFORE", "POOL-AFTER"]; + + let mut section: Option = None; + let mut data: IndexMap = IndexMap::new(); + for token in tokens { + if section.is_none() && token.is_empty() { + continue; + } + + if section.is_none() { + if !section_info.contains(&token.as_str()) { + panic!( + "The test file \"{}\" must not contain a section named \"{}\".", + file.replace(&format!("{}/", fixtures_dir), ""), + token + ); + } + section = Some(token); + continue; + } + + let section_data = token; + data.insert(section.take().unwrap(), section_data); + } + + for required_section in §ion_info { + if !data.contains_key(*required_section) { + panic!( + "The test file \"{}\" must have a section named \"{}\".", + file.replace(&format!("{}/", fixtures_dir), ""), + required_section + ); + } + } + + data +} + +fn collect_test_files(dir: &std::path::Path, out: &mut Vec) { + for entry in std::fs::read_dir(dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.is_dir() { + collect_test_files(&path, out); + } else { + out.push(path); + } + } +} + +fn provide_integration_tests() -> IndexMap< + String, + ( + PhpMixed, + Vec, + Vec, + String, + ), +> { + let fixtures_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../composer/tests/Composer/Test/DependencyResolver/Fixtures/pooloptimizer"); + let fixtures_dir = std::fs::canonicalize(&fixtures_dir).unwrap(); + let fixtures_dir_str = fixtures_dir.to_str().unwrap().to_string(); + + let mut files: Vec = Vec::new(); + collect_test_files(&fixtures_dir, &mut files); + + let mut tests: IndexMap< + String, + ( + PhpMixed, + Vec, + Vec, + String, + ), + > = IndexMap::new(); + for file in files { + let file = file.to_str().unwrap().to_string(); + + if !Preg::is_match(r"/\.test$/", &file) { + continue; + } + + let test_data = read_test_file(&file, &fixtures_dir_str); + let message = test_data["TEST"].clone(); + let request_data = JsonFile::parse_json(Some(&test_data["REQUEST"]), None).unwrap(); + let packages_before = load_packages( + JsonFile::parse_json(Some(&test_data["POOL-BEFORE"]), None) + .unwrap() + .as_list() + .unwrap(), + ); + let expected_packages = load_packages( + JsonFile::parse_json(Some(&test_data["POOL-AFTER"]), None) + .unwrap() + .as_list() + .unwrap(), + ); + + let basename = std::path::Path::new(&file) + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(); + tests.insert( + basename, + (request_data, packages_before, expected_packages, message), + ); + } + + tests +} + +fn run_test_pool_optimizer( + request_data: &PhpMixed, + packages_before: Vec, + expected_packages: &[BasePackageHandle], + message: &str, +) { + let request_data = request_data.as_array().unwrap(); + + let locked_repo = LockArrayRepositoryHandle::new(LockArrayRepository::new(vec![]).unwrap()); + + let mut request = Request::new(Some(locked_repo)); + let parser = VersionParser::new(); + + if let Some(locked) = request_data.get("locked") { + for package in locked.as_list().unwrap() { + request.lock_package(load_package(package)); + } + } + if let Some(fixed) = request_data.get("fixed") { + for package in fixed.as_list().unwrap() { + request.fix_package(load_package(package)); + } + } + + for (package, constraint) in request_data["require"].as_array().unwrap() { + request + .require_name( + package, + Some( + parser + .parse_constraints(constraint.as_string().unwrap()) + .unwrap(), + ), + ) + .unwrap(); + } + + let prefer_stable = request_data + .get("preferStable") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + let prefer_lowest = request_data + .get("preferLowest") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let pool = Pool::new( + packages_before, + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + let mut pool_optimizer = PoolOptimizer::new(Rc::new(DefaultPolicy::new( + prefer_stable, + prefer_lowest, + None, + ))); + + let pool = pool_optimizer.optimize(&request, &pool); + + assert_eq!( + reduce_packages_info_for_comparison(expected_packages), + reduce_packages_info_for_comparison(pool.get_packages()), + "{}", + message + ); +} + +#[ignore] #[test] -#[ignore = "not yet ported (fixture-driven PoolOptimizer; constraint parsing uses a look-around regex)"] fn test_pool_optimizer() { - todo!() + let tests = provide_integration_tests(); + for (_name, (request_data, packages_before, expected_packages, message)) in tests { + run_test_pool_optimizer(&request_data, packages_before, &expected_packages, &message); + } } diff --git a/crates/shirabe/tests/dependency_resolver/rule_set_test.rs b/crates/shirabe/tests/dependency_resolver/rule_set_test.rs index 5620b2a..f1e573e 100644 --- a/crates/shirabe/tests/dependency_resolver/rule_set_test.rs +++ b/crates/shirabe/tests/dependency_resolver/rule_set_test.rs @@ -3,10 +3,14 @@ use std::cell::RefCell; use std::rc::Rc; +use indexmap::IndexMap; use shirabe::dependency_resolver::{ - GenericRule, RULE_LEARNED, RULE_ROOT_REQUIRE, ReasonData, Rule, RuleSet, + GenericRule, Pool, RULE_LEARNED, RULE_ROOT_REQUIRE, ReasonData, Request, Rule, RuleSet, }; -use shirabe_semver::constraint::MatchAllConstraint; +use shirabe::repository::RepositorySet; +use shirabe_semver::constraint::{MatchAllConstraint, MatchNoneConstraint}; + +use crate::test_case::get_package; fn root_require_reason() -> ReasonData { ReasonData::RootRequire { @@ -156,10 +160,54 @@ fn test_get_iterator_without() { assert!(Rc::ptr_eq(&iterator.current(), &rule2)); } -// In PHP this mocks RepositorySet and Request to build the pretty string. The mocked -// collaborators cannot be reproduced here, and add() also reaches hash_raw (todo!()). +// The constraint is MatchNoneConstraint, so what_provides returns no packages and the +// "No package found" branch is taken; the RepositorySet/Request collaborators are never +// actually consulted (PHP mocks them with the constructor disabled). #[test] -#[ignore = "getPrettyString needs mocked RepositorySet/Request, and add() reaches hash_raw (todo!())"] +#[ignore] fn test_pretty_string() { - todo!() + let p = get_package("foo", "2.1"); + let mut pool = Pool::new( + vec![p.clone()], + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + + let repository_set = RepositorySet::new( + "stable", + IndexMap::new(), + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + let request = Request::new(None); + + let mut rule_set = RuleSet::new(); + let literal = p.get_id(); + let rule = Rc::new(RefCell::new(Rule::Generic(GenericRule::new( + vec![literal], + RULE_ROOT_REQUIRE, + ReasonData::RootRequire { + package_name: "foo/bar".to_string(), + constraint: MatchNoneConstraint::new(None).into(), + }, + )))); + + rule_set.add(rule, RuleSet::TYPE_REQUEST).unwrap(); + + let pretty = rule_set + .get_pretty_string( + Some(&repository_set), + Some(&request), + Some(&mut pool), + false, + ) + .unwrap(); + assert!( + pretty.contains("REQUEST : No package found to satisfy root composer.json require foo/bar") + ); } diff --git a/crates/shirabe/tests/dependency_resolver/rule_test.rs b/crates/shirabe/tests/dependency_resolver/rule_test.rs index 9806820..156931c 100644 --- a/crates/shirabe/tests/dependency_resolver/rule_test.rs +++ b/crates/shirabe/tests/dependency_resolver/rule_test.rs @@ -1,8 +1,16 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/RuleTest.php -use shirabe::dependency_resolver::{GenericRule, RULE_ROOT_REQUIRE, ReasonData, Rule, RuleSet}; +use indexmap::IndexMap; +use shirabe::dependency_resolver::{ + GenericRule, Pool, RULE_PACKAGE_REQUIRES, RULE_ROOT_REQUIRE, ReasonData, Request, Rule, RuleSet, +}; +use shirabe::package::Link; +use shirabe::repository::RepositorySet; +use shirabe_php_shim::{PHP_VERSION_ID, hash_raw, unpack}; use shirabe_semver::constraint::MatchAllConstraint; +use crate::test_case::get_package; + fn root_require_reason() -> ReasonData { ReasonData::RootRequire { package_name: String::new(), @@ -18,10 +26,23 @@ fn generic_rule(literals: Vec) -> Rule { )) } +#[ignore] #[test] -#[ignore = "Rule::get_hash reaches shirabe_php_shim::hash_raw, which is todo!()"] fn test_get_hash() { - todo!() + let rule = generic_rule(vec![123]); + + let algo = if PHP_VERSION_ID > 80100 { + "xxh3" + } else { + "sha1" + }; + let binary = hash_raw(algo, "123"); + let hash = unpack("ihash", &binary).unwrap(); + + assert_eq!( + hash.get("hash").unwrap().as_int(), + rule.get_hash().unwrap().as_int() + ); } #[test] @@ -85,10 +106,56 @@ fn test_is_assertions() { assert!(rule2.is_assertion()); } -// In PHP this mocks RepositorySet and Request and passes a Link reason to build the -// pretty string. The mocked collaborators cannot be reproduced here. +// PHP mocks RepositorySet and Request with the constructor disabled; the RULE_PACKAGE_REQUIRES +// branch with a non-empty requires list never consults them, so real minimal instances suffice. +#[ignore] #[test] -#[ignore = "getPrettyString needs mocked RepositorySet and Request; mocking is not available"] fn test_pretty_string() { - todo!() + let p1 = get_package("foo", "2.1"); + let p2 = get_package("baz", "1.1"); + let mut pool = Pool::new( + vec![p1.clone(), p2.clone()], + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + + let repository_set = RepositorySet::new( + "stable", + IndexMap::new(), + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + let request = Request::new(None); + + let empty_constraint = MatchAllConstraint::new(Some("*".to_string())); + + let rule = Rule::Generic(GenericRule::new( + vec![p1.get_id(), -p2.get_id()], + RULE_PACKAGE_REQUIRES, + ReasonData::Link(Link::new( + "baz".to_string(), + "foo".to_string(), + empty_constraint.into(), + None, + "*".to_string(), + )), + )); + + assert_eq!( + "baz 1.1 relates to foo * -> satisfiable by foo[2.1].", + rule.get_pretty_string( + &repository_set, + &request, + &mut pool, + false, + &IndexMap::new(), + &vec![], + ) + .unwrap() + ); } diff --git a/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs b/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs index 825d6b4..1232241 100644 --- a/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs +++ b/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs @@ -5,26 +5,105 @@ // crate cannot compile. The fixtures also build PackageRepository security-advisory data // and run the Auditor. +use indexmap::IndexMap; +use shirabe::advisory::AuditConfig; +use shirabe::advisory::Auditor; +use shirabe::dependency_resolver::SecurityAdvisoryPoolFilter; +use shirabe::dependency_resolver::pool::Pool; +use shirabe::dependency_resolver::request::Request; +use shirabe::package::handle::{CompletePackageHandle, PackageHandle, PackageInterfaceHandle}; +use shirabe_php_shim::PhpMixed; + #[test] -#[ignore = "filtering parses affectedVersions via a look-around regex the regex crate cannot compile"] +#[ignore = "requires PackageRepository to implement RepositoryInterface (filter takes Vec) and php-shim uniqid() used by generateSecurityAdvisory; neither exists"] fn test_filter_packages_by_advisories() { todo!() } #[test] -#[ignore = "filtering parses affectedVersions via a look-around regex the regex crate cannot compile"] +#[ignore = "requires PackageRepository to implement RepositoryInterface (filter takes Vec) and php-shim uniqid() used by generateSecurityAdvisory; neither exists"] fn test_dont_filter_packages_by_ignored_advisories() { todo!() } #[test] -#[ignore = "filtering parses affectedVersions via a look-around regex the regex crate cannot compile"] +#[ignore = "requires PackageRepository to implement RepositoryInterface (filter takes Vec) and php-shim uniqid() used by generateSecurityAdvisory; neither exists"] fn test_dont_filter_packages_with_block_insecure_disabled() { todo!() } +#[ignore] #[test] -#[ignore = "filtering parses affectedVersions via a look-around regex the regex crate cannot compile"] fn test_dont_filter_packages_with_abandoned_package() { - todo!() + let package_name_ignore_abandoned = "acme/ignore-abandoned"; + let mut ignore_abandoned: IndexMap> = IndexMap::new(); + ignore_abandoned.insert(package_name_ignore_abandoned.to_string(), None); + let audit_config = AuditConfig::new( + true, + Auditor::FORMAT_SUMMARY.to_string(), + Auditor::ABANDONED_FAIL.to_string(), + true, + true, + false, + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ignore_abandoned.clone(), + ignore_abandoned, + ); + let filter = SecurityAdvisoryPoolFilter::new(Auditor, audit_config); + + let abandoned_package = CompletePackageHandle::new( + "acme/package".to_string(), + "1.0.0.0".to_string(), + "1.0".to_string(), + ); + abandoned_package.set_abandoned(PhpMixed::Bool(true)); + let ignore_abandoned_package = CompletePackageHandle::new( + package_name_ignore_abandoned.to_string(), + "1.0.0.0".to_string(), + "1.0".to_string(), + ); + ignore_abandoned_package.set_abandoned(PhpMixed::Bool(true)); + let expected_package = PackageHandle::new( + "acme/other".to_string(), + "1.1.0.0".to_string(), + "1.1".to_string(), + ); + + let expected_package: PackageInterfaceHandle = expected_package.into(); + let abandoned_package: PackageInterfaceHandle = abandoned_package.into(); + let ignore_abandoned_package: PackageInterfaceHandle = ignore_abandoned_package.into(); + + let pool = Pool::new( + vec![ + expected_package.clone(), + abandoned_package.clone(), + ignore_abandoned_package.clone(), + ], + vec![], + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + IndexMap::new(), + ); + let filtered_pool = filter.filter(pool, vec![], &Request::new(None)).unwrap(); + + let packages = filtered_pool.get_packages(); + assert_eq!(packages.len(), 2); + assert!(packages[0].ptr_eq(&expected_package)); + assert!(packages[1].ptr_eq(&ignore_abandoned_package)); + assert_eq!( + filtered_pool + .get_all_abandoned_removed_package_versions() + .len(), + 1 + ); + assert_eq!( + filtered_pool + .get_all_security_removed_package_versions() + .len(), + 0 + ); } diff --git a/crates/shirabe/tests/dependency_resolver/solver_test.rs b/crates/shirabe/tests/dependency_resolver/solver_test.rs index b876c99..26f1312 100644 --- a/crates/shirabe/tests/dependency_resolver/solver_test.rs +++ b/crates/shirabe/tests/dependency_resolver/solver_test.rs @@ -1,13 +1,22 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/SolverTest.php +use std::cell::RefCell; +use std::rc::Rc; + use indexmap::IndexMap; +use shirabe::dependency_resolver::PolicyInterface; use shirabe::dependency_resolver::default_policy::DefaultPolicy; use shirabe::dependency_resolver::request::Request; +use shirabe::io::io_interface::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::package::handle::PackageInterfaceHandle; use shirabe::repository::array_repository::ArrayRepository; -use shirabe::repository::handle::LockArrayRepositoryHandle; +use shirabe::repository::handle::{LockArrayRepositoryHandle, RepositoryInterfaceHandle}; use shirabe::repository::lock_array_repository::LockArrayRepository; use shirabe::repository::repository_set::RepositorySet; +use crate::test_case::{get_alias_package, get_package, get_version_constraint}; + #[allow(dead_code)] struct Fixtures { repo_set: RepositorySet, @@ -41,285 +50,657 @@ fn set_up() -> Fixtures { } } -// These run the dependency Solver over packages/requests built from version constraints, -// whose parsing goes through a look-around regex the regex crate cannot compile; the setup -// also mirrors the larger solver fixtures. +/// One expected solver job. Mirrors the PHP `['job' => ..., 'package'|'from'|'to' => ...]` rows. +enum ExpectedJob { + Single { + job: &'static str, + package: PackageInterfaceHandle, + }, + Update { + from: PackageInterfaceHandle, + to: PackageInterfaceHandle, + }, +} + +/// ref: SolverTest::checkSolverResult (with reposComplete + createSolver folded in). +fn check_solver_result( + mut repo_set: RepositorySet, + repo: ArrayRepository, + repo_locked: LockArrayRepositoryHandle, + mut request: Request, + expected: Vec, +) { + // reposComplete() + repo_set + .add_repository(RepositoryInterfaceHandle::new(repo)) + .unwrap(); + repo_set.add_repository(repo_locked.into()).unwrap(); + + // createSolver() + let io: Rc> = Rc::new(RefCell::new(NullIO::new())); + let pool = repo_set + .create_pool(&mut request, io.clone(), None, None, vec![], None, None) + .unwrap(); + let policy: Rc = Rc::new(DefaultPolicy::new(false, false, None)); + let mut solver = + shirabe::dependency_resolver::solver::Solver::new(policy, Rc::new(RefCell::new(pool)), io); + + let transaction = solver.solve(&request, None).unwrap(); + + // Build readable (unique-name) and identity (ptr) representations of the result and + // the expectation, mirroring the dual assertEquals in the PHP helper. + let mut result_readable: Vec<(String, String)> = Vec::new(); + let mut result_ids: Vec<(String, Vec)> = Vec::new(); + for operation in transaction.get_operations() { + if let Some(update) = operation.as_update_operation() { + let from = update.get_initial_package(); + let to = update.get_target_package(); + result_readable.push(( + "update".to_string(), + format!("{} => {}", from.get_unique_name(), to.get_unique_name()), + )); + result_ids.push(("update".to_string(), vec![from.ptr_id(), to.ptr_id()])); + } else { + let op_type = operation.get_operation_type(); + let job = match op_type.as_str() { + "markAliasInstalled" => "markAliasInstalled", + "markAliasUninstalled" => "markAliasUninstalled", + "uninstall" => "remove", + "install" => "install", + other => panic!("Unexpected operation: {}", other), + }; + let package = operation.get_package(); + result_readable.push((job.to_string(), package.get_unique_name())); + result_ids.push((job.to_string(), vec![package.ptr_id()])); + } + } + + let mut expected_readable: Vec<(String, String)> = Vec::new(); + let mut expected_ids: Vec<(String, Vec)> = Vec::new(); + for job in &expected { + match job { + ExpectedJob::Single { job, package } => { + expected_readable.push((job.to_string(), package.get_unique_name())); + expected_ids.push((job.to_string(), vec![package.ptr_id()])); + } + ExpectedJob::Update { from, to } => { + expected_readable.push(( + "update".to_string(), + format!("{} => {}", from.get_unique_name(), to.get_unique_name()), + )); + expected_ids.push(("update".to_string(), vec![from.ptr_id(), to.ptr_id()])); + } + } + } + + assert_eq!(expected_readable, result_readable); + assert_eq!(expected_ids, result_ids); +} + +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_single() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo.add_package(package_a.clone()).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Single { + job: "install", + package: package_a, + }], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_remove_if_not_requested() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + fixtures.request, + vec![ExpectedJob::Single { + job: "remove", + package: package_a, + }], + ); } +#[ignore = "solve() error path discards SolverProblemsException (returns placeholder anyhow error); getProblems/getCode/getPrettyString not retrievable"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_non_existing_package_fails() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_same_package_from_different_repositories() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let mut repo_set = fixtures.repo_set; + + let repo1 = ArrayRepository::new(vec![]).unwrap(); + let repo2 = ArrayRepository::new(vec![]).unwrap(); + + let foo1 = get_package("foo", "1"); + let foo2 = get_package("foo", "1"); + repo1.add_package(foo1.clone()).unwrap(); + repo2.add_package(foo2.clone()).unwrap(); + + repo_set + .add_repository(RepositoryInterfaceHandle::new(repo1)) + .unwrap(); + repo_set + .add_repository(RepositoryInterfaceHandle::new(repo2)) + .unwrap(); + + let mut request = fixtures.request; + request.require_name("foo", None).unwrap(); + + // The two repos are already added here; the helper adds the (empty) default repos too. + check_solver_result( + repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Single { + job: "install", + package: foo1, + }], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_with_deps() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_honours_not_equal_operator() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_install_with_deps_in_order() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_multi_package_name_version_resolution_depends_on_require_order() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_multi_package_name_version_resolution_is_independent_of_require_order_if_ordered_descending_by_requirement() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_fix_locked() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + + let mut request = fixtures.request; + request.fix_package(package_a.clone()); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_fix_locked_with_alternative() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + fixtures.repo.add_package(get_package("A", "1.0")).unwrap(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + + let mut request = fixtures.request; + request.fix_package(package_a.clone()); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_does_only_update() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_single() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let new_package_a = get_package("A", "1.1"); + fixtures.repo.add_package(new_package_a.clone()).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Update { + from: package_a, + to: new_package_a, + }], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_all() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_current() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + fixtures + .repo_locked + .add_package(get_package("A", "1.0")) + .unwrap(); + fixtures.repo.add_package(get_package("A", "1.0")).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_only_updates_selected_package() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let package_b = get_package("B", "1.0"); + fixtures.repo_locked.add_package(package_b.clone()).unwrap(); + let package_a_newer = get_package("A", "1.1"); + fixtures.repo.add_package(package_a_newer.clone()).unwrap(); + let package_b_newer = get_package("B", "1.1"); + fixtures.repo.add_package(package_b_newer.clone()).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + request.fix_package(package_b.clone()); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Update { + from: package_a, + to: package_a_newer, + }], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_constrained() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let new_package_a = get_package("A", "1.2"); + fixtures.repo.add_package(new_package_a.clone()).unwrap(); + fixtures.repo.add_package(get_package("A", "2.0")).unwrap(); + + let mut request = fixtures.request; + request + .require_name("A", Some(get_version_constraint("<", "2.0.0.0"))) + .unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Update { + from: package_a, + to: new_package_a, + }], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_fully_constrained() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let new_package_a = get_package("A", "1.2"); + fixtures.repo.add_package(new_package_a.clone()).unwrap(); + fixtures.repo.add_package(get_package("A", "2.0")).unwrap(); + + let mut request = fixtures.request; + request + .require_name("A", Some(get_version_constraint("<", "2.0.0.0"))) + .unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Update { + from: package_a, + to: new_package_a, + }], + ); } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_update_fully_constrained_prunes_installed_packages() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo_locked.add_package(package_a.clone()).unwrap(); + let package_b = get_package("B", "1.0"); + fixtures.repo_locked.add_package(package_b.clone()).unwrap(); + let new_package_a = get_package("A", "1.2"); + fixtures.repo.add_package(new_package_a.clone()).unwrap(); + fixtures.repo.add_package(get_package("A", "2.0")).unwrap(); + + let mut request = fixtures.request; + request + .require_name("A", Some(get_version_constraint("<", "2.0.0.0"))) + .unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ + ExpectedJob::Single { + job: "remove", + package: package_b, + }, + ExpectedJob::Update { + from: package_a, + to: new_package_a, + }, + ], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_all_jobs() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setConflicts not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_three_alternative_require_and_conflict() { let _fixtures = set_up(); todo!() } +#[ignore = "setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_solver_obsolete() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_one_of_two_alternatives() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + let package_a = get_package("A", "1.0"); + fixtures.repo.add_package(package_a.clone()).unwrap(); + let package_b = get_package("A", "1.0"); + fixtures.repo.add_package(package_b.clone()).unwrap(); + + let mut request = fixtures.request; + request.require_name("A", None).unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ExpectedJob::Single { + job: "install", + package: package_a, + }], + ); } +#[ignore = "setRequires/setProvides not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_provider() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_skip_replacer_of_existing_package() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_no_install_replacer_of_missing_package() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_skip_replaced_package_if_replacer_is_selected() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_pick_older_if_newer_conflicts() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_circular_require() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setProvides not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_alternative_with_circular_require() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_use_replacer_if_necessary() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_issue265() { let _fixtures = set_up(); todo!() } +#[ignore = "setConflicts not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_conflict_result_empty() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle; also asserts SolverProblemsException details which solve() discards"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_unsatisfiable_requires() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle; also asserts SolverProblemsException details which solve() discards"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_require_mismatch_exception() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_learn_literals_with_sorted_rule_literals() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_recursive_alias_dependencies() { let _fixtures = set_up(); todo!() } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_dev_alias() { let _fixtures = set_up(); todo!() } +#[ignore] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_install_root_aliases_if_alias_of_is_installed() { - let _fixtures = set_up(); - todo!() + let fixtures = set_up(); + + // root aliased, required + let package_a = get_package("A", "1.0"); + fixtures.repo.add_package(package_a.clone()).unwrap(); + let package_a_alias = get_alias_package(&package_a, "1.1"); + fixtures.repo.add_package(package_a_alias.clone()).unwrap(); + package_a_alias + .as_alias() + .unwrap() + .set_root_package_alias(true); + // root aliased, not required, should still be installed as it is root alias + let package_b = get_package("B", "1.0"); + fixtures.repo.add_package(package_b.clone()).unwrap(); + let package_b_alias = get_alias_package(&package_b, "1.1"); + fixtures.repo.add_package(package_b_alias.clone()).unwrap(); + package_b_alias + .as_alias() + .unwrap() + .set_root_package_alias(true); + // regular alias, not required, alias should not be installed + let package_c = get_package("C", "1.0"); + fixtures.repo.add_package(package_c.clone()).unwrap(); + let package_c_alias = get_alias_package(&package_c, "1.1"); + fixtures.repo.add_package(package_c_alias.clone()).unwrap(); + + let mut request = fixtures.request; + request + .require_name("A", Some(get_version_constraint("==", "1.1"))) + .unwrap(); + request + .require_name("B", Some(get_version_constraint("==", "1.0"))) + .unwrap(); + request + .require_name("C", Some(get_version_constraint("==", "1.0"))) + .unwrap(); + + check_solver_result( + fixtures.repo_set, + fixtures.repo, + fixtures.repo_locked, + request, + vec![ + ExpectedJob::Single { + job: "install", + package: package_a, + }, + ExpectedJob::Single { + job: "markAliasInstalled", + package: package_a_alias, + }, + ExpectedJob::Single { + job: "install", + package: package_b, + }, + ExpectedJob::Single { + job: "markAliasInstalled", + package: package_b_alias, + }, + ExpectedJob::Single { + job: "install", + package: package_c, + }, + ExpectedJob::Single { + job: "markAliasInstalled", + package: package_c_alias, + }, + ], + ); } +#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"] #[test] -#[ignore = "not yet ported (runs the Solver; constraint parsing uses a look-around regex)"] fn test_learn_positive_literal() { let _fixtures = set_up(); todo!() diff --git a/crates/shirabe/tests/dependency_resolver/transaction_test.rs b/crates/shirabe/tests/dependency_resolver/transaction_test.rs index 2ea74aa..a5ca310 100644 --- a/crates/shirabe/tests/dependency_resolver/transaction_test.rs +++ b/crates/shirabe/tests/dependency_resolver/transaction_test.rs @@ -1,10 +1,7 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/TransactionTest.php -// Transaction::new sorts operations via shirabe_php_shim::uasort_map, which is todo!(). -// The fixture also calls setRequires/setProvides on non-root packages, which the public -// handle API does not allow, so the scenario cannot be expressed faithfully yet. #[test] -#[ignore = "Transaction::new reaches uasort_map (todo!()); fixture needs link setters on non-root packages"] +#[ignore = "CompletePackageHandle lacks set_type/set_requires/set_provides/set_extra; these setters exist only on RootPackageHandle, so the non-root package fixture cannot be expressed"] fn test_transaction_generation_and_sorting() { todo!() } -- cgit v1.3.1