use super::{FailureKind, SaveFailure, copy_located, locate};
use crate::test_support::{fixture, remove};
#[test]
fn a_present_file_is_located_and_copied() {
let base = fixture("source-present");
std::fs::write(base.join("a.rs"), b"half \xff resolved\n").expect("a");
let located = locate(base.as_path(), b"a.rs").expect("lookup").expect("present");
copy_located(&located, base.join("copy").as_path(), &mut || {}).expect("copy");
assert_eq!(std::fs::read(base.join("copy")).expect("copy"), b"half \xff resolved\n");
assert!(locate(base.as_path(), b"gone.rs").expect("lookup").is_none());
remove(base.as_path());
}
#[test]
fn an_ancestor_that_is_a_link_or_a_file_is_a_failure_naming_it() {
let base = fixture("source-ancestors");
std::fs::create_dir_all(base.join("elsewhere")).expect("elsewhere");
std::fs::write(base.join("elsewhere/file"), b"reached through the link\n").expect("file");
std::os::unix::fs::symlink("elsewhere", base.join("linked")).expect("link");
std::os::unix::fs::symlink("nowhere", base.join("dangling")).expect("dangling link");
std::fs::write(base.join("plain"), b"a file\n").expect("plain file");
std::fs::create_dir_all(base.join("real/inner")).expect("real directories");
std::os::unix::fs::symlink("../elsewhere", base.join("real/inner/deep")).expect("deep link");
for (relative, subject) in [
("linked/file", "linked"),
("dangling/file", "dangling"),
("plain/file", "plain"),
("real/inner/deep/file", "real/inner/deep"),
] {
let failure: SaveFailure = locate(base.as_path(), relative.as_bytes()).expect_err(relative);
assert_eq!(failure.subject, subject, "{relative}: {failure:?}");
assert_eq!(failure.kind, FailureKind::Ancestor, "{relative}: {failure:?}");
assert!(!failure.reason.is_empty(), "{relative}: {failure:?}");
}
assert!(locate(base.as_path(), b"absent/file").expect("a missing ancestor").is_none());
assert!(locate(base.as_path(), b"real/absent").expect("a missing name").is_none());
remove(base.as_path());
}
fn copy_while_changing(base: &std::path::Path, path: &str, change: &dyn Fn()) -> Result<(), SaveFailure> {
let located = locate(base, path.as_bytes()).expect("lookup").expect("present");
let mut changed: bool = false;
return copy_located(&located, base.join("copy").as_path(), &mut || {
if !changed {
changed = true;
change();
}
});
}
fn large_source(base: &std::path::Path) -> Vec<u8> {
let content: Vec<u8> = (0..300_000_u32).map(|each| { return u8::try_from(each % 251).expect("fits"); }).collect();
std::fs::write(base.join("a.rs"), content.as_slice()).expect("a");
let hour_ago: std::time::SystemTime = std::time::SystemTime::now() - std::time::Duration::from_secs(3600);
std::fs::File::options().write(true).open(base.join("a.rs")).expect("open").set_modified(hour_ago).expect("old time");
return content;
}
#[test]
fn a_file_that_holds_still_is_copied_whole() {
let base = fixture("source-still");
let content: Vec<u8> = large_source(base.as_path());
copy_while_changing(base.as_path(), "a.rs", &|| {}).expect("copy");
assert_eq!(std::fs::read(base.join("copy")).expect("copy"), content);
remove(base.as_path());
}
#[test]
fn a_source_that_changes_during_the_copy_is_a_failure_and_leaves_no_copy() {
type Change = Box<dyn Fn(&std::path::Path)>;
let changes: Vec<(&str, Change)> = vec![
(
"an atomic save over the path",
Box::new(|base: &std::path::Path| {
std::fs::write(base.join("a.rs.new"), b"the newer version\n").expect("new version");
std::fs::rename(base.join("a.rs.new"), base.join("a.rs")).expect("rename over");
}),
),
(
"an append in place",
Box::new(|base: &std::path::Path| {
let mut file = std::fs::OpenOptions::new().append(true).open(base.join("a.rs")).expect("open");
std::io::Write::write_all(&mut file, b"more\n").expect("append");
}),
),
(
"a truncation in place",
Box::new(|base: &std::path::Path| {
std::fs::File::options().write(true).open(base.join("a.rs")).expect("open").set_len(10).expect("truncate");
}),
),
(
"a rewrite in place that keeps the size",
Box::new(|base: &std::path::Path| {
let mut file = std::fs::OpenOptions::new().write(true).open(base.join("a.rs")).expect("open");
std::io::Write::write_all(&mut file, b"same size, other bytes").expect("rewrite");
}),
),
(
"the path now names a link",
Box::new(|base: &std::path::Path| {
std::fs::remove_file(base.join("a.rs")).expect("remove");
std::os::unix::fs::symlink("elsewhere", base.join("a.rs")).expect("link");
}),
),
];
for (round, (label, change)) in changes.into_iter().enumerate() {
let base = fixture(format!("source-changes-{round}").as_str());
large_source(base.as_path());
let error = copy_while_changing(base.as_path(), "a.rs", &|| { change(base.as_path()); }).expect_err(label);
assert!(!error.reason.is_empty() && error.kind == FailureKind::Changed, "{label}: {error:?}");
assert!(!base.join("copy").exists(), "{label}: no cut-short or stale copy stays");
remove(base.as_path());
}
}