aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-test-harness/src/pool_builder_parser.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-04 10:35:58 +0900
committernsfisis <nsfisis@gmail.com>2026-05-04 10:35:58 +0900
commit1427c101f98c72e551fcc72671ab3cde0991bb6d (patch)
treeb83fe05cc22a38813243d86890fde34721b7985a /crates/mozart-test-harness/src/pool_builder_parser.rs
parentbc72b70daea7db03456508540f96ab6f019ef5e3 (diff)
downloadphp-mozart-1427c101f98c72e551fcc72671ab3cde0991bb6d.tar.gz
php-mozart-1427c101f98c72e551fcc72671ab3cde0991bb6d.tar.zst
php-mozart-1427c101f98c72e551fcc72671ab3cde0991bb6d.zip
test(resolver): scaffold PoolBuilder fixture suite from Composer
Port the 31 .test fixtures under composer/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/ as #[ignore]'d cases in mozart-registry/tests/poolbuilder.rs. Each fixture is parsed eagerly so format-level regressions surface immediately, while the runner itself is unimplemented\!() — removing #[ignore] from a case will force the missing pool-build entry point into existence rather than silently mis-run. Generalize mozart-test-harness's split_sections to take a per-format valid-section list and add a poolbuilder parser alongside the installer one.
Diffstat (limited to 'crates/mozart-test-harness/src/pool_builder_parser.rs')
-rw-r--r--crates/mozart-test-harness/src/pool_builder_parser.rs178
1 files changed, 178 insertions, 0 deletions
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}");
+ }
+}