use super::{SeedError, ensure_seeded, seeded};
use crate::cli_outbound::Cli;
use crate::opslog::{self, OpEntry, Origin};
use crate::test_support::spawn_guard;
use crate::world::{Layout, layout_under};
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use tempfile::{TempDir, tempdir};
struct World {
bin: TempDir,
state: TempDir,
yog: TempDir,
}
impl World {
fn new() -> Self {
Self {
bin: tempdir().unwrap(),
state: tempdir().unwrap(),
yog: tempdir().unwrap(),
}
}
fn layout(&self) -> Layout {
layout_under(self.yog.path())
}
fn seed_marker(&self) {
let lernie = self.layout().lernie;
fs::create_dir_all(&lernie).unwrap();
fs::write(lernie.join("models.yaml"), b"models: {}\n").unwrap();
}
fn ops(&self) -> Vec<OpEntry> {
opslog::tail(self.state.path(), 16)
}
}
fn fake_lernie(dir: &Path, body: &str) -> std::path::PathBuf {
let path = dir.join("lernie");
fs::write(&path, body).unwrap();
let mut perms = fs::metadata(&path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&path, perms).unwrap();
path
}
#[test]
fn seeded_is_true_iff_models_yaml_is_present() {
let w = World::new();
assert!(!seeded(&w.layout()), "a fresh world has no models.yaml");
w.seed_marker();
assert!(seeded(&w.layout()), "the founded marker reads as seeded");
}
#[test]
fn ensure_seeded_skips_a_seeded_world() {
let w = World::new();
w.seed_marker();
let lernie = Cli::new("/definitely/not/a/real/lernie");
let primed = ensure_seeded(
&lernie,
w.state.path(),
"TS",
&w.layout(),
Origin::Conversation,
)
.unwrap();
assert!(!primed, "a seeded world is skipped");
assert!(w.ops().is_empty(), "the skip runs and logs nothing");
}
#[test]
fn ensure_seeded_primes_with_lernie_home_and_logs() {
let _g = spawn_guard();
let w = World::new();
let report = w.bin.path().join("home");
let body = format!(
"#!/bin/sh\nprintf '%s' \"$LERNIE_HOME\" > '{}'\n",
report.display()
);
let layout = w.layout();
let lernie = Cli::new(fake_lernie(w.bin.path(), &body)).with_env(vec![(
"LERNIE_HOME".to_owned(),
layout.lernie.to_string_lossy().into_owned(),
)]);
let primed =
ensure_seeded(&lernie, w.state.path(), "TS", &layout, Origin::Conversation).unwrap();
assert!(primed, "an unseeded world is primed");
let got = fs::read_to_string(&report).unwrap();
assert_eq!(Path::new(&got), layout.lernie);
let e = w.ops();
assert_eq!(e.len(), 1);
assert_eq!(&e[0].argv[1..], &["prime"]);
assert_eq!(e[0].exit, 0);
assert_eq!(e[0].cwd, "");
assert!(e[0].stderr.is_empty(), "a clean prime logs no error");
}
#[test]
fn ensure_seeded_errors_on_a_nonzero_prime() {
let _g = spawn_guard();
let w = World::new();
let body = "#!/bin/sh\nprintf '%s\\n' 'prime boom' 1>&2\nexit 3\n";
let lernie = Cli::new(fake_lernie(w.bin.path(), body));
let err = ensure_seeded(
&lernie,
w.state.path(),
"TS",
&w.layout(),
Origin::Conversation,
)
.unwrap_err();
let SeedError::Prime(out) = err else {
panic!("expected Prime, got {err:?}");
};
assert_eq!(out.exit, 3);
assert!(out.stderr.contains("prime boom"));
assert_eq!(w.ops().len(), 1);
assert!(SeedError::Prime(out).to_string().contains("exit 3"));
}
#[test]
fn ensure_seeded_surfaces_a_spawn_failure() {
let _g = spawn_guard();
let w = World::new();
let lernie = Cli::new("/definitely/not/a/real/lernie");
let err = ensure_seeded(
&lernie,
w.state.path(),
"TS",
&w.layout(),
Origin::Conversation,
)
.unwrap_err();
assert!(matches!(err, SeedError::Io(_)), "a missing binary is Io");
assert!(!err.to_string().is_empty(), "the Io error renders");
let ops = w.ops();
assert_eq!(ops.len(), 1, "a spawn failure appends a synthetic ops line");
assert_eq!(ops[0].exit, crate::opslog::SYNTHETIC_EXIT);
assert_eq!(ops[0].argv[0], "/definitely/not/a/real/lernie");
assert!(!ops[0].stderr.is_empty());
}