test(cli): pin --force against the repeat-install fast path (#15095)

A forced install re-materializes every slot, so the fast path must not
short-circuit it on an unchanged workspace. Nothing named that gate: it
was caught only incidentally, by a global-virtual-store test that would
have failed on stale bytes rather than on the reason for them.

Assert the observable signal instead — a forced install does not report
"Already up to date". Verified by removing the gate, which fails the new
test on its own assertion.

`install.rs` rather than `repeat_install.rs` so the case also runs on
Windows; the latter is `#![cfg(unix)]`.

The one-line note on `Config::force` replaces a sentence that described
the frozen path's skip discard as though it were the whole rule.

Related to pnpm/pnpm#15094
This commit is contained in:
Zoltan Kochan authored and GitHub committed 2026-09-19 12:16:23 +02:00
1 parent 6ea6aaf72c
commit ea9f3bf4a0
2 files changed
+47 -4

No files matched your search

+44
View File
@@ -738,6 +738,50 @@ fn set_dir_modes(path: &std::path::Path, mode: u32) {
fs::set_permissions(path, fs::Permissions::from_mode(mode)).expect("set directory mode");
}
/// The gate exists because `--force` re-materializes every slot, not only
/// because it bypasses the platform checks.
#[test]
fn force_defeats_the_up_to_date_fast_path() {
let CommandTempCwd {
pacquet,
root,
workspace,
npmrc_info,
..
} = CommandTempCwd::init().add_mocked_registry();
let AddMockedRegistry { mock_instance, .. } = npmrc_info;
fs::write(
workspace.join("package.json"),
serde_json::json!({ "dependencies": { "@foo/no-deps": "1.0.0" } }).to_string(),
)
.expect("write package.json");
let run_install = |args: &[&str]| {
let assert = pacquet_in(&workspace)
.with_env("PNPM_CONFIG_REGISTRY", mock_instance.url())
.with_args(args)
.assert()
.success();
String::from_utf8_lossy(&assert.get_output().stdout).into_owned()
};
pacquet
.with_env("PNPM_CONFIG_REGISTRY", mock_instance.url())
.with_arg("install")
.assert()
.success();
assert!(
run_install(&["install"]).contains("Already up to date"),
"an unchanged repeat install must take the fast path",
);
assert!(
!run_install(&["install", "--force"]).contains("Already up to date"),
"--force must defeat the fast path even though nothing changed",
);
drop((root, mock_instance));
}
/// Trust/policy settings key the lockfile-verification gate, which is
/// why pnpm records `trustPolicy*` in the workspace state.
#[test]
+3 -4
View File
@@ -849,12 +849,11 @@ pub struct Config {
/// instead of skipped, mirroring pnpm's `!opts.force &&
/// packageIsInstallable(...)` gate in its dep-graph builders.
///
/// It also re-materializes every slot, changed or not.
///
/// CLI-only (merged from `--force` on `pnpm install` / `pnpm add` /
/// `pnpm deploy` at the dispatch, like `ignoreScripts`); not a
/// `pnpm-workspace.yaml` / `.npmrc` setting. On the frozen path it
/// also discards the previous install's per-snapshot skip decision,
/// mirroring pnpm's `lockfileToDepGraph(…, opts.force ? null :
/// currentLockfile)`, so already-materialized packages are relinked.
/// `pnpm-workspace.yaml` / `.npmrc` setting.
pub force: bool,
/// Whether to consult the side-effects cache