aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests
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
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')
-rw-r--r--crates/shirabe/tests/advisory/audit_config_test.rs2
-rw-r--r--crates/shirabe/tests/command/init_command_test.rs132
-rw-r--r--crates/shirabe/tests/composer_test.rs6
-rw-r--r--crates/shirabe/tests/console/html_output_formatter_test.rs2
-rw-r--r--crates/shirabe/tests/dependency_resolver/pool_builder_test.rs646
-rw-r--r--crates/shirabe/tests/dependency_resolver/solver_test.rs1777
-rw-r--r--crates/shirabe/tests/dependency_resolver/transaction_test.rs283
-rw-r--r--crates/shirabe/tests/downloader/archive_downloader_test.rs163
-rw-r--r--crates/shirabe/tests/io/buffer_io_test.rs2
-rw-r--r--crates/shirabe/tests/json/json_file_test.rs20
-rw-r--r--crates/shirabe/tests/package/archiver/git_exclude_filter_test.rs2
-rw-r--r--crates/shirabe/tests/package/root_alias_package_test.rs8
-rw-r--r--crates/shirabe/tests/package/version/version_bumper_test.rs2
-rw-r--r--crates/shirabe/tests/package/version/version_parser_test.rs2
-rw-r--r--crates/shirabe/tests/platform/version_test.rs6
-rw-r--r--crates/shirabe/tests/repository/array_repository_test.rs2
-rw-r--r--crates/shirabe/tests/repository/artifact_repository_test.rs102
-rw-r--r--crates/shirabe/tests/repository/composite_repository_test.rs2
-rw-r--r--crates/shirabe/tests/repository/installed_repository_test.rs2
-rw-r--r--crates/shirabe/tests/repository/path_repository_test.rs239
-rw-r--r--crates/shirabe/tests/repository/repository_factory_test.rs50
-rw-r--r--crates/shirabe/tests/repository/repository_utils_test.rs2
-rw-r--r--crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs2
-rw-r--r--crates/shirabe/tests/repository/vcs/github_driver_test.rs2
-rw-r--r--crates/shirabe/tests/script/event_test.rs12
-rw-r--r--crates/shirabe/tests/util/config_validator_test.rs8
-rw-r--r--crates/shirabe/tests/util/forgejo_url_test.rs6
-rw-r--r--crates/shirabe/tests/util/http/request_proxy_test.rs2
-rw-r--r--crates/shirabe/tests/util/ini_helper_test.rs15
-rw-r--r--crates/shirabe/tests/util/no_proxy_pattern_test.rs4
-rw-r--r--crates/shirabe/tests/util/package_sorter_test.rs38
-rw-r--r--crates/shirabe/tests/util/platform_test.rs2
-rw-r--r--crates/shirabe/tests/util/silencer_test.rs2
-rw-r--r--crates/shirabe/tests/util/svn_test.rs134
-rw-r--r--crates/shirabe/tests/util/tar_test.rs14
-rw-r--r--crates/shirabe/tests/util/url_test.rs4
-rw-r--r--crates/shirabe/tests/util/zip_test.rs16
37 files changed, 3470 insertions, 243 deletions
diff --git a/crates/shirabe/tests/advisory/audit_config_test.rs b/crates/shirabe/tests/advisory/audit_config_test.rs
index 30bbb92..85337a4 100644
--- a/crates/shirabe/tests/advisory/audit_config_test.rs
+++ b/crates/shirabe/tests/advisory/audit_config_test.rs
@@ -95,7 +95,7 @@ fn test_detailed_format_block_only() {
}
#[test]
-#[ignore = "parse_ignore_with_apply does not handle integer keys, so the bare list entry is mis-parsed"]
+#[ignore]
fn test_mixed_formats() {
let audit_config = audit_config_from(arr(vec![(
"ignore",
diff --git a/crates/shirabe/tests/command/init_command_test.rs b/crates/shirabe/tests/command/init_command_test.rs
index 589afc5..6b118f3 100644
--- a/crates/shirabe/tests/command/init_command_test.rs
+++ b/crates/shirabe/tests/command/init_command_test.rs
@@ -1,38 +1,96 @@
//! ref: composer/tests/Composer/Test/Command/InitCommandTest.php
-// The author/namespace/git-config helpers are protected methods exercised via reflection
-// in PHP; the run cases need the ApplicationTester. Neither is available here.
+// The run cases (testRunCommand, testRunCommandInvalid, testRunGuessNameFromDirSanitizesDir,
+// testInteractiveRun) drive the full command via ApplicationTester / initTempComposer, which
+// does not exist here; they remain reason'd-ignore. The unit-style cases call the helper
+// methods directly via `__`-prefixed test-only wrappers.
use shirabe::command::init_command::InitCommand;
-use shirabe_php_shim::server_set;
+use shirabe_php_shim::{PhpMixed, server_set};
+use tempfile::TempDir;
fn set_up() {
server_set("COMPOSER_DEFAULT_AUTHOR", "John Smith".to_string());
server_set("COMPOSER_DEFAULT_EMAIL", "john@example.com".to_string());
}
-#[ignore = "InitCommand::parse_author_string is private; integration tests cannot reach it (PHP uses reflection)"]
+/// @return iterable<string, array{0: string, 1: string|null, 2: string}>
+fn valid_author_string_provider() -> Vec<(&'static str, Option<&'static str>, &'static str)> {
+ vec![
+ // simple
+ (
+ "John Smith",
+ Some("john@example.com"),
+ "John Smith <john@example.com>",
+ ),
+ // without email
+ ("John Smith", None, "John Smith"),
+ // UTF-8
+ (
+ "Matti Meikäläinen",
+ Some("matti@example.com"),
+ "Matti Meikäläinen <matti@example.com>",
+ ),
+ // UTF-8 with non-spacing marks (\xCC\x88 is U+0308 combining diaeresis)
+ (
+ "Matti Meika\u{0308}la\u{0308}inen",
+ Some("matti@example.com"),
+ "Matti Meika\u{0308}la\u{0308}inen <matti@example.com>",
+ ),
+ // numeric author name
+ ("h4x0r", Some("h4x@example.com"), "h4x0r <h4x@example.com>"),
+ // alias 1 (issue #5631)
+ (
+ "Johnathon \"Johnny\" Smith",
+ Some("john@example.com"),
+ "Johnathon \"Johnny\" Smith <john@example.com>",
+ ),
+ // alias 2 (issue #5631)
+ (
+ "Johnathon (Johnny) Smith",
+ Some("john@example.com"),
+ "Johnathon (Johnny) Smith <john@example.com>",
+ ),
+ ]
+}
+
+#[ignore]
#[test]
fn test_parse_valid_author_string() {
set_up();
- todo!()
+ for (name, email, input) in valid_author_string_provider() {
+ let command = InitCommand::new();
+ let author = command.__parse_author_string(input).unwrap();
+ assert_eq!(
+ Some(name.to_string()),
+ author.get("name").cloned().flatten()
+ );
+ assert_eq!(
+ email.map(|e| e.to_string()),
+ author.get("email").cloned().flatten()
+ );
+ }
}
-#[ignore = "InitCommand::parse_author_string is private; integration tests cannot reach it (PHP uses reflection)"]
+#[ignore]
#[test]
fn test_parse_empty_author_string() {
set_up();
- todo!()
+ let command = InitCommand::new();
+ let result = command.__parse_author_string("");
+ assert!(result.is_err());
}
-#[ignore = "InitCommand::parse_author_string is private; integration tests cannot reach it (PHP uses reflection)"]
+#[ignore]
#[test]
fn test_parse_author_string_with_invalid_email() {
set_up();
- todo!()
+ let command = InitCommand::new();
+ let result = command.__parse_author_string("John Smith <john>");
+ assert!(result.is_err());
}
#[test]
@@ -97,34 +155,74 @@ fn test_interactive_run() {
todo!()
}
-#[ignore = "InitCommand::format_authors is pub(crate); integration tests cannot reach it (PHP subclasses via DummyInitCommand)"]
+#[ignore]
#[test]
fn test_format_authors() {
set_up();
- todo!()
+ let author_with_email = "John Smith <john@example.com>";
+ let author_without_email = "John Smith";
+ let command = InitCommand::new();
+
+ let authors = command.__format_authors(author_with_email).unwrap();
+ let mut expected: indexmap::IndexMap<String, PhpMixed> = indexmap::IndexMap::new();
+ expected.insert(
+ "name".to_string(),
+ PhpMixed::String("John Smith".to_string()),
+ );
+ expected.insert(
+ "email".to_string(),
+ PhpMixed::String("john@example.com".to_string()),
+ );
+ assert_eq!(expected, authors[0]);
+
+ let authors = command.__format_authors(author_without_email).unwrap();
+ let mut expected: indexmap::IndexMap<String, PhpMixed> = indexmap::IndexMap::new();
+ expected.insert(
+ "name".to_string(),
+ PhpMixed::String("John Smith".to_string()),
+ );
+ assert_eq!(expected, authors[0]);
}
-#[ignore = "InitCommand::get_git_config is pub(crate); integration tests cannot reach it (PHP subclasses via DummyInitCommand)"]
+#[ignore]
#[test]
fn test_get_git_config() {
set_up();
- todo!()
+ let mut command = InitCommand::new();
+ let git_config = command.__get_git_config();
+ assert!(git_config.contains_key("user.name"));
+ assert!(git_config.contains_key("user.email"));
}
-#[ignore = "InitCommand::add_vendor_ignore is pub(crate) and test needs TestCase::get_unique_tmp_directory; neither reachable from integration tests"]
+#[ignore]
#[test]
fn test_add_vendor_ignore() {
set_up();
- todo!()
+ let tmp = TempDir::new().unwrap();
+ let ignore_file = tmp.path().join("ignore");
+ let ignore_file = ignore_file.to_str().unwrap();
+
+ let command = InitCommand::new();
+ command.__add_vendor_ignore(ignore_file, "/vendor/");
+ assert!(std::path::Path::new(ignore_file).exists());
+ let content = std::fs::read_to_string(ignore_file).unwrap();
+ assert!(content.contains("/vendor/"));
}
-#[ignore = "InitCommand::has_vendor_ignore/add_vendor_ignore are pub(crate) and test needs TestCase::get_unique_tmp_directory; neither reachable from integration tests"]
+#[ignore]
#[test]
fn test_has_vendor_ignore() {
set_up();
- todo!()
+ let tmp = TempDir::new().unwrap();
+ let ignore_file = tmp.path().join("ignore");
+ let ignore_file = ignore_file.to_str().unwrap();
+
+ let command = InitCommand::new();
+ assert!(!command.__has_vendor_ignore(ignore_file, "vendor"));
+ command.__add_vendor_ignore(ignore_file, "/vendor/");
+ assert!(command.__has_vendor_ignore(ignore_file, "vendor"));
}
diff --git a/crates/shirabe/tests/composer_test.rs b/crates/shirabe/tests/composer_test.rs
index 75a554c..28d9d47 100644
--- a/crates/shirabe/tests/composer_test.rs
+++ b/crates/shirabe/tests/composer_test.rs
@@ -55,7 +55,7 @@ fn test_set_get_package() {
}
#[test]
-#[ignore = "Locker construction builds an HttpDownloader, which reaches curl_multi_init (todo!()) in the php-shim"]
+#[ignore]
fn test_set_get_locker() {
let mut composer = Composer::new();
let io = null_io();
@@ -74,7 +74,7 @@ fn test_set_get_locker() {
}
#[test]
-#[ignore = "RepositoryManager construction builds an HttpDownloader, which reaches curl_multi_init (todo!()) in the php-shim"]
+#[ignore]
fn test_set_get_repository_manager() {
let mut composer = Composer::new();
let io = null_io();
@@ -101,7 +101,7 @@ fn test_set_get_download_manager() {
}
#[test]
-#[ignore = "InstallationManager construction builds a Loop/HttpDownloader, which reaches curl_multi_init (todo!()) in the php-shim"]
+#[ignore]
fn test_set_get_installation_manager() {
let mut composer = Composer::new();
let io = null_io();
diff --git a/crates/shirabe/tests/console/html_output_formatter_test.rs b/crates/shirabe/tests/console/html_output_formatter_test.rs
index af75551..2274ffe 100644
--- a/crates/shirabe/tests/console/html_output_formatter_test.rs
+++ b/crates/shirabe/tests/console/html_output_formatter_test.rs
@@ -7,7 +7,7 @@ use shirabe_external_packages::symfony::console::formatter::{
};
#[test]
-#[ignore = "HtmlOutputFormatter::format uses a regex pattern the regex crate cannot compile (backreferences)"]
+#[ignore]
fn test_formatting() {
let mut styles: IndexMap<String, Box<dyn OutputFormatterStyleInterface>> = IndexMap::new();
styles.insert(
diff --git a/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs b/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs
index 4c72091..87f02aa 100644
--- a/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs
+++ b/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs
@@ -1,9 +1,647 @@
//! ref: composer/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php
-// 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.
+use std::cell::RefCell;
+use std::path::PathBuf;
+use std::rc::Rc;
+
+use indexmap::IndexMap;
+use shirabe::config::Config;
+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, UpdateAllowTransitiveDeps};
+use shirabe::io::io_interface::IOInterface;
+use shirabe::io::null_io::NullIO;
+use shirabe::json::JsonFile;
+use shirabe::package::BasePackageHandle;
+use shirabe::package::STABILITIES;
+use shirabe::package::loader::{ArrayLoader, LoaderInterface};
+use shirabe::package::version::version_parser::VersionParser;
+use shirabe::repository::array_repository::ArrayRepository;
+use shirabe::repository::filter_repository::FilterRepository;
+use shirabe::repository::handle::{LockArrayRepositoryHandle, RepositoryInterfaceHandle};
+use shirabe::repository::lock_array_repository::LockArrayRepository;
+use shirabe::repository::repository_factory::RepositoryFactory;
+use shirabe::repository::repository_set::{RepositorySet, RootAliasInput};
+use shirabe_external_packages::composer::pcre::preg::Preg;
+use shirabe_php_shim::PREG_SPLIT_DELIM_CAPTURE;
+use shirabe_php_shim::PhpMixed;
+
+/// Maps the PHP `$loadPackage` closure: pops the optional `id` from the data, loads the
+/// package and records it in `package_ids` keyed by that id (erroring on duplicates).
+fn load_package(
+ loader: &ArrayLoader,
+ package_ids: &mut IndexMap<i64, BasePackageHandle>,
+ data: &IndexMap<String, PhpMixed>,
+) -> BasePackageHandle {
+ let mut data = data.clone();
+
+ let mut id: Option<i64> = None;
+ // PHP: !empty($data['id'])
+ if let Some(id_val) = data.get("id") {
+ if let Some(i) = id_val.as_int() {
+ if i != 0 {
+ id = Some(i);
+ data.shift_remove("id");
+ }
+ }
+ }
+
+ let pkg = loader.load(data, None).unwrap();
+
+ if let Some(id) = id {
+ if package_ids.contains_key(&id) {
+ panic!("Duplicate package id {} defined", id);
+ }
+ package_ids.insert(id, pkg.clone());
+ }
+
+ pkg
+}
+
+fn read_test_file(file: &str, fixtures_dir: &str) -> IndexMap<String, String> {
+ 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,
+ );
+
+ // PHP section_info is a map of name => required flag.
+ let section_info: Vec<(&str, bool)> = vec![
+ ("TEST", true),
+ ("ROOT", false),
+ ("REQUEST", true),
+ ("FIXED", false),
+ ("PACKAGE-REPOS", true),
+ ("EXPECT", true),
+ ("EXPECT-OPTIMIZED", false),
+ ];
+
+ let mut section: Option<String> = None;
+ let mut data: IndexMap<String, String> = IndexMap::new();
+ for token in tokens {
+ if section.is_none() && token.is_empty() {
+ continue;
+ }
+
+ if section.is_none() {
+ if !section_info.iter().any(|(name, _)| *name == 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 (section, required) in &section_info {
+ if *required && !data.contains_key(*section) {
+ panic!(
+ "The test file \"{}\" must have a section named \"{}\".",
+ file.replace(&format!("{}/", fixtures_dir), ""),
+ section
+ );
+ }
+ }
+
+ data
+}
+
+fn collect_test_files(dir: &std::path::Path, out: &mut Vec<PathBuf>) {
+ 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);
+ }
+ }
+}
+
+struct IntegrationTest {
+ file: String,
+ message: String,
+ expect: PhpMixed,
+ expect_optimized: PhpMixed,
+ root: PhpMixed,
+ request: PhpMixed,
+ package_repos: PhpMixed,
+ fixed: PhpMixed,
+}
+
+fn get_integration_tests(fixtures_dir: &std::path::Path) -> IndexMap<String, IntegrationTest> {
+ let fixtures_dir_str = fixtures_dir.to_str().unwrap().to_string();
+
+ let mut files: Vec<PathBuf> = Vec::new();
+ collect_test_files(fixtures_dir, &mut files);
+
+ let mut tests: IndexMap<String, IntegrationTest> = 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 = JsonFile::parse_json(Some(&test_data["REQUEST"]), None).unwrap();
+ // PHP: !empty($testData['ROOT']) ? parseJson(...) : []
+ let root = match test_data.get("ROOT") {
+ Some(s) if !s.is_empty() => JsonFile::parse_json(Some(s), None).unwrap(),
+ _ => PhpMixed::List(vec![]),
+ };
+
+ let package_repos = JsonFile::parse_json(Some(&test_data["PACKAGE-REPOS"]), None).unwrap();
+ let fixed = match test_data.get("FIXED") {
+ Some(s) if !s.is_empty() => JsonFile::parse_json(Some(s), None).unwrap(),
+ _ => PhpMixed::List(vec![]),
+ };
+ let expect = JsonFile::parse_json(Some(&test_data["EXPECT"]), None).unwrap();
+ let expect_optimized = match test_data.get("EXPECT-OPTIMIZED") {
+ Some(s) if !s.is_empty() => JsonFile::parse_json(Some(s), None).unwrap(),
+ _ => expect.clone(),
+ };
+
+ let basename = std::path::Path::new(&file)
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .to_string();
+ tests.insert(
+ basename,
+ IntegrationTest {
+ file: file.replace(&format!("{}/", fixtures_dir_str), ""),
+ message,
+ expect,
+ expect_optimized,
+ root,
+ request,
+ package_repos,
+ fixed,
+ },
+ );
+ }
+
+ tests
+}
+
+/// PHP `sort()` with SORT_REGULAR over a mixed int/string list. Mirrors PHP 8 loose
+/// comparison: two ints compare numerically; a string and an int that looks numeric
+/// compare numerically; otherwise both are compared as strings.
+fn php_sort_mixed(values: &mut [PhpMixed]) {
+ fn cmp(a: &PhpMixed, b: &PhpMixed) -> std::cmp::Ordering {
+ use std::cmp::Ordering;
+ let num = |m: &PhpMixed| -> Option<f64> {
+ match m {
+ PhpMixed::Int(i) => Some(*i as f64),
+ PhpMixed::Float(f) => Some(*f),
+ PhpMixed::String(s) => s.trim().parse::<f64>().ok(),
+ _ => None,
+ }
+ };
+ let str_of = |m: &PhpMixed| -> String {
+ match m {
+ PhpMixed::Int(i) => i.to_string(),
+ PhpMixed::Float(f) => f.to_string(),
+ PhpMixed::String(s) => s.clone(),
+ _ => String::new(),
+ }
+ };
+ match (a, b) {
+ (PhpMixed::Int(x), PhpMixed::Int(y)) => x.cmp(y),
+ _ => match (num(a), num(b)) {
+ (Some(x), Some(y)) => x.partial_cmp(&y).unwrap_or(Ordering::Equal),
+ _ => str_of(a).cmp(&str_of(b)),
+ },
+ }
+ }
+ values.sort_by(cmp);
+}
+
+/// ref: PoolBuilderTest::getPackageResultSet
+fn get_package_result_set(
+ pool: &Pool,
+ package_ids: &IndexMap<i64, BasePackageHandle>,
+) -> Vec<PhpMixed> {
+ let mut result: Vec<BasePackageHandle> = Vec::new();
+ let count = pool.__count();
+ // PHP: for ($i = 1; $i <= $count; $i++)
+ let mut i = 1;
+ while i <= count {
+ result.push(pool.package_by_id(i));
+ i += 1;
+ }
+
+ // PHP: array_search($package, $packageIds, true) — identity lookup.
+ let find_id = |package: &BasePackageHandle| -> Option<i64> {
+ for (id, candidate) in package_ids {
+ if candidate.ptr_id() == package.ptr_id() {
+ return Some(*id);
+ }
+ }
+ None
+ };
+
+ result
+ .into_iter()
+ .map(|package| {
+ // PHP: if ($id = array_search(...)) — id keys start at 1 so always truthy when found.
+ if let Some(id) = find_id(&package) {
+ return PhpMixed::Int(id);
+ }
+
+ let mut suffix = String::new();
+ if let Some(source_reference) = package.get_source_reference() {
+ suffix = format!("#{}", source_reference);
+ }
+ if let Some(repo) = package.get_repository() {
+ if repo.is::<LockArrayRepository>() {
+ suffix.push_str(" (locked)");
+ }
+ }
+
+ if let Some(alias) = package.as_alias() {
+ let alias_of = alias.get_alias_of();
+ // PHP: array_search($package->getAliasOf(), $packageIds, true)
+ let mut matched: Option<i64> = None;
+ for (id, candidate) in package_ids {
+ if candidate.ptr_id() == alias_of.ptr_id() {
+ matched = Some(*id);
+ break;
+ }
+ }
+ if let Some(id) = matched {
+ return PhpMixed::String(format!(
+ "{}-{}{} (alias of {})",
+ package.get_name(),
+ package.get_version(),
+ suffix,
+ id
+ ));
+ }
+
+ return PhpMixed::String(format!(
+ "{}-{}{} (alias of {})",
+ package.get_name(),
+ package.get_version(),
+ suffix,
+ alias_of.get_version()
+ ));
+ }
+
+ PhpMixed::String(format!(
+ "{}-{}{}",
+ package.get_name(),
+ package.get_version(),
+ suffix
+ ))
+ })
+ .collect()
+}
+
+/// ref: PoolBuilderTest::testPoolBuilder
+#[allow(clippy::too_many_arguments)]
+fn run_test_pool_builder(
+ fixtures_dir: &std::path::Path,
+ _file: &str,
+ message: &str,
+ expect: &PhpMixed,
+ expect_optimized: &PhpMixed,
+ root: &PhpMixed,
+ request_data: &PhpMixed,
+ package_repos: &PhpMixed,
+ fixed: &PhpMixed,
+) {
+ // PHP: $root is array; empty checks against keys.
+ let root_map = root.as_array();
+ let get_root = |key: &str| -> Option<&PhpMixed> { root_map.and_then(|m| m.get(key)) };
+ let is_empty = |v: Option<&PhpMixed>| -> bool {
+ match v {
+ None => true,
+ Some(PhpMixed::Null) => true,
+ Some(PhpMixed::Bool(false)) => true,
+ Some(PhpMixed::Int(0)) => true,
+ Some(PhpMixed::String(s)) => s.is_empty(),
+ Some(PhpMixed::List(l)) => l.is_empty(),
+ Some(PhpMixed::Array(a)) => a.is_empty(),
+ Some(PhpMixed::Object(a)) => a.is_empty(),
+ _ => false,
+ }
+ };
+
+ // PHP: $rootAliases = !empty($root['aliases']) ? $root['aliases'] : [];
+ let root_aliases_data: Vec<IndexMap<String, PhpMixed>> = if !is_empty(get_root("aliases")) {
+ get_root("aliases")
+ .and_then(|v| v.as_list())
+ .map(|l| {
+ l.iter()
+ .filter_map(|v| v.as_array().cloned())
+ .collect::<Vec<_>>()
+ })
+ .unwrap_or_default()
+ } else {
+ vec![]
+ };
+
+ let minimum_stability: String = if !is_empty(get_root("minimum-stability")) {
+ get_root("minimum-stability")
+ .and_then(|v| v.as_string())
+ .unwrap_or("stable")
+ .to_string()
+ } else {
+ "stable".to_string()
+ };
+
+ // PHP: $stabilityFlags map of name => stability string.
+ let stability_flags_data: IndexMap<String, String> = if !is_empty(get_root("stability-flags")) {
+ get_root("stability-flags")
+ .and_then(|v| v.as_array())
+ .map(|m| {
+ m.iter()
+ .filter_map(|(k, v)| v.as_string().map(|s| (k.clone(), s.to_string())))
+ .collect()
+ })
+ .unwrap_or_default()
+ } else {
+ IndexMap::new()
+ };
+
+ let root_references: IndexMap<String, String> = if !is_empty(get_root("references")) {
+ get_root("references")
+ .and_then(|v| v.as_array())
+ .map(|m| {
+ m.iter()
+ .filter_map(|(k, v)| v.as_string().map(|s| (k.clone(), s.to_string())))
+ .collect()
+ })
+ .unwrap_or_default()
+ } else {
+ IndexMap::new()
+ };
+
+ // PHP: array_map over stability flags, mapping to BasePackage::STABILITIES[$stability].
+ let mut stability_flags: IndexMap<String, i64> = IndexMap::new();
+ for (name, stability) in &stability_flags_data {
+ let Some(value) = STABILITIES.get(stability.as_str()) else {
+ panic!("Invalid stability given: {}", stability);
+ };
+ stability_flags.insert(name.clone(), *value);
+ }
+
+ let parser = VersionParser::new();
+ // PHP: foreach ($rootAliases as $index => $alias) { normalize version + alias }
+ let mut root_aliases: Vec<RootAliasInput> = Vec::new();
+ for alias in &root_aliases_data {
+ let package = alias
+ .get("package")
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ .to_string();
+ let version = parser
+ .normalize(
+ alias
+ .get("version")
+ .and_then(|v| v.as_string())
+ .unwrap_or(""),
+ None,
+ )
+ .unwrap();
+ let alias_str = alias
+ .get("alias")
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ .to_string();
+ let alias_normalized = parser
+ .normalize(
+ alias.get("alias").and_then(|v| v.as_string()).unwrap_or(""),
+ None,
+ )
+ .unwrap();
+ root_aliases.push(RootAliasInput {
+ package,
+ version,
+ alias: alias_str,
+ alias_normalized,
+ });
+ }
+
+ let loader = ArrayLoader::new(None, true);
+ let mut package_ids: IndexMap<i64, BasePackageHandle> = IndexMap::new();
+
+ // PHP: $oldCwd = Platform::getCwd(); chdir(__DIR__.'/Fixtures/poolbuilder/');
+ let old_cwd = std::env::current_dir().unwrap();
+ std::env::set_current_dir(fixtures_dir).unwrap();
+
+ let mut repository_set = RepositorySet::new(
+ &minimum_stability,
+ stability_flags,
+ root_aliases,
+ root_references,
+ IndexMap::new(),
+ IndexMap::new(),
+ );
+ let config = Rc::new(RefCell::new(Config::new(false, None)));
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ let mut rm = RepositoryFactory::manager(io.clone(), &config, None, None, None).unwrap();
+
+ // PHP: foreach ($packageRepos as $packages)
+ for repo_entry in package_repos.as_list().unwrap() {
+ // PHP: isset($packages['type'])
+ if let Some(repo_map) = repo_entry.as_array() {
+ if repo_map.contains_key("type") {
+ let repo = RepositoryFactory::create_repo(
+ io.clone(),
+ &config,
+ repo_map.clone(),
+ Some(&mut rm),
+ )
+ .unwrap();
+ repository_set.add_repository(repo).unwrap();
+ continue;
+ }
+ }
+
+ let repo = ArrayRepository::new(vec![]).unwrap();
+ let repo_handle = RepositoryInterfaceHandle::new(repo);
+
+ // PHP: isset($packages['canonical']) || isset($packages['only']) || isset($packages['exclude'])
+ let packages_list: Vec<IndexMap<String, PhpMixed>>;
+ if let Some(repo_map) = repo_entry.as_array() {
+ if repo_map.contains_key("canonical")
+ || repo_map.contains_key("only")
+ || repo_map.contains_key("exclude")
+ {
+ let mut options = repo_map.clone();
+ let packages = options
+ .shift_remove("packages")
+ .unwrap_or(PhpMixed::List(vec![]));
+ repository_set
+ .add_repository(RepositoryInterfaceHandle::new(
+ FilterRepository::new(repo_handle.clone(), options).unwrap(),
+ ))
+ .unwrap();
+ packages_list = packages
+ .as_list()
+ .map(|l| l.iter().filter_map(|v| v.as_array().cloned()).collect())
+ .unwrap_or_default();
+ } else {
+ repository_set.add_repository(repo_handle.clone()).unwrap();
+ packages_list = repo_map
+ .values()
+ .filter_map(|v| v.as_array().cloned())
+ .collect();
+ }
+ } else {
+ repository_set.add_repository(repo_handle.clone()).unwrap();
+ packages_list = repo_entry
+ .as_list()
+ .map(|l| l.iter().filter_map(|v| v.as_array().cloned()).collect())
+ .unwrap_or_default();
+ }
+
+ let array_repo = repo_handle.downcast_rc::<ArrayRepository>().unwrap();
+ for package in &packages_list {
+ array_repo
+ .borrow()
+ .add_package(load_package(&loader, &mut package_ids, package))
+ .unwrap();
+ }
+ }
+
+ let locked_repo = LockArrayRepositoryHandle::new(LockArrayRepository::new(vec![]).unwrap());
+ repository_set
+ .add_repository(locked_repo.clone().into())
+ .unwrap();
+
+ let request_map = request_data.as_array().unwrap();
+
+ // PHP: if (isset($requestData['locked']))
+ if let Some(locked) = request_map.get("locked") {
+ for package in locked.as_list().unwrap() {
+ locked_repo
+ .borrow()
+ .add_package(load_package(
+ &loader,
+ &mut package_ids,
+ package.as_array().unwrap(),
+ ))
+ .unwrap();
+ }
+ }
+
+ let mut request = Request::new(Some(locked_repo.clone()));
+ for (package, constraint) in request_map["require"].as_array().unwrap() {
+ request
+ .require_name(
+ package,
+ Some(
+ parser
+ .parse_constraints(constraint.as_string().unwrap())
+ .unwrap(),
+ ),
+ )
+ .unwrap();
+ }
+
+ // PHP: if (isset($requestData['allowList']))
+ if let Some(allow_list) = request_map.get("allowList") {
+ let mut transitive_deps = UpdateAllowTransitiveDeps::UpdateOnlyListed;
+ if request_map
+ .get("allowTransitiveDepsNoRootRequire")
+ .and_then(|v| v.as_bool())
+ .unwrap_or(false)
+ {
+ transitive_deps =
+ UpdateAllowTransitiveDeps::UpdateListedWithTransitiveDepsNoRootRequire;
+ }
+ if request_map
+ .get("allowTransitiveDeps")
+ .and_then(|v| v.as_bool())
+ .unwrap_or(false)
+ {
+ transitive_deps = UpdateAllowTransitiveDeps::UpdateListedWithTransitiveDeps;
+ }
+ let allow_list_names: Vec<String> = allow_list
+ .as_list()
+ .unwrap()
+ .iter()
+ .filter_map(|v| v.as_string().map(|s| s.to_string()))
+ .collect();
+ request.set_update_allow_list(allow_list_names, transitive_deps);
+ }
+
+ // PHP: foreach ($fixed as $fixedPackage)
+ for fixed_package in fixed.as_list().unwrap() {
+ request.fix_package(load_package(
+ &loader,
+ &mut package_ids,
+ fixed_package.as_array().unwrap(),
+ ));
+ }
+
+ let pool = repository_set
+ .create_pool(&mut request, io.clone(), None, None, vec![], None, None)
+ .unwrap();
+
+ let mut result = get_package_result_set(&pool, &package_ids);
+
+ let mut expect = expect.as_list().unwrap().clone();
+ php_sort_mixed(&mut expect);
+ php_sort_mixed(&mut result);
+ assert_eq!(
+ expect, result,
+ "Unoptimized pool does not match expected package set ({})",
+ message
+ );
+
+ let mut optimizer = PoolOptimizer::new(Rc::new(DefaultPolicy::new(false, false, None)));
+ let optimized = optimizer.optimize(&request, &pool);
+ let mut result = get_package_result_set(&optimized, &package_ids);
+ let mut expect_optimized = expect_optimized.as_list().unwrap().clone();
+ php_sort_mixed(&mut expect_optimized);
+ php_sort_mixed(&mut result);
+ assert_eq!(
+ expect_optimized, result,
+ "Optimized pool does not match expected package set ({})",
+ message
+ );
+
+ // PHP: chdir($oldCwd);
+ std::env::set_current_dir(&old_cwd).unwrap();
+}
+
+#[ignore]
#[test]
-#[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!()
+ let fixtures_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .join("../../composer/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder");
+ let fixtures_dir = std::fs::canonicalize(&fixtures_dir).unwrap();
+
+ let tests = get_integration_tests(&fixtures_dir);
+ for (_name, test) in tests {
+ run_test_pool_builder(
+ &fixtures_dir,
+ &test.file,
+ &test.message,
+ &test.expect,
+ &test.expect_optimized,
+ &test.root,
+ &test.request,
+ &test.package_repos,
+ &test.fixed,
+ );
+ }
}
diff --git a/crates/shirabe/tests/dependency_resolver/solver_test.rs b/crates/shirabe/tests/dependency_resolver/solver_test.rs
index 26f1312..c729fea 100644
--- a/crates/shirabe/tests/dependency_resolver/solver_test.rs
+++ b/crates/shirabe/tests/dependency_resolver/solver_test.rs
@@ -9,11 +9,13 @@ 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::Link;
use shirabe::package::handle::PackageInterfaceHandle;
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_semver::constraint::{AnyConstraint, MatchAllConstraint, MultiConstraint};
use crate::test_case::{get_alias_package, get_package, get_version_constraint};
@@ -50,6 +52,40 @@ fn set_up() -> Fixtures {
}
}
+/// PHP `new Link($source, $target, $constraint, $type)`: prettyConstraint defaults to
+/// `(string) $constraint`.
+fn link(source: &str, target: &str, 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,
+ )
+}
+
+/// PHP `new Link($source, $target, $constraint, $type, $prettyConstraint)`.
+fn link_pretty(
+ source: &str,
+ target: &str,
+ constraint: AnyConstraint,
+ r#type: &str,
+ pretty: &str,
+) -> Link {
+ Link::new(
+ source.to_string(),
+ target.to_string(),
+ constraint,
+ Some(r#type.to_string()),
+ pretty.to_string(),
+ )
+}
+
+fn multi(constraints: Vec<AnyConstraint>) -> AnyConstraint {
+ MultiConstraint::new(constraints, true, None).into()
+}
+
/// One expected solver job. Mirrors the PHP `['job' => ..., 'package'|'from'|'to' => ...]` rows.
enum ExpectedJob {
Single {
@@ -67,7 +103,7 @@ fn check_solver_result(
mut repo_set: RepositorySet,
repo: ArrayRepository,
repo_locked: LockArrayRepositoryHandle,
- mut request: Request,
+ request: Request,
expected: Vec<ExpectedJob>,
) {
// reposComplete()
@@ -76,6 +112,15 @@ fn check_solver_result(
.unwrap();
repo_set.add_repository(repo_locked.into()).unwrap();
+ check_solver_result_repo_set(&mut repo_set, request, expected);
+}
+
+/// ref: SolverTest::checkSolverResult, against an already-completed RepositorySet (createSolver).
+fn check_solver_result_repo_set(
+ repo_set: &mut RepositorySet,
+ mut request: Request,
+ expected: Vec<ExpectedJob>,
+) {
// createSolver()
let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
let pool = repo_set
@@ -137,6 +182,29 @@ fn check_solver_result(
assert_eq!(expected_ids, result_ids);
}
+/// ref: SolverTest::createSolver + solve, returning the error for expectException-only tests.
+fn solve_expecting_error(
+ mut repo_set: RepositorySet,
+ repo: ArrayRepository,
+ repo_locked: LockArrayRepositoryHandle,
+ mut request: Request,
+) {
+ repo_set
+ .add_repository(RepositoryInterfaceHandle::new(repo))
+ .unwrap();
+ repo_set.add_repository(repo_locked.into()).unwrap();
+
+ let io: Rc<RefCell<dyn IOInterface>> = 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<dyn PolicyInterface> = Rc::new(DefaultPolicy::new(false, false, None));
+ let mut solver =
+ shirabe::dependency_resolver::solver::Solver::new(policy, Rc::new(RefCell::new(pool)), io);
+
+ assert!(solver.solve(&request, None).is_err());
+}
+
#[ignore]
#[test]
fn test_solver_install_single() {
@@ -178,7 +246,7 @@ fn test_solver_remove_if_not_requested() {
);
}
-#[ignore = "solve() error path discards SolverProblemsException (returns placeholder anyhow error); getProblems/getCode/getPrettyString not retrievable"]
+#[ignore = "asserts SolverProblemsException details (getProblems/getCode/getPrettyString) which solve() discards"]
#[test]
fn test_install_non_existing_package_fails() {
let _fixtures = set_up();
@@ -222,40 +290,364 @@ fn test_solver_install_same_package_from_different_repositories() {
);
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_install_with_deps() {
- 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("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ let new_package_b = get_package("B", "1.1");
+ fixtures.repo.add_package(new_package_b.clone()).unwrap();
+
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint("<", "1.1"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ 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_b,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ],
+ );
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_install_honours_not_equal_operator() {
- 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("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ let new_package_b11 = get_package("B", "1.1");
+ fixtures.repo.add_package(new_package_b11.clone()).unwrap();
+ let new_package_b12 = get_package("B", "1.2");
+ fixtures.repo.add_package(new_package_b12.clone()).unwrap();
+ let new_package_b13 = get_package("B", "1.3");
+ fixtures.repo.add_package(new_package_b13.clone()).unwrap();
+
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ multi(vec![
+ get_version_constraint("<=", "1.3"),
+ get_version_constraint("<>", "1.3"),
+ get_version_constraint("!=", "1.2"),
+ ]),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ 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: new_package_b11,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ],
+ );
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_install_with_deps_in_order() {
- 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("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ let package_c = get_package("C", "1.0");
+ fixtures.repo.add_package(package_c.clone()).unwrap();
+
+ package_b
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([
+ (
+ "a".to_string(),
+ link(
+ "B",
+ "A",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ (
+ "c".to_string(),
+ link(
+ "B",
+ "C",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ ]));
+ package_c
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "a".to_string(),
+ link(
+ "C",
+ "A",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request.require_name("A", None).unwrap();
+ request.require_name("B", None).unwrap();
+ request.require_name("C", None).unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_c,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_b,
+ },
+ ],
+ );
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_multi_package_name_version_resolution_depends_on_require_order() {
- let _fixtures = set_up();
- todo!()
+ let fixtures = set_up();
+ let php74 = get_package("ourcustom/PHP", "7.4.23");
+ fixtures.repo.add_package(php74.clone()).unwrap();
+ let php80 = get_package("ourcustom/PHP", "8.0.10");
+ fixtures.repo.add_package(php80.clone()).unwrap();
+ let ext_for_php74 = get_package("ourcustom/ext-foobar", "1.0");
+ fixtures.repo.add_package(ext_for_php74.clone()).unwrap();
+ let ext_for_php80 = get_package("ourcustom/ext-foobar", "1.0");
+ fixtures.repo.add_package(ext_for_php80.clone()).unwrap();
+
+ ext_for_php74
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "ourcustom/php".to_string(),
+ link(
+ "ourcustom/ext-foobar",
+ "ourcustom/PHP",
+ multi(vec![
+ get_version_constraint(">=", "7.4.0"),
+ get_version_constraint("<", "7.5.0"),
+ ]),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ ext_for_php80
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "ourcustom/php".to_string(),
+ link(
+ "ourcustom/ext-foobar",
+ "ourcustom/PHP",
+ multi(vec![
+ get_version_constraint(">=", "8.0.0"),
+ get_version_constraint("<", "8.1.0"),
+ ]),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ // reposComplete()
+ let mut repo_set = fixtures.repo_set;
+ repo_set
+ .add_repository(RepositoryInterfaceHandle::new(fixtures.repo))
+ .unwrap();
+ repo_set
+ .add_repository(fixtures.repo_locked.clone().into())
+ .unwrap();
+
+ let mut request = fixtures.request;
+ request.require_name("ourcustom/PHP", None).unwrap();
+ request.require_name("ourcustom/ext-foobar", None).unwrap();
+
+ check_solver_result_repo_set(
+ &mut repo_set,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: php80.clone(),
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: ext_for_php80,
+ },
+ ],
+ );
+
+ // now we flip the requirements around: we request "ext-foobar" before "php"
+ let mut request = Request::new(Some(fixtures.repo_locked.clone()));
+ request.require_name("ourcustom/ext-foobar", None).unwrap();
+ request.require_name("ourcustom/PHP", None).unwrap();
+
+ check_solver_result_repo_set(
+ &mut repo_set,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: php74,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: ext_for_php74,
+ },
+ ],
+ );
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_multi_package_name_version_resolution_is_independent_of_require_order_if_ordered_descending_by_requirement()
{
- let _fixtures = set_up();
- todo!()
+ let fixtures = set_up();
+ let php74 = get_package("ourcustom/PHP", "7.4");
+ fixtures.repo.add_package(php74.clone()).unwrap();
+ let php80 = get_package("ourcustom/PHP", "8.0");
+ fixtures.repo.add_package(php80.clone()).unwrap();
+ // note we are inserting this one into the repo first, unlike in the previous test
+ let ext_for_php80 = get_package("ourcustom/ext-foobar", "1.0");
+ fixtures.repo.add_package(ext_for_php80.clone()).unwrap();
+ let ext_for_php74 = get_package("ourcustom/ext-foobar", "1.0");
+ fixtures.repo.add_package(ext_for_php74.clone()).unwrap();
+
+ ext_for_php80
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "ourcustom/php".to_string(),
+ link(
+ "ourcustom/ext-foobar",
+ "ourcustom/PHP",
+ multi(vec![
+ get_version_constraint(">=", "8.0.0"),
+ get_version_constraint("<", "8.1.0"),
+ ]),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ ext_for_php74
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "ourcustom/php".to_string(),
+ link(
+ "ourcustom/ext-foobar",
+ "ourcustom/PHP",
+ multi(vec![
+ get_version_constraint(">=", "7.4.0"),
+ get_version_constraint("<", "7.5.0"),
+ ]),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ // reposComplete()
+ let mut repo_set = fixtures.repo_set;
+ repo_set
+ .add_repository(RepositoryInterfaceHandle::new(fixtures.repo))
+ .unwrap();
+ repo_set
+ .add_repository(fixtures.repo_locked.clone().into())
+ .unwrap();
+
+ let mut request = fixtures.request;
+ request.require_name("ourcustom/PHP", None).unwrap();
+ request.require_name("ourcustom/ext-foobar", None).unwrap();
+
+ check_solver_result_repo_set(
+ &mut repo_set,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: php80.clone(),
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: ext_for_php80.clone(),
+ },
+ ],
+ );
+
+ // unlike in the previous test, the order of requirements no longer matters now
+ let mut request = Request::new(Some(fixtures.repo_locked.clone()));
+ request.require_name("ourcustom/ext-foobar", None).unwrap();
+ request.require_name("ourcustom/PHP", None).unwrap();
+
+ check_solver_result_repo_set(
+ &mut repo_set,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: php80,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: ext_for_php80,
+ },
+ ],
+ );
}
#[ignore]
@@ -297,11 +689,46 @@ fn test_solver_fix_locked_with_alternative() {
);
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_update_does_only_update() {
- 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_b = get_package("B", "1.1");
+ fixtures.repo.add_package(new_package_b.clone()).unwrap();
+
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "1.0.0.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request.fix_package(package_a.clone());
+ request
+ .require_name("B", Some(get_version_constraint("=", "1.1.0.0")))
+ .unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![ExpectedJob::Update {
+ from: package_b,
+ to: new_package_b,
+ }],
+ );
}
#[ignore]
@@ -328,11 +755,63 @@ fn test_solver_update_single() {
);
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_update_all() {
- 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.1");
+ fixtures.repo.add_package(new_package_a.clone()).unwrap();
+ let new_package_b = get_package("B", "1.1");
+ fixtures.repo.add_package(new_package_b.clone()).unwrap();
+
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ MatchAllConstraint::new(None).into(),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ new_package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ MatchAllConstraint::new(None).into(),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ 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_b,
+ to: new_package_b,
+ },
+ ExpectedJob::Update {
+ from: package_a,
+ to: new_package_a,
+ },
+ ],
+ );
}
#[ignore]
@@ -475,25 +954,168 @@ fn test_solver_update_fully_constrained_prunes_installed_packages() {
);
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_all_jobs() {
- let _fixtures = set_up();
- todo!()
+ let fixtures = set_up();
+ let package_d = get_package("D", "1.0");
+ fixtures.repo_locked.add_package(package_d.clone()).unwrap();
+ let old_package_c = get_package("C", "1.0");
+ fixtures
+ .repo_locked
+ .add_package(old_package_c.clone())
+ .unwrap();
+
+ let package_a = get_package("A", "2.0");
+ fixtures.repo.add_package(package_a.clone()).unwrap();
+ let package_b = get_package("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ let new_package_b = get_package("B", "1.1");
+ fixtures.repo.add_package(new_package_b.clone()).unwrap();
+ let package_c = get_package("C", "1.1");
+ fixtures.repo.add_package(package_c.clone()).unwrap();
+ fixtures.repo.add_package(get_package("D", "1.0")).unwrap();
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint("<", "1.1"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request.require_name("A", None).unwrap();
+ request.require_name("C", None).unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "remove",
+ package: package_d,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_b,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ExpectedJob::Update {
+ from: old_package_c,
+ to: package_c,
+ },
+ ],
+ );
}
-#[ignore = "setRequires/setConflicts not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_three_alternative_require_and_conflict() {
- let _fixtures = set_up();
- todo!()
+ let fixtures = set_up();
+ let package_a = get_package("A", "2.0");
+ fixtures.repo.add_package(package_a.clone()).unwrap();
+ let middle_package_b = get_package("B", "1.0");
+ fixtures.repo.add_package(middle_package_b.clone()).unwrap();
+ let new_package_b = get_package("B", "1.1");
+ fixtures.repo.add_package(new_package_b.clone()).unwrap();
+ let old_package_b = get_package("B", "0.9");
+ fixtures.repo.add_package(old_package_b.clone()).unwrap();
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint("<", "1.1"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_conflicts(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint("<", "1.0"),
+ Link::TYPE_CONFLICT,
+ ),
+ )]));
+
+ 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: middle_package_b,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ],
+ );
}
-#[ignore = "setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_solver_obsolete() {
- 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.add_package(package_b.clone()).unwrap();
+ package_b
+ .as_complete_package()
+ .unwrap()
+ .__set_replaces(IndexMap::from([(
+ "a".to_string(),
+ link(
+ "B",
+ "A",
+ MatchAllConstraint::new(None).into(),
+ Link::TYPE_REPLACE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request.require_name("B", None).unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "remove",
+ package: package_a,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_b,
+ },
+ ],
+ );
}
#[ignore]
@@ -520,109 +1142,925 @@ fn test_install_one_of_two_alternatives() {
);
}
-#[ignore = "setRequires/setProvides not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_install_provider() {
- 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_q = get_package("Q", "1.0");
+ fixtures.repo.add_package(package_q.clone()).unwrap();
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_q
+ .as_complete_package()
+ .unwrap()
+ .__set_provides(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "Q",
+ "B",
+ get_version_constraint("=", "1.0"),
+ Link::TYPE_PROVIDE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request.require_name("A", None).unwrap();
+
+ // must explicitly pick the provider, so error in this case
+ solve_expecting_error(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ );
}
-#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_skip_replacer_of_existing_package() {
- 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_q = get_package("Q", "1.0");
+ fixtures.repo.add_package(package_q.clone()).unwrap();
+ let package_b = get_package("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_q
+ .as_complete_package()
+ .unwrap()
+ .__set_replaces(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "Q",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REPLACE,
+ ),
+ )]));
+
+ 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_b,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ],
+ );
}
-#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_no_install_replacer_of_missing_package() {
- 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_q = get_package("Q", "1.0");
+ fixtures.repo.add_package(package_q.clone()).unwrap();
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_q
+ .as_complete_package()
+ .unwrap()
+ .__set_replaces(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "Q",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REPLACE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request.require_name("A", None).unwrap();
+
+ solve_expecting_error(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ );
}
-#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_skip_replaced_package_if_replacer_is_selected() {
- 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_q = get_package("Q", "1.0");
+ fixtures.repo.add_package(package_q.clone()).unwrap();
+ let package_b = get_package("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_q
+ .as_complete_package()
+ .unwrap()
+ .__set_replaces(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "Q",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REPLACE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request.require_name("A", None).unwrap();
+ request.require_name("Q", None).unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: package_q,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ],
+ );
}
-#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_pick_older_if_newer_conflicts() {
- let _fixtures = set_up();
- todo!()
+ let fixtures = set_up();
+ let package_x = get_package("X", "1.0");
+ fixtures.repo.add_package(package_x.clone()).unwrap();
+ package_x
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([
+ (
+ "a".to_string(),
+ link(
+ "X",
+ "A",
+ get_version_constraint(">=", "2.0.0.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ (
+ "b".to_string(),
+ link(
+ "X",
+ "B",
+ get_version_constraint(">=", "2.0.0.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ ]));
+
+ let package_a = get_package("A", "2.0.0");
+ fixtures.repo.add_package(package_a.clone()).unwrap();
+ let new_package_a = get_package("A", "2.1.0");
+ fixtures.repo.add_package(new_package_a.clone()).unwrap();
+ let new_package_b = get_package("B", "2.1.0");
+ fixtures.repo.add_package(new_package_b.clone()).unwrap();
+
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "2.0.0.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ // new package A depends on version of package B that does not exist
+ // => new package A is not installable
+ new_package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "2.2.0.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ // add a package S replacing both A and B, so that S and B or S and A cannot be simultaneously installed
+ // but an alternative option for A and B both exists
+ // this creates a more difficult so solve conflict
+ let package_s = get_package("S", "2.0.0");
+ fixtures.repo.add_package(package_s.clone()).unwrap();
+ package_s
+ .as_complete_package()
+ .unwrap()
+ .__set_replaces(IndexMap::from([
+ (
+ "a".to_string(),
+ link(
+ "S",
+ "A",
+ get_version_constraint(">=", "2.0.0.0"),
+ Link::TYPE_REPLACE,
+ ),
+ ),
+ (
+ "b".to_string(),
+ link(
+ "S",
+ "B",
+ get_version_constraint(">=", "2.0.0.0"),
+ Link::TYPE_REPLACE,
+ ),
+ ),
+ ]));
+
+ let mut request = fixtures.request;
+ request.require_name("X", None).unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: new_package_b,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_x,
+ },
+ ],
+ );
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_install_circular_require() {
- 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_b1 = get_package("B", "0.9");
+ fixtures.repo.add_package(package_b1.clone()).unwrap();
+ let package_b2 = get_package("B", "1.1");
+ fixtures.repo.add_package(package_b2.clone()).unwrap();
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_b2
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "a".to_string(),
+ link(
+ "B",
+ "A",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ 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_b2,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ],
+ );
}
-#[ignore = "setRequires/setProvides not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_install_alternative_with_circular_require() {
- 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("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ let package_c = get_package("C", "1.0");
+ fixtures.repo.add_package(package_c.clone()).unwrap();
+ let package_d = get_package("D", "1.0");
+ fixtures.repo.add_package(package_d.clone()).unwrap();
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_b
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "virtual".to_string(),
+ link(
+ "B",
+ "Virtual",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_c
+ .as_complete_package()
+ .unwrap()
+ .__set_provides(IndexMap::from([(
+ "virtual".to_string(),
+ link(
+ "C",
+ "Virtual",
+ get_version_constraint("==", "1.0"),
+ Link::TYPE_PROVIDE,
+ ),
+ )]));
+ package_d
+ .as_complete_package()
+ .unwrap()
+ .__set_provides(IndexMap::from([(
+ "virtual".to_string(),
+ link(
+ "D",
+ "Virtual",
+ get_version_constraint("==", "1.0"),
+ Link::TYPE_PROVIDE,
+ ),
+ )]));
+
+ package_c
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "a".to_string(),
+ link(
+ "C",
+ "A",
+ get_version_constraint("==", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_d
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "a".to_string(),
+ link(
+ "D",
+ "A",
+ get_version_constraint("==", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request.require_name("A", None).unwrap();
+ request.require_name("C", None).unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: package_b,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_c,
+ },
+ ],
+ );
}
-#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_use_replacer_if_necessary() {
- 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("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ let package_d = get_package("D", "1.0");
+ fixtures.repo.add_package(package_d.clone()).unwrap();
+ let package_d2 = get_package("D", "1.1");
+ fixtures.repo.add_package(package_d2.clone()).unwrap();
+
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([
+ (
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ (
+ "c".to_string(),
+ link(
+ "A",
+ "C",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ ]));
+
+ package_d
+ .as_complete_package()
+ .unwrap()
+ .__set_replaces(IndexMap::from([
+ (
+ "b".to_string(),
+ link(
+ "D",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REPLACE,
+ ),
+ ),
+ (
+ "c".to_string(),
+ link(
+ "D",
+ "C",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REPLACE,
+ ),
+ ),
+ ]));
+
+ package_d2
+ .as_complete_package()
+ .unwrap()
+ .__set_replaces(IndexMap::from([
+ (
+ "b".to_string(),
+ link(
+ "D",
+ "B",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REPLACE,
+ ),
+ ),
+ (
+ "c".to_string(),
+ link(
+ "D",
+ "C",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REPLACE,
+ ),
+ ),
+ ]));
+
+ let mut request = fixtures.request;
+ request.require_name("A", None).unwrap();
+ request.require_name("D", None).unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: package_d2,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a,
+ },
+ ],
+ );
}
-#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_issue265() {
- let _fixtures = set_up();
- todo!()
+ let fixtures = set_up();
+ let package_a1 = get_package("A", "2.0.999999-dev");
+ fixtures.repo.add_package(package_a1.clone()).unwrap();
+ let package_a2 = get_package("A", "2.1-dev");
+ fixtures.repo.add_package(package_a2.clone()).unwrap();
+ let package_a3 = get_package("A", "2.2-dev");
+ fixtures.repo.add_package(package_a3.clone()).unwrap();
+ let package_b1 = get_package("B", "2.0.10");
+ fixtures.repo.add_package(package_b1.clone()).unwrap();
+ let package_b2 = get_package("B", "2.0.9");
+ fixtures.repo.add_package(package_b2.clone()).unwrap();
+ let package_c = get_package("C", "2.0-dev");
+ fixtures.repo.add_package(package_c.clone()).unwrap();
+ let package_d = get_package("D", "2.0.9");
+ fixtures.repo.add_package(package_d.clone()).unwrap();
+
+ package_c
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([
+ (
+ "a".to_string(),
+ link(
+ "C",
+ "A",
+ get_version_constraint(">=", "2.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ (
+ "d".to_string(),
+ link(
+ "C",
+ "D",
+ get_version_constraint(">=", "2.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ ]));
+
+ package_d
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([
+ (
+ "a".to_string(),
+ link(
+ "D",
+ "A",
+ get_version_constraint(">=", "2.1"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ (
+ "b".to_string(),
+ link(
+ "D",
+ "B",
+ get_version_constraint(">=", "2.0-dev"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ ]));
+
+ package_b1
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "a".to_string(),
+ link(
+ "B",
+ "A",
+ get_version_constraint("==", "2.1.0.0-dev"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_b2
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "a".to_string(),
+ link(
+ "B",
+ "A",
+ get_version_constraint("==", "2.1.0.0-dev"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ package_b2
+ .as_complete_package()
+ .unwrap()
+ .__set_replaces(IndexMap::from([(
+ "d".to_string(),
+ link(
+ "B",
+ "D",
+ get_version_constraint("==", "2.0.9.0"),
+ Link::TYPE_REPLACE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request
+ .require_name("C", Some(get_version_constraint("==", "2.0.0.0-dev")))
+ .unwrap();
+
+ solve_expecting_error(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ );
}
-#[ignore = "setConflicts not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore = "asserts SolverProblemsException details (getProblems/getPrettyString) which solve() discards"]
#[test]
fn test_conflict_result_empty() {
let _fixtures = set_up();
todo!()
}
-#[ignore = "setRequires not available on CompletePackageHandle; also asserts SolverProblemsException details which solve() discards"]
+#[ignore = "asserts SolverProblemsException details (getProblems/getPrettyString) which solve() discards"]
#[test]
fn test_unsatisfiable_requires() {
let _fixtures = set_up();
todo!()
}
-#[ignore = "setRequires not available on CompletePackageHandle; also asserts SolverProblemsException details which solve() discards"]
+#[ignore = "asserts SolverProblemsException details (getProblems/getPrettyString) which solve() discards"]
#[test]
fn test_require_mismatch_exception() {
let _fixtures = set_up();
todo!()
}
-#[ignore = "setRequires/setReplaces not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_learn_literals_with_sorted_rule_literals() {
- let _fixtures = set_up();
- todo!()
+ let fixtures = set_up();
+ let package_twig2 = get_package("twig/twig", "2.0");
+ fixtures.repo.add_package(package_twig2.clone()).unwrap();
+ let package_twig16 = get_package("twig/twig", "1.6");
+ fixtures.repo.add_package(package_twig16.clone()).unwrap();
+ let package_twig15 = get_package("twig/twig", "1.5");
+ fixtures.repo.add_package(package_twig15.clone()).unwrap();
+ let package_symfony = get_package("symfony/symfony", "2.0");
+ fixtures.repo.add_package(package_symfony.clone()).unwrap();
+ let package_twig_bridge = get_package("symfony/twig-bridge", "2.0");
+ fixtures
+ .repo
+ .add_package(package_twig_bridge.clone())
+ .unwrap();
+
+ package_twig_bridge
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "twig/twig".to_string(),
+ link(
+ "symfony/twig-bridge",
+ "twig/twig",
+ get_version_constraint("<", "2.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ package_symfony
+ .as_complete_package()
+ .unwrap()
+ .__set_replaces(IndexMap::from([(
+ "symfony/twig-bridge".to_string(),
+ link(
+ "symfony/symfony",
+ "symfony/twig-bridge",
+ get_version_constraint("==", "2.0"),
+ Link::TYPE_REPLACE,
+ ),
+ )]));
+
+ let mut request = fixtures.request;
+ request.require_name("symfony/twig-bridge", None).unwrap();
+ request.require_name("twig/twig", None).unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: package_twig16,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_twig_bridge,
+ },
+ ],
+ );
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_install_recursive_alias_dependencies() {
- 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("B", "2.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ let package_a2 = get_package("A", "2.0");
+ fixtures.repo.add_package(package_a2.clone()).unwrap();
+
+ package_a2
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "b".to_string(),
+ link_pretty(
+ "A",
+ "B",
+ get_version_constraint("==", "2.0"),
+ Link::TYPE_REQUIRE,
+ "== 2.0",
+ ),
+ )]));
+ package_b
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "a".to_string(),
+ link(
+ "B",
+ "A",
+ get_version_constraint(">=", "2.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ let package_a2_alias = get_alias_package(&package_a2, "1.1");
+ fixtures.repo.add_package(package_a2_alias.clone()).unwrap();
+
+ let mut request = fixtures.request;
+ request
+ .require_name("A", Some(get_version_constraint("==", "1.1.0.0")))
+ .unwrap();
+
+ check_solver_result(
+ fixtures.repo_set,
+ fixtures.repo,
+ fixtures.repo_locked,
+ request,
+ vec![
+ ExpectedJob::Single {
+ job: "install",
+ package: package_b,
+ },
+ ExpectedJob::Single {
+ job: "install",
+ package: package_a2,
+ },
+ ExpectedJob::Single {
+ job: "markAliasInstalled",
+ package: package_a2_alias,
+ },
+ ],
+ );
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_install_dev_alias() {
- let _fixtures = set_up();
- todo!()
+ let fixtures = set_up();
+ let package_a = get_package("A", "2.0");
+ fixtures.repo.add_package(package_a.clone()).unwrap();
+ let package_b = get_package("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+
+ package_b
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "a".to_string(),
+ link(
+ "B",
+ "A",
+ get_version_constraint("<", "2.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ let package_a_alias = get_alias_package(&package_a, "1.1");
+ fixtures.repo.add_package(package_a_alias.clone()).unwrap();
+
+ let mut request = fixtures.request;
+ request
+ .require_name("A", Some(get_version_constraint("==", "2.0")))
+ .unwrap();
+ request.require_name("B", None).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,
+ },
+ ],
+ );
}
#[ignore]
@@ -699,9 +2137,200 @@ fn test_install_root_aliases_if_alias_of_is_installed() {
);
}
-#[ignore = "setRequires not available on CompletePackageHandle (only RootPackageHandle exposes link setters)"]
+#[ignore]
#[test]
fn test_learn_positive_literal() {
- 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("B", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ let package_c1 = get_package("C", "1.0");
+ fixtures.repo.add_package(package_c1.clone()).unwrap();
+ let package_c2 = get_package("C", "2.0");
+ fixtures.repo.add_package(package_c2.clone()).unwrap();
+ let package_d = get_package("D", "1.0");
+ fixtures.repo.add_package(package_d.clone()).unwrap();
+ let package_e = get_package("E", "1.0");
+ fixtures.repo.add_package(package_e.clone()).unwrap();
+ let package_f1 = get_package("F", "1.0");
+ fixtures.repo.add_package(package_f1.clone()).unwrap();
+ let package_f2 = get_package("F", "2.0");
+ fixtures.repo.add_package(package_f2.clone()).unwrap();
+ let package_g1 = get_package("G", "1.0");
+ fixtures.repo.add_package(package_g1.clone()).unwrap();
+ let package_g2 = get_package("G", "2.0");
+ fixtures.repo.add_package(package_g2.clone()).unwrap();
+ let package_g3 = get_package("G", "3.0");
+ fixtures.repo.add_package(package_g3.clone()).unwrap();
+
+ package_a
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([
+ (
+ "b".to_string(),
+ link(
+ "A",
+ "B",
+ get_version_constraint("==", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ (
+ "c".to_string(),
+ link(
+ "A",
+ "C",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ (
+ "d".to_string(),
+ link(
+ "A",
+ "D",
+ get_version_constraint("==", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ ]));
+
+ package_b
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "e".to_string(),
+ link(
+ "B",
+ "E",
+ get_version_constraint("==", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ package_c1
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "f".to_string(),
+ link(
+ "C",
+ "F",
+ get_version_constraint("==", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+ package_c2
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([
+ (
+ "f".to_string(),
+ link(
+ "C",
+ "F",
+ get_version_constraint("==", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ (
+ "g".to_string(),
+ link(
+ "C",
+ "G",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ ),
+ ]));
+
+ package_d
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "f".to_string(),
+ link(
+ "D",
+ "F",
+ get_version_constraint(">=", "1.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ package_e
+ .as_complete_package()
+ .unwrap()
+ .__set_requires(IndexMap::from([(
+ "g".to_string(),
+ link(
+ "E",
+ "G",
+ get_version_constraint("<=", "2.0"),
+ Link::TYPE_REQUIRE,
+ ),
+ )]));
+
+ // reposComplete() + createSolver() inline so the testFlagLearnedPositiveLiteral flag can be
+ // asserted on the same solver instance used to solve.
+ let mut repo_set = fixtures.repo_set;
+ repo_set
+ .add_repository(RepositoryInterfaceHandle::new(fixtures.repo))
+ .unwrap();
+ repo_set
+ .add_repository(fixtures.repo_locked.into())
+ .unwrap();
+
+ let mut request = fixtures.request;
+ request.require_name("A", None).unwrap();
+
+ let io: Rc<RefCell<dyn IOInterface>> = 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<dyn PolicyInterface> = Rc::new(DefaultPolicy::new(false, false, None));
+ let mut solver =
+ shirabe::dependency_resolver::solver::Solver::new(policy, Rc::new(RefCell::new(pool)), io);
+
+ // check correct setup for assertion later
+ assert!(!solver.test_flag_learned_positive_literal);
+
+ let transaction = solver.solve(&request, None).unwrap();
+
+ let expected = vec![
+ ("install".to_string(), package_f1.get_unique_name()),
+ ("install".to_string(), package_d.get_unique_name()),
+ ("install".to_string(), package_g2.get_unique_name()),
+ ("install".to_string(), package_c2.get_unique_name()),
+ ("install".to_string(), package_e.get_unique_name()),
+ ("install".to_string(), package_b.get_unique_name()),
+ ("install".to_string(), package_a.get_unique_name()),
+ ];
+ let mut result: Vec<(String, String)> = Vec::new();
+ for operation in transaction.get_operations() {
+ if let Some(update) = operation.as_update_operation() {
+ result.push((
+ "update".to_string(),
+ format!(
+ "{} => {}",
+ update.get_initial_package().get_unique_name(),
+ update.get_target_package().get_unique_name()
+ ),
+ ));
+ } else {
+ let op_type = operation.get_operation_type();
+ let job = if op_type == "uninstall" {
+ "remove".to_string()
+ } else {
+ op_type
+ };
+ result.push((job, operation.get_package().get_unique_name()));
+ }
+ }
+ assert_eq!(expected, result);
+
+ // verify that the code path leading to a negative literal resulting in a positive learned
+ // literal is actually executed
+ assert!(solver.test_flag_learned_positive_literal);
}
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);
}
diff --git a/crates/shirabe/tests/downloader/archive_downloader_test.rs b/crates/shirabe/tests/downloader/archive_downloader_test.rs
index dfba3bf..7062d48 100644
--- a/crates/shirabe/tests/downloader/archive_downloader_test.rs
+++ b/crates/shirabe/tests/downloader/archive_downloader_test.rs
@@ -1,41 +1,174 @@
//! ref: composer/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php
-// These exercise getFileName/processUrl on an anonymous ArchiveDownloader subclass built
-// with a mocked IO and a real HttpDownloader (curl_multi_init is todo!()), and pass mocked
-// packages.
+// The PHP test builds an anonymous ArchiveDownloader subclass; getFileName/processUrl are
+// inherited unchanged from FileDownloader, so the concrete FileDownloader is exercised here
+// directly. The PHP mocks of IOInterface/Config/PackageInterface are replaced by a NullIO, a
+// real Config merged with `vendor-dir`, and real CompletePackage instances with dist
+// url/reference set.
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use indexmap::IndexMap;
+use shirabe::config::Config;
+use shirabe::downloader::FileDownloader;
+use shirabe::io::IOInterface;
+use shirabe::io::null_io::NullIO;
+use shirabe::package::handle::{CompletePackageHandle, PackageInterfaceHandle};
+use shirabe::util::HttpDownloader;
+use shirabe_php_shim::PhpMixed;
+use shirabe_semver::version_parser::VersionParser;
+
+/// ref: TestCase::getPackage (default class CompletePackage)
+fn get_package(name: &str, version: &str) -> PackageInterfaceHandle {
+ let norm_version = VersionParser.normalize(version, None).unwrap();
+ CompletePackageHandle::new(name.to_string(), norm_version, version.to_string()).into()
+}
+
+/// ref: ArchiveDownloaderTest::getArchiveDownloaderMock (the inherited getFileName/processUrl
+/// live on FileDownloader, so the concrete downloader is built directly).
+fn get_archive_downloader(vendor_dir: Option<&str>) -> FileDownloader {
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+
+ let mut config = Config::new(false, None);
+ if let Some(vendor_dir) = vendor_dir {
+ let mut config_options: IndexMap<String, PhpMixed> = IndexMap::new();
+ config_options.insert(
+ "vendor-dir".to_string(),
+ PhpMixed::String(vendor_dir.to_string()),
+ );
+ let mut merged: IndexMap<String, PhpMixed> = IndexMap::new();
+ merged.insert("config".to_string(), PhpMixed::Array(config_options));
+ config.merge(&merged, "test");
+ }
+ let config = Rc::new(RefCell::new(config));
+
+ let http_downloader = Rc::new(RefCell::new(HttpDownloader::new(
+ io.clone(),
+ config.clone(),
+ IndexMap::new(),
+ false,
+ )));
+
+ FileDownloader::new(io, config, http_downloader, None, None, None, None)
+}
#[test]
-#[ignore = "FileDownloader::get_file_name is pub(crate); not reachable from the integration test crate"]
+#[ignore]
fn test_get_file_name() {
- todo!()
+ let package = get_package("dummy/pkg", "1.0.0");
+ package.set_dist_url(Some("http://example.com/script.js".to_string()));
+
+ let downloader = get_archive_downloader(Some("/vendor"));
+
+ let first = downloader.__get_file_name(package.clone(), "/path");
+ let re = regex::Regex::new(r"/vendor/composer/tmp-[a-z0-9]+\.js").unwrap();
+ assert!(re.is_match(&first));
+ assert_eq!(first, downloader.__get_file_name(package, "/path"));
}
#[test]
-#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"]
+#[ignore]
fn test_process_url() {
- todo!()
+ let downloader = get_archive_downloader(None);
+
+ let expected = "https://github.com/composer/composer/zipball/master";
+ let package = get_package("dummy/pkg", "1.0.0");
+ let url = downloader.__process_url(package, expected).unwrap();
+
+ assert_eq!(expected, url);
}
#[test]
-#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"]
+#[ignore]
fn test_process_url2() {
- todo!()
+ let downloader = get_archive_downloader(None);
+
+ let expected = "https://github.com/composer/composer/archive/master.tar.gz";
+ let package = get_package("dummy/pkg", "1.0.0");
+ let url = downloader.__process_url(package, expected).unwrap();
+
+ assert_eq!(expected, url);
}
#[test]
-#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"]
+#[ignore]
fn test_process_url3() {
- todo!()
+ let downloader = get_archive_downloader(None);
+
+ let expected = "https://api.github.com/repos/composer/composer/zipball/master";
+ let package = get_package("dummy/pkg", "1.0.0");
+ let url = downloader.__process_url(package, expected).unwrap();
+
+ assert_eq!(expected, url);
+}
+
+/// ref: ArchiveDownloaderTest::provideUrls
+fn provide_urls() -> Vec<&'static str> {
+ vec![
+ "https://api.github.com/repos/composer/composer/zipball/master",
+ "https://api.github.com/repos/composer/composer/tarball/master",
+ "https://github.com/composer/composer/zipball/master",
+ "https://www.github.com/composer/composer/tarball/master",
+ "https://github.com/composer/composer/archive/master.zip",
+ "https://github.com/composer/composer/archive/master.tar.gz",
+ ]
}
#[test]
-#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"]
+#[ignore]
fn test_process_url_rewrite_dist() {
- todo!()
+ let downloader = get_archive_downloader(None);
+
+ for url in provide_urls() {
+ let r#type = if url.contains("tar") { "tar" } else { "zip" };
+ let expected = format!(
+ "https://api.github.com/repos/composer/composer/{}ball/ref",
+ r#type
+ );
+
+ let package = get_package("dummy/pkg", "1.0.0");
+ package.set_dist_reference(Some("ref".to_string()));
+ let url = downloader.__process_url(package, url).unwrap();
+
+ assert_eq!(expected, url);
+ }
+}
+
+/// ref: ArchiveDownloaderTest::provideBitbucketUrls
+fn provide_bitbucket_urls() -> Vec<(&'static str, &'static str)> {
+ vec![
+ (
+ "https://bitbucket.org/davereid/drush-virtualhost/get/77ca490c26ac818e024d1138aa8bd3677d1ef21f",
+ "zip",
+ ),
+ (
+ "https://bitbucket.org/davereid/drush-virtualhost/get/master",
+ "tar.gz",
+ ),
+ (
+ "https://bitbucket.org/davereid/drush-virtualhost/get/v1.0",
+ "tar.bz2",
+ ),
+ ]
}
#[test]
-#[ignore = "FileDownloader::process_url is pub(crate); not reachable from the integration test crate"]
+#[ignore]
fn test_process_url_rewrite_bitbucket_dist() {
- todo!()
+ let downloader = get_archive_downloader(None);
+
+ for (url, extension) in provide_bitbucket_urls() {
+ let url = format!("{}.{}", url, extension);
+ let expected = format!(
+ "https://bitbucket.org/davereid/drush-virtualhost/get/ref.{}",
+ extension
+ );
+
+ let package = get_package("dummy/pkg", "1.0.0");
+ package.set_dist_reference(Some("ref".to_string()));
+ let url = downloader.__process_url(package, &url).unwrap();
+
+ assert_eq!(expected, url);
+ }
}
diff --git a/crates/shirabe/tests/io/buffer_io_test.rs b/crates/shirabe/tests/io/buffer_io_test.rs
index 62abe0a..7205808 100644
--- a/crates/shirabe/tests/io/buffer_io_test.rs
+++ b/crates/shirabe/tests/io/buffer_io_test.rs
@@ -6,7 +6,7 @@ use shirabe_external_packages::symfony::console::output::output_interface::VERBO
use shirabe_php_shim::PhpMixed;
#[test]
-#[ignore = "BufferIO::set_user_inputs is todo!() (needs StreamableInputInterface downcast wiring)"]
+#[ignore]
fn test_set_user_inputs() {
let mut buffer_io = BufferIO::new(String::new(), VERBOSITY_NORMAL, None).unwrap();
diff --git a/crates/shirabe/tests/json/json_file_test.rs b/crates/shirabe/tests/json/json_file_test.rs
index e8d87a3..032d0fd 100644
--- a/crates/shirabe/tests/json/json_file_test.rs
+++ b/crates/shirabe/tests/json/json_file_test.rs
@@ -54,70 +54,70 @@ fn create_temp_file() -> String {
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_detect_extra_comma() {
let json = "{\n \"foo\": \"bar\",\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_detect_extra_comma_in_array() {
let json = "{\n \"foo\": [\n \"bar\",\n ]\n}";
expect_parse_exception("Parse error on line 3", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_detect_unescaped_backslash() {
let json = "{\n \"fo\\o\": \"bar\"\n}";
expect_parse_exception("Parse error on line 1", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_skips_escaped_backslash() {
let json = "{\n \"fo\\\\o\": \"bar\"\n \"a\": \"b\"\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_detect_single_quotes() {
let json = "{\n 'foo': \"bar\"\n}";
expect_parse_exception("Parse error on line 1", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_detect_missing_quotes() {
let json = "{\n foo: \"bar\"\n}";
expect_parse_exception("Parse error on line 1", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_detect_array_as_hash() {
let json = "{\n \"foo\": [\"bar\": \"baz\"]\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_detect_missing_comma() {
let json = "{\n \"foo\": \"bar\"\n \"bar\": \"foo\"\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_detect_missing_comma_multiline() {
let json = "{\n \"foo\": \"barbar\"\n\n \"bar\": \"foo\"\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
+#[ignore]
fn test_parse_error_detect_missing_colon() {
let json = "{\n \"foo\": \"bar\",\n \"bar\" \"foo\"\n}";
expect_parse_exception("Parse error on line 3", json);
diff --git a/crates/shirabe/tests/package/archiver/git_exclude_filter_test.rs b/crates/shirabe/tests/package/archiver/git_exclude_filter_test.rs
index 431cb4f..80f9f31 100644
--- a/crates/shirabe/tests/package/archiver/git_exclude_filter_test.rs
+++ b/crates/shirabe/tests/package/archiver/git_exclude_filter_test.rs
@@ -3,7 +3,7 @@
use shirabe::package::archiver::git_exclude_filter::GitExcludeFilter;
#[test]
-#[ignore = "Preg::split panics on the `\\s+` pattern in the regex-crate-based shim"]
+#[ignore]
fn test_pattern_escape() {
for (ignore, expected) in provide_patterns() {
let filter = GitExcludeFilter::new("/".to_string());
diff --git a/crates/shirabe/tests/package/root_alias_package_test.rs b/crates/shirabe/tests/package/root_alias_package_test.rs
index f168e13..c275da3 100644
--- a/crates/shirabe/tests/package/root_alias_package_test.rs
+++ b/crates/shirabe/tests/package/root_alias_package_test.rs
@@ -37,7 +37,7 @@ fn test_update_requires() {
}
#[test]
-#[ignore = "RootAliasPackage::set_dev_requires only updates alias_of, not the alias's own copy, so get_dev_requires stays empty"]
+#[ignore]
fn test_update_dev_requires() {
let alias = alias();
assert!(alias.get_dev_requires().is_empty());
@@ -46,7 +46,7 @@ fn test_update_dev_requires() {
}
#[test]
-#[ignore = "RootAliasPackage::set_conflicts only updates alias_of, not the alias's own copy, so get_conflicts stays empty"]
+#[ignore]
fn test_update_conflicts() {
let alias = alias();
assert!(alias.get_conflicts().is_empty());
@@ -55,7 +55,7 @@ fn test_update_conflicts() {
}
#[test]
-#[ignore = "RootAliasPackage::set_provides only updates alias_of, not the alias's own copy, so get_provides stays empty"]
+#[ignore]
fn test_update_provides() {
let alias = alias();
assert!(alias.get_provides().is_empty());
@@ -64,7 +64,7 @@ fn test_update_provides() {
}
#[test]
-#[ignore = "RootAliasPackage::set_replaces only updates alias_of, not the alias's own copy, so get_replaces stays empty"]
+#[ignore]
fn test_update_replaces() {
let alias = alias();
assert!(alias.get_replaces().is_empty());
diff --git a/crates/shirabe/tests/package/version/version_bumper_test.rs b/crates/shirabe/tests/package/version/version_bumper_test.rs
index 08217f7..2c376fa 100644
--- a/crates/shirabe/tests/package/version/version_bumper_test.rs
+++ b/crates/shirabe/tests/package/version/version_bumper_test.rs
@@ -8,7 +8,7 @@ use shirabe_php_shim::PhpMixed;
use shirabe_semver::version_parser::VersionParser;
#[test]
-#[ignore = "VersionBumper::bump_requirement uses a look-around regex the regex crate cannot compile"]
+#[ignore]
fn test_bump_requirement() {
for (requirement, pretty_version, expected_requirement, branch_alias) in
provide_bump_requirement_tests()
diff --git a/crates/shirabe/tests/package/version/version_parser_test.rs b/crates/shirabe/tests/package/version/version_parser_test.rs
index c403580..2e93d03 100644
--- a/crates/shirabe/tests/package/version/version_parser_test.rs
+++ b/crates/shirabe/tests/package/version/version_parser_test.rs
@@ -15,7 +15,7 @@ fn entry(fields: &[(&str, &str)]) -> IndexMap<String, String> {
}
#[test]
-#[ignore = "parseNameVersionPairs uses a lookbehind/lookahead pattern the regex crate cannot compile"]
+#[ignore]
fn test_parse_name_version_pairs() {
for (input, result) in provide_parse_name_version_pairs_data() {
let version_parser = VersionParser::new();
diff --git a/crates/shirabe/tests/platform/version_test.rs b/crates/shirabe/tests/platform/version_test.rs
index c2511d5..3fe0259 100644
--- a/crates/shirabe/tests/platform/version_test.rs
+++ b/crates/shirabe/tests/platform/version_test.rs
@@ -57,7 +57,7 @@ fn provide_openssl_versions() -> Vec<(&'static str, &'static str, bool, Option<&
}
#[test]
-#[ignore = "compile_php_pattern in the php-shim cannot yet parse Version's PCRE patterns"]
+#[ignore]
fn test_parse_openssl_versions() {
for (input, parsed_version, fips_expected, normalized_version) in provide_openssl_versions() {
let mut is_fips = false;
@@ -76,7 +76,7 @@ fn test_parse_openssl_versions() {
}
#[test]
-#[ignore = "compile_php_pattern in the php-shim cannot yet parse Version's PCRE patterns"]
+#[ignore]
fn test_parse_libjpeg_version() {
let cases = [
("9", "9.0"),
@@ -95,7 +95,7 @@ fn test_parse_libjpeg_version() {
}
#[test]
-#[ignore = "compile_php_pattern in the php-shim cannot yet parse Version's PCRE patterns"]
+#[ignore]
fn test_parse_zoneinfo_version() {
let cases = [
("2019c", "2019.3"),
diff --git a/crates/shirabe/tests/repository/array_repository_test.rs b/crates/shirabe/tests/repository/array_repository_test.rs
index ee6dfb9..60bea16 100644
--- a/crates/shirabe/tests/repository/array_repository_test.rs
+++ b/crates/shirabe/tests/repository/array_repository_test.rs
@@ -100,7 +100,7 @@ fn test_find_packages() {
}
#[test]
-#[ignore = "AliasPackage::get_unique_name returns the aliased package's name, so has_package misses the alias version"]
+#[ignore]
fn test_automatically_add_aliased_package_but_not_remove() {
let mut repo = ArrayRepository::new(vec![]).unwrap();
diff --git a/crates/shirabe/tests/repository/artifact_repository_test.rs b/crates/shirabe/tests/repository/artifact_repository_test.rs
index 14475da..765bdac 100644
--- a/crates/shirabe/tests/repository/artifact_repository_test.rs
+++ b/crates/shirabe/tests/repository/artifact_repository_test.rs
@@ -1,9 +1,12 @@
//! ref: composer/tests/Composer/Test/Repository/ArtifactRepositoryTest.php
-// ArtifactRepository::getPackages scans the fixture directory and opens each archive via
-// ZipArchive / PharData, both of which are todo!() in the php-shim.
+use std::cell::RefCell;
+use std::rc::Rc;
-use shirabe_php_shim::extension_loaded;
+use indexmap::IndexMap;
+use shirabe::io::{IOInterface, NullIO};
+use shirabe::repository::ArtifactRepository;
+use shirabe_php_shim::{PhpMixed, extension_loaded};
fn set_up() {
if !extension_loaded("zip") {
@@ -12,23 +15,104 @@ fn set_up() {
}
}
+fn artifacts_dir() -> String {
+ format!(
+ "{}/../../composer/tests/Composer/Test/Repository/Fixtures/artifacts",
+ env!("CARGO_MANIFEST_DIR")
+ )
+}
+
+fn create_repo(url: &str) -> ArtifactRepository {
+ let mut coordinates: IndexMap<String, PhpMixed> = IndexMap::new();
+ coordinates.insert("type".to_string(), PhpMixed::String("artifact".to_string()));
+ coordinates.insert("url".to_string(), PhpMixed::String(url.to_string()));
+
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ ArtifactRepository::new(coordinates, io).unwrap()
+}
+
#[test]
-#[ignore = "ArtifactRepository exposes no public get_packages(); it does not impl RepositoryInterface and initialize/scan_directory are private"]
+#[ignore]
fn test_extracts_configs_from_zip_archives() {
set_up();
- todo!()
+
+ let mut expected_packages = vec![
+ "vendor0/package0-0.0.1".to_string(),
+ "composer/composer-1.0.0-alpha6".to_string(),
+ "vendor1/package2-4.3.2".to_string(),
+ "vendor3/package1-5.4.3".to_string(),
+ "test/jsonInRoot-1.0.0".to_string(),
+ "test/jsonInRootTarFile-1.0.0".to_string(),
+ "test/jsonInFirstLevel-1.0.0".to_string(),
+ // The files not-an-artifact.zip and jsonSecondLevel are not valid
+ // artifacts and do not get detected.
+ ];
+
+ let mut repo = create_repo(&artifacts_dir());
+
+ let mut found_packages: Vec<String> = repo
+ .__get_packages()
+ .unwrap()
+ .iter()
+ .map(|package| {
+ format!(
+ "{}-{}",
+ package.get_pretty_name(),
+ package.get_pretty_version()
+ )
+ })
+ .collect();
+
+ expected_packages.sort();
+ found_packages.sort();
+
+ assert_eq!(expected_packages, found_packages);
+
+ let tar_package: Vec<_> = repo
+ .__get_packages()
+ .unwrap()
+ .into_iter()
+ .filter(|package| package.get_pretty_name() == "test/jsonInRootTarFile")
+ .collect();
+ assert_eq!(1, tar_package.len());
+ let tar_package = tar_package.into_iter().next_back().unwrap();
+ assert_eq!(Some("tar".to_string()), tar_package.get_dist_type());
}
#[test]
-#[ignore = "ArtifactRepository exposes no public get_packages(); it does not impl RepositoryInterface and initialize/scan_directory are private"]
+#[ignore]
fn test_absolute_repo_url_creates_absolute_url_packages() {
set_up();
- todo!()
+
+ let absolute_path = artifacts_dir();
+ let mut repo = create_repo(&absolute_path);
+
+ for package in repo.__get_packages().unwrap() {
+ assert_eq!(
+ package
+ .get_dist_url()
+ .unwrap_or_default()
+ .find(&absolute_path.replace('\\', "/")),
+ Some(0)
+ );
+ }
}
#[test]
-#[ignore = "ArtifactRepository exposes no public get_packages(); it does not impl RepositoryInterface and initialize/scan_directory are private"]
+#[ignore]
fn test_relative_repo_url_creates_relative_url_packages() {
set_up();
- todo!()
+
+ let relative_path = "tests/Composer/Test/Repository/Fixtures/artifacts";
+ let mut repo = create_repo(relative_path);
+
+ for package in repo.__get_packages().unwrap() {
+ assert_eq!(
+ package
+ .get_dist_url()
+ .unwrap_or_default()
+ .find(relative_path),
+ Some(0)
+ );
+ }
}
diff --git a/crates/shirabe/tests/repository/composite_repository_test.rs b/crates/shirabe/tests/repository/composite_repository_test.rs
index e21ac65..f01a3fd 100644
--- a/crates/shirabe/tests/repository/composite_repository_test.rs
+++ b/crates/shirabe/tests/repository/composite_repository_test.rs
@@ -27,7 +27,7 @@ fn test_has_package() {
}
#[test]
-#[ignore = "constraint parsing uses a look-around regex the regex crate does not support"]
+#[ignore]
fn test_find_package() {
let mut repo = CompositeRepository::new(vec![
array_repo(vec![get_package("foo", "1")]),
diff --git a/crates/shirabe/tests/repository/installed_repository_test.rs b/crates/shirabe/tests/repository/installed_repository_test.rs
index b2c88a6..6720260 100644
--- a/crates/shirabe/tests/repository/installed_repository_test.rs
+++ b/crates/shirabe/tests/repository/installed_repository_test.rs
@@ -34,7 +34,7 @@ fn provided_link() -> PhpMixed {
}
#[test]
-#[ignore = "InstalledRepository::add_repository asserts on a fixed set of repo types that omits InstalledRepositoryInterface, so adding an InstalledArrayRepository panics"]
+#[ignore]
fn test_find_packages_with_replacers_and_providers() {
let foo = loaded("foo", "1", vec![("replace", provided_link())]);
let foo2 = get_package("foo", "2");
diff --git a/crates/shirabe/tests/repository/path_repository_test.rs b/crates/shirabe/tests/repository/path_repository_test.rs
index c2cf892..cea6a85 100644
--- a/crates/shirabe/tests/repository/path_repository_test.rs
+++ b/crates/shirabe/tests/repository/path_repository_test.rs
@@ -1,54 +1,253 @@
//! ref: composer/tests/Composer/Test/Repository/PathRepositoryTest.php
-// PathRepository does not implement RepositoryInterface and exposes no public
-// getPackages/count/hasPackage; the delegation to its inner ArrayRepository is not yet
-// ported, so there is no way to drive these tests. The unversioned cases additionally
-// require the VersionGuesser (git).
+use std::cell::RefCell;
+use std::rc::Rc;
-#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"]
+use indexmap::IndexMap;
+use shirabe::config::Config;
+use shirabe::io::{IOInterface, NullIO};
+use shirabe::repository::PathRepository;
+use shirabe::util::{Platform, ProcessExecutor};
+use shirabe_php_shim::{
+ DIRECTORY_SEPARATOR, PhpMixed, file_get_contents, hash, realpath, serialize,
+};
+
+use crate::test_case::get_package;
+
+fn fixtures_dir() -> String {
+ format!(
+ "{}/../../composer/tests/Composer/Test/Repository/Fixtures",
+ env!("CARGO_MANIFEST_DIR")
+ )
+}
+
+/// ref: PathRepositoryTest::createPathRepo
+fn create_path_repo(options: IndexMap<String, PhpMixed>) -> PathRepository {
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+
+ let config = Rc::new(RefCell::new(Config::new(true, None)));
+ let proc = Rc::new(RefCell::new(ProcessExecutor::new(None)));
+
+ PathRepository::new(options, io, config, None, None, Some(proc)).unwrap()
+}
+
+fn coordinates(pairs: Vec<(&str, PhpMixed)>) -> IndexMap<String, PhpMixed> {
+ let mut map: IndexMap<String, PhpMixed> = IndexMap::new();
+ for (key, value) in pairs {
+ map.insert(key.to_string(), value);
+ }
+ map
+}
+
+#[ignore]
#[test]
fn test_load_package_from_file_system_with_incorrect_path() {
- todo!()
+ let repository_url =
+ [fixtures_dir(), "path".to_string(), "missing".to_string()].join(DIRECTORY_SEPARATOR);
+ let mut repository =
+ create_path_repo(coordinates(vec![("url", PhpMixed::String(repository_url))]));
+ assert!(repository.__get_packages().is_err());
}
-#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"]
+#[ignore]
#[test]
fn test_load_package_from_file_system_with_version() {
- todo!()
+ let repository_url = [
+ fixtures_dir(),
+ "path".to_string(),
+ "with-version".to_string(),
+ ]
+ .join(DIRECTORY_SEPARATOR);
+ let mut repository =
+ create_path_repo(coordinates(vec![("url", PhpMixed::String(repository_url))]));
+ repository.__get_packages().unwrap();
+
+ assert_eq!(1, repository.__count().unwrap());
+ assert!(
+ repository
+ .__has_package(get_package("test/path-versioned", "0.0.2"))
+ .unwrap()
+ );
}
-#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"]
+#[ignore]
#[test]
fn test_load_package_from_file_system_without_version() {
- todo!()
+ let repository_url = [
+ fixtures_dir(),
+ "path".to_string(),
+ "without-version".to_string(),
+ ]
+ .join(DIRECTORY_SEPARATOR);
+ let mut repository =
+ create_path_repo(coordinates(vec![("url", PhpMixed::String(repository_url))]));
+ let packages = repository.__get_packages().unwrap();
+
+ assert!(repository.__count().unwrap() >= 1);
+
+ let package = &packages[0];
+ assert_eq!("test/path-unversioned", package.get_name());
+
+ let package_version = package.get_version();
+ assert!(!package_version.is_empty());
}
-#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"]
+#[ignore]
#[test]
fn test_load_package_from_file_system_with_wildcard() {
- todo!()
+ let repository_url =
+ [fixtures_dir(), "path".to_string(), "*".to_string()].join(DIRECTORY_SEPARATOR);
+ let mut repository =
+ create_path_repo(coordinates(vec![("url", PhpMixed::String(repository_url))]));
+ let packages = repository.__get_packages().unwrap();
+ let mut names: Vec<String> = Vec::new();
+
+ assert!(repository.__count().unwrap() >= 2);
+
+ let package = &packages[0];
+ names.push(package.get_name());
+
+ let package = &packages[1];
+ names.push(package.get_name());
+
+ names.sort();
+ assert_eq!(
+ vec![
+ "test/path-unversioned".to_string(),
+ "test/path-versioned".to_string()
+ ],
+ names
+ );
}
-#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"]
+#[ignore]
#[test]
fn test_load_package_with_explicit_versions() {
- todo!()
+ let mut versions: IndexMap<String, PhpMixed> = IndexMap::new();
+ versions.insert(
+ "test/path-unversioned".to_string(),
+ PhpMixed::String("4.3.2.1".to_string()),
+ );
+ versions.insert(
+ "test/path-versioned".to_string(),
+ PhpMixed::String("3.2.1.0".to_string()),
+ );
+ let options = coordinates(vec![("versions", PhpMixed::Array(versions))]);
+
+ let repository_url =
+ [fixtures_dir(), "path".to_string(), "*".to_string()].join(DIRECTORY_SEPARATOR);
+ let mut repository = create_path_repo(coordinates(vec![
+ ("url", PhpMixed::String(repository_url)),
+ ("options", PhpMixed::Array(options)),
+ ]));
+ let packages = repository.__get_packages().unwrap();
+
+ let mut versions: IndexMap<String, String> = IndexMap::new();
+
+ assert_eq!(2, repository.__count().unwrap());
+
+ let package = &packages[0];
+ versions.insert(package.get_name(), package.get_version());
+
+ let package = &packages[1];
+ versions.insert(package.get_name(), package.get_version());
+
+ versions.sort_keys();
+ let expected: IndexMap<String, String> = [
+ ("test/path-unversioned".to_string(), "4.3.2.1".to_string()),
+ ("test/path-versioned".to_string(), "3.2.1.0".to_string()),
+ ]
+ .into_iter()
+ .collect();
+ assert_eq!(expected, versions);
}
-#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"]
+/// Verify relative repository URLs remain relative, see #4439
+#[ignore]
#[test]
fn test_url_remains_relative() {
- todo!()
+ // realpath() does not fully expand the paths
+ // PHP Bug https://bugs.php.net/bug.php?id=72642
+ let repository_url = [
+ realpath(&realpath(&fixtures_dir().replace("/Fixtures", "")).unwrap_or_default())
+ .unwrap_or_default(),
+ "Fixtures".to_string(),
+ "path".to_string(),
+ "with-version".to_string(),
+ ]
+ .join(DIRECTORY_SEPARATOR);
+ // getcwd() not necessarily match __DIR__
+ // PHP Bug https://bugs.php.net/bug.php?id=73797
+ let cwd = realpath(&realpath(&Platform::get_cwd(false).unwrap()).unwrap_or_default())
+ .unwrap_or_default();
+ let relative_url = repository_url[cwd.len().min(repository_url.len())..]
+ .trim_start_matches(DIRECTORY_SEPARATOR)
+ .to_string();
+
+ let mut repository = create_path_repo(coordinates(vec![(
+ "url",
+ PhpMixed::String(relative_url.clone()),
+ )]));
+ let packages = repository.__get_packages().unwrap();
+
+ assert_eq!(1, repository.__count().unwrap());
+
+ let package = &packages[0];
+ assert_eq!("test/path-versioned", package.get_name());
+
+ // Convert platform specific separators back to generic URL slashes
+ let relative_url = relative_url.replace(DIRECTORY_SEPARATOR, "/");
+ assert_eq!(Some(relative_url), package.get_dist_url());
}
-#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"]
+#[ignore]
#[test]
fn test_reference_none() {
- todo!()
+ let options = coordinates(vec![("reference", PhpMixed::String("none".to_string()))]);
+ let repository_url =
+ [fixtures_dir(), "path".to_string(), "*".to_string()].join(DIRECTORY_SEPARATOR);
+ let mut repository = create_path_repo(coordinates(vec![
+ ("url", PhpMixed::String(repository_url)),
+ ("options", PhpMixed::Array(options)),
+ ]));
+ let packages = repository.__get_packages().unwrap();
+
+ assert!(repository.__count().unwrap() >= 2);
+
+ for package in &packages {
+ assert_eq!(package.get_dist_reference(), None);
+ }
}
-#[ignore = "PathRepository exposes no public RepositoryInterface::get_packages/count/has_package; the inner ArrayRepository delegation is not ported"]
+#[ignore]
#[test]
fn test_reference_config() {
- todo!()
+ let options = coordinates(vec![
+ ("reference", PhpMixed::String("config".to_string())),
+ ("relative", PhpMixed::Bool(true)),
+ ]);
+ let repository_url =
+ [fixtures_dir(), "path".to_string(), "*".to_string()].join(DIRECTORY_SEPARATOR);
+ let mut repository = create_path_repo(coordinates(vec![
+ ("url", PhpMixed::String(repository_url)),
+ ("options", PhpMixed::Array(options.clone())),
+ ]));
+ let packages = repository.__get_packages().unwrap();
+
+ assert!(repository.__count().unwrap() >= 2);
+
+ for package in &packages {
+ let dist_url = package.get_dist_url().unwrap_or_default();
+ assert_eq!(
+ package.get_dist_reference(),
+ Some(hash(
+ "sha1",
+ &format!(
+ "{}{}",
+ file_get_contents(format!("{}/composer.json", dist_url)).unwrap_or_default(),
+ serialize(&PhpMixed::Array(options.clone()))
+ )
+ ))
+ );
+ }
}
diff --git a/crates/shirabe/tests/repository/repository_factory_test.rs b/crates/shirabe/tests/repository/repository_factory_test.rs
index a0eb81f..c018c45 100644
--- a/crates/shirabe/tests/repository/repository_factory_test.rs
+++ b/crates/shirabe/tests/repository/repository_factory_test.rs
@@ -1,13 +1,57 @@
//! ref: composer/tests/Composer/Test/Repository/RepositoryFactoryTest.php
+use std::cell::RefCell;
+use std::rc::Rc;
+
use indexmap::IndexMap;
+use shirabe::config::Config;
+use shirabe::io::IOInterface;
+use shirabe::io::null_io::NullIO;
use shirabe::repository::RepositoryFactory;
+use shirabe::util::http_downloader::HttpDownloader;
use shirabe_php_shim::PhpMixed;
#[test]
-#[ignore = "PHP test uses ReflectionProperty to read the private RepositoryManager::repository_classes field; no public accessor for repository_classes keys exists in the Rust impl"]
+#[ignore]
fn test_manager_with_all_repository_types() {
- todo!()
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ let config = Rc::new(RefCell::new(Config::new(false, None)));
+ let http_downloader = Rc::new(RefCell::new(HttpDownloader::new(
+ io.clone(),
+ config.clone(),
+ IndexMap::new(),
+ true,
+ )));
+
+ let manager =
+ RepositoryFactory::manager(io, &config, Some(http_downloader), None, None).unwrap();
+
+ let repository_classes: Vec<&str> = manager
+ .__repository_classes()
+ .keys()
+ .map(|k| k.as_str())
+ .collect();
+
+ assert_eq!(
+ vec![
+ "composer",
+ "vcs",
+ "package",
+ "pear",
+ "git",
+ "bitbucket",
+ "git-bitbucket",
+ "github",
+ "gitlab",
+ "svn",
+ "fossil",
+ "perforce",
+ "hg",
+ "artifact",
+ "path",
+ ],
+ repository_classes
+ );
}
fn generate_repository_name_provider() -> Vec<(
@@ -53,7 +97,7 @@ fn generate_repository_name_provider() -> Vec<(
}
#[test]
-#[ignore = "generate_repository_name does not stringify an integer index (PhpMixed::as_string returns None for Int), so a numeric index with no url yields \"\" instead of e.g. \"0\""]
+#[ignore]
fn test_generate_repository_name() {
for (index, repo_pairs, existing_keys, expected) in generate_repository_name_provider() {
let repo: IndexMap<String, PhpMixed> = repo_pairs
diff --git a/crates/shirabe/tests/repository/repository_utils_test.rs b/crates/shirabe/tests/repository/repository_utils_test.rs
index 3f55bfd..3ea1439 100644
--- a/crates/shirabe/tests/repository/repository_utils_test.rs
+++ b/crates/shirabe/tests/repository/repository_utils_test.rs
@@ -153,7 +153,7 @@ fn provide_filter_require_tests() -> Vec<FilterCase> {
}
#[test]
-#[ignore = "building packages with links via ArrayLoader parses constraints with a look-around regex the regex crate cannot compile"]
+#[ignore]
fn test_filter_required_packages() {
for case in provide_filter_require_tests() {
let pkgs = build_packages();
diff --git a/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs b/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs
index 0836fc5..ea130ea 100644
--- a/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs
+++ b/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs
@@ -76,7 +76,7 @@ fn supports_provider() -> Vec<(bool, &'static str)> {
}
#[test]
-#[ignore = "ForgejoDriver::supports uses a regex with a character class the regex crate cannot compile (unclosed character class)"]
+#[ignore]
fn test_supports() {
for (expected, repo_url) in supports_provider() {
let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
diff --git a/crates/shirabe/tests/repository/vcs/github_driver_test.rs b/crates/shirabe/tests/repository/vcs/github_driver_test.rs
index 5313c0b..f5958a4 100644
--- a/crates/shirabe/tests/repository/vcs/github_driver_test.rs
+++ b/crates/shirabe/tests/repository/vcs/github_driver_test.rs
@@ -64,7 +64,7 @@ fn supports_provider() -> Vec<(bool, &'static str)> {
}
#[test]
-#[ignore = "GitHubDriver::supports reaches non-strict in_array, which is todo!() in the php-shim"]
+#[ignore]
fn test_supports() {
let SetUp { home, config: _ } = set_up();
let _tear_down = TearDown::new(home.path().to_path_buf());
diff --git a/crates/shirabe/tests/script/event_test.rs b/crates/shirabe/tests/script/event_test.rs
index b045bc4..e7e574b 100644
--- a/crates/shirabe/tests/script/event_test.rs
+++ b/crates/shirabe/tests/script/event_test.rs
@@ -7,6 +7,7 @@ use indexmap::IndexMap;
use shirabe::composer::{ComposerHandle, PartialOrFullComposer};
use shirabe::config::Config;
use shirabe::event_dispatcher::Event as BaseEvent;
+use shirabe::event_dispatcher::EventInterface;
use shirabe::io::IOInterface;
use shirabe::io::null_io::NullIO;
use shirabe::package::{RootPackageHandle, RootPackageInterfaceHandle};
@@ -28,7 +29,7 @@ fn create_composer_instance() -> ComposerHandle {
}
#[test]
-#[ignore = "Event::calculate_originating_event (called by set_originating_event) is todo!()"]
+#[ignore]
fn test_event_sets_originating_event() {
let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
let composer = create_composer_instance();
@@ -51,7 +52,14 @@ fn test_event_sets_originating_event() {
script_event.set_originating_event(originating_event);
- assert!(script_event.get_originating_event().is_some());
+ // assertSame: the originating event passed in is returned unchanged.
+ assert_eq!(
+ script_event
+ .get_originating_event()
+ .map(EventInterface::get_name),
+ Some("originatingEvent"),
+ "getOriginatingEvent() SHOULD return test event"
+ );
}
// In PHP, the intermediate originating event is itself a Script\Event, and
diff --git a/crates/shirabe/tests/util/config_validator_test.rs b/crates/shirabe/tests/util/config_validator_test.rs
index da962e0..2e4aed0 100644
--- a/crates/shirabe/tests/util/config_validator_test.rs
+++ b/crates/shirabe/tests/util/config_validator_test.rs
@@ -29,7 +29,7 @@ fn validate(file: &str) -> Vec<String> {
/// Test ConfigValidator warns on commit reference
#[test]
-#[ignore = "ConfigValidator::validate reaches a todo!() in the php-shim"]
+#[ignore]
fn test_config_validator_commit_ref_warning() {
let warnings = validate(&fixture("composer_commit-ref.json"));
@@ -39,7 +39,7 @@ fn test_config_validator_commit_ref_warning() {
}
#[test]
-#[ignore = "ConfigValidator::validate reaches a todo!() in the php-shim"]
+#[ignore]
fn test_config_validator_warns_on_script_description_for_nonexistent_script() {
let warnings = validate(&fixture("composer_scripts-descriptions.json"));
@@ -52,7 +52,7 @@ fn test_config_validator_warns_on_script_description_for_nonexistent_script() {
}
#[test]
-#[ignore = "ConfigValidator::validate reaches a todo!() in the php-shim"]
+#[ignore]
fn test_config_validator_warns_on_script_alias_for_nonexistent_script() {
let warnings = validate(&fixture("composer_scripts-aliases.json"));
@@ -62,7 +62,7 @@ fn test_config_validator_warns_on_script_alias_for_nonexistent_script() {
}
#[test]
-#[ignore = "ConfigValidator::validate reaches a todo!() in the php-shim"]
+#[ignore]
fn test_config_validator_warns_on_unnecessary_provide_replace() {
let warnings = validate(&fixture("composer_provide-replace-requirements.json"));
diff --git a/crates/shirabe/tests/util/forgejo_url_test.rs b/crates/shirabe/tests/util/forgejo_url_test.rs
index e29c05e..5c33596 100644
--- a/crates/shirabe/tests/util/forgejo_url_test.rs
+++ b/crates/shirabe/tests/util/forgejo_url_test.rs
@@ -3,7 +3,7 @@
use shirabe::util::forgejo_url::ForgejoUrl;
#[test]
-#[ignore = "Preg::match panics: ForgejoUrl::URL_REGEX is an undelimited pattern"]
+#[ignore]
fn test_create() {
for repo_url in create_provider() {
let forgejo_url = ForgejoUrl::try_from(Some(repo_url));
@@ -29,13 +29,13 @@ fn create_provider() -> Vec<&'static str> {
}
#[test]
-#[ignore = "Preg::match panics: ForgejoUrl::URL_REGEX is an undelimited pattern"]
+#[ignore]
fn test_create_invalid() {
assert!(ForgejoUrl::create("https://example.org").is_err());
}
#[test]
-#[ignore = "Preg::match panics: ForgejoUrl::URL_REGEX is an undelimited pattern"]
+#[ignore]
fn test_generate_ssh_url() {
let forgejo_url = ForgejoUrl::create("git@codeberg.org:acme/repo.git").unwrap();
diff --git a/crates/shirabe/tests/util/http/request_proxy_test.rs b/crates/shirabe/tests/util/http/request_proxy_test.rs
index 502ec31..3e96a7b 100644
--- a/crates/shirabe/tests/util/http/request_proxy_test.rs
+++ b/crates/shirabe/tests/util/http/request_proxy_test.rs
@@ -128,7 +128,7 @@ fn test_get_curl_options() {
}
#[test]
-#[ignore = "secure proxies require curl_version, which is a todo!() in the php-shim"]
+#[ignore]
fn test_get_curl_options_with_ssl() {
let mut cafile_opts: IndexMap<String, PhpMixed> = IndexMap::new();
cafile_opts.insert(
diff --git a/crates/shirabe/tests/util/ini_helper_test.rs b/crates/shirabe/tests/util/ini_helper_test.rs
index 59dc967..f511d76 100644
--- a/crates/shirabe/tests/util/ini_helper_test.rs
+++ b/crates/shirabe/tests/util/ini_helper_test.rs
@@ -7,10 +7,11 @@ use shirabe_php_shim::{PATH_SEPARATOR, getenv, putenv};
#[allow(dead_code)]
fn set_up() -> TearDown {
// Register our name with XdebugHandler.
- // TODO: XdebugHandler is the external composer/xdebug-handler package and is not ported.
- todo!();
+ // The PHP test constructs `new XdebugHandler('composer')` so that
+ // getAllIniFiles() reads the COMPOSER_ORIGINAL_INIS env var. The Rust
+ // XdebugHandler is a unit struct with no name-registration API, so this
+ // step is a no-op here.
// Save current state
- #[allow(unreachable_code)]
let env_original = getenv("COMPOSER_ORIGINAL_INIS");
TearDown { env_original }
}
@@ -44,7 +45,7 @@ fn set_env(paths: &[&str]) {
}
#[test]
-#[ignore = "IniHelper::get_all reaches XdebugHandler::get_all_ini_files, which is todo!()"]
+#[ignore]
fn test_with_no_ini() {
let paths = [""];
@@ -57,7 +58,7 @@ fn test_with_no_ini() {
}
#[test]
-#[ignore = "IniHelper::get_all reaches XdebugHandler::get_all_ini_files, which is todo!()"]
+#[ignore]
fn test_with_loaded_ini_only() {
let paths = ["loaded.ini"];
@@ -66,7 +67,7 @@ fn test_with_loaded_ini_only() {
}
#[test]
-#[ignore = "IniHelper::get_all reaches XdebugHandler::get_all_ini_files, which is todo!()"]
+#[ignore]
fn test_with_loaded_ini_and_additional() {
let paths = ["loaded.ini", "one.ini", "two.ini"];
@@ -79,7 +80,7 @@ fn test_with_loaded_ini_and_additional() {
}
#[test]
-#[ignore = "IniHelper::get_all reaches XdebugHandler::get_all_ini_files, which is todo!()"]
+#[ignore]
fn test_without_loaded_ini_and_additional() {
let paths = ["", "one.ini", "two.ini"];
diff --git a/crates/shirabe/tests/util/no_proxy_pattern_test.rs b/crates/shirabe/tests/util/no_proxy_pattern_test.rs
index 7a423b9..51017ea 100644
--- a/crates/shirabe/tests/util/no_proxy_pattern_test.rs
+++ b/crates/shirabe/tests/util/no_proxy_pattern_test.rs
@@ -42,7 +42,7 @@ fn test_host_name() {
}
#[test]
-#[ignore = "NoProxyPattern does not match IPv4-mapped IPv6 addresses against IPv4 rules (port logic gap)"]
+#[ignore]
fn test_ip_address() {
let noproxy = "192.168.1.1, 2001:db8::52:0:1";
@@ -55,7 +55,7 @@ fn test_ip_address() {
}
#[test]
-#[ignore = "NoProxyPattern does not match IPv4-mapped IPv6 addresses against IPv4/CIDR rules (port logic gap)"]
+#[ignore]
fn test_ip_range() {
let noproxy = "10.0.0.0/30, 2002:db8:a::45/121";
diff --git a/crates/shirabe/tests/util/package_sorter_test.rs b/crates/shirabe/tests/util/package_sorter_test.rs
index f84ff09..0a8d395 100644
--- a/crates/shirabe/tests/util/package_sorter_test.rs
+++ b/crates/shirabe/tests/util/package_sorter_test.rs
@@ -1,30 +1,30 @@
//! ref: composer/tests/Composer/Test/Util/PackageSorterTest.php
use indexmap::IndexMap;
-use shirabe::package::handle::PackageInterfaceHandle;
-use shirabe::package::loader::array_loader::ArrayLoader;
+use shirabe::package::Link;
+use shirabe::package::handle::{PackageHandle, PackageInterfaceHandle};
use shirabe::util::package_sorter::PackageSorter;
-use shirabe_php_shim::PhpMixed;
+use shirabe_semver::constraint::MatchAllConstraint;
-/// PHP `createPackage` sets requires directly on a `Package`; the public handle API only allows
-/// link setters on root packages, so the equivalent here builds the package through ArrayLoader.
fn create_package(name: &str, requires: &[&str]) -> PackageInterfaceHandle {
- let mut config: IndexMap<String, PhpMixed> = IndexMap::new();
- config.insert("name".to_string(), PhpMixed::String(name.to_string()));
- config.insert("version".to_string(), PhpMixed::String("1.0.0".to_string()));
+ let package = PackageHandle::new(name.to_string(), "1.0.0.0".to_string(), "1.0.0".to_string());
- if !requires.is_empty() {
- let mut links: IndexMap<String, PhpMixed> = IndexMap::new();
- for require_name in requires {
- links.insert(require_name.to_string(), PhpMixed::String("*".to_string()));
- }
- config.insert("require".to_string(), PhpMixed::Array(links));
+ let mut links: IndexMap<String, Link> = IndexMap::new();
+ for require_name in requires {
+ links.insert(
+ require_name.to_string(),
+ Link::new(
+ package.get_name(),
+ require_name.to_string(),
+ MatchAllConstraint::new(None).into(),
+ None,
+ "*".to_string(),
+ ),
+ );
}
+ package.__set_requires(links);
- ArrayLoader::new(None, false)
- .load_packages(vec![config])
- .unwrap()
- .remove(0)
+ package.into()
}
fn names(packages: &[PackageInterfaceHandle]) -> Vec<String> {
@@ -141,7 +141,7 @@ fn sorting_orders_dependencies_higher_than_package_cases() -> Vec<(
}
#[test]
-#[ignore = "constraint parsing reaches a look-around regex unsupported by the regex-crate shim (Preg)"]
+#[ignore]
fn test_sorting_orders_dependencies_higher_than_package() {
for (packages, expected_ordered_list, weights) in
sorting_orders_dependencies_higher_than_package_cases()
diff --git a/crates/shirabe/tests/util/platform_test.rs b/crates/shirabe/tests/util/platform_test.rs
index 626e358..c5fe190 100644
--- a/crates/shirabe/tests/util/platform_test.rs
+++ b/crates/shirabe/tests/util/platform_test.rs
@@ -4,7 +4,7 @@ use shirabe::util::platform::Platform;
use shirabe_php_shim::defined;
#[test]
-#[ignore = "Platform::expand_path does not read the env var set via put_env in this runtime"]
+#[ignore]
fn test_expand_path() {
Platform::put_env("TESTENV", "/home/test");
assert_eq!(
diff --git a/crates/shirabe/tests/util/silencer_test.rs b/crates/shirabe/tests/util/silencer_test.rs
index 88d33c6..1b65324 100644
--- a/crates/shirabe/tests/util/silencer_test.rs
+++ b/crates/shirabe/tests/util/silencer_test.rs
@@ -7,7 +7,7 @@ use shirabe_php_shim::{
/// Test succeeds when no warnings are emitted externally, and original level is restored.
#[test]
-#[ignore = "trigger_error is todo!() in the php-shim"]
+#[ignore]
fn test_silencer() {
let before = error_reporting(None);
diff --git a/crates/shirabe/tests/util/svn_test.rs b/crates/shirabe/tests/util/svn_test.rs
index 90a6b45..25e3f4e 100644
--- a/crates/shirabe/tests/util/svn_test.rs
+++ b/crates/shirabe/tests/util/svn_test.rs
@@ -1,31 +1,145 @@
//! ref: composer/tests/Composer/Test/Util/SvnTest.php
-#[ignore = "Svn::get_credential_args is pub(crate) (crate-private); unreachable from an integration test"]
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use indexmap::IndexMap;
+use shirabe::config::Config;
+use shirabe::io::IOInterface;
+use shirabe::io::null_io::NullIO;
+use shirabe::util::svn::Svn;
+use shirabe_php_shim::PhpMixed;
+
+fn map(pairs: Vec<(&str, PhpMixed)>) -> IndexMap<String, PhpMixed> {
+ pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect()
+}
+
+/// Builds a `['config' => ['http-basic' => [host => ['username' => .., 'password' => ..]]]]`
+/// map for `Config::merge`.
+fn http_basic_config(host: &str, username: &str, password: &str) -> IndexMap<String, PhpMixed> {
+ let creds = map(vec![
+ ("username", PhpMixed::String(username.to_string())),
+ ("password", PhpMixed::String(password.to_string())),
+ ]);
+ let http_basic = map(vec![(host, PhpMixed::Array(creds))]);
+ let config = map(vec![("http-basic", PhpMixed::Array(http_basic))]);
+ map(vec![("config", PhpMixed::Array(config))])
+}
+
+/// ref: SvnTest::urlProvider
+fn url_provider() -> Vec<(&'static str, Vec<&'static str>)> {
+ vec![
+ (
+ "http://till:test@svn.example.org/",
+ vec!["--username", "till", "--password", "test"],
+ ),
+ ("http://svn.apache.org/", vec![]),
+ (
+ "svn://johndoe@example.org",
+ vec!["--username", "johndoe", "--password", ""],
+ ),
+ ]
+}
+
#[test]
+#[ignore]
fn test_credentials() {
- todo!()
+ for (url, expect) in url_provider() {
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ let config = Rc::new(RefCell::new(Config::new(true, None)));
+ let mut svn = Svn::new(url.to_string(), io, config, None);
+
+ let expect: Vec<String> = expect.iter().map(|s| s.to_string()).collect();
+ assert_eq!(expect, svn.__get_credential_args());
+ }
}
-#[ignore = "Svn::get_command is pub(crate) (crate-private); unreachable from an integration test"]
#[test]
+#[ignore]
fn test_interactive_string() {
- todo!()
+ let url = "http://svn.example.org";
+
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ let config = Rc::new(RefCell::new(Config::new(true, None)));
+ let mut svn = Svn::new(url.to_string(), io, config, None);
+
+ assert_eq!(
+ vec![
+ "svn".to_string(),
+ "ls".to_string(),
+ "--non-interactive".to_string(),
+ "--".to_string(),
+ "http://svn.example.org".to_string(),
+ ],
+ svn.__get_command(vec!["svn".to_string(), "ls".to_string()], url, None)
+ );
}
-#[ignore = "Svn::get_credential_args is pub(crate) (crate-private); unreachable from an integration test"]
#[test]
+#[ignore]
fn test_credentials_from_config() {
- todo!()
+ let url = "http://svn.apache.org";
+
+ let mut config = Config::new(true, None);
+ config.merge(&http_basic_config("svn.apache.org", "foo", "bar"), "test");
+
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ let mut svn = Svn::new(url.to_string(), io, Rc::new(RefCell::new(config)), None);
+
+ assert_eq!(
+ vec![
+ "--username".to_string(),
+ "foo".to_string(),
+ "--password".to_string(),
+ "bar".to_string(),
+ ],
+ svn.__get_credential_args()
+ );
}
-#[ignore = "Svn::get_credential_args is pub(crate) (crate-private); unreachable from an integration test"]
#[test]
+#[ignore]
fn test_credentials_from_config_with_cache_credentials_true() {
- todo!()
+ let url = "http://svn.apache.org";
+
+ let mut config = Config::new(true, None);
+ config.merge(&http_basic_config("svn.apache.org", "foo", "bar"), "test");
+
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ let mut svn = Svn::new(url.to_string(), io, Rc::new(RefCell::new(config)), None);
+ svn.set_cache_credentials(true);
+
+ assert_eq!(
+ vec![
+ "--username".to_string(),
+ "foo".to_string(),
+ "--password".to_string(),
+ "bar".to_string(),
+ ],
+ svn.__get_credential_args()
+ );
}
-#[ignore = "Svn::get_credential_args is pub(crate) (crate-private); unreachable from an integration test"]
#[test]
+#[ignore]
fn test_credentials_from_config_with_cache_credentials_false() {
- todo!()
+ let url = "http://svn.apache.org";
+
+ let mut config = Config::new(true, None);
+ config.merge(&http_basic_config("svn.apache.org", "foo", "bar"), "test");
+
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ let mut svn = Svn::new(url.to_string(), io, Rc::new(RefCell::new(config)), None);
+ svn.set_cache_credentials(false);
+
+ assert_eq!(
+ vec![
+ "--no-auth-cache".to_string(),
+ "--username".to_string(),
+ "foo".to_string(),
+ "--password".to_string(),
+ "bar".to_string(),
+ ],
+ svn.__get_credential_args()
+ );
}
diff --git a/crates/shirabe/tests/util/tar_test.rs b/crates/shirabe/tests/util/tar_test.rs
index 6234108..48f1cbe 100644
--- a/crates/shirabe/tests/util/tar_test.rs
+++ b/crates/shirabe/tests/util/tar_test.rs
@@ -12,7 +12,7 @@ fn fixture(name: &str) -> String {
}
#[test]
-#[ignore = "PharData::new is todo!() in the php-shim"]
+#[ignore]
fn test_returns_nullif_the_tar_is_not_found() {
let result = Tar::get_composer_json(&fixture("invalid.zip")).unwrap();
@@ -20,26 +20,26 @@ fn test_returns_nullif_the_tar_is_not_found() {
}
#[test]
-#[ignore = "PharData::new is todo!() in the php-shim"]
+#[ignore]
fn test_returns_null_if_the_tar_is_empty() {
let result = Tar::get_composer_json(&fixture("empty.tar.gz")).unwrap();
assert_eq!(None, result);
}
#[test]
-#[ignore = "PharData::new is todo!() in the php-shim"]
+#[ignore]
fn test_throws_exception_if_the_tar_has_no_composer_json() {
assert!(Tar::get_composer_json(&fixture("nojson.tar.gz")).is_err());
}
#[test]
-#[ignore = "PharData::new is todo!() in the php-shim"]
+#[ignore]
fn test_throws_exception_if_the_composer_json_is_in_a_sub_subfolder() {
assert!(Tar::get_composer_json(&fixture("subfolders.tar.gz")).is_err());
}
#[test]
-#[ignore = "PharData::new is todo!() in the php-shim"]
+#[ignore]
fn test_returns_composer_json_in_tar_root() {
let result = Tar::get_composer_json(&fixture("root.tar.gz")).unwrap();
assert_eq!(
@@ -49,7 +49,7 @@ fn test_returns_composer_json_in_tar_root() {
}
#[test]
-#[ignore = "PharData::new is todo!() in the php-shim"]
+#[ignore]
fn test_returns_composer_json_in_first_folder() {
let result = Tar::get_composer_json(&fixture("folder.tar.gz")).unwrap();
assert_eq!(
@@ -59,7 +59,7 @@ fn test_returns_composer_json_in_first_folder() {
}
#[test]
-#[ignore = "PharData::new is todo!() in the php-shim"]
+#[ignore]
fn test_multiple_top_level_dirs_is_invalid() {
assert!(Tar::get_composer_json(&fixture("multiple.tar.gz")).is_err());
}
diff --git a/crates/shirabe/tests/util/url_test.rs b/crates/shirabe/tests/util/url_test.rs
index cbbfb64..4adb4f2 100644
--- a/crates/shirabe/tests/util/url_test.rs
+++ b/crates/shirabe/tests/util/url_test.rs
@@ -22,7 +22,7 @@ fn conf(entries: &[(&str, &[&str])]) -> IndexMap<String, PhpMixed> {
}
#[test]
-#[ignore = "update_dist_reference uses a regex pattern the regex crate cannot compile"]
+#[ignore]
fn test_update_dist_reference() {
for (url, expected_url, c, r#ref) in dist_refs_provider() {
let mut config = Config::new(true, None);
@@ -143,7 +143,7 @@ fn dist_refs_provider() -> Vec<(
}
#[test]
-#[ignore = "Url::sanitize uses a regex pattern the regex crate cannot compile"]
+#[ignore]
fn test_sanitize() {
for (expected, url) in sanitize_provider() {
assert_eq!(expected, Url::sanitize(url.to_string()));
diff --git a/crates/shirabe/tests/util/zip_test.rs b/crates/shirabe/tests/util/zip_test.rs
index b80dd17..4d883e0 100644
--- a/crates/shirabe/tests/util/zip_test.rs
+++ b/crates/shirabe/tests/util/zip_test.rs
@@ -18,7 +18,7 @@ fn test_throws_exception_if_zip_extension_is_not_loaded() {
}
#[test]
-#[ignore = "ZipArchive::new is todo!() in the php-shim"]
+#[ignore]
fn test_returns_nullif_the_zip_is_not_found() {
let result = Zip::get_composer_json(&fixture("invalid.zip")).unwrap();
@@ -26,7 +26,7 @@ fn test_returns_nullif_the_zip_is_not_found() {
}
#[test]
-#[ignore = "ZipArchive::new is todo!() in the php-shim"]
+#[ignore]
fn test_returns_null_if_the_zip_is_empty() {
let result = Zip::get_composer_json(&fixture("empty.zip")).unwrap();
@@ -34,19 +34,19 @@ fn test_returns_null_if_the_zip_is_empty() {
}
#[test]
-#[ignore = "ZipArchive::new is todo!() in the php-shim"]
+#[ignore]
fn test_throws_exception_if_the_zip_has_no_composer_json() {
assert!(Zip::get_composer_json(&fixture("nojson.zip")).is_err());
}
#[test]
-#[ignore = "ZipArchive::new is todo!() in the php-shim"]
+#[ignore]
fn test_throws_exception_if_the_composer_json_is_in_a_sub_subfolder() {
assert!(Zip::get_composer_json(&fixture("subfolders.zip")).is_err());
}
#[test]
-#[ignore = "ZipArchive::new is todo!() in the php-shim"]
+#[ignore]
fn test_returns_composer_json_in_zip_root() {
let result = Zip::get_composer_json(&fixture("root.zip")).unwrap();
@@ -57,7 +57,7 @@ fn test_returns_composer_json_in_zip_root() {
}
#[test]
-#[ignore = "ZipArchive::new is todo!() in the php-shim"]
+#[ignore]
fn test_returns_composer_json_in_first_folder() {
let result = Zip::get_composer_json(&fixture("folder.zip")).unwrap();
@@ -68,13 +68,13 @@ fn test_returns_composer_json_in_first_folder() {
}
#[test]
-#[ignore = "ZipArchive::new is todo!() in the php-shim"]
+#[ignore]
fn test_multiple_top_level_dirs_is_invalid() {
assert!(Zip::get_composer_json(&fixture("multiple.zip")).is_err());
}
#[test]
-#[ignore = "ZipArchive::new is todo!() in the php-shim"]
+#[ignore]
fn test_returns_composer_json_from_first_subfolder() {
let result = Zip::get_composer_json(&fixture("single-sub.zip")).unwrap();