aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-registry/src/installer_executor
diff options
context:
space:
mode:
Diffstat (limited to 'crates/mozart-registry/src/installer_executor')
-rw-r--r--crates/mozart-registry/src/installer_executor/filesystem.rs7
-rw-r--r--crates/mozart-registry/src/installer_executor/mod.rs14
-rw-r--r--crates/mozart-registry/src/installer_executor/trace_recorder.rs104
3 files changed, 123 insertions, 2 deletions
diff --git a/crates/mozart-registry/src/installer_executor/filesystem.rs b/crates/mozart-registry/src/installer_executor/filesystem.rs
index 82acc42..e36006c 100644
--- a/crates/mozart-registry/src/installer_executor/filesystem.rs
+++ b/crates/mozart-registry/src/installer_executor/filesystem.rs
@@ -81,7 +81,12 @@ impl InstallerExecutor for FilesystemExecutor {
Ok(())
}
- fn uninstall_package(&mut self, name: &str, ctx: &ExecuteContext) -> anyhow::Result<()> {
+ fn uninstall_package(
+ &mut self,
+ name: &str,
+ _version: &str,
+ ctx: &ExecuteContext,
+ ) -> anyhow::Result<()> {
let pkg_dir = ctx.vendor_dir.join(name);
if pkg_dir.exists() {
std::fs::remove_dir_all(&pkg_dir)?;
diff --git a/crates/mozart-registry/src/installer_executor/mod.rs b/crates/mozart-registry/src/installer_executor/mod.rs
index fde4c49..1fab19f 100644
--- a/crates/mozart-registry/src/installer_executor/mod.rs
+++ b/crates/mozart-registry/src/installer_executor/mod.rs
@@ -18,8 +18,10 @@ use std::path::PathBuf;
use crate::lockfile::LockedPackage;
pub mod filesystem;
+pub mod trace_recorder;
pub use filesystem::FilesystemExecutor;
+pub use trace_recorder::TraceRecorderExecutor;
/// One install or update operation handed to [`InstallerExecutor::install_package`].
#[derive(Debug, Clone, Copy)]
@@ -73,7 +75,17 @@ pub trait InstallerExecutor: Send + Sync {
) -> anyhow::Result<()>;
/// Perform side effects for one uninstall.
- fn uninstall_package(&mut self, name: &str, ctx: &ExecuteContext) -> anyhow::Result<()>;
+ ///
+ /// `version` is the previously-installed version (from installed.json),
+ /// passed so the trace recorder can format Composer's
+ /// `Uninstalling pkg/name (version)` line. The filesystem implementation
+ /// ignores it — `name` alone is enough to locate the vendor directory.
+ fn uninstall_package(
+ &mut self,
+ name: &str,
+ version: &str,
+ ctx: &ExecuteContext,
+ ) -> anyhow::Result<()>;
/// Hook called once after every uninstall has run. Default no-op.
/// Composer cleans up empty namespace directories here; the recorder
diff --git a/crates/mozart-registry/src/installer_executor/trace_recorder.rs b/crates/mozart-registry/src/installer_executor/trace_recorder.rs
new file mode 100644
index 0000000..bb20eb1
--- /dev/null
+++ b/crates/mozart-registry/src/installer_executor/trace_recorder.rs
@@ -0,0 +1,104 @@
+//! Recording-only [`InstallerExecutor`] for in-process tests.
+//!
+//! Mirrors `Composer\Test\Mock\InstallationManagerMock` — every call appends
+//! a string to a `Vec<String>` matching Composer's
+//! `(string) $operation` output (after `strip_tags`). No filesystem or
+//! network I/O happens. The recorded trace is what tests assert against
+//! `--EXPECT--` in Composer's `.test` fixture format.
+//!
+//! Trace line shapes (byte-equivalent to Composer's `*Operation::__toString`
+//! after `strip_tags`):
+//!
+//! - Install: `Installing <name> (<version>)`
+//! - Update (upgrade direction): `Upgrading <name> (<oldVersion> => <newVersion>)`
+//! - Update (downgrade direction): `Downgrading <name> (<oldVersion> => <newVersion>)`
+//! - Uninstall: `Uninstalling <name> (<version>)`
+
+use mozart_semver::Version;
+
+use super::{ExecuteContext, InstallerExecutor, PackageOperation};
+
+/// Recording-only executor. Construct with [`TraceRecorderExecutor::new`],
+/// then read [`TraceRecorderExecutor::trace`] after the run completes.
+pub struct TraceRecorderExecutor {
+ trace: Vec<String>,
+}
+
+impl TraceRecorderExecutor {
+ pub fn new() -> Self {
+ Self { trace: Vec::new() }
+ }
+
+ /// Recorded operation strings, in the order [`InstallerExecutor`] was
+ /// invoked. Pass this to `assert_eq!` against the fixture's `--EXPECT--`
+ /// section after splitting on newlines.
+ pub fn trace(&self) -> &[String] {
+ &self.trace
+ }
+
+ /// Take ownership of the recorded trace. Use after the run if the
+ /// executor is going out of scope.
+ pub fn into_trace(self) -> Vec<String> {
+ self.trace
+ }
+}
+
+impl Default for TraceRecorderExecutor {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[async_trait::async_trait]
+impl InstallerExecutor for TraceRecorderExecutor {
+ async fn install_package(
+ &mut self,
+ op: PackageOperation<'_>,
+ _ctx: &ExecuteContext,
+ ) -> anyhow::Result<()> {
+ match op {
+ PackageOperation::Install { package } => {
+ self.trace.push(format!(
+ "Installing {} ({})",
+ package.name, package.version
+ ));
+ }
+ PackageOperation::Update {
+ from_version,
+ package,
+ } => {
+ let action = if is_upgrade(from_version, &package.version) {
+ "Upgrading"
+ } else {
+ "Downgrading"
+ };
+ self.trace.push(format!(
+ "{} {} ({} => {})",
+ action, package.name, from_version, package.version
+ ));
+ }
+ }
+ Ok(())
+ }
+
+ fn uninstall_package(
+ &mut self,
+ name: &str,
+ version: &str,
+ _ctx: &ExecuteContext,
+ ) -> anyhow::Result<()> {
+ self.trace
+ .push(format!("Uninstalling {} ({})", name, version));
+ Ok(())
+ }
+}
+
+/// Mirrors `Composer\Package\Version\VersionParser::isUpgrade` — returns
+/// true when `to` is a strictly higher version than `from`. Both unparseable
+/// or both equal means treat as upgrade (Composer's behavior on edge cases).
+fn is_upgrade(from: &str, to: &str) -> bool {
+ match (Version::parse(from), Version::parse(to)) {
+ (Ok(a), Ok(b)) => b >= a,
+ _ => true,
+ }
+}