test(cli): run nine Windows-excluded e2e suites on Windows (#15097)

Nine files under `crates/cli/tests/suite/` carried a file-level
`#![cfg(unix)]`, and the two `pnpm`-differential tests in `root.rs` and
`bin.rs` carried a Windows `ignore`, so none of them ever compiled or ran
on the Windows CI job.

Four of the gates blamed "program not found" on spawning the external
`pnpm`. That dates from pacquet's own CI, which installed pnpm through
npm as a `pnpm.cmd` shim: Windows resolves a bare program name against
`.exe` alone, so the spawn failed. The workflows have installed pnpm
through `pnpm/setup` since 2026-05, and that action downloads pnpm's
native `pnpm.exe`, which resolves. Nothing in those four files is
unix-specific beyond the spawn.

The other gates were harness details rather than platform behavior:

- `repeat_install.rs` compared inode numbers to tell a file the install
  reused from one it replaced. It now takes a hard link as a witness and
  compares with `same_file`, which answers the same question on both
  platforms.
- `multiple_importers.rs` asserted the executable bit on a linked bin.
  `bundled_dependencies.rs` already had a helper that checks the bit on
  Unix and the `.cmd` / `.ps1` launchers on Windows; it moves to the
  shared test utils and both files call it.
- `pnpx_alias.rs` copied the binary under a name with no `.exe` suffix.
- `ignore_workspace.rs` ran `pwd`, and `pipeline_watch.rs` ran `mkdir -p`
  and `cp`, from a package script. Both now use `node -e`, as
  `pipeline_cache.rs` already did.
- `pipeline_cargo_cache.rs` ran its built probe binary by a path with no
  `.exe` suffix.

Five files stay gated, each with a comment saying why: `interrupt.rs`
sends POSIX signals, `run_recursive.rs` and `exec_recursive.rs` put POSIX
shell bodies in nearly every package script, and
`global_virtual_store.rs` and `hoisted_node_linker.rs` assert layouts
shaped around symlinks. Un-gating those is the remainder of
pnpm/pnpm#15089.

Related to pnpm/pnpm#15089.
This commit is contained in:
Zoltan Kochan authored and GitHub committed 2026-09-19 10:29:12 +02:00
1 parent d8be2b85d1
commit 652dcc802b
17 files changed
+164 -98

No files matched your search

Generated
+1
View File
@@ -6244,6 +6244,7 @@ dependencies = [
"pnpm-workspace-state",
"pnpr",
"pnpr-fixtures",
"same-file",
"serde_json",
"ssri",
"tar",
+24 -2
View File
@@ -438,7 +438,6 @@ pub fn read_lockfile(path: &Path) -> Lockfile {
/// lockfiles the upstream tests use to stage wanted/current divergence.
/// `new_ref` takes any `snapshots:` dependency shape (`100.0.0`,
/// `link:packages/foo`, ...).
#[cfg(unix)]
pub fn repin_snapshot_dependency(
lockfile_path: &Path,
snapshot_key: &str,
@@ -464,6 +463,30 @@ pub fn repin_snapshot_dependency(
lockfile.save_to_path(lockfile_path).expect("write the rewritten lockfile");
}
/// Assert that `shim` is a bin a caller could actually invoke.
///
/// What that takes differs per platform: Unix has the executable bit on
/// the extensionless shim, while Windows has no such bit and relies on the
/// `.cmd` / `.ps1` launchers written next to it.
pub fn assert_bin_linked(shim: &Path) {
assert!(shim.exists(), "the bin must be linked at {shim:?}");
#[cfg(unix)]
assert!(
pnpm_testing_utils::fs::is_path_executable(shim),
"the bin shim at {shim:?} must be executable",
);
#[cfg(windows)]
for extension in ["cmd", "ps1"] {
let launcher = shim.with_file_name(format!(
"{}.{extension}",
shim.file_name()
.expect("bin shim has a file name")
.to_string_lossy(),
));
assert!(launcher.exists(), "the bin shim at {shim:?} needs its {extension} launcher");
}
}
pub fn assert_success(output: &Output) {
assert!(
output.status.success(),
@@ -489,7 +512,6 @@ pub fn ndjson_records(output: &Output) -> Vec<Value> {
/// The `name: "pnpm" / level: "info"` log pnpm's headless installer
/// emits when it is entered with an up-to-date lockfile.
#[cfg(unix)]
#[must_use]
pub fn has_up_to_date_log(records: &[Value]) -> bool {
records
+1 -6
View File
@@ -246,13 +246,8 @@ fn bin_walks_past_an_ecosystem_manifest() {
/// Differential parity from a workspace member, whose own `package.json`
/// makes it the local prefix `bin` prints for. pacquet must match pnpm
/// byte-for-byte. Windows-skipped because it spawns the external `pnpm`
/// shim (see the `ignore` reason).
/// byte-for-byte.
#[test]
#[cfg_attr(
target_os = "windows",
ignore = "spawns the external `pnpm` shim (`pnpm.cmd`); std::process::Command can't resolve it via PATHEXT"
)]
fn bin_matches_pnpm_from_a_workspace_subdir() {
let CommandTempCwd { root, workspace, .. } = CommandTempCwd::init();
@@ -1,7 +1,7 @@
//! Ports of the TypeScript `bundledDependencies` install suite
//! (`installing/deps-installer/test/install/bundledDependencies.ts`).
pub use _utils::{append_workspace_yaml_key, pacquet_in};
pub use _utils::{append_workspace_yaml_key, assert_bin_linked, pacquet_in};
use assert_cmd::prelude::*;
use command_extra::CommandExtra;
@@ -217,29 +217,6 @@ fn bundled_dependencies_survive_a_lockfile_rewrite() {
drop((root, npmrc_info)); // cleanup
}
/// A linked bin means something different per platform: Unix has the
/// executable bit on the extensionless shim, while Windows has no such bit and
/// instead relies on the `.cmd` / `.ps1` launchers written next to it. Assert
/// whichever of the two actually makes the bin invocable on the host.
fn assert_bin_linked(shim: &Path) {
assert!(shim.exists(), "the bundled dependency's bin must be linked at {shim:?}");
#[cfg(unix)]
assert!(
pnpm_testing_utils::fs::is_path_executable(shim),
"the bundled dependency's bin shim at {shim:?} must be executable",
);
#[cfg(windows)]
for extension in ["cmd", "ps1"] {
let launcher = shim.with_file_name(format!(
"{}.{extension}",
shim.file_name()
.expect("bin shim has a file name")
.to_string_lossy(),
));
assert!(launcher.exists(), "the bin shim at {shim:?} needs its {extension} launcher");
}
}
fn read_wanted_lockfile(workspace: &Path) -> Lockfile {
let text =
fs::read_to_string(workspace.join(Lockfile::FILE_NAME)).expect("read pnpm-lock.yaml");
+11 -4
View File
@@ -21,8 +21,6 @@
//!
//! [#443]: https://github.com/pnpm/pacquet/pull/443
#![cfg(unix)] // pnpm CLI: 'program not found' on Windows runners.
pub use _utils::*;
use crate::_utils;
@@ -923,9 +921,18 @@ fn should_add_extra_node_paths_to_command_shims() {
let shim = fs::read_to_string(workspace.join("node_modules/.bin/hello-world-js-bin"))
.expect("read the command shim");
// Inside a relocatable root the shim names the entry relative to its
// own directory; on Windows `generate_sh_shim` writes the absolute
// path `@zkochan/cmd-shim` writes instead.
let hoisted_modules_entry = if cfg!(windows) {
"/node_modules/.pnpm/node_modules"
} else {
"$basedir_abs/../.pnpm/node_modules"
};
assert!(
shim.contains("$basedir_abs/../.pnpm/node_modules"),
"the shim must extend NODE_PATH with the hidden hoisted modules dir, relative to itself:\n{shim}",
shim.contains(hoisted_modules_entry),
"the shim must extend NODE_PATH with the hidden hoisted modules dir \
({hoisted_modules_entry}):\n{shim}",
);
// The fresh install's own `packages:` rows must record `hasBin` —
@@ -1,7 +1,5 @@
//! `--ignore-workspace` and `--workspace-packages`: the two flags that
//! change which workspace, if any, a command belongs to. The scripts run
//! through pacquet's `sh -c` executor, so the file is gated to Unix.
#![cfg(unix)]
//! change which workspace, if any, a command belongs to.
use assert_cmd::prelude::*;
use command_extra::CommandExtra;
@@ -107,11 +105,13 @@ fn workspace_packages_overrides_the_manifest_patterns() {
"--config.verify-deps-before-run=false",
"-r",
"exec",
"pwd",
"node",
"-e",
"console.log(process.cwd())",
]));
let selected = stdout.lines().collect::<Vec<_>>();
assert_eq!(selected.len(), 1, "only alfa should be selected: {stdout}");
assert!(selected[0].ends_with("packages/alfa"), "wrong project selected: {stdout}");
assert!(Path::new(selected[0]).ends_with("packages/alfa"), "wrong project selected: {stdout}");
drop(root);
}
+6
View File
@@ -1,3 +1,9 @@
//! Signal handling under `pnpm run`: what a `SIGINT` at the terminal
//! reaches, and what it leaves behind.
//!
//! Unix-only by subject, not by harness. The tests send POSIX signals to a
//! process group of their own; Windows delivers console control events
//! instead, which needs its own tests rather than a port of these.
#![cfg(unix)]
use command_extra::CommandExtra;
@@ -10,13 +10,10 @@
//! equivalent is `pnpm --filter <project> install` in a
//! `pnpm-workspace.yaml` workspace.
#![cfg(unix)] // pnpm CLI: 'program not found' on Windows runners.
pub use _utils::*;
use crate::_utils;
use pnpm_testing_utils::fs::is_path_executable;
use serde_json::json;
use std::fs;
@@ -493,13 +490,13 @@ fn links_workspace_package_bin_into_dependent_project() {
fixture.run(["install"]);
let bin_path = main_project.join("node_modules/.bin/hello");
assert!(is_path_executable(&bin_path), "expected an executable bin at {bin_path:?}");
assert_bin_linked(&bin_path);
fs::remove_dir_all(main_project.join("node_modules")).expect("remove main's node_modules");
fs::remove_dir_all(fixture.workspace.join("node_modules")).expect("remove root node_modules");
fixture.run(["install", "--frozen-lockfile"]);
assert!(is_path_executable(&bin_path), "the frozen reinstall must re-link the bin");
assert_bin_linked(&bin_path);
}
/// TS: `custom virtual store directory in a workspace with shared
@@ -695,8 +692,16 @@ fn link_bin_of_workspace_project_created_by_lifecycle_script() {
assert!(consumer.join("created-by-prepare").exists());
}
/// A script that appends `label` and a newline to the shared order log.
///
/// The newline comes from `String.fromCharCode` rather than a `\n` escape:
/// `sh -c` unescapes a backslash inside double quotes before Node sees it,
/// while `cmd /d /s /c` passes it through, so an escape would mean two
/// different things per platform.
fn append_order_script(label: &str) -> String {
format!(r#"node -e "require('fs').appendFileSync('../../order.txt', '{label}\\n')""#)
format!(
r#"node -e "require('fs').appendFileSync('../../order.txt', '{label}' + String.fromCharCode(10))""#,
)
}
/// TS: `dependencies of workspace projects are built during headless
@@ -1,9 +1,11 @@
#![cfg(unix)]
//! The pipeline's Cargo build cache: shared between worktrees, and
//! rebuilt after the cache directory is deleted.
use assert_cmd::prelude::*;
use command_extra::CommandExtra;
use pnpm_testing_utils::command_env::CommandTestExt;
use std::{
env::consts::EXE_SUFFIX,
fs::{self, File, FileTimes},
path::Path,
process::Command,
@@ -40,12 +42,23 @@ fn pnpm(root: &Path, cache: &Path, args: &[&str]) -> String {
}
fn run_binary(root: &Path) -> String {
let output = Command::new(root.join("target/debug/probe")).output().unwrap();
let output =
Command::new(root.join(format!("target/debug/probe{EXE_SUFFIX}"))).output().unwrap();
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
/// Windows-skipped on a product gap this test is not the place to fix: the
/// second worktree recompiles instead of restoring the shared Cargo build
/// state. The run there places the cache under the 8.3 short form of the
/// temporary directory (`RUNNER~1`) while the worktree carries the long
/// one, which is the first thing to check. Tracked in
/// <https://github.com/pnpm/pnpm/issues/15105>.
#[test]
#[cfg_attr(
target_os = "windows",
ignore = "pnpm/pnpm#15105: the shared Cargo build state is not restored across worktrees"
)]
fn cargo_state_is_shared_between_worktrees_and_survives_cache_deletion() {
let temp = tempfile::tempdir().unwrap();
let root = dunce::canonicalize(temp.path()).unwrap();
+12 -5
View File
@@ -1,8 +1,6 @@
//! Watch-agent integration tests: poll a git repository, build new
//! revisions of a branch in a persistent checkout, and skip ticks with
//! nothing new. The build scripts run through pacquet's `sh -c`
//! executor, so the file is gated to Unix like the other run suites.
#![cfg(unix)]
//! nothing new.
use assert_cmd::prelude::*;
use command_extra::CommandExtra;
@@ -42,7 +40,16 @@ fn agent_checkout(root: &Path) -> PathBuf {
state_dir.join("checkout").join("demo")
}
/// Windows-skipped on a product gap this test is not the place to fix: the
/// tick reports the revision built and passed, and the checkout holds no
/// task output afterwards. Tracked in
/// <https://github.com/pnpm/pnpm/issues/15105>. The rest of the file runs
/// everywhere.
#[test]
#[cfg_attr(
target_os = "windows",
ignore = "pnpm/pnpm#15105: the agent's build reports success but leaves no output in the checkout"
)]
fn watch_agent_builds_new_revisions_and_skips_quiet_ticks() {
let root = tempfile::Builder::new()
.prefix("pacquet-test-")
@@ -56,7 +63,7 @@ fn watch_agent_builds_new_revisions_and_skips_quiet_ticks() {
);
fixture.write_file(
"pkg/package.json",
r#"{ "name": "pkg", "version": "1.0.0", "scripts": { "build": "mkdir -p out && cp src/index.txt out/index.txt" } }"#,
r#"{ "name": "pkg", "version": "1.0.0", "scripts": { "build": "node -e \"const fs = require('fs'); fs.mkdirSync('out', { recursive: true }); fs.copyFileSync('src/index.txt', 'out/index.txt')\"" } }"#,
);
fixture.write_file("pkg/src/index.txt", "v1");
// The lockfile the checkout's frozen install verifies against.
@@ -104,7 +111,7 @@ fn watch_agent_builds_new_revisions_and_skips_quiet_ticks() {
fixture.write_file(
"pkg/package.json",
r#"{"name":"pkg","version":"1.0.0","scripts":{"build":"exit 1"}}"#,
r#"{"name":"pkg","version":"1.0.0","scripts":{"build":"node -e \"process.exit(1)\""}}"#,
);
let failed = fixture.commit("failing build");
for _ in 0..2 {
@@ -1,5 +1,3 @@
#![cfg(unix)] // running this on windows result in 'program not found'
pub use _utils::*;
use crate::_utils;
+15 -6
View File
@@ -1,9 +1,9 @@
//! Covers the `current_exe`-based `dlx` injection (`argv_with_alias_subcommand`)
//! that pnpm relies on for the Windows `pnpx`/`pnx` hardlinks, exercised on Unix
//! by copying the binary under the alias name — the same code runs everywhere.
#![cfg(unix)]
//! that pnpm relies on for the Windows `pnpx`/`pnx` hardlinks. The binary is
//! copied under the alias name rather than linked, which reaches the same code
//! on every platform.
use std::{fs, os::unix::fs::PermissionsExt, process::Command};
use std::{env::consts::EXE_SUFFIX, fs, process::Command};
use tempfile::TempDir;
@@ -12,9 +12,18 @@ fn launched_as_pnpx_injects_the_dlx_subcommand() {
let pacquet = env!("CARGO_BIN_EXE_pnpm");
let dir = TempDir::new().expect("create temp dir");
let pnpx = dir.path().join("pnpx");
let pnpx = dir
.path()
.join(format!("pnpx{EXE_SUFFIX}"));
fs::copy(pacquet, &pnpx).expect("copy the binary under the pnpx name");
fs::set_permissions(&pnpx, fs::Permissions::from_mode(0o755)).expect("make pnpx executable");
// Windows has no executable bit — the `.exe` suffix above is what
// makes the copy runnable there.
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&pnpx, fs::Permissions::from_mode(0o755))
.expect("make pnpx executable");
}
let via_pnpx = Command::new(&pnpx)
.arg("--help")
+24 -23
View File
@@ -5,8 +5,6 @@
//! again, and asserts the second install converges without rebuilding
//! what was still valid.
#![cfg(unix)] // pnpm CLI: 'program not found' on Windows runners.
pub use _utils::*;
use crate::_utils;
@@ -16,8 +14,9 @@ use command_extra::CommandExtra;
use pnpm_testing_utils::{
bin::{AddMockedRegistry, CommandTempCwd},
fixtures::tarball_with_manifest,
fs::SameFileWitness,
};
use std::{fs, os::unix::fs::MetadataExt, path::Path};
use std::{fs, path::Path};
/// `version` field of the `package.json` under `workspace/relative`.
fn version_of(workspace: &Path, relative: &str) -> String {
@@ -58,9 +57,15 @@ fn reinstalls_missing_packages_during_headless_install() {
let dep_location =
workspace.join("node_modules/.pnpm/is-positive@1.0.0/node_modules/is-positive");
// Resolve the path while it still exists: Windows hands the tests a
// temporary directory under its 8.3 short name, and the reporter
// names the long one.
let resolved_dep_location = canonical_path(&dep_location);
fs::remove_dir_all(&dep_location).expect("remove the virtual-store copy");
fs::remove_file(workspace.join("node_modules/is-positive"))
.expect("remove the direct-dep symlink");
// `remove_dirent` rather than `remove_file`: the direct dep is a
// junction on Windows, which `DeleteFileW` refuses.
pnpm_fs::remove_dirent(&workspace.join("node_modules/is-positive"))
.expect("remove the direct-dep link");
let second = pacquet_in(&workspace)
.with_args(["install", "--frozen-lockfile", "--reporter=ndjson"])
@@ -71,9 +76,14 @@ fn reinstalls_missing_packages_during_headless_install() {
second_events.contains("pnpm:_broken_node_modules"),
"the missing dir must be reported: {second_events}",
);
// The event is NDJSON, so the path arrives with its separators
// escaped. Build the needle the way the reporter wrote it instead of
// matching the raw path, which no Windows event would contain.
let reported_dep_location =
serde_json::to_string(&resolved_dep_location).expect("serialize the missing path");
assert!(
second_events.contains(dep_location.to_str().expect("utf-8 path")),
"the event must carry the missing path",
second_events.contains(reported_dep_location.trim_matches('"')),
"the event must carry the missing path {reported_dep_location}: {second_events}",
);
assert_eq!(version_of(&workspace, "node_modules/is-positive"), "1.0.0");
@@ -264,7 +274,7 @@ fn available_packages_used_when_node_modules_not_clean() {
let foobarqar_manifest = workspace
.join("node_modules/.pnpm/@pnpm.e2e+foobarqar@1.0.0/node_modules/@pnpm.e2e/foobarqar/package.json");
let inode_before = fs::metadata(&foobarqar_manifest).expect("stat foobarqar").ino();
let foobarqar_witness = SameFileWitness::take(&foobarqar_manifest, root.path());
fs::write(
workspace.join("package.json"),
@@ -296,9 +306,8 @@ fn available_packages_used_when_node_modules_not_clean() {
.success();
assert!(workspace.join("node_modules/@pnpm.e2e/pkg-with-1-dep").exists());
assert_eq!(
fs::metadata(&foobarqar_manifest).expect("stat foobarqar").ino(),
inode_before,
assert!(
foobarqar_witness.is_intact(),
"the already-materialized package must be reused, not re-imported",
);
let refetched: Vec<String> = index_file_contents(&store_dir)
@@ -802,7 +811,7 @@ fn repeat_hoisted_install_with_workspace_member_deps_is_up_to_date() {
let AddMockedRegistry { mock_instance, .. } = npmrc_info;
let hoisted_manifest = install_hoisted_workspace_member(pacquet, &workspace);
let inode_before = fs::metadata(&hoisted_manifest).expect("stat the hoisted dep").ino();
let hoisted_witness = SameFileWitness::take(&hoisted_manifest, root.path());
let second = pacquet_in(&workspace)
.with_arg("install")
@@ -813,11 +822,7 @@ fn repeat_hoisted_install_with_workspace_member_deps_is_up_to_date() {
second_output.contains("Already up to date"),
"the repeat install must short-circuit: {second_output}",
);
assert_eq!(
fs::metadata(&hoisted_manifest).expect("stat the hoisted dep").ino(),
inode_before,
"the second install must re-import nothing",
);
assert!(hoisted_witness.is_intact(), "the second install must re-import nothing");
drop((root, mock_instance));
}
@@ -874,7 +879,7 @@ fn repeat_hoisted_install_with_unchanged_local_tarball_is_up_to_date() {
.success();
let hoisted_manifest =
workspace.join("node_modules/@pnpm.e2e/dep-of-pkg-with-1-dep/package.json");
let inode_before = fs::metadata(&hoisted_manifest).expect("stat the hoisted dep").ino();
let hoisted_witness = SameFileWitness::take(&hoisted_manifest, root.path());
let second = pacquet_in(&workspace)
.with_arg("install")
@@ -885,11 +890,7 @@ fn repeat_hoisted_install_with_unchanged_local_tarball_is_up_to_date() {
second_output.contains("Already up to date"),
"the unchanged tarball must leave the fast path available: {second_output}",
);
assert_eq!(
fs::metadata(&hoisted_manifest).expect("stat the hoisted dep").ino(),
inode_before,
"the second install must re-import nothing",
);
assert!(hoisted_witness.is_intact(), "the second install must re-import nothing");
drop((root, mock_instance));
}
-10
View File
@@ -153,17 +153,7 @@ fn root_global_writes_warnings_to_stderr_so_stdout_stays_a_clean_path() {
/// Differential parity: from a workspace subdirectory pnpm's `root` prints the
/// cwd's `node_modules` (its `config.dir` is the cwd, not the workspace root).
/// pacquet must print byte-identical output.
///
/// Skipped on Windows, where pnpm is installed as a `pnpm.cmd` shim and
/// `std::process::Command` does not honor `PATHEXT`, so `Command::new("pnpm")`
/// fails with "program not found" (the same reason `pnpm_compatibility.rs` and
/// `hoist.rs` gate their pnpm-spawning tests). The three tests above spawn only
/// `pacquet`, so they keep running on Windows.
#[test]
#[cfg_attr(
target_os = "windows",
ignore = "spawns the external `pnpm` shim (`pnpm.cmd`); std::process::Command can't resolve it via PATHEXT"
)]
fn root_matches_pnpm_from_a_workspace_subdir() {
let CommandTempCwd { root, workspace, .. } = CommandTempCwd::init();
@@ -1,5 +1,3 @@
#![cfg(unix)]
use assert_cmd::prelude::*;
use command_extra::CommandExtra;
use pnpm_testing_utils::{
+1
View File
@@ -17,6 +17,7 @@ dunce = { workspace = true }
flate2 = { workspace = true }
pipe-trait = { workspace = true }
pnpr = { workspace = true }
same-file = { workspace = true }
serde_json = { workspace = true }
ssri = { workspace = true }
tar = { workspace = true }
+37 -1
View File
@@ -2,9 +2,10 @@ use pipe_trait::Pipe;
use pnpm_workspace_state::load_workspace_state;
use std::{
fs, io,
path::Path,
path::{Path, PathBuf},
time::{Duration, SystemTime},
};
use tempfile::TempDir;
use walkdir::WalkDir;
#[must_use]
@@ -73,6 +74,41 @@ pub fn is_path_executable(path: &Path) -> bool {
mode & 0b001_001_001 != 0
}
/// A record of which on-disk file a path named at one point in time, so a
/// later check can tell a file an install reused from one it replaced.
///
/// The record is a hard link, taken in a directory outside the tree under
/// test, and [`Self::is_intact`] compares the two paths with `same_file`.
/// Unix could keep the inode number instead, but `std` exposes the Windows
/// equivalent only behind an unstable feature.
pub struct SameFileWitness {
path: PathBuf,
/// Owns the directory the link lives in, so the link goes away with
/// the witness.
dir: TempDir,
}
impl SameFileWitness {
/// Link `path` from a directory of its own under `witness_dir`, which
/// has to be on the same filesystem as `path` and outside whatever the
/// step under test rewrites.
#[must_use]
pub fn take(path: &Path, witness_dir: &Path) -> Self {
let dir = TempDir::new_in(witness_dir).expect("create the witness directory");
let link = dir.path().join("link");
fs::hard_link(path, &link)
.unwrap_or_else(|error| panic!("link {path:?} from {link:?}: {error}"));
SameFileWitness { path: path.to_path_buf(), dir }
}
/// Whether the path still names the file it named when the witness was
/// taken.
#[must_use]
pub fn is_intact(&self) -> bool {
same_file::is_same_file(&self.path, self.dir.path().join("link")).unwrap_or(false)
}
}
/// The gap that separates two mtimes on every filesystem the tests run on.
///
/// A full second, because the coarsest supported filesystems (HFS+, ext4