aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/command/require_command_test.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/tests/command/require_command_test.rs')
-rw-r--r--crates/shirabe/tests/command/require_command_test.rs393
1 files changed, 385 insertions, 8 deletions
diff --git a/crates/shirabe/tests/command/require_command_test.rs b/crates/shirabe/tests/command/require_command_test.rs
index 0f43342..2464f3d 100644
--- a/crates/shirabe/tests/command/require_command_test.rs
+++ b/crates/shirabe/tests/command/require_command_test.rs
@@ -1,25 +1,402 @@
//! ref: composer/tests/Composer/Test/Command/RequireCommandTest.php
+use crate::test_case::{
+ RunOptions, create_composer_lock, create_installed_json, get_application_tester, get_package,
+ init_temp_composer,
+};
+use serial_test::serial;
+use shirabe::json::JsonFile;
+use shirabe_php_shim::PhpMixed;
+
+fn input(pairs: Vec<(&str, PhpMixed)>) -> Vec<(PhpMixed, PhpMixed)> {
+ pairs
+ .into_iter()
+ .map(|(k, v)| (PhpMixed::from(k), v))
+ .collect()
+}
+
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer/getApplicationTester harness (not implemented)"]
+#[serial]
+#[ignore = "shirabe_php_shim::phpversion(extension) is a todo!() (per-extension version strings not modeled); reached while checking the ext-foobar platform requirement during version selection"]
fn test_require_throws_if_none_matches() {
- todo!()
+ let composer_json = serde_json::json!({
+ "repositories": {
+ "packages": {
+ "type": "package",
+ "package": [
+ { "name": "required/pkg", "version": "1.0.0", "require": { "ext-foobar": "^1" } },
+ ],
+ },
+ },
+ });
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ let mut app_tester = get_application_tester();
+ let err = app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("require")),
+ ("--dry-run", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ (
+ "packages",
+ PhpMixed::List(vec![PhpMixed::from("required/pkg")]),
+ ),
+ ]),
+ RunOptions::default(),
+ )
+ .expect_err("expected InvalidArgumentException for incompatible platform requirements");
+
+ assert!(
+ err.to_string().contains(
+ "Package required/pkg has requirements incompatible with your PHP version, PHP extensions and Composer version:"
+ ) && err
+ .to_string()
+ .contains("- required/pkg 1.0.0 requires ext-foobar ^1 but it is not present."),
+ "got: {err}"
+ );
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer/getApplicationTester/setInputs harness (not implemented)"]
+#[serial]
+#[ignore = "Phase-C re-entrancy: EventDispatcher::make_autoloader calls Composer borrow_mut while the Composer is already borrowed up the installer-event-dispatch stack, panicking with \"RefCell already borrowed\" (composer.rs:507)"]
fn test_require_warns_if_resolved_to_feature_branch() {
- todo!()
+ let composer_json = serde_json::json!({
+ "repositories": {
+ "packages": {
+ "type": "package",
+ "package": [
+ { "name": "required/pkg", "version": "2.0.0", "require": { "common/dep": "^1" } },
+ { "name": "required/pkg", "version": "dev-foo-bar", "require": { "common/dep": "^2" } },
+ { "name": "common/dep", "version": "2.0.0" },
+ ],
+ },
+ },
+ "require": { "common/dep": "^2.0" },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ });
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ let mut app_tester = get_application_tester();
+ app_tester.set_inputs(vec!["n".to_string()]);
+ app_tester
+ .run(
+ input(vec![
+ ("command", PhpMixed::from("require")),
+ ("--dry-run", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ (
+ "packages",
+ PhpMixed::List(vec![PhpMixed::from("required/pkg")]),
+ ),
+ ]),
+ RunOptions {
+ interactive: Some(true),
+ ..RunOptions::default()
+ },
+ )
+ .unwrap();
+
+ assert_eq!(
+ "./composer.json has been updated
+Running composer update required/pkg
+Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 2 installs, 0 updates, 0 removals
+ - Locking common/dep (2.0.0)
+ - Locking required/pkg (dev-foo-bar)
+Installing dependencies from lock file (including require-dev)
+Package operations: 2 installs, 0 updates, 0 removals
+ - Installing common/dep (2.0.0)
+ - Installing required/pkg (dev-foo-bar)
+Using version dev-foo-bar for required/pkg
+<warning>Version dev-foo-bar looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state</warning>
+Are you sure you want to use this constraint (y) or would you rather abort (n) the whole operation [y,n]? .
+Installation failed, reverting ./composer.json to its original content.
+",
+ app_tester.get_display()
+ );
+}
+
+/// ref: RequireCommandTest::provideRequire
+fn provide_require() -> Vec<(
+ &'static str,
+ serde_json::Value,
+ Vec<(&'static str, PhpMixed)>,
+ &'static str,
+)> {
+ vec![
+ (
+ "warn once for missing ext but a lower package matches",
+ serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "required/pkg", "version": "1.2.0", "require": { "ext-foobar": "^1" } },
+ { "name": "required/pkg", "version": "1.1.0", "require": { "ext-foobar": "^1" } },
+ { "name": "required/pkg", "version": "1.0.0" },
+ ] } },
+ }),
+ vec![("packages", PhpMixed::List(vec![PhpMixed::from("required/pkg")]))],
+ "<warning>Cannot use required/pkg's latest version 1.2.0 as it requires ext-foobar ^1 which is missing from your platform.
+./composer.json has been updated
+Running composer update required/pkg
+Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 1 install, 0 updates, 0 removals
+ - Locking required/pkg (1.0.0)
+Installing dependencies from lock file (including require-dev)
+Package operations: 1 install, 0 updates, 0 removals
+ - Installing required/pkg (1.0.0)
+Using version ^1.0 for required/pkg",
+ ),
+ (
+ "warn multiple times when verbose",
+ serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "required/pkg", "version": "1.2.0", "require": { "ext-foobar": "^1" } },
+ { "name": "required/pkg", "version": "1.1.0", "require": { "ext-foobar": "^1" } },
+ { "name": "required/pkg", "version": "1.0.0" },
+ ] } },
+ }),
+ vec![
+ ("packages", PhpMixed::List(vec![PhpMixed::from("required/pkg")])),
+ ("--no-install", PhpMixed::from(true)),
+ ("-v", PhpMixed::from(true)),
+ ],
+ "<warning>Cannot use required/pkg's latest version 1.2.0 as it requires ext-foobar ^1 which is missing from your platform.
+<warning>Cannot use required/pkg 1.1.0 as it requires ext-foobar ^1 which is missing from your platform.
+./composer.json has been updated
+Running composer update required/pkg
+Loading composer repositories with package information
+Updating dependencies
+Dependency resolution completed in %d seconds
+Analyzed %d packages to resolve dependencies
+Analyzed %d rules to resolve dependencies
+Lock file operations: 1 install, 0 updates, 0 removals
+Installs: required/pkg:1.0.0
+ - Locking required/pkg (1.0.0)
+Using version ^1.0 for required/pkg",
+ ),
+ (
+ "warn for not satisfied req which is satisfied by lower version",
+ serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "required/pkg", "version": "1.1.0", "require": { "php": "^20" } },
+ { "name": "required/pkg", "version": "1.0.0", "require": { "php": ">=7" } },
+ ] } },
+ }),
+ vec![
+ ("packages", PhpMixed::List(vec![PhpMixed::from("required/pkg")])),
+ ("--no-install", PhpMixed::from(true)),
+ ],
+ "<warning>Cannot use required/pkg's latest version 1.1.0 as it requires php ^20 which is not satisfied by your platform.
+./composer.json has been updated
+Running composer update required/pkg
+Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 1 install, 0 updates, 0 removals
+ - Locking required/pkg (1.0.0)
+Using version ^1.0 for required/pkg",
+ ),
+ (
+ "version selection happens early even if not completely accurate if no update is requested",
+ serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "required/pkg", "version": "1.1.0", "require": { "php": "^20" } },
+ { "name": "required/pkg", "version": "1.0.0", "require": { "php": ">=7" } },
+ ] } },
+ }),
+ vec![
+ ("packages", PhpMixed::List(vec![PhpMixed::from("required/pkg")])),
+ ("--no-update", PhpMixed::from(true)),
+ ],
+ "<warning>Cannot use required/pkg's latest version 1.1.0 as it requires php ^20 which is not satisfied by your platform.
+Using version ^1.0 for required/pkg
+./composer.json has been updated",
+ ),
+ (
+ "pick best matching version when not provided",
+ serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "existing/dep", "version": "1.1.0", "require": { "required/pkg": "^1" } },
+ { "name": "required/pkg", "version": "2.0.0" },
+ { "name": "required/pkg", "version": "1.1.0" },
+ { "name": "required/pkg", "version": "1.0.0" },
+ ] } },
+ "require": { "existing/dep": "^1" },
+ }),
+ vec![
+ ("packages", PhpMixed::List(vec![PhpMixed::from("required/pkg")])),
+ ("--no-install", PhpMixed::from(true)),
+ ],
+ "./composer.json has been updated
+Running composer update required/pkg
+Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 2 installs, 0 updates, 0 removals
+ - Locking existing/dep (1.1.0)
+ - Locking required/pkg (1.1.0)
+Using version ^1.1 for required/pkg",
+ ),
+ (
+ "use exact constraint with --fixed",
+ serde_json::json!({
+ "type": "project",
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "required/pkg", "version": "1.1.0" },
+ ] } },
+ }),
+ vec![
+ ("packages", PhpMixed::List(vec![PhpMixed::from("required/pkg")])),
+ ("--no-install", PhpMixed::from(true)),
+ ("--fixed", PhpMixed::from(true)),
+ ],
+ "./composer.json has been updated
+Running composer update required/pkg
+Loading composer repositories with package information
+Updating dependencies
+Lock file operations: 1 install, 0 updates, 0 removals
+ - Locking required/pkg (1.1.0)
+Using version 1.1.0 for required/pkg",
+ ),
+ ]
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer/getApplicationTester harness (not implemented)"]
+#[serial]
+#[ignore = "Phase-C re-entrancy: EventDispatcher::make_autoloader calls Composer borrow_mut while the Composer is already borrowed up the installer-event-dispatch stack, panicking with \"RefCell already borrowed\" (composer.rs:507)"]
fn test_require() {
- todo!()
+ for (label, composer_json, command, expected) in provide_require() {
+ let _tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ let mut app_tester = get_application_tester();
+ let mut args = vec![
+ ("command", PhpMixed::from("require")),
+ ("--dry-run", PhpMixed::from(true)),
+ ("--no-audit", PhpMixed::from(true)),
+ ];
+ args.extend(command);
+ app_tester.run(input(args), RunOptions::default()).unwrap();
+
+ let display = app_tester.get_display();
+ let display = display.trim();
+ if expected.contains("%d") {
+ // The PHP test builds a regex by quoting the expected and replacing %d with [0-9.]+.
+ // Compare line-by-line, treating "%d" as a numeric wildcard.
+ let exp_lines: Vec<&str> = expected.trim().lines().collect();
+ let got_lines: Vec<&str> = display.lines().collect();
+ assert_eq!(
+ exp_lines.len(),
+ got_lines.len(),
+ "case: {label}\n--- expected ---\n{expected}\n--- got ---\n{display}"
+ );
+ for (exp, got) in exp_lines.iter().zip(got_lines.iter()) {
+ if exp.contains("%d") {
+ let prefix = &exp[..exp.find("%d").unwrap()];
+ assert!(
+ got.starts_with(prefix),
+ "case: {label}\nexpected line prefix {prefix:?}, got {got:?}"
+ );
+ } else {
+ assert_eq!(exp, got, "case: {label}");
+ }
+ }
+ } else {
+ assert_eq!(expected.trim(), display, "case: {label}");
+ }
+ }
+}
+
+/// ref: RequireCommandTest::provideInconsistentRequireKeys
+fn provide_inconsistent_require_keys() -> Vec<(bool, bool, &'static str)> {
+ vec![
+ (
+ true,
+ false,
+ "<warning>required/pkg is currently present in the require key and you ran the command with the --dev flag, which will move it to the require-dev key.</warning>",
+ ),
+ (
+ false,
+ false,
+ "<warning>required/pkg is currently present in the require-dev key and you ran the command without the --dev flag, which will move it to the require key.</warning>",
+ ),
+ (
+ true,
+ true,
+ "<warning>required/pkg is currently present in the require key and you ran the command with the --dev flag, which will move it to the require-dev key.</warning>",
+ ),
+ (
+ false,
+ true,
+ "<warning>required/pkg is currently present in the require-dev key and you ran the command without the --dev flag, which will move it to the require key.</warning>",
+ ),
+ ]
}
#[test]
-#[ignore = "requires ApplicationTester and TestCase::initTempComposer/createComposerLock/createInstalledJson/getApplicationTester harness (not implemented)"]
+#[serial]
+#[ignore = "update/solver pipeline incomplete: the require run fails with \"Fixed package __root__ 1.0.0+no-version-set was not added to solver pool.\" (the root package is not seeded into the solver pool)"]
fn test_inconsistent_require_keys() {
- todo!()
+ for (is_dev, is_interactive, expected_warning) in provide_inconsistent_require_keys() {
+ let current_key = if is_dev { "require" } else { "require-dev" };
+ let other_key = if is_dev { "require-dev" } else { "require" };
+
+ let composer_json = serde_json::json!({
+ "repositories": { "packages": { "type": "package", "package": [
+ { "name": "required/pkg", "version": "1.0.0" },
+ ] } },
+ current_key: { "required/pkg": "^1.0" },
+ });
+ let tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+ let dir = tear_down.working_dir();
+
+ let package = get_package("required/pkg", "1.0.0");
+ if is_dev {
+ create_composer_lock(&[], std::slice::from_ref(&package));
+ create_installed_json(&[], std::slice::from_ref(&package), true);
+ } else {
+ create_composer_lock(std::slice::from_ref(&package), &[]);
+ create_installed_json(std::slice::from_ref(&package), &[], false);
+ }
+
+ let mut app_tester = get_application_tester();
+ let mut command = vec![
+ ("command", PhpMixed::from("require")),
+ ("--no-audit", PhpMixed::from(true)),
+ ("--dev", PhpMixed::from(is_dev)),
+ ("--no-install", PhpMixed::from(true)),
+ (
+ "packages",
+ PhpMixed::List(vec![PhpMixed::from("required/pkg")]),
+ ),
+ ];
+ if is_interactive {
+ app_tester.set_inputs(vec!["yes".to_string()]);
+ } else {
+ command.push(("--no-interaction", PhpMixed::from(true)));
+ }
+
+ app_tester
+ .run(input(command), RunOptions::default())
+ .unwrap();
+
+ assert!(
+ app_tester.get_display().contains(expected_warning),
+ "case is_dev={is_dev} is_interactive={is_interactive}\ngot: {}",
+ app_tester.get_display()
+ );
+
+ let mut composer_content =
+ JsonFile::new(format!("{}/composer.json", dir.display()), None, None).unwrap();
+ let content = composer_content.read().unwrap();
+ let content: serde_json::Value = serde_json::from_str(&JsonFile::encode(&content)).unwrap();
+ assert!(
+ content.get(other_key).is_some(),
+ "expected key {other_key} present"
+ );
+ assert!(
+ content.get(current_key).is_none(),
+ "expected key {current_key} absent"
+ );
+ }
}