aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/dependency_resolver/transaction_test.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-22 23:42:07 +0900
committernsfisis <nsfisis@gmail.com>2026-06-22 23:42:07 +0900
commit5ab5f3b316798c1411ce8e6a7f5b091fda93589c (patch)
treec7669f94f5d9bd9adc07bc1aab3f14cfdec1ae5f /crates/shirabe/tests/dependency_resolver/transaction_test.rs
parentb291e714bc739262140323e08fe2fb9e91e00ee7 (diff)
downloadphp-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.rs283
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);
}