use std::os::unix::fs::PermissionsExt as _;
use shep_core::config::DaemonConfig;
use super::*;
fn mode_of(path: &Path) -> u32 {
std::fs::metadata(path).unwrap().permissions().mode() & 0o777
}
const PRESERVATION_SEED: &str =
"# the shepherd's own knobs\n[daemon]\nlog_level = \"info\" # chatty\nlog_json = false\n";
#[track_caller]
fn assert_preserves_comments_and_key_order(written: &str) {
for line in PRESERVATION_SEED.lines() {
if let Some(hash) = line.find('#') {
let comment = &line[hash..];
assert!(written.contains(comment), "{comment} gone: {written}");
}
}
assert!(
written
.find("log_level")
.zip(written.find("log_json"))
.is_some_and(|(level, json)| level < json),
"log_level must still precede log_json: {written}"
);
}
#[test]
fn a_file_that_will_not_parse_is_refused_rather_than_replaced() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
std::fs::write(&path, "[daemon\nlog_json = true\n").unwrap();
assert!(matches!(
ShepToml::try_edit(&path, |doc| doc.enable_dog("metrics")),
Err(ShepTomlError::Parse { .. })
));
assert_eq!(
std::fs::read_to_string(&path).unwrap(),
"[daemon\nlog_json = true\n"
);
}
#[test]
fn a_missing_file_opens_empty_and_edit_creates_it() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("nested").join("shep.toml");
ShepToml::try_edit(&path, |doc| doc.enable_dog("metrics")).unwrap();
assert!(path.exists());
}
#[test]
fn a_missing_file_reads_as_an_empty_document_and_creates_nothing() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
let cfg = ShepToml::read_only(&path).unwrap();
assert_eq!(cfg.daemon_log_level(), None);
assert!(!path.exists(), "a read must never create the file");
}
#[test]
fn a_first_edit_creates_the_home_and_the_file_owner_only() {
let dir = tempfile::tempdir().unwrap();
let home = dir.path().join("cold");
let path = home.join("shep.toml");
ShepToml::try_edit(&path, |doc| doc.enable_dog("bark")).unwrap();
assert_eq!(
mode_of(&home),
0o700,
"$SHEP_HOME is readable by other local users until the first boot"
);
assert_eq!(
mode_of(&path),
0o600,
"the file a webhook token goes in, and the mode a `tar` of it keeps"
);
}
#[test]
fn editing_a_world_readable_config_leaves_it_owner_only() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
std::fs::write(&path, "[daemon]\n").unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
ShepToml::try_edit(&path, |doc| doc.enable_dog("bark")).unwrap();
assert_eq!(mode_of(&path), 0o600);
}
#[test]
fn parse_error_debug_never_prints_the_document() {
let path = PathBuf::from("/home/ada/.shep/shep.toml");
let secret = "https://hooks.example.com/services/T00/B00/super-secret-token";
let broken = format!("[dog.bark]\nwebhook = \"{secret}\"\n[daemon\n");
let source = broken.parse::<DocumentMut>().unwrap_err();
let err = ShepTomlError::Parse { path, source };
let debug = format!("{err:?}");
assert!(
!debug.contains(secret),
"the document must never reach Debug: {debug}"
);
assert!(!debug.contains("webhook"), "{debug}");
assert!(!debug.contains("hooks.example.com"), "{debug}");
assert_eq!(
debug,
"Parse { path: \"/home/ada/.shep/shep.toml\", message: \"invalid table header\\n\
expected `.`, `]`\" }"
);
let display = err.to_string();
assert!(display.contains("invalid table header"));
}
const CHILD_PATH_VAR: &str = "SHEP_CONFIG_RACE_PATH";
const CHILD_TAG_VAR: &str = "SHEP_CONFIG_RACE_TAG";
const EDITS_PER_WRITER: usize = 100;
const ADOPTING_TAG: &str = "alpha";
#[test]
#[ignore = "child process of two_writer_processes_do_not_lose_each_other_s_edits"]
fn config_race_child() {
let Ok(path) = std::env::var(CHILD_PATH_VAR) else {
panic!("{CHILD_PATH_VAR} unset — this test is only run as a child process");
};
let tag = std::env::var(CHILD_TAG_VAR).expect("child needs a tag");
let path = PathBuf::from(path);
for i in 0..EDITS_PER_WRITER {
let name = format!("{tag}-{i}");
ShepToml::edit(&path, |doc| {
if tag == ADOPTING_TAG {
doc.adopt_dog(&name, Path::new("/usr/local/bin/shep-otel"), false)
.unwrap();
} else {
doc.enable_dog(&name).unwrap();
}
})
.expect("child edit");
}
}
#[test]
fn two_writer_processes_do_not_lose_each_other_s_edits() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
let exe = std::env::current_exe().expect("test binary path");
let children: Vec<_> = [ADOPTING_TAG, "beta"]
.iter()
.map(|tag| {
std::process::Command::new(&exe)
.args([
"--exact",
"--ignored",
"commands::shep_toml::tests::config_race_child",
])
.env(CHILD_PATH_VAR, &path)
.env(CHILD_TAG_VAR, tag)
.stdout(std::process::Stdio::piped())
.spawn()
.expect("spawn writer")
})
.collect();
for child in children {
let out = child.wait_with_output().expect("wait for writer");
assert!(
out.status.success(),
"a writer process failed: {}\n{}",
out.status,
String::from_utf8_lossy(&out.stdout)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("running 1 test"),
"the child ran no test, so the `--exact` path is stale: {stdout}"
);
}
let written = std::fs::read_to_string(&path).unwrap();
let cfg = DaemonConfig::load(Some(&written), &|_| None).unwrap();
for i in 0..EDITS_PER_WRITER {
let adopted = format!("{ADOPTING_TAG}-{i}");
let enabled = format!("beta-{i}");
assert!(
cfg.daemon.adopted_dogs.contains_key(&adopted),
"{adopted}: an adopt was overwritten by the other writer"
);
assert!(
cfg.daemon.enabled_dogs.contains(&adopted),
"{adopted}: the adopt's own enable was overwritten"
);
assert!(
cfg.daemon.enabled_dogs.contains(&enabled),
"{enabled}: an enable was overwritten by the other writer"
);
}
assert_eq!(
cfg.daemon.enabled_dogs.len(),
2 * EDITS_PER_WRITER,
"the config enables dogs nobody asked for"
);
}
mod dogs;
mod grants;
mod grants_record;
mod grants_write;
mod hosts;
mod scalars;
mod style;