#![cfg(all(feature = "embedded", unix))]
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
fn scratch(case: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("iki-sc-{}-{case}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(dir.join("config").join("ikigai")).expect("scratch config home");
std::fs::create_dir_all(dir.join("files")).expect("scratch workspace");
dir
}
fn ikigai(home: &Path, args: &[&str]) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_ikigai"));
command
.args(args)
.current_dir(home)
.env("HOME", home)
.env("XDG_CONFIG_HOME", home.join("config"))
.env("IKIGAI_FILES", home.join("files"))
.stdout(Stdio::piped())
.stderr(Stdio::piped());
command
}
fn piped(mut command: Command, input: &[u8]) -> Output {
let mut child = command.stdin(Stdio::piped()).spawn().expect("spawn ikigai");
let mut stdin = child.stdin.take().expect("piped stdin");
stdin.write_all(input).expect("write stdin");
drop(stdin);
child.wait_with_output().expect("reap ikigai")
}
fn stdout(output: &Output) -> String {
String::from_utf8_lossy(&output.stdout).into_owned()
}
const BATCH: [&str; 4] = [
"-c",
"source urn:kernel:scheduler",
"-c",
"source urn:kernel:cache",
];
fn entries(listing: &str) -> &str {
listing
.lines()
.find(|line| line.trim_start().starts_with("entries"))
.unwrap_or_else(|| panic!("no `entries` line in:\n{listing}"))
.trim()
}
#[test]
fn a_batch_on_an_empty_stdin_caches_what_it_asked_for_and_nothing_else() {
let home = scratch("empty");
let null = ikigai(&home, &BATCH)
.stdin(Stdio::null())
.output()
.expect("run ikigai");
let pipe = piped(ikigai(&home, &BATCH), b"");
let file = home.join("empty.txt");
std::fs::write(&file, b"").expect("empty file");
let from_file = ikigai(&home, &BATCH)
.stdin(std::fs::File::open(&file).expect("open"))
.output()
.expect("run ikigai");
for (case, output) in [
("/dev/null", &null),
("closed empty pipe", &pipe),
("empty file", &from_file),
] {
assert!(output.status.success(), "{case}: {output:?}");
let listing = stdout(output);
assert!(
entries(&listing).starts_with("entries 0 /"),
"{case}: a contract was fetched for an empty stdin's sake:\n{listing}"
);
}
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn a_batch_stdin_with_bytes_still_fills_the_required_argument() {
let home = scratch("bytes");
let output = piped(
ikigai(&home, &["-c", "source urn:iki:fn:toUpper"]),
b"piped",
);
assert!(output.status.success(), "{output:?}");
assert_eq!(stdout(&output).trim_end(), "PIPED");
let _ = std::fs::remove_dir_all(&home);
}
#[test]
fn a_quiet_repl_on_a_pipe_prints_only_its_answers() {
let home = scratch("quiet");
let lines =
"source urn:iki:fn:toUpper hi\nsource urn:kernel:scheduler\nsource urn:kernel:cache\n";
let quiet = piped(ikigai(&home, &["--quiet"]), lines.as_bytes());
assert!(quiet.status.success(), "{quiet:?}");
let out = stdout(&quiet);
let err = String::from_utf8_lossy(&quiet.stderr);
assert!(
out.starts_with("HI\n"),
"the first answer comes first:\n{out}"
);
assert!(
!out.contains("ikigai>"),
"no prompt on a piped stdin:\n{out}"
);
assert!(!out.contains("REPL"), "no banner:\n{out}");
assert!(!err.contains("ikigai: "), "no topology lines:\n{err}");
assert!(!out.ends_with("\n\n\n"), "no extra line at EOF:\n{out:?}");
assert!(
entries(&out).starts_with("entries 2 /"),
"only toUpper's contract and answer are cached:\n{out}"
);
let plain = piped(ikigai(&home, &["--plain"]), lines.as_bytes());
let out = stdout(&plain);
assert!(out.starts_with("ikigai "), "the banner:\n{out}");
assert!(out.contains("ikigai> HI\n"), "the prompt:\n{out}");
let _ = std::fs::remove_dir_all(&home);
}