use super::{World, write_exec};
use crate::cli_outbound::Cli;
use crate::start::{DETACHED_EXIT, execute_prompt, identity_preamble};
use crate::test_support::spawn_guard;
use std::path::Path;
fn make_fifo(path: &Path) {
let status = std::process::Command::new("mkfifo")
.args(["-m", "600"])
.arg(path)
.status()
.expect("spawn mkfifo");
assert!(status.success(), "mkfifo");
}
#[test]
fn prompt_stamps_identity_layers_yog_name_and_logs_the_sentinel() {
let _g = spawn_guard();
let w = World::new();
let fifo = w.bin.path().join("report");
make_fifo(&fifo);
let body = format!(
"#!/bin/sh\nprintf '%s\\037%s\\037%s\\037%s' \"$1\" \"$2\" \"$3\" \"$YOG_NAME\" > '{}'\n",
fifo.display()
);
let lernie = Cli::new(write_exec(w.bin.path(), "lernie", &body));
let ws = w.yog.path().join("ws");
let cwd = w.balls.path(); execute_prompt(
&lernie,
w.state.path(),
"TS",
"cobalt-gecko",
cwd,
&ws,
"do it",
)
.unwrap();
let recorded = std::fs::read_to_string(&fifo).unwrap();
let fields: Vec<&str> = recorded.split('\u{1f}').collect();
assert_eq!(fields[0], "prompt");
assert_eq!(fields[1], ws.to_string_lossy());
assert_eq!(
fields[2],
format!("{}\n\ndo it", identity_preamble("cobalt-gecko"))
);
assert_eq!(fields[3], "cobalt-gecko", "YOG_NAME layered (§8)");
let e = &w.ops()[0];
assert_eq!(e.argv[1], "prompt");
assert_eq!(e.exit, DETACHED_EXIT);
assert_eq!(e.cwd, cwd.display().to_string());
assert!(e.stderr.is_empty(), "a clean launch logs no error");
assert!(e.argv[3].contains("You are cobalt-gecko."));
}
#[test]
fn prompt_routes_the_drivers_stderr_to_its_per_spawn_sink() {
let _g = spawn_guard();
let w = World::new();
let fifo = w.bin.path().join("report");
make_fifo(&fifo);
let body = format!(
"#!/bin/sh\nprintf 'refusing: version skew\\n' >&2\nprintf done > '{}'\n",
fifo.display()
);
let lernie = Cli::new(write_exec(w.bin.path(), "lernie", &body));
let ws = w.yog.path().join("ws");
execute_prompt(&lernie, w.state.path(), "TS", "n", w.balls.path(), &ws, "g").unwrap();
assert_eq!(std::fs::read_to_string(&fifo).unwrap(), "done");
let e = &w.ops()[0];
assert!(
e.stderr.is_empty(),
"the *logged* line records only the launch"
);
let folded = crate::opslog::detached::fold(w.state.path(), e);
assert_eq!(folded.stderr, "refusing: version skew\n");
assert!(crate::opslog::OpRow::from(&folded).failed());
}
#[test]
fn prompt_clips_a_large_logged_goal() {
let _g = spawn_guard();
let w = World::new();
let lernie = Cli::new(super::fake_lernie(w.bin.path()));
let ws = w.yog.path().join("ws");
let big = "\u{1}".repeat(9000);
execute_prompt(
&lernie,
w.state.path(),
"TS",
"n",
w.balls.path(),
&ws,
&big,
)
.unwrap();
let entry = &w.ops()[0];
assert!(
entry.argv[3].contains("bytes elided"),
"the logged goal is clipped (§4.2)"
);
assert!(
crate::opslog::build_line(entry).len() <= crate::opslog::CAP,
"the serialized ops line stays ≤ CAP after JSON escaping"
);
}
#[test]
fn prompt_logs_the_spawn_failure_and_returns_err() {
let _g = spawn_guard();
let w = World::new();
let lernie = Cli::new("/definitely/not/a/real/lernie-prompt");
let ws = w.yog.path().join("ws");
let err = execute_prompt(&lernie, w.state.path(), "TS", "n", w.balls.path(), &ws, "g").is_err();
assert!(err, "a missing binary is surfaced");
let e = &w.ops()[0];
assert_eq!(e.exit, DETACHED_EXIT);
assert!(!e.stderr.is_empty(), "the spawn error rides in stderr");
}