use super::*;
use tempfile::TempDir;
fn record(dir: &Path, current: &str) -> VersionTransition {
let pending = begin(dir, current);
let transition = pending.transition().clone();
pending.commit();
transition
}
#[test]
fn first_sighting_is_fresh_and_records_a_baseline() {
let dir = TempDir::new().expect("tempdir");
assert_eq!(record(dir.path(), "0.77.0"), VersionTransition::Fresh);
assert!(dir.path().join(VERSION_FILE).exists());
}
#[test]
fn an_unchanged_version_reports_unchanged() {
let dir = TempDir::new().expect("tempdir");
record(dir.path(), "0.77.0");
assert_eq!(record(dir.path(), "0.77.0"), VersionTransition::Unchanged);
}
#[test]
fn an_upgrade_reports_the_version_it_came_from() {
let dir = TempDir::new().expect("tempdir");
record(dir.path(), "0.76.2");
assert_eq!(
record(dir.path(), "0.77.0"),
VersionTransition::Changed {
from: "0.76.2".to_owned()
}
);
}
#[test]
fn an_upgrade_is_reported_once_not_on_every_later_run() {
let dir = TempDir::new().expect("tempdir");
record(dir.path(), "0.76.2");
record(dir.path(), "0.77.0");
assert_eq!(record(dir.path(), "0.77.0"), VersionTransition::Unchanged);
}
#[test]
fn a_downgrade_reports_like_any_other_change() {
let dir = TempDir::new().expect("tempdir");
record(dir.path(), "0.77.0");
assert_eq!(
record(dir.path(), "0.76.2"),
VersionTransition::Changed {
from: "0.77.0".to_owned()
}
);
}
#[test]
fn surrounding_whitespace_is_not_a_version_change() {
let dir = TempDir::new().expect("tempdir");
fs::write(dir.path().join(VERSION_FILE), " 0.77.0 \n").expect("seed");
assert_eq!(record(dir.path(), "0.77.0"), VersionTransition::Unchanged);
}
#[test]
fn an_empty_record_is_treated_as_no_record() {
let dir = TempDir::new().expect("tempdir");
fs::write(dir.path().join(VERSION_FILE), "\n").expect("seed");
assert_eq!(record(dir.path(), "0.77.0"), VersionTransition::Fresh);
}
#[test]
fn creates_the_state_directory_when_missing() {
let dir = TempDir::new().expect("tempdir");
let nested = dir.path().join("missing").join("state");
assert_eq!(record(&nested, "0.77.0"), VersionTransition::Fresh);
assert!(nested.join(VERSION_FILE).exists());
}
#[test]
fn an_unwritable_state_path_never_reports_a_change() {
let dir = TempDir::new().expect("tempdir");
let blocked = dir.path().join("blocked");
fs::write(&blocked, "not a directory").expect("seed");
assert_eq!(record(&blocked, "0.76.2"), VersionTransition::Fresh);
assert_eq!(record(&blocked, "0.77.0"), VersionTransition::Fresh);
}
#[cfg(unix)]
#[test]
fn an_unwritable_directory_never_reports_a_change() {
use std::os::unix::fs::PermissionsExt;
let dir = TempDir::new().expect("tempdir");
let locked = dir.path().join("locked");
fs::create_dir(&locked).expect("mkdir");
fs::set_permissions(&locked, fs::Permissions::from_mode(0o500)).expect("chmod");
if fs::write(locked.join("probe"), "").is_ok() {
let _ = fs::remove_file(locked.join("probe"));
let _ = fs::set_permissions(&locked, fs::Permissions::from_mode(0o700));
return;
}
assert_eq!(record(&locked, "0.76.2"), VersionTransition::Fresh);
assert_eq!(record(&locked, "0.77.0"), VersionTransition::Fresh);
let _ = fs::set_permissions(&locked, fs::Permissions::from_mode(0o700));
}
#[cfg(unix)]
#[test]
fn an_unreadable_record_is_not_overwritten() {
use std::os::unix::fs::PermissionsExt;
let dir = TempDir::new().expect("tempdir");
let record_path = dir.path().join(VERSION_FILE);
fs::write(&record_path, "0.76.2\n").expect("seed");
fs::set_permissions(&record_path, fs::Permissions::from_mode(0o000)).expect("chmod");
if fs::read_to_string(&record_path).is_ok() {
let _ = fs::set_permissions(&record_path, fs::Permissions::from_mode(0o600));
return;
}
let pending = begin(dir.path(), "0.77.0");
assert_eq!(pending.transition(), &VersionTransition::Fresh);
assert!(
!pending.commit(),
"an unreadable record must not be reported as a change"
);
let _ = fs::set_permissions(&record_path, fs::Permissions::from_mode(0o600));
assert_eq!(
fs::read_to_string(&record_path).expect("record survived"),
"0.76.2\n"
);
}
#[test]
fn a_successful_commit_reports_success() {
let dir = TempDir::new().expect("tempdir");
assert!(begin(dir.path(), "0.77.0").commit());
}
#[test]
fn committing_an_unchanged_version_is_not_a_transition() {
let dir = TempDir::new().expect("tempdir");
begin(dir.path(), "0.77.0").commit();
let pending = begin(dir.path(), "0.77.0");
assert_eq!(pending.transition(), &VersionTransition::Unchanged);
assert!(!pending.commit());
}
#[test]
fn a_failed_commit_does_not_report_and_leaves_the_old_record() {
let dir = TempDir::new().expect("tempdir");
assert!(begin(dir.path(), "0.76.2").commit());
fs::create_dir(dir.path().join(VERSION_TEMP_FILE)).expect("block the staged write");
let pending = begin(dir.path(), "0.77.0");
assert_eq!(
pending.transition(),
&VersionTransition::Changed {
from: "0.76.2".to_owned()
}
);
assert!(!pending.commit(), "a failed write must not report success");
let retry = begin(dir.path(), "0.77.0");
assert_eq!(
retry.transition(),
&VersionTransition::Changed {
from: "0.76.2".to_owned()
}
);
}
#[test]
fn only_one_holder_of_the_lock_sees_the_transition() {
let dir = TempDir::new().expect("tempdir");
assert!(begin(dir.path(), "0.76.2").commit());
let first = begin(dir.path(), "0.77.0");
let second = begin(dir.path(), "0.77.0");
assert_eq!(
second.transition(),
&VersionTransition::Fresh,
"a process that cannot take the lock must not see a transition",
);
assert!(!second.commit());
assert_eq!(
first.transition(),
&VersionTransition::Changed {
from: "0.76.2".to_owned()
}
);
assert!(first.commit());
}
#[test]
fn dropping_without_committing_releases_the_lock() {
let dir = TempDir::new().expect("tempdir");
assert!(begin(dir.path(), "0.76.2").commit());
drop(begin(dir.path(), "0.77.0"));
let next = begin(dir.path(), "0.77.0");
assert_eq!(
next.transition(),
&VersionTransition::Changed {
from: "0.76.2".to_owned()
}
);
}