use camino::Utf8PathBuf;
use cap_std::{ambient_authority, fs_utf8::Dir};
use rstest::fixture;
use tempfile::{TempDir, tempdir};
use super::*;
struct ConditionalFixture {
_temporary: TempDir,
directory: Dir,
target: Utf8PathBuf,
}
#[test_macros::allow_fixture_expansion_lints]
#[fixture]
fn conditional_fixture() -> ConditionalFixture {
let temporary = tempdir().expect("create temporary directory");
let root =
camino::Utf8Path::from_path(temporary.path()).expect("the temporary directory is UTF-8");
let directory =
Dir::open_ambient_dir(root, ambient_authority()).expect("open the directory capability");
ConditionalFixture {
_temporary: temporary,
directory,
target: Utf8PathBuf::from("sample.md"),
}
}
fn entry_names(directory: &Dir) -> Vec<String> {
let mut names: Vec<String> = directory
.read_dir(".")
.expect("read the fixture directory")
.map(|entry| {
entry
.expect("read fixture entry")
.file_name()
.expect("fixture entry has a UTF-8 name")
.clone()
})
.collect();
names.sort();
names
}
#[rstest::rstest]
fn conditional_replacement_replaces_a_matching_target(conditional_fixture: ConditionalFixture) {
let original = "|A|B|\n|1|2|";
let formatted = "| A | B |\n| 1 | 2 |\n";
conditional_fixture
.directory
.write(&conditional_fixture.target, original)
.expect("write fixture");
let replaced = replace_file_if_unchanged(
&conditional_fixture.directory,
&conditional_fixture.target,
original,
formatted,
)
.expect("replace the target that still matches");
assert!(replaced, "a target holding what was read is replaced");
assert_eq!(
conditional_fixture
.directory
.read_to_string(&conditional_fixture.target)
.expect("read target"),
formatted
);
assert_eq!(entry_names(&conditional_fixture.directory), ["sample.md"]);
}
#[rstest::rstest]
fn conditional_replacement_declines_a_target_that_moved_on(
conditional_fixture: ConditionalFixture,
) {
let read = "|A|B|\n|1|2|";
let moved_on = "|X|Y|\n|3|4|";
conditional_fixture
.directory
.write(&conditional_fixture.target, moved_on)
.expect("write the other writer's version");
let replaced = replace_file_if_unchanged(
&conditional_fixture.directory,
&conditional_fixture.target,
read,
"| A | B |\n| 1 | 2 |\n",
)
.expect("a declined replacement is not an error");
assert!(!replaced, "the target no longer holds the text read");
assert_eq!(
conditional_fixture
.directory
.read_to_string(&conditional_fixture.target)
.expect("read target"),
moved_on,
"the other writer's text must survive untouched"
);
assert_eq!(
entry_names(&conditional_fixture.directory),
["sample.md"],
"a declined replacement must remove the temporary file it wrote"
);
}
#[rstest::rstest]
fn conditional_replacement_declines_a_writer_that_lands_inside_the_swap(
conditional_fixture: ConditionalFixture,
) {
let read = "|A|B|\n|1|2|";
let intruder = "|X|Y|\n|3|4|";
conditional_fixture
.directory
.write(&conditional_fixture.target, read)
.expect("write fixture");
let _armed = competing_writer_seam::arm(move |directory, path| {
directory
.write(path, intruder)
.expect("the arriving write lands in the swap's window");
});
let replaced = replace_file_if_unchanged(
&conditional_fixture.directory,
&conditional_fixture.target,
read,
"| A | B |\n| 1 | 2 |\n",
)
.expect("a declined replacement is not an error");
assert!(
!replaced,
"a swap another writer reached first replaces nothing"
);
assert_eq!(
conditional_fixture
.directory
.read_to_string(&conditional_fixture.target)
.expect("read target"),
intruder,
"the other writer's text must survive the declined swap"
);
assert_eq!(
entry_names(&conditional_fixture.directory),
["sample.md"],
"a declined replacement must remove the temporary file it wrote"
);
}
#[rstest::rstest]
fn conditional_replacement_reports_a_failed_cleanup(conditional_fixture: ConditionalFixture) {
let expected = "|A|B|\n|1|2|";
let moved_on = "|X|Y|\n|3|4|";
conditional_fixture
.directory
.write(&conditional_fixture.target, moved_on)
.expect("write the other writer's version");
let _cleanup = cleanup_failure_seam::arm();
let error = replace_file_if_unchanged(
&conditional_fixture.directory,
&conditional_fixture.target,
expected,
"| A | B |\n| 1 | 2 |\n",
)
.expect_err("a failed cleanup must be reported");
assert!(error.to_string().contains("cleanup failure seam"));
assert_eq!(
conditional_fixture
.directory
.read_to_string(&conditional_fixture.target)
.expect("read target"),
moved_on
);
}
#[rstest::rstest]
fn conditional_replacement_reports_a_target_it_cannot_read_back(
conditional_fixture: ConditionalFixture,
) {
conditional_fixture
.directory
.write(&conditional_fixture.target, "|A|B|\n|1|2|")
.expect("write fixture");
conditional_fixture
.directory
.remove_file(&conditional_fixture.target)
.expect("remove the target before the comparison");
let error = replace_file_if_unchanged(
&conditional_fixture.directory,
&conditional_fixture.target,
"|A|B|\n|1|2|",
"| A | B |\n| 1 | 2 |\n",
)
.expect_err("a target that cannot be read back cannot be replaced");
assert_eq!(error.kind(), std::io::ErrorKind::NotFound);
assert_eq!(
entry_names(&conditional_fixture.directory),
Vec::<String>::new(),
"the temporary file must not survive the error"
);
}