use super::*;
use crate::test_support::{fixture, remove};
use std::path::PathBuf;
#[cfg(unix)]
#[test]
fn a_refused_link_falls_back_to_the_rename() {
let root: PathBuf = fixture("publish-link-refused");
let staged: PathBuf = root.join("staged");
let path: PathBuf = root.join("published");
std::fs::create_dir(&staged).expect("staged directory");
std::fs::write(staged.join("inner"), b"whole").expect("staged content");
let probe: PathBuf = root.join("probe");
let refusal: std::io::Error = std::fs::hard_link(&staged, &probe).expect_err("the control: a directory cannot be linked");
assert_ne!(refusal.kind(), std::io::ErrorKind::AlreadyExists, "the control: the refusal is not a taken name");
assert!(!probe.exists(), "the control: the refused link created nothing");
publish_exclusive(staged.as_path(), path.as_path()).expect("a refused link is published by rename");
assert_eq!(std::fs::read(path.join("inner")).expect("published content"), b"whole");
assert!(!staged.exists(), "the rename moved the staged entry");
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn a_taken_name_is_refused_with_the_operating_systems_error() {
let root: PathBuf = fixture("publish-taken");
let staged: PathBuf = root.join("staged");
let path: PathBuf = root.join("record");
std::fs::write(&staged, b"new").expect("staged file");
std::fs::write(&path, b"old").expect("record");
let error: std::io::Error = publish_exclusive(staged.as_path(), path.as_path()).expect_err("the name is taken");
assert_eq!(error.kind(), std::io::ErrorKind::AlreadyExists);
assert_eq!(error.raw_os_error(), Some(libc::EEXIST), "the link's own refusal, not a rebuilt one");
assert_eq!(std::fs::read(&path).expect("record"), b"old");
assert_eq!(std::fs::read(&staged).expect("staged"), b"new");
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn a_link_that_fails_for_another_reason_still_checks_the_taken_name() {
let root: PathBuf = fixture("publish-staged-missing");
let staged: PathBuf = root.join("staged");
let path: PathBuf = root.join("record");
std::fs::write(&path, b"old").expect("record");
let control: std::io::Error = std::fs::hard_link(&staged, &path).expect_err("the control: the link fails");
assert_eq!(control.kind(), std::io::ErrorKind::NotFound, "the control: the source is checked before the name");
let error: std::io::Error = publish_exclusive(staged.as_path(), path.as_path()).expect_err("the name is taken");
assert_eq!(error.kind(), std::io::ErrorKind::AlreadyExists);
assert_eq!(std::fs::read(&path).expect("record"), b"old");
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn a_failed_directory_flush_stays_an_error() {
for code in [libc::EIO, libc::ENOSPC, libc::EDQUOT] {
let error: std::io::Error = settle_directory_sync(Path::new("/unused"), Err(std::io::Error::from_raw_os_error(code)))
.expect_err("a failed flush is not skipped");
assert_eq!(error.raw_os_error(), Some(code));
}
}
#[cfg(unix)]
#[test]
fn a_filesystem_without_directory_sync_counts_as_synced() {
for code in [libc::EINVAL, libc::ENOTSUP, libc::ENOSYS] {
settle_directory_sync(Path::new("/unused"), Err(std::io::Error::from_raw_os_error(code)))
.expect("an unsupported directory sync is skipped");
}
settle_directory_sync(Path::new("/unused"), Ok(())).expect("a sync that worked");
}
#[test]
fn only_a_real_removal_failure_is_reported() {
let staged: &Path = Path::new("/records/.record.partial");
assert_eq!(staging_removal_report(staged, Ok(())), None);
assert_eq!(staging_removal_report(staged, Err(std::io::Error::from(std::io::ErrorKind::NotFound))), None);
let report: String = staging_removal_report(staged, Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)))
.expect("a permission failure is reported");
assert!(report.contains("/records/.record.partial"), "the report names the staging file: {report}");
}