use super::*;
use crate::bundle::STAGING_DIR;
#[test]
fn link_or_copy_atomic_materializes_identical_bytes() {
let dir = tempfile::tempdir().unwrap();
let landing = Landing::open(dir.path()).unwrap();
let src = dir.path().join("src.bin");
let dest = dir.path().join("nested/dest.bin");
std::fs::write(&src, b"the-canonical-bytes").unwrap();
landing.link_or_copy_atomic(&src, &dest).unwrap();
assert_eq!(std::fs::read(&dest).unwrap(), b"the-canonical-bytes");
assert!(src.exists());
}
#[test]
fn link_or_copy_atomic_replaces_existing_dest() {
let dir = tempfile::tempdir().unwrap();
let landing = Landing::open(dir.path()).unwrap();
let src = dir.path().join("src.bin");
let dest = dir.path().join("dest.bin");
std::fs::write(&src, b"new").unwrap();
std::fs::write(&dest, b"stale-and-longer").unwrap();
landing.link_or_copy_atomic(&src, &dest).unwrap();
assert_eq!(std::fs::read(&dest).unwrap(), b"new");
}
#[test]
fn link_or_copy_atomic_leaves_no_staging_name_behind() {
let dir = tempfile::tempdir().unwrap();
let landing = Landing::open(dir.path()).unwrap();
let src = dir.path().join("src.bin");
std::fs::write(&src, b"bytes").unwrap();
landing
.link_or_copy_atomic(&src, &dir.path().join("sub/dest.bin"))
.unwrap();
let names: Vec<String> = std::fs::read_dir(dir.path().join("sub"))
.unwrap()
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
.collect();
assert_eq!(names, ["dest.bin"]);
}
#[test]
fn materialize_replaces_dest_and_keeps_staging() {
let dir = tempfile::tempdir().unwrap();
let landing = Landing::open(dir.path()).unwrap();
let staging = dir.path().join("blob.partial");
std::fs::write(&staging, b"new-content").unwrap();
let dest = dir.path().join("out.bin");
std::fs::write(&dest, b"stale-old").unwrap();
let n = landing.materialize(&staging, &dest).unwrap();
assert_eq!(n, b"new-content".len() as u64);
assert_eq!(std::fs::read(&dest).unwrap(), b"new-content");
assert!(staging.exists(), "staging must survive for retries");
assert_eq!(std::fs::read(&staging).unwrap(), b"new-content");
}
#[test]
fn first_write_moves_a_non_donor_blob_into_place() {
let dir = tempfile::tempdir().unwrap();
let landing = Landing::open(dir.path()).unwrap();
let staging = dir.path().join("blob");
std::fs::write(&staging, b"new-content").unwrap();
let dest = dir.path().join("sub").join("out.bin");
std::fs::create_dir_all(dest.parent().unwrap()).unwrap();
std::fs::write(&dest, b"stale-old").unwrap();
let n = landing.first_write(&staging, &dest, false).unwrap();
assert_eq!(n, b"new-content".len() as u64);
assert_eq!(std::fs::read(&dest).unwrap(), b"new-content");
assert!(!staging.exists(), "the blob moved, it was not copied");
}
#[cfg(unix)]
#[test]
fn first_write_links_a_donor_blob_and_keeps_staging() {
use std::os::unix::fs::MetadataExt;
let dir = tempfile::tempdir().unwrap();
let landing = Landing::open(dir.path()).unwrap();
let staging = dir.path().join("blob");
std::fs::write(&staging, b"donor-bytes").unwrap();
let dest = dir.path().join("out.bin");
let n = landing.first_write(&staging, &dest, true).unwrap();
assert_eq!(n, b"donor-bytes".len() as u64);
assert_eq!(std::fs::read(&dest).unwrap(), b"donor-bytes");
assert!(staging.exists(), "a donor's staging name survives");
assert_eq!(
std::fs::metadata(&staging).unwrap().ino(),
std::fs::metadata(&dest).unwrap().ino(),
"the first destination is a hard link, not a copy"
);
}
#[test]
fn open_creates_a_missing_output_directory() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("new/out");
let landing = Landing::open(&out).unwrap();
assert_eq!(landing.root(), out);
assert!(out.is_dir());
}
#[test]
fn a_destination_outside_the_root_is_refused() {
let dir = tempfile::tempdir().unwrap();
let other = tempfile::tempdir().unwrap();
let landing = Landing::open(dir.path()).unwrap();
let staging = dir.path().join("blob");
std::fs::write(&staging, b"bytes").unwrap();
let err = landing
.materialize(&staging, &other.path().join("file"))
.unwrap_err();
assert!(
format!("{err:#}").contains("not under the output directory"),
"{err:#}"
);
assert!(!other.path().join("file").exists());
}
#[cfg(unix)]
fn escaping_root() -> (tempfile::TempDir, tempfile::TempDir, Landing) {
let out = tempfile::tempdir().unwrap();
let outside = tempfile::tempdir().unwrap();
std::os::unix::fs::symlink(outside.path(), out.path().join("link")).unwrap();
let landing = Landing::open(out.path()).unwrap();
(out, outside, landing)
}
#[cfg(unix)]
fn assert_refused(err: &anyhow::Error, outside: &Path) {
let text = format!("{err:#}");
assert!(text.contains("outside the output directory"), "{text}");
assert!(text.contains("link/file"), "{text}");
let litter: Vec<_> = std::fs::read_dir(outside).unwrap().collect();
assert!(
litter.is_empty(),
"nothing lands outside the root: {litter:?}"
);
}
#[cfg(unix)]
#[test]
fn move_into_place_refuses_a_symlinked_ancestor_that_leaves_the_root() {
let (out, outside, landing) = escaping_root();
let staging = out.path().join(STAGING_DIR).join("blob");
std::fs::create_dir_all(staging.parent().unwrap()).unwrap();
std::fs::write(&staging, b"bytes").unwrap();
let err = landing
.move_into_place(&staging, &out.path().join("link/file"))
.unwrap_err();
assert_refused(&err, outside.path());
assert!(staging.exists(), "the fetched blob is kept for a rerun");
}
#[cfg(unix)]
#[test]
fn materialize_refuses_a_symlinked_ancestor_that_leaves_the_root() {
let (out, outside, landing) = escaping_root();
let staging = out.path().join("blob");
std::fs::write(&staging, b"bytes").unwrap();
let err = landing
.materialize(&staging, &out.path().join("link/file"))
.unwrap_err();
assert_refused(&err, outside.path());
assert!(staging.exists());
}
#[cfg(unix)]
#[test]
fn link_or_copy_atomic_refuses_a_symlinked_ancestor_that_leaves_the_root() {
let (out, outside, landing) = escaping_root();
let src = out.path().join("src.bin");
std::fs::write(&src, b"bytes").unwrap();
let err = landing
.link_or_copy_atomic(&src, &out.path().join("link/file"))
.unwrap_err();
assert_refused(&err, outside.path());
}
#[cfg(unix)]
#[test]
fn a_relative_symlink_inside_the_root_still_lands() {
let out = tempfile::tempdir().unwrap();
std::fs::create_dir(out.path().join("real")).unwrap();
std::os::unix::fs::symlink("real", out.path().join("alias")).unwrap();
let landing = Landing::open(out.path()).unwrap();
let staging = out.path().join("blob");
std::fs::write(&staging, b"bytes").unwrap();
let n = landing
.first_write(&staging, &out.path().join("alias/file"), false)
.unwrap();
assert_eq!(n, 5);
assert_eq!(
std::fs::read(out.path().join("real/file")).unwrap(),
b"bytes"
);
}
#[cfg(unix)]
#[test]
fn an_absolute_symlink_inside_the_root_is_an_escape() {
let out = tempfile::tempdir().unwrap();
std::fs::create_dir(out.path().join("real")).unwrap();
std::os::unix::fs::symlink(out.path().join("real"), out.path().join("alias")).unwrap();
let landing = Landing::open(out.path()).unwrap();
let staging = out.path().join("blob");
std::fs::write(&staging, b"bytes").unwrap();
let err = landing
.first_write(&staging, &out.path().join("alias/file"), false)
.unwrap_err();
assert!(
format!("{err:#}").contains("outside the output directory"),
"{err:#}"
);
assert!(!out.path().join("real/file").exists());
}
#[cfg(unix)]
#[test]
fn a_symlinked_staging_dir_lands_through_the_copy_fallback() {
let out = tempfile::tempdir().unwrap();
let elsewhere = tempfile::tempdir().unwrap();
std::os::unix::fs::symlink(elsewhere.path(), out.path().join(STAGING_DIR)).unwrap();
let landing = Landing::open(out.path()).unwrap();
let staging = out.path().join(STAGING_DIR).join("blob");
std::fs::write(&staging, b"bytes").unwrap();
let dest = out.path().join("file");
let n = landing.move_into_place(&staging, &dest).unwrap();
assert_eq!(n, 5);
assert_eq!(std::fs::read(&dest).unwrap(), b"bytes");
assert!(staging.exists(), "copied, not moved");
}
#[cfg(unix)]
#[test]
fn check_refuses_an_escaping_parent_and_accepts_a_missing_one() {
let (out, _outside, landing) = escaping_root();
let err = landing.check(&out.path().join("link/file")).unwrap_err();
assert!(
format!("{err:#}").contains("outside the output directory"),
"{err:#}"
);
landing.check(&out.path().join("new/deep/file")).unwrap();
landing.check(&out.path().join("top")).unwrap();
assert!(
!out.path().join("new").exists(),
"the check creates nothing"
);
}
#[test]
fn open_refuses_a_path_that_is_a_file() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("out");
std::fs::write(&file, b"x").unwrap();
let err = Landing::open(&file).unwrap_err();
assert!(format!("{err:#}").contains("output directory"), "{err:#}");
}
#[test]
fn check_refuses_a_parent_that_is_a_file() {
let out = tempfile::tempdir().unwrap();
std::fs::write(out.path().join("a"), b"x").unwrap();
let landing = Landing::open(out.path()).unwrap();
let err = landing.check(&out.path().join("a/b")).unwrap_err();
assert!(format!("{err:#}").contains("is not a directory"), "{err:#}");
}
#[cfg(unix)]
#[test]
fn a_directory_swapped_for_a_symlink_after_the_check_is_still_refused() {
let out = tempfile::tempdir().unwrap();
let outside = tempfile::tempdir().unwrap();
let landing = Landing::open(out.path()).unwrap();
let staging = out.path().join(STAGING_DIR).join("blob");
std::fs::create_dir_all(staging.parent().unwrap()).unwrap();
std::fs::write(&staging, b"bytes").unwrap();
let src = out.path().join("src.bin");
std::fs::write(&src, b"bytes").unwrap();
let dest = out.path().join("link/file");
std::fs::create_dir(out.path().join("link")).unwrap();
landing.check(&dest).unwrap();
std::fs::remove_dir(out.path().join("link")).unwrap();
std::os::unix::fs::symlink(outside.path(), out.path().join("link")).unwrap();
let moved = landing.move_into_place(&staging, &dest).unwrap_err();
assert_refused(&moved, outside.path());
let copied = landing.materialize(&staging, &dest).unwrap_err();
assert_refused(&copied, outside.path());
let linked = landing.link_or_copy_atomic(&src, &dest).unwrap_err();
assert_refused(&linked, outside.path());
assert!(staging.exists(), "the fetched blob is kept for a rerun");
}
#[cfg(unix)]
#[test]
fn a_donor_under_a_symlinked_staging_dir_lands_through_the_copy_fallback() {
let out = tempfile::tempdir().unwrap();
let elsewhere = tempfile::tempdir().unwrap();
std::os::unix::fs::symlink(elsewhere.path(), out.path().join(STAGING_DIR)).unwrap();
let landing = Landing::open(out.path()).unwrap();
let staging = out.path().join(STAGING_DIR).join("blob");
std::fs::write(&staging, b"donor-bytes").unwrap();
let dest = out.path().join("sub/file");
let n = landing.first_write(&staging, &dest, true).unwrap();
assert_eq!(n, 11);
assert_eq!(std::fs::read(&dest).unwrap(), b"donor-bytes");
assert!(staging.exists(), "a donor's staging name survives");
let names: Vec<String> = std::fs::read_dir(out.path().join("sub"))
.unwrap()
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
.collect();
assert_eq!(names, ["file"]);
}
#[cfg(unix)]
#[test]
fn a_symlink_at_the_entry_itself_is_replaced_not_followed() {
let out = tempfile::tempdir().unwrap();
let outside = tempfile::tempdir().unwrap();
let landing = Landing::open(out.path()).unwrap();
let staging = out.path().join("blob");
std::fs::write(&staging, b"bytes").unwrap();
for name in ["copied", "moved", "linked"] {
std::os::unix::fs::symlink(outside.path().join(name), out.path().join(name)).unwrap();
}
landing
.materialize(&staging, &out.path().join("copied"))
.unwrap();
landing
.move_into_place(&staging, &out.path().join("moved"))
.unwrap();
landing
.link_or_copy_atomic(&out.path().join("copied"), &out.path().join("linked"))
.unwrap();
for name in ["copied", "moved", "linked"] {
let landed = out.path().join(name);
assert!(
!std::fs::symlink_metadata(&landed).unwrap().is_symlink(),
"{name}"
);
assert_eq!(std::fs::read(&landed).unwrap(), b"bytes", "{name}");
}
assert!(std::fs::read_dir(outside.path()).unwrap().next().is_none());
}
#[test]
fn open_treats_an_empty_root_as_the_current_directory() {
let landing = Landing::open(Path::new("")).unwrap();
assert_eq!(landing.root(), Path::new("."));
}