use crate::support::{Fixture, TestResult};
use serde_json::{Value, json};
use std::fs;
use std::os::unix::fs::PermissionsExt;
fn backups(fixture: &Fixture) -> Result<Vec<std::path::PathBuf>, Box<dyn std::error::Error>> {
let mut result = Vec::new();
for entry in fs::read_dir(fixture.root())? {
let entry = entry?;
if entry
.file_name()
.to_string_lossy()
.starts_with("door.json.before-give-up-after-")
{
result.push(entry.path());
}
}
Ok(result)
}
#[test]
fn r5_missing_count_is_upgraded_backed_up_and_logged_before_start() -> TestResult {
let mut fixture = Fixture::new()?;
let mut child = fixture.child(
"old-child",
"import sys\nprint('fixture ready old', file=sys.stderr, flush=True)\nsys.stdin.read()",
);
child
.as_object_mut()
.ok_or("child is not an object")?
.remove("give_up_after");
fixture.config["children"] = json!([child]);
let mut door = fixture.start()?;
let upgraded = door.until("door_config_upgraded: give_up_after=3 written for old-child;")?;
door.until("haematite door: listening on ")?;
let old = backups(&fixture)?;
assert_eq!(old.len(), 1);
assert_eq!(fs::read(&old[0])?, serde_json::to_vec(&fixture.config)?);
assert_eq!(fs::metadata(&old[0])?.permissions().mode() & 0o777, 0o600);
assert!(upgraded.contains(&old[0].display().to_string()));
let mut expected = fixture.config.clone();
expected["children"][0]["give_up_after"] = json!(3);
let actual: Value = serde_json::from_slice(&fs::read(fixture.root().join("door.json"))?)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn r5_no_children_and_explicit_counts_preserve_every_byte() -> TestResult {
for children in [false, true] {
let mut fixture = Fixture::new()?;
if children {
fixture.config["children"] = json!([fixture.child("explicit", "import sys\nprint('fixture ready explicit', file=sys.stderr, flush=True)\nsys.stdin.read()")]);
}
let mut door = fixture.start()?;
door.until("haematite door: listening on ")?;
assert_eq!(
fs::read(fixture.root().join("door.json"))?,
serde_json::to_vec(&fixture.config)?
);
assert!(backups(&fixture)?.is_empty());
assert!(
!door
.lines
.iter()
.any(|line| line.contains("door_config_upgraded"))
);
}
Ok(())
}
#[test]
fn r5_unwritable_upgrade_is_named_before_any_child_starts() -> TestResult {
let mut fixture = Fixture::new()?;
let mut child = fixture.child("old-child", "import sys\nwith open(sys.argv[1], 'a') as f: f.write('started\\n')\nprint('fixture ready', file=sys.stderr, flush=True)\nsys.stdin.read()");
child
.as_object_mut()
.ok_or("child is not an object")?
.remove("give_up_after");
fixture.config["children"] = json!([child]);
let mut door = fixture.start_readonly()?;
let failure = door.until("door_config_upgrade_failed:")?;
assert!(failure.contains("door.json"), "{failure}");
assert!(!fixture.root().join("records").exists());
assert_eq!(
fs::read(fixture.root().join("door.json"))?,
serde_json::to_vec(&fixture.config)?
);
assert!(backups(&fixture)?.is_empty());
Ok(())
}