#![cfg(all(feature = "embedded", feature = "ipc", unix))]
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output, Stdio};
use std::time::Duration;
const TRIES: usize = 1800;
const GAP: Duration = Duration::from_millis(50);
struct Door(Child);
impl Drop for Door {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
fn scratch(case: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("iki-wv-{}-{case}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let config = dir.join("config").join("ikigai");
std::fs::create_dir_all(&config).expect("scratch config home");
std::fs::create_dir_all(dir.join("files")).expect("scratch workspace");
std::fs::create_dir_all(dir.join("tmp")).expect("scratch temp dir");
std::fs::write(config.join("config.toml"), "show.opener = \"none\"\n")
.expect("scratch config.toml");
dir
}
fn command(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"))
.env("TMPDIR", home.join("tmp"));
command
}
fn run(home: &Path, args: &[&str]) -> Output {
command(home, args)
.output()
.expect("the binary under test runs")
}
fn text(bytes: &[u8]) -> String {
String::from_utf8_lossy(bytes).into_owned()
}
#[test]
fn show_writes_the_picture_and_prints_where() {
let home = scratch("show");
let out = run(&home, &["-c", "show urn:diagram:kernel"]);
let stdout = text(&out.stdout);
assert_eq!(out.status.code(), Some(0), "{}", text(&out.stderr));
assert!(
stdout.contains("not opened (show.opener = none)"),
"{stdout}"
);
let path = stdout
.split_whitespace()
.nth(1)
.map(PathBuf::from)
.unwrap_or_else(|| panic!("no path in: {stdout}"));
assert!(
path.starts_with(home.join("tmp")) || path.starts_with(home.canonicalize().unwrap()),
"written under TMPDIR: {}",
path.display()
);
let svg = std::fs::read_to_string(&path).expect("the picture was written");
assert!(
svg.starts_with("<svg") && svg.contains("role=\"img\""),
"{svg}"
);
let out = run(&home, &["-c", "show urn:host:info"]);
assert_eq!(out.status.code(), Some(1));
let stderr = text(&out.stderr);
assert!(stderr.contains("no picture face"), "{stderr}");
let written = std::fs::read_dir(home.join("tmp")).unwrap().count();
assert_eq!(written, 1, "a refusal writes nothing");
}
#[test]
fn the_views_answer_over_a_socket() {
let home = scratch("ipc");
let sock = format!("/tmp/iki-wv-{}.sock", std::process::id());
let _ = std::fs::remove_file(&sock);
let _door = Door(
command(&home, &["serve", &sock])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("the door starts"),
);
let mut bound = false;
for _ in 0..TRIES {
if Path::new(&sock).exists() {
bound = true;
break;
}
std::thread::sleep(GAP);
}
assert!(bound, "`{sock}` was never bound");
let out = run(
&home,
&[
"--connect",
&sock,
"--plain",
"-c",
"source urn:host:info",
"-c",
"explain urn:host:info",
"-c",
"why urn:host:info",
"-c",
"dependents urn:kernel:bindings",
"-c",
"explain urn:nothing:bound:here",
],
);
let stdout = text(&out.stdout);
assert_eq!(out.status.code(), Some(0), "{stdout}{}", text(&out.stderr));
assert!(stdout.contains("explain source urn:host:info"), "{stdout}");
assert!(stdout.contains("endpoint host-info"), "{stdout}");
assert!(stdout.contains("why urn:host:info"), "{stdout}");
assert!(stdout.contains("declared [root]"), "{stdout}");
assert!(
stdout.contains("dependents of urn:kernel:bindings"),
"{stdout}"
);
assert!(
stdout.contains("unresolved: nothing in the chain answers this name"),
"{stdout}"
);
let _ = std::fs::remove_file(&sock);
}