aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/tests/installer_in_process.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-02 17:56:17 +0900
committernsfisis <nsfisis@gmail.com>2026-05-02 18:04:24 +0900
commitc446337e75ba9fd674dd63d56ec25d7bd5b5fa31 (patch)
tree0df8f83fd9e95e87406e350ce48816451b6d07af /crates/mozart/tests/installer_in_process.rs
parent16e856a20307a3ca20524d96ea13348db7f2cffd (diff)
downloadphp-mozart-c446337e75ba9fd674dd63d56ec25d7bd5b5fa31.tar.gz
php-mozart-c446337e75ba9fd674dd63d56ec25d7bd5b5fa31.tar.zst
php-mozart-c446337e75ba9fd674dd63d56ec25d7bd5b5fa31.zip
test(installer): switch fixtures to in-process harness
Replaces the spawn-based runner in tests/installer.rs with the in-process harness from Step E. Every fixture now goes through mozart::commands::{install,update}::run with an empty RepositorySet (Composer's `'packagist' => false` test config) and a TraceRecorderExecutor (Composer's InstallationManagerMock), and the EXPECT section is now asserted against the recorder's trace - load-bearing for behavior parity, not just exit-code. The original CI failure (suggest_replaced) is now legitimately tested: the empty RepositorySet makes b/b unreachable just like Composer's test config, the inline package repo's eager preload finds c/c which replaces b/b, and the topological install order in compute_operations produces the c/c -> a/a trace the fixture pins. Strict trace assertion surfaced 60 Mozart-vs-Composer divergences that the exit-code-only spawn runner had been silently ignoring. Each is marked `installer_fixture\!(name, ignore)` for now; the categories break down roughly as: - alias handling (alias_in_lock2, install_aliased_alias, update_alias*) - replace / provider trace shape (replace_priorities, provider_satisfies_its_own_requirement, replacer_*) - update direction strings (update_changes_url, update_reference, update_dev_*) - partial-update + lock interactions (partial_update_*) - allow-list with replace/dependency interactions (update_allow_list_with_dependencies_require_new*) These each become individual follow-up Mozart bugs rather than mass silent-pass. Also marks prefer_lowest_branches as ignore: it's a real flake driven by HashSet iteration order in the resolver, where two equivalent candidates can be picked in either order. That's a separate determinism bug worth its own fix. The proxy-hack env-vars in mozart-test-harness::runner are removed - no test currently spawns the binary, and the in-process harness expresses Packagist disablement directly via RepositorySet::empty rather than relying on TCP failure to suppress network calls. Headline numbers: 75 passed (in-process, exit-code + EXPECT trace) + 112 ignored, vs prior 136 passed (spawn, exit-code only) + 51 ignored. The drop in passing count reflects the stricter assertion bar, not new regressions. Also removes tests/installer_in_process.rs - its single proof-of- concept fixture (suggest_replaced) is now part of the unified installer.rs harness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart/tests/installer_in_process.rs')
-rw-r--r--crates/mozart/tests/installer_in_process.rs162
1 files changed, 0 insertions, 162 deletions
diff --git a/crates/mozart/tests/installer_in_process.rs b/crates/mozart/tests/installer_in_process.rs
deleted file mode 100644
index f3e8ce2..0000000
--- a/crates/mozart/tests/installer_in_process.rs
+++ /dev/null
@@ -1,162 +0,0 @@
-//! In-process installer fixture runner.
-//!
-//! Mirrors Composer's PHPUnit-driven `InstallerTest`: parses the same
-//! `.test` fixture files, sets up a tempdir with `composer.json` /
-//! `composer.lock` / `vendor/composer/installed.json`, then invokes
-//! `mozart::commands::{install,update}::run` directly with an empty
-//! `RepositorySet` (Composer's `'packagist' => false` test config) and a
-//! `TraceRecorderExecutor` (Composer's `InstallationManagerMock`).
-//!
-//! Step F will move every fixture in `installer.rs` over to this harness;
-//! for now this file just demonstrates the path on a single fixture
-//! (`suggest_replaced` — the original CI failure that motivated the whole
-//! DI refactor).
-
-use std::path::{Path, PathBuf};
-use std::sync::Arc;
-
-use clap::Parser;
-use mozart::commands::{Cli, Commands, install, update};
-use mozart_core::console::Console;
-use mozart_core::exit_code::MozartError;
-use mozart_registry::installer_executor::TraceRecorderExecutor;
-use mozart_registry::repository::RepositorySet;
-use mozart_test_harness::{ParsedTest, parse_test_file};
-use tempfile::TempDir;
-
-fn fixtures_dir() -> PathBuf {
- Path::new(env!("CARGO_MANIFEST_DIR"))
- .join("../../composer/tests/Composer/Test/Fixtures/installer")
-}
-
-/// Outcome of a single in-process fixture run.
-struct InProcessRunResult {
- /// Kept alive so the caller can inspect on-disk artifacts; dropped
- /// (and removed) when this struct goes out of scope.
- _working_dir: TempDir,
- /// Composer-shape operation trace from `TraceRecorderExecutor`.
- /// Compare against the fixture's `--EXPECT--` section.
- trace: Vec<String>,
- /// Final `composer.lock` JSON, as written to disk by the runner.
- final_lock: Option<String>,
- /// Final `vendor/composer/installed.json`, as written to disk.
- final_installed: Option<String>,
- /// Mapped exit code: 0 for success, otherwise the carried
- /// [`MozartError::exit_code`] (or 1 for unclassified errors).
- exit_code: i32,
-}
-
-async fn run_fixture_in_process(test: &ParsedTest) -> anyhow::Result<InProcessRunResult> {
- let working_dir = TempDir::new()?;
- let root = working_dir.path();
-
- std::fs::write(root.join("composer.json"), &test.composer)?;
- if let Some(lock) = &test.lock {
- std::fs::write(root.join("composer.lock"), lock)?;
- }
- if let Some(installed) = &test.installed {
- let vendor_composer = root.join("vendor").join("composer");
- std::fs::create_dir_all(&vendor_composer)?;
- std::fs::write(vendor_composer.join("installed.json"), installed)?;
- }
-
- // Parse the `--RUN--` line through clap so we get the same arg semantics
- // the real CLI does — including default flags, validators, etc.
- let argv: Vec<String> = std::iter::once("mozart".to_string())
- .chain(test.run.split_whitespace().map(String::from))
- .collect();
- let cli = Cli::try_parse_from(&argv)?;
-
- // Quiet console: tests assert on `trace` / lock / installed, not on
- // captured stdout/stderr (Console doesn't yet support buffered sinks).
- let console = Console::new(0, true, false, true, true);
- let repositories = Arc::new(RepositorySet::empty());
- let mut executor = TraceRecorderExecutor::new();
-
- let outcome: anyhow::Result<()> = match &cli.command {
- Some(Commands::Install(args)) => {
- install::run(root, args, &console, repositories, &mut executor).await
- }
- Some(Commands::Update(args)) => {
- update::run(root, args, &console, repositories, &mut executor).await
- }
- other => anyhow::bail!(
- "unsupported run command in fixture: {:?}",
- other.is_some()
- ),
- };
-
- let exit_code = match &outcome {
- Ok(()) => 0,
- Err(e) => e
- .downcast_ref::<MozartError>()
- .map(|m| m.exit_code)
- .unwrap_or(1),
- };
-
- let final_lock = std::fs::read_to_string(root.join("composer.lock")).ok();
- let final_installed =
- std::fs::read_to_string(root.join("vendor").join("composer").join("installed.json")).ok();
-
- Ok(InProcessRunResult {
- _working_dir: working_dir,
- trace: executor.into_trace(),
- final_lock,
- final_installed,
- exit_code,
- })
-}
-
-fn run_fixture(ident: &str) {
- let filename = format!("{}.test", ident.replace('_', "-"));
- let path = fixtures_dir().join(&filename);
- let parsed = parse_test_file(&path)
- .unwrap_or_else(|e| panic!("failed to parse {}: {:#}", path.display(), e));
-
- let runtime = tokio::runtime::Builder::new_current_thread()
- .enable_all()
- .build()
- .expect("failed to build tokio runtime");
- let result = runtime
- .block_on(run_fixture_in_process(&parsed))
- .unwrap_or_else(|e| panic!("failed to run {}: {:#}", path.display(), e));
-
- let expected_exit = parsed.expect_exit_code.unwrap_or(0);
- assert_eq!(
- result.exit_code,
- expected_exit,
- "exit code mismatch for {}\n--- trace ---\n{}",
- path.display(),
- result.trace.join("\n"),
- );
-
- // EXPECT (the trace) is the load-bearing assertion in Composer's
- // PHPUnit harness — every line of the operation log must match
- // byte-for-byte against `(string) $operation` after `strip_tags`.
- let expected_trace = parsed.expect.trim();
- let actual_trace = result.trace.join("\n");
- assert_eq!(
- actual_trace.trim(),
- expected_trace,
- "EXPECT trace mismatch for {}\n--- expected ---\n{}\n--- actual ---\n{}\n--- final lock ---\n{}\n--- final installed ---\n{}",
- path.display(),
- expected_trace,
- actual_trace,
- result.final_lock.as_deref().unwrap_or("(absent)"),
- result.final_installed.as_deref().unwrap_or("(absent)"),
- );
-}
-
-// ────────────────────────────────────────────────────────────────────────────
-// In-process fixtures
-//
-// Step F will migrate every fixture from `installer.rs` to this harness.
-// For now this file holds just the proof-of-concept: `suggest_replaced`,
-// the original CI failure (the spawn runner can't reach Packagist for
-// `b/b`, even though `c/c` replaces it).
-// ────────────────────────────────────────────────────────────────────────────
-
-#[test]
-fn suggest_replaced_in_process() {
- run_fixture("suggest_replaced");
-}