aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/command
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-25 17:49:35 +0900
committernsfisis <nsfisis@gmail.com>2026-06-26 00:20:05 +0900
commitd4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad (patch)
tree0a85bd085961fcdfd5570cafc3b44ba5eb8cb152 /crates/shirabe/tests/command
parent22d0b327368e5f39de6f381046c081d08efdba15 (diff)
downloadphp-shirabe-d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad.tar.gz
php-shirabe-d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad.tar.zst
php-shirabe-d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad.zip
test: port 32 command/repository/downloader tests
Add create_installed_json/create_composer_lock test helpers. Port command (8), repository path/forgejo/perforce/vcs (11), and fossil/hg/download_manager (13) tests. Fix production porting bugs: root_package_loader/forgejo_url/version_bumper regex delimiters, repository_manager create_repository_by_class, array_loader isset, licenses_command RefCell borrow; implement disk_free_space and touch2/touch3 via libc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/command')
-rw-r--r--crates/shirabe/tests/command/bump_command_test.rs206
-rw-r--r--crates/shirabe/tests/command/fund_command_test.rs198
-rw-r--r--crates/shirabe/tests/command/licenses_command_test.rs321
-rw-r--r--crates/shirabe/tests/command/status_command_test.rs41
-rw-r--r--crates/shirabe/tests/command/suggests_command_test.rs387
5 files changed, 1106 insertions, 47 deletions
diff --git a/crates/shirabe/tests/command/bump_command_test.rs b/crates/shirabe/tests/command/bump_command_test.rs
index 7accb75..509b42a 100644
--- a/crates/shirabe/tests/command/bump_command_test.rs
+++ b/crates/shirabe/tests/command/bump_command_test.rs
@@ -1,16 +1,206 @@
//! ref: composer/tests/Composer/Test/Command/BumpCommandTest.php
-use crate::test_case::{RunOptions, get_application_tester, init_temp_composer};
+use crate::test_case::{
+ RunOptions, create_composer_lock, create_installed_json, get_application_tester,
+ get_complete_package, init_temp_composer,
+};
use serial_test::serial;
+use shirabe::json::JsonFile;
+use shirabe::package::handle::PackageInterfaceHandle;
use shirabe_php_shim::PhpMixed;
-#[ignore = "needs create_installed_json / create_composer_lock helpers \
- (InstalledFilesystemRepository / Locker::set_lock_data), and BumpCommand::execute calls \
- require_composer -> Factory, reaching ProcessExecutor (git) -> shirabe-php-shim \
- stream_set_blocking (stream.rs todo!(), requires fcntl(2))"]
+/// ref: BumpCommandTest::testBump (data provider rolled into one body).
+fn run_bump_case(
+ composer_json: serde_json::Value,
+ command: &[(&str, PhpMixed)],
+ expected: serde_json::Value,
+ lock: bool,
+ exit_code: i32,
+) {
+ let tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ let packages: Vec<PackageInterfaceHandle> = vec![
+ get_complete_package("first/pkg", "2.3.4").into(),
+ get_complete_package("second/pkg", "3.4.0").into(),
+ ];
+ let dev_packages: Vec<PackageInterfaceHandle> =
+ vec![get_complete_package("dev/pkg", "2.3.4.5").into()];
+
+ create_installed_json(&packages, &dev_packages, true);
+ if lock {
+ create_composer_lock(&packages, &dev_packages);
+ }
+
+ let mut input: Vec<(PhpMixed, PhpMixed)> =
+ vec![(PhpMixed::from("command"), PhpMixed::from("bump"))];
+ for (k, v) in command {
+ input.push((PhpMixed::from(*k), v.clone()));
+ }
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester.run(input, RunOptions::default()).unwrap();
+ assert_eq!(exit_code, status_code);
+
+ let mut json = JsonFile::new("./composer.json".to_string(), None, None).unwrap();
+ let read = json.read().unwrap();
+ let actual: serde_json::Value = serde_json::from_str(&JsonFile::encode(&read)).unwrap();
+ assert_eq!(expected, actual);
+
+ drop(tear_down);
+}
+
#[test]
+#[serial]
fn test_bump() {
- todo!()
+ // 'bump all by default'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "first/pkg": "^v2.0", "second/pkg": "3.*" },
+ "require-dev": { "dev/pkg": "~2.0" },
+ }),
+ &[],
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "^3.4" },
+ "require-dev": { "dev/pkg": "^2.3.4.5" },
+ }),
+ true,
+ 0,
+ );
+
+ // 'bump only dev with --dev-only'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "first/pkg": "^2.0", "second/pkg": "3.*" },
+ "require-dev": { "dev/pkg": "~2.0" },
+ }),
+ &[("--dev-only", PhpMixed::from(true))],
+ serde_json::json!({
+ "require": { "first/pkg": "^2.0", "second/pkg": "3.*" },
+ "require-dev": { "dev/pkg": "^2.3.4.5" },
+ }),
+ true,
+ 0,
+ );
+
+ // 'bump only non-dev with --no-dev-only'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "first/pkg": "^2.0", "second/pkg": "3.*" },
+ "require-dev": { "dev/pkg": "~2.0" },
+ }),
+ &[("--no-dev-only", PhpMixed::from(true))],
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "^3.4" },
+ "require-dev": { "dev/pkg": "~2.0" },
+ }),
+ true,
+ 0,
+ );
+
+ // 'bump only listed with packages arg'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "first/pkg": "^2.0", "second/pkg": "3.*" },
+ "require-dev": { "dev/pkg": "~2.0" },
+ }),
+ &[(
+ "packages",
+ PhpMixed::List(vec![
+ PhpMixed::from("first/pkg:3.0.1"),
+ PhpMixed::from("dev/*"),
+ ]),
+ )],
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "3.*" },
+ "require-dev": { "dev/pkg": "^2.3.4.5" },
+ }),
+ true,
+ 0,
+ );
+
+ // 'bump works from installed repo without lock file'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "first/pkg": "^2.0", "second/pkg": "3.*" },
+ }),
+ &[],
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "^3.4" },
+ }),
+ false,
+ 0,
+ );
+
+ // 'bump with --dry-run with packages to bump'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "first/pkg": "^2.0", "second/pkg": "3.*" },
+ "require-dev": { "dev/pkg": "~2.0" },
+ }),
+ &[("--dry-run", PhpMixed::from(true))],
+ serde_json::json!({
+ "require": { "first/pkg": "^2.0", "second/pkg": "3.*" },
+ "require-dev": { "dev/pkg": "~2.0" },
+ }),
+ true,
+ 1,
+ );
+
+ // 'bump with --dry-run without packages to bump'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "^3.4" },
+ "require-dev": { "dev/pkg": "^2.3.4.5" },
+ }),
+ &[("--dry-run", PhpMixed::from(true))],
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "^3.4" },
+ "require-dev": { "dev/pkg": "^2.3.4.5" },
+ }),
+ true,
+ 0,
+ );
+
+ // 'bump works with non-standard package'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "php": ">=5.3", "first/pkg": "^2.3.4", "second/pkg": "^3.4" },
+ "require-dev": { "dev/pkg": "^2.3.4.5" },
+ }),
+ &[],
+ serde_json::json!({
+ "require": { "php": ">=5.3", "first/pkg": "^2.3.4", "second/pkg": "^3.4" },
+ "require-dev": { "dev/pkg": "^2.3.4.5" },
+ }),
+ true,
+ 0,
+ );
+
+ // 'bump works with unknown package'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "^3.4", "third/pkg": "^1.2" },
+ }),
+ &[],
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "^3.4", "third/pkg": "^1.2" },
+ }),
+ true,
+ 0,
+ );
+
+ // 'bump works with aliased package'
+ run_bump_case(
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "dev-bugfix as 3.4.x-dev" },
+ }),
+ &[],
+ serde_json::json!({
+ "require": { "first/pkg": "^2.3.4", "second/pkg": "dev-bugfix as 3.4.x-dev" },
+ }),
+ true,
+ 0,
+ );
}
#[test]
@@ -44,10 +234,6 @@ fn test_bump_fails_on_non_existing_composer_file() {
#[test]
#[serial]
-#[ignore = "CurlDownloader::new no longer panics (HTTP layer is now reqwest-based), but this path \
- still reaches an unrelated todo!() in shirabe-php-shim stream_set_blocking (stream.rs), \
- which requires fcntl(2). The companion test_bump_fails_on_non_existing_composer_file \
- covers the same error-output capture path without reaching that shim."]
fn test_bump_fails_on_write_error_to_composer_file() {
if shirabe_php_shim::function_exists("posix_getuid") && shirabe_php_shim::posix_getuid() == 0 {
// ref: $this->markTestSkipped('Cannot run as root');
diff --git a/crates/shirabe/tests/command/fund_command_test.rs b/crates/shirabe/tests/command/fund_command_test.rs
index f75090c..bbcc589 100644
--- a/crates/shirabe/tests/command/fund_command_test.rs
+++ b/crates/shirabe/tests/command/fund_command_test.rs
@@ -1,7 +1,201 @@
//! ref: composer/tests/Composer/Test/Command/FundCommandTest.php
-#[ignore = "missing TestCase::init_temp_composer, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"]
+use crate::test_case::{
+ RunOptions, create_installed_json, get_application_tester, get_complete_package,
+ init_temp_composer,
+};
+use indexmap::IndexMap;
+use serial_test::serial;
+use shirabe::package::handle::{CompletePackageHandle, PackageInterfaceHandle};
+use shirabe_php_shim::PhpMixed;
+
+fn funding_entry(r#type: &str, url: &str) -> IndexMap<String, PhpMixed> {
+ let mut m = IndexMap::new();
+ m.insert("type".to_string(), PhpMixed::from(r#type));
+ m.insert("url".to_string(), PhpMixed::from(url));
+ m
+}
+
+/// Runs one `useCaseProvider` case.
+fn run_fund_case(
+ composer_json: serde_json::Value,
+ command: &[(&str, PhpMixed)],
+ funding: &[(&str, IndexMap<String, PhpMixed>)],
+ expected: &str,
+) {
+ let tear_down = init_temp_composer(Some(&composer_json), None, None, true);
+
+ let first = get_complete_package("first/pkg", "2.3.4");
+ let stable = get_complete_package("stable/pkg", "1.0.0");
+ let dev = get_complete_package("dev/pkg", "2.3.4.5");
+
+ let by_name: IndexMap<&str, &CompletePackageHandle> = [
+ ("first/pkg", &first),
+ ("stable/pkg", &stable),
+ ("dev/pkg", &dev),
+ ]
+ .into_iter()
+ .collect();
+
+ for (pkg_name, info) in funding {
+ if let Some(pkg) = by_name.get(pkg_name) {
+ pkg.set_funding(vec![info.clone()]);
+ }
+ }
+
+ let packages: Vec<PackageInterfaceHandle> = vec![first.into(), stable.into()];
+ let dev_packages: Vec<PackageInterfaceHandle> = vec![dev.into()];
+
+ create_installed_json(&packages, &dev_packages, true);
+
+ let mut input: Vec<(PhpMixed, PhpMixed)> =
+ vec![(PhpMixed::from("command"), PhpMixed::from("fund"))];
+ for (k, v) in command {
+ input.push((PhpMixed::from(*k), v.clone()));
+ }
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ input,
+ RunOptions {
+ capture_stderr_separately: true,
+ ..RunOptions::default()
+ },
+ )
+ .unwrap();
+ assert_eq!(0, status_code);
+ assert_eq!(expected.trim(), app_tester.get_display().trim());
+
+ drop(tear_down);
+}
+
#[test]
+#[serial]
+#[ignore = "FundCommand queries every repository (incl. the default packagist ComposerRepository) over \
+ HTTP for funding metadata before falling back to locally installed data, reaching \
+ shirabe-php-shim curl.rs curl_version (todo!()); this path needs real network access"]
fn test_fund_command() {
- todo!()
+ // 'no funding links present, locally or remotely'
+ run_fund_case(
+ serde_json::json!({
+ "repositories": [],
+ "require": { "first/pkg": "^2.0" },
+ "require-dev": { "dev/pkg": "~4.0" },
+ }),
+ &[],
+ &[],
+ "No funding links were found in your package dependencies. This doesn't mean they don't need your support!",
+ );
+
+ // 'funding links set locally are used as fallback if not found remotely'
+ run_fund_case(
+ serde_json::json!({
+ "repositories": [],
+ "require": { "first/pkg": "^2.0" },
+ "require-dev": { "dev/pkg": "~4.0" },
+ }),
+ &[],
+ &[
+ (
+ "first/pkg",
+ funding_entry("github", "https://github.com/composer-test-data"),
+ ),
+ (
+ "dev/pkg",
+ funding_entry("github", "https://github.com/composer-test-data-dev"),
+ ),
+ ],
+ "The following packages were found in your dependencies which publish funding information:
+
+dev
+ pkg
+ https://github.com/sponsors/composer-test-data-dev
+
+first
+ https://github.com/sponsors/composer-test-data
+
+Please consider following these links and sponsoring the work of package authors!
+Thank you!",
+ );
+
+ // 'funding links set remotely are used as primary if found'
+ run_fund_case(
+ serde_json::json!({
+ "repositories": [
+ {
+ "type": "package",
+ "package": [
+ { "name": "first/pkg", "version": "dev-foo", "funding": [{ "type": "github", "url": "https://github.com/test-should-not-be-used" }] },
+ { "name": "first/pkg", "version": "dev-main", "default-branch": true, "funding": [{ "type": "custom", "url": "https://example.org" }] },
+ { "name": "dev/pkg", "version": "dev-foo", "default-branch": true, "funding": [{ "type": "github", "url": "https://github.com/org" }] },
+ { "name": "stable/pkg", "version": "1.0.0", "funding": [{ "type": "github", "url": "org2" }] },
+ ],
+ },
+ ],
+ "require": { "first/pkg": "^2.0", "stable/pkg": "^1.0" },
+ "require-dev": { "dev/pkg": "~4.0" },
+ }),
+ &[],
+ &[
+ (
+ "first/pkg",
+ funding_entry("github", "https://github.com/composer-test-data"),
+ ),
+ (
+ "dev/pkg",
+ funding_entry("github", "https://github.com/composer-test-data-dev"),
+ ),
+ (
+ "stable/pkg",
+ funding_entry("github", "https://github.com/composer-test-data-stable"),
+ ),
+ ],
+ "The following packages were found in your dependencies which publish funding information:
+
+dev
+ pkg
+ https://github.com/sponsors/org
+
+first
+ https://example.org
+
+stable
+ https://github.com/sponsors/composer-test-data-stable
+
+Please consider following these links and sponsoring the work of package authors!
+Thank you!",
+ );
+
+ // 'format funding links as JSON'
+ run_fund_case(
+ serde_json::json!({
+ "repositories": [],
+ "require": { "first/pkg": "^2.0" },
+ "require-dev": { "dev/pkg": "~4.0" },
+ }),
+ &[("--format", PhpMixed::from("json"))],
+ &[
+ (
+ "first/pkg",
+ funding_entry("github", "https://github.com/composer-test-data"),
+ ),
+ (
+ "dev/pkg",
+ funding_entry("github", "https://github.com/composer-test-data-dev"),
+ ),
+ ],
+ r#"{
+ "dev": {
+ "https://github.com/sponsors/composer-test-data-dev": [
+ "pkg"
+ ]
+ },
+ "first": {
+ "https://github.com/sponsors/composer-test-data": [
+ "pkg"
+ ]
+ }
+}"#,
+ );
}
diff --git a/crates/shirabe/tests/command/licenses_command_test.rs b/crates/shirabe/tests/command/licenses_command_test.rs
index 6bfe832..f680621 100644
--- a/crates/shirabe/tests/command/licenses_command_test.rs
+++ b/crates/shirabe/tests/command/licenses_command_test.rs
@@ -1,71 +1,336 @@
//! ref: composer/tests/Composer/Test/Command/LicensesCommandTest.php
-fn set_up() {
- // Builds the temp project and installed.json/composer.lock fixtures via
- // initTempComposer/createInstalledJson/createComposerLock, none of which are ported yet.
- todo!()
+use crate::test_case::{
+ RunOptions, TearDown, create_composer_lock, create_installed_json, get_application_tester,
+ get_complete_package, init_temp_composer,
+};
+use serial_test::serial;
+use shirabe::package::handle::PackageInterfaceHandle;
+use shirabe_php_shim::PhpMixed;
+
+/// ref: LicensesCommandTest::setUp
+fn set_up() -> TearDown {
+ let tear_down = init_temp_composer(
+ Some(&serde_json::json!({
+ "name": "test/pkg",
+ "version": "1.2.3",
+ "license": "MIT",
+ "require": {
+ "first/pkg": "^2.0",
+ "second/pkg": "3.*",
+ "third/pkg": "^1.3",
+ },
+ "require-dev": {
+ "dev/pkg": "~2.0",
+ },
+ })),
+ None,
+ None,
+ true,
+ );
+
+ let first = get_complete_package("first/pkg", "2.3.4");
+ first.set_license(vec!["MIT".to_string()]);
+
+ let second = get_complete_package("second/pkg", "3.4.0");
+ second.set_license(vec!["LGPL-2.0-only".to_string()]);
+ second.set_homepage("https://example.org".to_string());
+
+ let third = get_complete_package("third/pkg", "1.5.4");
+
+ let dev = get_complete_package("dev/pkg", "2.3.4.5");
+ dev.set_license(vec!["MIT".to_string()]);
+
+ let packages: Vec<PackageInterfaceHandle> = vec![first.into(), second.into(), third.into()];
+ let dev_packages: Vec<PackageInterfaceHandle> = vec![dev.into()];
+
+ create_installed_json(&packages, &dev_packages, true);
+ create_composer_lock(&packages, &dev_packages);
+
+ tear_down
+}
+
+/// Asserts each non-blank line of `display` matches the regex built from `expected[i]`
+/// (`implode("\s+", expected[i])` in PHP).
+fn assert_lines(display: &str, expected: &[Vec<&str>]) {
+ let mut i = 0usize;
+ for line in display.split('\n') {
+ if line.trim().is_empty() {
+ i += 1;
+ continue;
+ }
+ assert!(i < expected.len(), "Got more output lines than expected");
+ let pattern = expected[i]
+ .iter()
+ .map(|s| regex::escape(s))
+ .collect::<Vec<_>>()
+ .join(r"\s+");
+ let re = regex::Regex::new(&pattern).unwrap();
+ assert!(
+ re.is_match(line),
+ "line {} {:?} did not match {:?}",
+ i,
+ line,
+ pattern
+ );
+ i += 1;
+ }
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[serial]
+#[ignore = "renders the text-format Table, which reaches shirabe_php_shim::instance_of (var.rs todo!()); \
+ PhpMixed::Object carries no runtime class tag, so the Symfony Table helper cannot \
+ distinguish TableSeparator/TableCell rows yet"]
fn test_basic_run() {
- set_up();
+ let _tear_down = set_up();
- todo!()
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![(PhpMixed::from("command"), PhpMixed::from("license"))],
+ RunOptions::default(),
+ )
+ .unwrap();
+ assert_eq!(0, status_code);
+
+ let expected = vec![
+ vec!["Name:", "test/pkg"],
+ vec!["Version:", "1.2.3"],
+ vec!["Licenses:", "MIT"],
+ vec!["Dependencies:"],
+ vec![],
+ vec!["Name", "Version", "Licenses"],
+ vec!["dev/pkg", "2.3.4.5", "MIT"],
+ vec!["first/pkg", "2.3.4", "MIT"],
+ vec!["second/pkg", "3.4.0", "LGPL-2.0-only"],
+ vec!["third/pkg", "1.5.4", "none"],
+ ];
+ assert_lines(&app_tester.get_display(), &expected);
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[serial]
+#[ignore = "renders the text-format Table, which reaches shirabe_php_shim::instance_of (var.rs todo!()); \
+ see test_basic_run"]
fn test_no_dev() {
- set_up();
+ let _tear_down = set_up();
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![
+ (PhpMixed::from("command"), PhpMixed::from("license")),
+ (PhpMixed::from("--no-dev"), PhpMixed::from(true)),
+ ],
+ RunOptions::default(),
+ )
+ .unwrap();
+ assert_eq!(0, status_code);
- todo!()
+ let expected = vec![
+ vec!["Name:", "test/pkg"],
+ vec!["Version:", "1.2.3"],
+ vec!["Licenses:", "MIT"],
+ vec!["Dependencies:"],
+ vec![],
+ vec!["Name", "Version", "Licenses"],
+ vec!["first/pkg", "2.3.4", "MIT"],
+ vec!["second/pkg", "3.4.0", "LGPL-2.0-only"],
+ vec!["third/pkg", "1.5.4", "none"],
+ ];
+ assert_lines(&app_tester.get_display(), &expected);
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[serial]
fn test_format_json() {
- set_up();
+ let _tear_down = set_up();
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![
+ (PhpMixed::from("command"), PhpMixed::from("license")),
+ (PhpMixed::from("--format"), PhpMixed::from("json")),
+ ],
+ RunOptions {
+ capture_stderr_separately: true,
+ ..RunOptions::default()
+ },
+ )
+ .unwrap();
+ assert_eq!(0, status_code);
- todo!()
+ let expected = serde_json::json!({
+ "name": "test/pkg",
+ "version": "1.2.3",
+ "license": ["MIT"],
+ "dependencies": {
+ "dev/pkg": { "version": "2.3.4.5", "license": ["MIT"] },
+ "first/pkg": { "version": "2.3.4", "license": ["MIT"] },
+ "second/pkg": { "version": "3.4.0", "license": ["LGPL-2.0-only"] },
+ "third/pkg": { "version": "1.5.4", "license": [] },
+ },
+ });
+
+ let actual: serde_json::Value = serde_json::from_str(&app_tester.get_display()).unwrap();
+ assert_eq!(expected, actual);
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[serial]
+#[ignore = "renders a Table via SymfonyStyle::table, which reaches shirabe_php_shim::instance_of \
+ (var.rs todo!()); see test_basic_run"]
fn test_format_summary() {
- set_up();
+ let _tear_down = set_up();
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![
+ (PhpMixed::from("command"), PhpMixed::from("license")),
+ (PhpMixed::from("--format"), PhpMixed::from("summary")),
+ ],
+ RunOptions::default(),
+ )
+ .unwrap();
+ assert_eq!(0, status_code);
- todo!()
+ let expected = vec![
+ ("-", "-"),
+ ("License", "Number of dependencies"),
+ ("-", "-"),
+ ("MIT", "2"),
+ ("LGPL-2.0-only", "1"),
+ ("none", "1"),
+ ("-", "-"),
+ ];
+
+ let display = app_tester.get_display();
+ let lines: Vec<&str> = display.split('\n').collect();
+ for (i, (key, value)) in expected.iter().enumerate() {
+ let pattern = format!(r"{}\s+{}", regex::escape(key), regex::escape(value));
+ let re = regex::Regex::new(&pattern).unwrap();
+ assert!(
+ re.is_match(lines[i]),
+ "line {} {:?} did not match {:?}",
+ i,
+ lines[i],
+ pattern
+ );
+ }
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[serial]
fn test_format_unknown() {
- set_up();
+ let _tear_down = set_up();
- todo!()
+ let mut app_tester = get_application_tester();
+ let result = app_tester.run(
+ vec![
+ (PhpMixed::from("command"), PhpMixed::from("license")),
+ (PhpMixed::from("--format"), PhpMixed::from("unknown")),
+ ],
+ RunOptions::default(),
+ );
+ assert!(
+ result.is_err(),
+ "expected a RuntimeException, got {:?}",
+ result
+ );
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[serial]
+#[ignore = "renders the text-format Table, which reaches shirabe_php_shim::instance_of (var.rs todo!()); \
+ see test_basic_run"]
fn test_locked() {
- set_up();
+ let _tear_down = set_up();
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![
+ (PhpMixed::from("command"), PhpMixed::from("license")),
+ (PhpMixed::from("--locked"), PhpMixed::from(true)),
+ ],
+ RunOptions::default(),
+ )
+ .unwrap();
+ assert_eq!(0, status_code);
- todo!()
+ let expected = vec![
+ vec!["Name:", "test/pkg"],
+ vec!["Version:", "1.2.3"],
+ vec!["Licenses:", "MIT"],
+ vec!["Dependencies:"],
+ vec![],
+ vec!["Name", "Version", "Licenses"],
+ vec!["dev/pkg", "2.3.4.5", "MIT"],
+ vec!["first/pkg", "2.3.4", "MIT"],
+ vec!["second/pkg", "3.4.0", "LGPL-2.0-only"],
+ vec!["third/pkg", "1.5.4", "none"],
+ ];
+ assert_lines(&app_tester.get_display(), &expected);
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[serial]
+#[ignore = "renders the text-format Table, which reaches shirabe_php_shim::instance_of (var.rs todo!()); \
+ see test_basic_run"]
fn test_locked_no_dev() {
- set_up();
+ let _tear_down = set_up();
- todo!()
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![
+ (PhpMixed::from("command"), PhpMixed::from("license")),
+ (PhpMixed::from("--locked"), PhpMixed::from(true)),
+ (PhpMixed::from("--no-dev"), PhpMixed::from(true)),
+ ],
+ RunOptions::default(),
+ )
+ .unwrap();
+ assert_eq!(0, status_code);
+
+ let expected = vec![
+ vec!["Name:", "test/pkg"],
+ vec!["Version:", "1.2.3"],
+ vec!["Licenses:", "MIT"],
+ vec!["Dependencies:"],
+ vec![],
+ vec!["Name", "Version", "Licenses"],
+ vec!["first/pkg", "2.3.4", "MIT"],
+ vec!["second/pkg", "3.4.0", "LGPL-2.0-only"],
+ vec!["third/pkg", "1.5.4", "none"],
+ ];
+ assert_lines(&app_tester.get_display(), &expected);
}
#[test]
-#[ignore = "missing TestCase::init_temp_composer / create_installed_json / create_composer_lock and get_application_tester (ApplicationTester) infrastructure"]
+#[serial]
fn test_locked_without_lock_file() {
- set_up();
+ let _tear_down = set_up();
+
+ // Remove the lock file
+ let _ = std::fs::remove_file("./composer.lock");
- todo!()
+ let mut app_tester = get_application_tester();
+ let result = app_tester.run(
+ vec![
+ (PhpMixed::from("command"), PhpMixed::from("license")),
+ (PhpMixed::from("--locked"), PhpMixed::from(true)),
+ ],
+ RunOptions::default(),
+ );
+ let err = result.expect_err("expected an UnexpectedValueException");
+ assert!(
+ err.to_string()
+ .contains("Valid composer.json and composer.lock files are required to run this command with --locked"),
+ "unexpected error: {}",
+ err
+ );
}
diff --git a/crates/shirabe/tests/command/status_command_test.rs b/crates/shirabe/tests/command/status_command_test.rs
index 6154ea0..aa0ecc6 100644
--- a/crates/shirabe/tests/command/status_command_test.rs
+++ b/crates/shirabe/tests/command/status_command_test.rs
@@ -1,12 +1,47 @@
//! ref: composer/tests/Composer/Test/Command/StatusCommandTest.php
-#[ignore = "missing TestCase::init_temp_composer, create_composer_lock, create_installed_json, and get_application_tester (ApplicationTester) infrastructure"]
+use crate::test_case::{
+ RunOptions, create_composer_lock, create_installed_json, get_application_tester,
+ get_complete_package, init_temp_composer,
+};
+use serial_test::serial;
+use shirabe::package::handle::PackageInterfaceHandle;
+use shirabe_php_shim::PhpMixed;
+
#[test]
+#[serial]
fn test_no_local_changes() {
- todo!()
+ let tear_down = init_temp_composer(
+ Some(&serde_json::json!({ "require": { "root/req": "1.*" } })),
+ None,
+ None,
+ true,
+ );
+
+ let package = get_complete_package("root/req", "1.0.0");
+ package.__set_type("metapackage".to_string());
+
+ let packages: Vec<PackageInterfaceHandle> = vec![package.into()];
+
+ create_composer_lock(&packages, &[]);
+ create_installed_json(&packages, &[], true);
+
+ let mut app_tester = get_application_tester();
+ app_tester
+ .run(
+ vec![(PhpMixed::from("command"), PhpMixed::from("status"))],
+ RunOptions::default(),
+ )
+ .unwrap();
+
+ assert_eq!("No local changes", app_tester.get_display().trim());
+
+ drop(tear_down);
}
-#[ignore = "missing TestCase::init_temp_composer and get_application_tester (ApplicationTester) infrastructure"]
+#[ignore = "exercises `install` over the network (downloads composer/class-map-generator from a git \
+ source or smarty/smarty from a dist zip), then mutates the installed package and runs \
+ `status`; the install path needs real network access"]
#[test]
fn test_locally_modified_packages() {
todo!()
diff --git a/crates/shirabe/tests/command/suggests_command_test.rs b/crates/shirabe/tests/command/suggests_command_test.rs
index c23691b..8bbb950 100644
--- a/crates/shirabe/tests/command/suggests_command_test.rs
+++ b/crates/shirabe/tests/command/suggests_command_test.rs
@@ -1,13 +1,392 @@
//! ref: composer/tests/Composer/Test/Command/SuggestsCommandTest.php
-#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, and get_application_tester (ApplicationTester) infrastructure"]
+use crate::test_case::{
+ RunOptions, create_composer_lock, create_installed_json, get_application_tester,
+ get_complete_package, get_version_constraint, init_temp_composer,
+};
+use indexmap::IndexMap;
+use serial_test::serial;
+use shirabe::package::Link;
+use shirabe::package::handle::{CompletePackageHandle, PackageInterfaceHandle};
+use shirabe_php_shim::PhpMixed;
+
+/// ref: SuggestsCommandTest::getPackageWithSuggestAndRequires
+fn get_package_with_suggest_and_requires(
+ name: &str,
+ version: &str,
+ suggests: &[(&str, &str)],
+ requires: &[(&str, Link)],
+ require_devs: &[(&str, Link)],
+) -> CompletePackageHandle {
+ let pkg = get_complete_package(name, version);
+ let suggests_map: IndexMap<String, String> = suggests
+ .iter()
+ .map(|(k, v)| (k.to_string(), v.to_string()))
+ .collect();
+ pkg.__set_suggests(suggests_map);
+ let requires_map: IndexMap<String, Link> = requires
+ .iter()
+ .map(|(k, v)| (k.to_string(), v.clone()))
+ .collect();
+ pkg.__set_requires(requires_map);
+ let dev_requires_map: IndexMap<String, Link> = require_devs
+ .iter()
+ .map(|(k, v)| (k.to_string(), v.clone()))
+ .collect();
+ pkg.__set_dev_requires(dev_requires_map);
+ pkg
+}
+
+fn link(source: &str, target: &str, description: &str) -> Link {
+ Link::new(
+ source.to_string(),
+ target.to_string(),
+ get_version_constraint(">=", "1.0"),
+ Some(description.to_string()),
+ "^1.0".to_string(),
+ )
+}
+
#[test]
+#[serial]
fn test_installed_packages_with_no_suggestions() {
- todo!()
+ let tear_down = init_temp_composer(
+ Some(&serde_json::json!({
+ "repositories": {
+ "packages": {
+ "type": "package",
+ "package": [
+ { "name": "vendor1/package1", "version": "1.0.0" },
+ { "name": "vendor2/package2", "version": "1.0.0" },
+ ],
+ },
+ },
+ "require": {
+ "vendor1/package1": "1.*",
+ "vendor2/package2": "1.*",
+ },
+ })),
+ None,
+ None,
+ true,
+ );
+
+ let packages: Vec<PackageInterfaceHandle> = vec![
+ get_complete_package("vendor1/package1", "1.0.0").into(),
+ get_complete_package("vendor2/package2", "1.0.0").into(),
+ ];
+
+ create_installed_json(&packages, &[], true);
+ create_composer_lock(&packages, &[]);
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester
+ .run(
+ vec![(PhpMixed::from("command"), PhpMixed::from("suggest"))],
+ RunOptions::default(),
+ )
+ .unwrap();
+ assert_eq!(0, status_code);
+ assert!(
+ app_tester.get_display().trim().is_empty(),
+ "expected empty display, got: {:?}",
+ app_tester.get_display()
+ );
+
+ drop(tear_down);
+}
+
+/// Builds the package set shared by every `test_suggest` case.
+fn suggest_packages() -> (Vec<PackageInterfaceHandle>, Vec<PackageInterfaceHandle>) {
+ let packages: Vec<PackageInterfaceHandle> = vec![
+ get_package_with_suggest_and_requires(
+ "vendor1/package1",
+ "1.0.0",
+ &[("vendor3/suggested", "helpful for vendor1/package1")],
+ &[(
+ "vendor6/package6",
+ link("vendor1/package1", "vendor6/package6", Link::TYPE_REQUIRE),
+ )],
+ &[
+ (
+ "vendor4/dev-suggested",
+ link(
+ "vendor1/package1",
+ "vendor4/dev-suggested",
+ Link::TYPE_DEV_REQUIRE,
+ ),
+ ),
+ (
+ "vendor3/suggested",
+ link(
+ "vendor1/package1",
+ "vendor3/suggested",
+ Link::TYPE_DEV_REQUIRE,
+ ),
+ ),
+ ],
+ )
+ .into(),
+ get_package_with_suggest_and_requires(
+ "vendor6/package6",
+ "1.0.0",
+ &[("vendor7/transitive", "helpful for vendor6/package6")],
+ &[],
+ &[],
+ )
+ .into(),
+ ];
+ let dev_packages: Vec<PackageInterfaceHandle> = vec![
+ get_package_with_suggest_and_requires(
+ "vendor2/package2",
+ "1.0.0",
+ &[("vendor4/dev-suggested", "helpful for vendor2/package2")],
+ &[(
+ "vendor5/dev-package",
+ link(
+ "vendor2/package2",
+ "vendor5/dev-package",
+ Link::TYPE_REQUIRE,
+ ),
+ )],
+ &[],
+ )
+ .into(),
+ get_package_with_suggest_and_requires(
+ "vendor5/dev-package",
+ "1.0.0",
+ &[("vendor8/dev-transitive", "helpful for vendor5/dev-package")],
+ &[],
+ &[(
+ "vendor8/dev-transitive",
+ link(
+ "vendor5/dev-package",
+ "vendor8/dev-transitive",
+ Link::TYPE_DEV_REQUIRE,
+ ),
+ )],
+ )
+ .into(),
+ ];
+ (packages, dev_packages)
+}
+
+fn run_suggest_case(has_lock_file: bool, command: &[(&str, PhpMixed)], expected: &str) {
+ let tear_down = init_temp_composer(
+ Some(&serde_json::json!({
+ "repositories": {
+ "packages": {
+ "type": "package",
+ "package": [
+ { "name": "vendor1/package1", "version": "1.0.0", "suggests": { "vendor3/suggested": "helpful for vendor1/package1" }, "require": { "vendor6/package6": "^1.0" }, "require-dev": { "vendor3/suggested": "^1.0", "vendor4/dev-suggested": "^1.0" } },
+ { "name": "vendor2/package2", "version": "1.0.0", "suggests": { "vendor4/dev-suggested": "helpful for vendor2/package2" }, "require": { "vendor5/dev-package": "^1.0" } },
+ { "name": "vendor5/dev-package", "version": "1.0.0", "suggests": { "vendor8/dev-transitive": "helpful for vendor5/dev-package" }, "require-dev": { "vendor8/dev-transitive": "^1.0" } },
+ { "name": "vendor6/package6", "version": "1.0.0", "suggests": { "vendor7/transitive": "helpful for vendor6/package6" } },
+ ],
+ },
+ },
+ "require": { "vendor1/package1": "^1" },
+ "require-dev": { "vendor2/package2": "^1" },
+ })),
+ None,
+ None,
+ true,
+ );
+
+ let (packages, dev_packages) = suggest_packages();
+ create_installed_json(&packages, &dev_packages, true);
+ if has_lock_file {
+ create_composer_lock(&packages, &dev_packages);
+ }
+
+ let mut input: Vec<(PhpMixed, PhpMixed)> =
+ vec![(PhpMixed::from("command"), PhpMixed::from("suggest"))];
+ for (k, v) in command {
+ input.push((PhpMixed::from(*k), v.clone()));
+ }
+
+ let mut app_tester = get_application_tester();
+ let status_code = app_tester.run(input, RunOptions::default()).unwrap();
+ assert_eq!(0, status_code);
+ assert_eq!(expected.trim(), app_tester.get_display().trim());
+
+ drop(tear_down);
}
-#[ignore = "missing TestCase::init_temp_composer, create_installed_json, create_composer_lock, get_application_tester (ApplicationTester), and get_version_parser infrastructure"]
#[test]
+#[serial]
fn test_suggest() {
- todo!()
+ let t = PhpMixed::from(true);
+ let by_package = ("--by-package", t.clone());
+ let by_suggestion = ("--by-suggestion", t.clone());
+ let no_dev = ("--no-dev", t.clone());
+ let all = ("--all", t.clone());
+ let list = ("--list", t.clone());
+
+ // 'with lockfile, show suggested' / 'without lockfile, show suggested'
+ let basic = "vendor1/package1 suggests:
+ - vendor3/suggested: helpful for vendor1/package1
+
+vendor2/package2 suggests:
+ - vendor4/dev-suggested: helpful for vendor2/package2
+
+2 additional suggestions by transitive dependencies can be shown with --all";
+ run_suggest_case(true, &[], basic);
+ run_suggest_case(false, &[], basic);
+
+ // 'with lockfile, show suggested (excluding dev)'
+ run_suggest_case(
+ true,
+ &[no_dev.clone()],
+ "vendor1/package1 suggests:
+ - vendor3/suggested: helpful for vendor1/package1
+
+1 additional suggestions by transitive dependencies can be shown with --all",
+ );
+ // 'without lockfile, show suggested (excluding dev)'
+ run_suggest_case(false, &[no_dev.clone()], basic);
+
+ let all_suggested = "vendor1/package1 suggests:
+ - vendor3/suggested: helpful for vendor1/package1
+
+vendor2/package2 suggests:
+ - vendor4/dev-suggested: helpful for vendor2/package2
+
+vendor5/dev-package suggests:
+ - vendor8/dev-transitive: helpful for vendor5/dev-package
+
+vendor6/package6 suggests:
+ - vendor7/transitive: helpful for vendor6/package6";
+ run_suggest_case(true, &[all.clone()], all_suggested);
+ run_suggest_case(false, &[all.clone()], all_suggested);
+
+ // 'with lockfile, show all suggested (excluding dev)'
+ run_suggest_case(
+ true,
+ &[all.clone(), no_dev.clone()],
+ "vendor1/package1 suggests:
+ - vendor3/suggested: helpful for vendor1/package1
+
+vendor6/package6 suggests:
+ - vendor7/transitive: helpful for vendor6/package6",
+ );
+ run_suggest_case(false, &[all.clone(), no_dev.clone()], all_suggested);
+
+ // grouped by package
+ run_suggest_case(true, &[by_package.clone()], basic);
+ run_suggest_case(false, &[by_package.clone()], basic);
+ run_suggest_case(
+ true,
+ &[by_package.clone(), no_dev.clone()],
+ "vendor1/package1 suggests:
+ - vendor3/suggested: helpful for vendor1/package1
+
+1 additional suggestions by transitive dependencies can be shown with --all",
+ );
+ run_suggest_case(false, &[by_package.clone(), no_dev.clone()], basic);
+
+ // grouped by suggestion
+ let by_suggestion_out = "vendor3/suggested is suggested by:
+ - vendor1/package1: helpful for vendor1/package1
+
+vendor4/dev-suggested is suggested by:
+ - vendor2/package2: helpful for vendor2/package2
+
+2 additional suggestions by transitive dependencies can be shown with --all";
+ run_suggest_case(true, &[by_suggestion.clone()], by_suggestion_out);
+ run_suggest_case(false, &[by_suggestion.clone()], by_suggestion_out);
+ run_suggest_case(
+ true,
+ &[by_suggestion.clone(), no_dev.clone()],
+ "vendor3/suggested is suggested by:
+ - vendor1/package1: helpful for vendor1/package1
+
+1 additional suggestions by transitive dependencies can be shown with --all",
+ );
+ run_suggest_case(
+ false,
+ &[by_suggestion.clone(), no_dev.clone()],
+ by_suggestion_out,
+ );
+
+ // grouped by package and suggestion
+ let by_both = "vendor1/package1 suggests:
+ - vendor3/suggested: helpful for vendor1/package1
+
+vendor2/package2 suggests:
+ - vendor4/dev-suggested: helpful for vendor2/package2
+
+------------------------------------------------------------------------------
+vendor3/suggested is suggested by:
+ - vendor1/package1: helpful for vendor1/package1
+
+vendor4/dev-suggested is suggested by:
+ - vendor2/package2: helpful for vendor2/package2
+
+2 additional suggestions by transitive dependencies can be shown with --all";
+ run_suggest_case(true, &[by_package.clone(), by_suggestion.clone()], by_both);
+ run_suggest_case(false, &[by_package.clone(), by_suggestion.clone()], by_both);
+ run_suggest_case(
+ true,
+ &[by_package.clone(), by_suggestion.clone(), no_dev.clone()],
+ "vendor1/package1 suggests:
+ - vendor3/suggested: helpful for vendor1/package1
+
+------------------------------------------------------------------------------
+vendor3/suggested is suggested by:
+ - vendor1/package1: helpful for vendor1/package1
+
+1 additional suggestions by transitive dependencies can be shown with --all",
+ );
+ run_suggest_case(
+ false,
+ &[by_package.clone(), by_suggestion.clone(), no_dev.clone()],
+ by_both,
+ );
+
+ // show suggested for package
+ let for_pkg = "vendor2/package2 suggests:
+ - vendor4/dev-suggested: helpful for vendor2/package2";
+ run_suggest_case(
+ true,
+ &[(
+ "packages",
+ PhpMixed::List(vec![PhpMixed::from("vendor2/package2")]),
+ )],
+ for_pkg,
+ );
+ run_suggest_case(
+ false,
+ &[(
+ "packages",
+ PhpMixed::List(vec![PhpMixed::from("vendor2/package2")]),
+ )],
+ for_pkg,
+ );
+
+ // list suggested
+ let list_out = "vendor3/suggested
+vendor4/dev-suggested";
+ run_suggest_case(true, &[list.clone()], list_out);
+ run_suggest_case(false, &[list.clone()], list_out);
+ run_suggest_case(true, &[list.clone(), no_dev.clone()], "vendor3/suggested");
+ run_suggest_case(false, &[list.clone(), no_dev.clone()], list_out);
+
+ let list_all = "vendor3/suggested
+vendor4/dev-suggested
+vendor7/transitive
+vendor8/dev-transitive";
+ run_suggest_case(true, &[list.clone(), all.clone()], list_all);
+ run_suggest_case(false, &[list.clone(), all.clone()], list_all);
+ run_suggest_case(
+ true,
+ &[list.clone(), all.clone(), no_dev.clone()],
+ "vendor3/suggested
+vendor7/transitive",
+ );
+ run_suggest_case(
+ false,
+ &[list.clone(), all.clone(), no_dev.clone()],
+ list_all,
+ );
}