aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/mozart-registry/Cargo.toml3
-rw-r--r--crates/mozart-registry/tests/poolbuilder.rs80
-rw-r--r--crates/mozart-test-harness/src/lib.rs4
-rw-r--r--crates/mozart-test-harness/src/parser.rs14
-rw-r--r--crates/mozart-test-harness/src/pool_builder_parser.rs178
5 files changed, 276 insertions, 3 deletions
diff --git a/crates/mozart-registry/Cargo.toml b/crates/mozart-registry/Cargo.toml
index 10eaf3b..6816e8d 100644
--- a/crates/mozart-registry/Cargo.toml
+++ b/crates/mozart-registry/Cargo.toml
@@ -26,3 +26,6 @@ tempfile.workspace = true
tokio.workspace = true
tracing.workspace = true
zip.workspace = true
+
+[dev-dependencies]
+mozart-test-harness.workspace = true
diff --git a/crates/mozart-registry/tests/poolbuilder.rs b/crates/mozart-registry/tests/poolbuilder.rs
new file mode 100644
index 0000000..d8511e4
--- /dev/null
+++ b/crates/mozart-registry/tests/poolbuilder.rs
@@ -0,0 +1,80 @@
+//! Pool-builder fixture suite, ported from
+//! `composer/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php`.
+//!
+//! Composer drives this suite through a `@dataProvider`; each `.test` file
+//! becomes one parameterized case. Mirrored here as one `#[test]` per
+//! fixture so the count surfaces in `cargo test` output and individual
+//! cases can be re-enabled as the runner is fleshed out.
+//!
+//! Every test is currently `#[ignore]` because the runner is a stub: the
+//! orchestration that takes a `RepositorySet` + `Request` and produces a
+//! populated `Pool` lives inline in `mozart_registry::resolver::resolve`,
+//! not as an extracted entry point. Wiring those up — alias handling,
+//! stability flags, fixed/locked packages, the optimizer pass — is the
+//! follow-up work this scaffolding exists to track.
+
+use std::path::{Path, PathBuf};
+
+use mozart_test_harness::{ParsedPoolBuilderTest, parse_pool_builder_test_file};
+
+fn fixtures_dir() -> PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("../../composer/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder")
+}
+
+fn run_poolbuilder_fixture(ident: &str) {
+ let filename = format!("{}.test", ident.replace('_', "-"));
+ let path = fixtures_dir().join(&filename);
+ let _parsed: ParsedPoolBuilderTest = parse_pool_builder_test_file(&path)
+ .unwrap_or_else(|e| panic!("failed to parse {}: {:#}", path.display(), e));
+
+ // Runner is intentionally not implemented yet — see module docs.
+ // Removing `#[ignore]` from a case will surface this `unimplemented!`
+ // and force the missing pool-builder entry point into existence.
+ unimplemented!(
+ "PoolBuilderTest runner not yet wired up; cannot execute {}",
+ path.display()
+ );
+}
+
+macro_rules! poolbuilder_fixture {
+ ($name:ident) => {
+ #[test]
+ #[ignore]
+ fn $name() {
+ run_poolbuilder_fixture(stringify!($name));
+ }
+ };
+}
+
+poolbuilder_fixture!(alias_priority_conflicting);
+poolbuilder_fixture!(alias_with_reference);
+poolbuilder_fixture!(constraint_expansion_works_with_exact_versions);
+poolbuilder_fixture!(filter_impossible_packages);
+poolbuilder_fixture!(filter_impossible_packages_locked_replacer);
+poolbuilder_fixture!(filter_impossible_packages_only_required);
+poolbuilder_fixture!(filter_impossible_packages_only_required_provides);
+poolbuilder_fixture!(filter_impossible_packages_only_required_replaces);
+poolbuilder_fixture!(filter_impossible_packages_provides);
+poolbuilder_fixture!(filter_impossible_packages_replaces);
+poolbuilder_fixture!(fixed_packages_do_not_load_from_repos);
+poolbuilder_fixture!(fixed_packages_replaced_do_not_load_from_repos);
+poolbuilder_fixture!(load_replaced_package_if_replacer_dropped);
+poolbuilder_fixture!(load_replaced_root_package_if_replacer_dropped);
+poolbuilder_fixture!(multi_repo_replace);
+poolbuilder_fixture!(multi_repo_replace_partial_update_all);
+poolbuilder_fixture!(must_expand_root_reqs);
+poolbuilder_fixture!(package_versions_are_not_loaded_if_not_required_expansion);
+poolbuilder_fixture!(package_versions_are_not_loaded_if_not_required_recursive);
+poolbuilder_fixture!(packages_that_do_not_exist);
+poolbuilder_fixture!(partial_update);
+poolbuilder_fixture!(partial_update_transitive_deps_no_root_unfix);
+poolbuilder_fixture!(partial_update_transitive_deps_unfix);
+poolbuilder_fixture!(partial_update_unfixes_path_repo_replacer_with_transitive_deps);
+poolbuilder_fixture!(partial_update_unfixes_path_repos_always_but_not_their_transitive_deps);
+poolbuilder_fixture!(partial_update_unfixing_locked_deps);
+poolbuilder_fixture!(partial_update_unfixing_replacers);
+poolbuilder_fixture!(partial_update_unfixing_with_replacers);
+poolbuilder_fixture!(partial_update_unfixing_with_replacers_providers);
+poolbuilder_fixture!(root_requirements_avoid_loading_further_versions);
+poolbuilder_fixture!(stability_flags_take_over_minimum_stability_and_filter_packages);
diff --git a/crates/mozart-test-harness/src/lib.rs b/crates/mozart-test-harness/src/lib.rs
index f8fa2b3..ea125cc 100644
--- a/crates/mozart-test-harness/src/lib.rs
+++ b/crates/mozart-test-harness/src/lib.rs
@@ -6,7 +6,11 @@
//! runner; actual `.test` fixtures and tests live elsewhere.
mod parser;
+mod pool_builder_parser;
mod runner;
pub use parser::{ParsedTest, parse_test_file, parse_test_str};
+pub use pool_builder_parser::{
+ ParsedPoolBuilderTest, parse_pool_builder_test_file, parse_pool_builder_test_str,
+};
pub use runner::{RunResult, run_test};
diff --git a/crates/mozart-test-harness/src/parser.rs b/crates/mozart-test-harness/src/parser.rs
index 827272b..ecefb37 100644
--- a/crates/mozart-test-harness/src/parser.rs
+++ b/crates/mozart-test-harness/src/parser.rs
@@ -45,7 +45,7 @@ pub fn parse_test_file(path: &Path) -> Result<ParsedTest> {
}
pub fn parse_test_str(content: &str) -> Result<ParsedTest> {
- let mut sections = split_sections(content)?;
+ let mut sections = split_sections(content, VALID_SECTIONS)?;
for required in REQUIRED_SECTIONS {
if !sections.contains_key(*required) {
@@ -86,7 +86,15 @@ pub fn parse_test_str(content: &str) -> Result<ParsedTest> {
})
}
-fn split_sections(content: &str) -> Result<IndexMap<String, String>> {
+/// Split a `.test` fixture into its `--SECTION--` blocks.
+///
+/// Shared helper for both [`parse_test_str`] and the sibling pool-builder
+/// parser; each caller passes its own allowed-section list so unknown
+/// headers still surface as parse errors rather than silently ignored.
+pub(crate) fn split_sections(
+ content: &str,
+ valid_sections: &[&str],
+) -> Result<IndexMap<String, String>> {
let header_re = regex::Regex::new(r"^--([A-Z][A-Z-]*)--$").unwrap();
let mut sections: IndexMap<String, String> = IndexMap::new();
@@ -97,7 +105,7 @@ fn split_sections(content: &str) -> Result<IndexMap<String, String>> {
let trimmed = line.trim_end_matches('\n').trim_end_matches('\r');
if let Some(caps) = header_re.captures(trimmed) {
let name = caps[1].to_string();
- if !VALID_SECTIONS.contains(&name.as_str()) {
+ if !valid_sections.contains(&name.as_str()) {
bail!("unknown section: --{name}--");
}
if let Some(prev) = current_section.take() {
diff --git a/crates/mozart-test-harness/src/pool_builder_parser.rs b/crates/mozart-test-harness/src/pool_builder_parser.rs
new file mode 100644
index 0000000..11d2179
--- /dev/null
+++ b/crates/mozart-test-harness/src/pool_builder_parser.rs
@@ -0,0 +1,178 @@
+//! Parser for Composer's `PoolBuilderTest` `.test` fixture format.
+//!
+//! Mirrors `composer/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php::readTestFile`.
+//! Section bodies are stored as raw strings (typically JSON); the runner is
+//! responsible for interpreting them.
+
+use anyhow::{Context, Result, bail};
+use std::fs;
+use std::path::Path;
+
+use crate::parser::split_sections;
+
+const VALID_SECTIONS: &[&str] = &[
+ "TEST",
+ "ROOT",
+ "REQUEST",
+ "FIXED",
+ "PACKAGE-REPOS",
+ "EXPECT",
+ "EXPECT-OPTIMIZED",
+];
+
+const REQUIRED_SECTIONS: &[&str] = &["TEST", "REQUEST", "PACKAGE-REPOS", "EXPECT"];
+
+#[derive(Debug, Clone)]
+pub struct ParsedPoolBuilderTest {
+ pub test: String,
+ pub root: Option<String>,
+ pub request: String,
+ pub fixed: Option<String>,
+ pub package_repos: String,
+ pub expect: String,
+ pub expect_optimized: Option<String>,
+}
+
+pub fn parse_pool_builder_test_file(path: &Path) -> Result<ParsedPoolBuilderTest> {
+ let content =
+ fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
+ parse_pool_builder_test_str(&content)
+ .with_context(|| format!("failed to parse {}", path.display()))
+}
+
+pub fn parse_pool_builder_test_str(content: &str) -> Result<ParsedPoolBuilderTest> {
+ let mut sections = split_sections(content, VALID_SECTIONS)?;
+
+ for required in REQUIRED_SECTIONS {
+ if !sections.contains_key(*required) {
+ bail!("missing required section: --{required}--");
+ }
+ }
+
+ let mut take = |key: &str| sections.shift_remove(key);
+
+ let test = take("TEST").unwrap();
+ let request = take("REQUEST").unwrap();
+ let package_repos = take("PACKAGE-REPOS").unwrap();
+ let expect = take("EXPECT").unwrap();
+
+ Ok(ParsedPoolBuilderTest {
+ test,
+ root: take("ROOT"),
+ request,
+ fixed: take("FIXED"),
+ package_repos,
+ expect,
+ expect_optimized: take("EXPECT-OPTIMIZED"),
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn parses_minimal_required_sections() {
+ let input = "\
+--TEST--
+A pool builder test
+--REQUEST--
+{\"require\": {\"a/a\": \"*\"}}
+--PACKAGE-REPOS--
+[[{\"name\": \"a/a\", \"version\": \"1.0.0\"}]]
+--EXPECT--
+[\"a/a-1.0.0.0\"]
+";
+ let t = parse_pool_builder_test_str(input).unwrap();
+ assert_eq!(t.test, "A pool builder test");
+ assert_eq!(t.request, "{\"require\": {\"a/a\": \"*\"}}");
+ assert_eq!(
+ t.package_repos,
+ "[[{\"name\": \"a/a\", \"version\": \"1.0.0\"}]]"
+ );
+ assert_eq!(t.expect, "[\"a/a-1.0.0.0\"]");
+ assert!(t.root.is_none());
+ assert!(t.fixed.is_none());
+ assert!(t.expect_optimized.is_none());
+ }
+
+ #[test]
+ fn parses_all_optional_sections() {
+ let input = "\
+--TEST--
+desc
+--ROOT--
+{\"minimum-stability\": \"dev\"}
+--REQUEST--
+{\"require\": {}}
+--FIXED--
+[{\"name\": \"x/x\", \"version\": \"1.0.0\", \"id\": 1}]
+--PACKAGE-REPOS--
+[]
+--EXPECT--
+[1]
+--EXPECT-OPTIMIZED--
+[1]
+";
+ let t = parse_pool_builder_test_str(input).unwrap();
+ assert_eq!(t.test, "desc");
+ assert_eq!(t.root.as_deref(), Some("{\"minimum-stability\": \"dev\"}"));
+ assert_eq!(
+ t.fixed.as_deref(),
+ Some("[{\"name\": \"x/x\", \"version\": \"1.0.0\", \"id\": 1}]")
+ );
+ assert_eq!(t.expect_optimized.as_deref(), Some("[1]"));
+ }
+
+ #[test]
+ fn rejects_unknown_section() {
+ let input = "\
+--TEST--
+x
+--MYSTERY--
+y
+--REQUEST--
+{}
+--PACKAGE-REPOS--
+[]
+--EXPECT--
+[]
+";
+ let err = parse_pool_builder_test_str(input).unwrap_err();
+ assert!(err.to_string().contains("unknown section"), "{err}");
+ }
+
+ #[test]
+ fn rejects_missing_required_section() {
+ let input = "\
+--TEST--
+x
+--REQUEST--
+{}
+--EXPECT--
+[]
+";
+ let err = parse_pool_builder_test_str(input).unwrap_err();
+ assert!(err.to_string().contains("PACKAGE-REPOS"), "{err}");
+ }
+
+ #[test]
+ fn rejects_installer_only_section() {
+ // `--RUN--` is part of InstallerTest fixtures; PoolBuilder fixtures
+ // have no such section, so it must be flagged as unknown here.
+ let input = "\
+--TEST--
+x
+--REQUEST--
+{}
+--PACKAGE-REPOS--
+[]
+--RUN--
+install
+--EXPECT--
+[]
+";
+ let err = parse_pool_builder_test_str(input).unwrap_err();
+ assert!(err.to_string().contains("RUN"), "{err}");
+ }
+}