diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-22 23:42:07 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-22 23:42:07 +0900 |
| commit | 5ab5f3b316798c1411ce8e6a7f5b091fda93589c (patch) | |
| tree | c7669f94f5d9bd9adc07bc1aab3f14cfdec1ae5f /crates/shirabe/tests/dependency_resolver/transaction_test.rs | |
| parent | b291e714bc739262140323e08fe2fb9e91e00ee7 (diff) | |
| download | php-shirabe-5ab5f3b316798c1411ce8e6a7f5b091fda93589c.tar.gz php-shirabe-5ab5f3b316798c1411ce8e6a7f5b091fda93589c.tar.zst php-shirabe-5ab5f3b316798c1411ce8e6a7f5b091fda93589c.zip | |
test: port previously-ignored Composer tests via __ test hatches
Re-evaluate the reason'd #[ignore] tests under the Phase D criterion:
a test is unportable ONLY if the APIs/types needed to WRITE it do not
exist. A test that compiles but panics at runtime (todo!() body, a
regex the regex crate cannot compile) or fails at runtime (incomplete
or incorrect impl behavior) is portable -- it is written in full and
marked with a reason-less #[ignore].
About 120 test functions move from reason'd #[ignore] to reason-less
#[ignore] (the ported-but-not-yet-passing signal). Impl crates gain
only additive __ test hatches (init_command, pool, file_downloader,
package handle link setters, artifact/path repository, repository
manager, svn); no existing logic changes. Tests whose required APIs
genuinely do not exist (mock/reflection harness, ApplicationTester,
solve() discarding SolverProblemsException, a script::Event that
cannot be passed as an originating event) keep their reason'd
#[ignore].
cargo check -p shirabe --tests passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/dependency_resolver/transaction_test.rs')
| -rw-r--r-- | crates/shirabe/tests/dependency_resolver/transaction_test.rs | 283 |
1 files changed, 281 insertions, 2 deletions
diff --git a/crates/shirabe/tests/dependency_resolver/transaction_test.rs b/crates/shirabe/tests/dependency_resolver/transaction_test.rs index a5ca310..7013816 100644 --- a/crates/shirabe/tests/dependency_resolver/transaction_test.rs +++ b/crates/shirabe/tests/dependency_resolver/transaction_test.rs @@ -1,7 +1,286 @@ //! ref: composer/tests/Composer/Test/DependencyResolver/TransactionTest.php +use indexmap::IndexMap; +use shirabe::dependency_resolver::transaction::Transaction; +use shirabe::package::Link; +use shirabe::package::handle::PackageInterfaceHandle; +use shirabe_php_shim::PhpMixed; + +use crate::test_case::{get_alias_package, get_package, get_version_constraint}; + +/// PHP `new Link($source, $target, $constraint, $type)`: prettyConstraint defaults to +/// `(string) $constraint`. +fn mk_link( + source: &str, + target: &str, + constraint: shirabe_semver::constraint::AnyConstraint, + r#type: &str, +) -> Link { + let pretty = constraint.get_pretty_string(); + Link::new( + source.to_string(), + target.to_string(), + constraint, + Some(r#type.to_string()), + pretty, + ) +} + +/// Mirrors a PHP expected/actual operation entry. +#[derive(Debug)] +enum OperationEntry { + Job { + job: String, + package: PackageInterfaceHandle, + }, + Update { + from: PackageInterfaceHandle, + to: PackageInterfaceHandle, + }, +} + +impl PartialEq for OperationEntry { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + Self::Job { + job: j1, + package: p1, + }, + Self::Job { + job: j2, + package: p2, + }, + ) => j1 == j2 && p1.ptr_eq(p2), + (Self::Update { from: f1, to: t1 }, Self::Update { from: f2, to: t2 }) => { + f1.ptr_eq(f2) && t1.ptr_eq(t2) + } + _ => false, + } + } +} + +fn check_transaction_operations(transaction: &Transaction, expected: Vec<OperationEntry>) { + let mut result: Vec<OperationEntry> = vec![]; + for operation in transaction.get_operations() { + if let Some(update) = operation.as_update_operation() { + result.push(OperationEntry::Update { + from: update.get_initial_package(), + to: update.get_target_package(), + }); + } else { + result.push(OperationEntry::Job { + job: operation.get_operation_type(), + package: operation.get_package(), + }); + } + } + + assert_eq!(expected, result); +} + #[test] -#[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"] +#[ignore] fn test_transaction_generation_and_sorting() { - todo!() + let package_a = get_package("a/a", "dev-master"); + let package_a_alias = get_alias_package(&package_a, "1.0.x-dev"); + let package_b = get_package("b/b", "1.0.0"); + let package_e = get_package("e/e", "dev-foo"); + let package_e_alias = get_alias_package(&package_e, "1.0.x-dev"); + let package_c = get_package("c/c", "1.0.0"); + let present_packages = vec![ + package_a.clone(), + package_a_alias.clone(), + package_b.clone(), + package_e.clone(), + package_e_alias.clone(), + package_c.clone(), + ]; + + let package_b_new = get_package("b/b", "2.1.3"); + let package_d = get_package("d/d", "1.2.3"); + let package_f = get_package("f/f", "1.0.0"); + let package_f_alias1 = get_alias_package(&package_f, "dev-foo"); + let package_g = get_package("g/g", "1.0.0"); + let package_a0_first = get_package("a0/first", "1.2.3"); + let package_f_alias2 = get_alias_package(&package_f, "dev-bar"); + let plugin = get_package("x/plugin", "1.0.0"); + let plugin2_dep = get_package("x/plugin2-dep", "1.0.0"); + let plugin2 = get_package("x/plugin2", "1.0.0"); + let dl_modifying_plugin = get_package("x/downloads-modifying", "1.0.0"); + let dl_modifying_plugin2_dep = get_package("x/downloads-modifying2-dep", "1.0.0"); + let dl_modifying_plugin2 = get_package("x/downloads-modifying2", "1.0.0"); + let result_packages = vec![ + package_a.clone(), + package_a_alias.clone(), + package_b_new.clone(), + package_d.clone(), + package_f.clone(), + package_f_alias1.clone(), + package_g.clone(), + package_a0_first.clone(), + package_f_alias2.clone(), + plugin.clone(), + plugin2_dep.clone(), + plugin2.clone(), + dl_modifying_plugin.clone(), + dl_modifying_plugin2_dep.clone(), + dl_modifying_plugin2.clone(), + ]; + + plugin + .as_complete_package() + .unwrap() + .__set_type("composer-installer".to_string()); + for plugin_package in [&plugin2, &dl_modifying_plugin, &dl_modifying_plugin2] { + plugin_package + .as_complete_package() + .unwrap() + .__set_type("composer-plugin".to_string()); + } + + plugin2 + .as_complete_package() + .unwrap() + .__set_requires(IndexMap::from([( + "x/plugin2-dep".to_string(), + mk_link( + "x/plugin2", + "x/plugin2-dep", + get_version_constraint("=", "1.0.0"), + Link::TYPE_REQUIRE, + ), + )])); + dl_modifying_plugin2 + .as_complete_package() + .unwrap() + .__set_requires(IndexMap::from([( + "x/downloads-modifying2-dep".to_string(), + mk_link( + "x/downloads-modifying2", + "x/downloads-modifying2-dep", + get_version_constraint("=", "1.0.0"), + Link::TYPE_REQUIRE, + ), + )])); + dl_modifying_plugin + .as_complete_package() + .unwrap() + .__set_extra(IndexMap::from([( + "plugin-modifies-downloads".to_string(), + PhpMixed::Bool(true), + )])); + dl_modifying_plugin2 + .as_complete_package() + .unwrap() + .__set_extra(IndexMap::from([( + "plugin-modifies-downloads".to_string(), + PhpMixed::Bool(true), + )])); + + package_d + .as_complete_package() + .unwrap() + .__set_requires(IndexMap::from([ + ( + "f/f".to_string(), + mk_link( + "d/d", + "f/f", + get_version_constraint(">", "0.2"), + Link::TYPE_REQUIRE, + ), + ), + ( + "g/provider".to_string(), + mk_link( + "d/d", + "g/provider", + get_version_constraint(">", "0.2"), + Link::TYPE_REQUIRE, + ), + ), + ])); + package_g + .as_complete_package() + .unwrap() + .__set_provides(IndexMap::from([( + "g/provider".to_string(), + mk_link( + "g/g", + "g/provider", + get_version_constraint("==", "1.0.0"), + Link::TYPE_PROVIDE, + ), + )])); + + let expected_operations = vec![ + OperationEntry::Job { + job: "uninstall".to_string(), + package: package_c.clone(), + }, + OperationEntry::Job { + job: "uninstall".to_string(), + package: package_e.clone(), + }, + OperationEntry::Job { + job: "markAliasUninstalled".to_string(), + package: package_e_alias.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: dl_modifying_plugin.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: dl_modifying_plugin2_dep.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: dl_modifying_plugin2.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: plugin.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: plugin2_dep.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: plugin2.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: package_a0_first.clone(), + }, + OperationEntry::Update { + from: package_b.clone(), + to: package_b_new.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: package_g.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: package_f.clone(), + }, + OperationEntry::Job { + job: "markAliasInstalled".to_string(), + package: package_f_alias2.clone(), + }, + OperationEntry::Job { + job: "markAliasInstalled".to_string(), + package: package_f_alias1.clone(), + }, + OperationEntry::Job { + job: "install".to_string(), + package: package_d.clone(), + }, + ]; + + let transaction = Transaction::new(present_packages, result_packages); + check_transaction_operations(&transaction, expected_operations); } |
