use std::path::{Path, PathBuf};
use std::process::Command;
fn data_dir() -> PathBuf {
std::env::var("ESPEAK_DATA_PATH")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("espeak-ng-data"))
}
fn run(args: &[&str]) -> String {
let out = Command::new(env!("CARGO_BIN_EXE_espeak_cli"))
.args(args)
.env("ESPEAK_DATA_PATH", data_dir())
.output()
.expect("run cli");
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
String::from_utf8_lossy(&out.stdout).into_owned()
}
#[test]
fn trace_matches_upstream_for_a_rules_word() {
let out = run(&["-X", "-q", "-v", "en", "cat"]);
assert_eq!(
out,
"Translate 'cat'\n \
1\tc [k]\n\n \
1\ta [a]\n\n \
1\tt [t]\n\n\
k'at\n",
"trace differs:\n{out}"
);
}
#[test]
fn trace_reports_a_dictionary_hit() {
let out = run(&["-X", "-q", "-v", "en", "the"]);
assert!(out.starts_with("Found: 'the' [D@2]"), "{out}");
assert!(out.contains("$nounf"), "dictionary flags should be decoded: {out}");
assert!(out.trim_end().ends_with("D'@"), "phonemes follow the trace: {out}");
}
#[test]
fn trace_renders_rule_context() {
let out = run(&["-X", "-q", "-v", "en", "hi"]);
assert!(out.contains(" 43\th) i (_ [,aI]"), "{out}");
assert!(out.contains(" 1\th [h]"), "{out}");
assert!(out.trim_end().ends_with("h'aI"), "{out}");
}
#[test]
fn deterministic_flag_is_accepted() {
let dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join("trace_cli");
std::fs::create_dir_all(&dir).unwrap();
let (a, b) = (dir.join("a.wav"), dir.join("b.wav"));
run(&["-D", "-v", "en", "-w", a.to_str().unwrap(), "hello world"]);
run(&["-v", "en", "-w", b.to_str().unwrap(), "hello world"]);
assert!(std::fs::metadata(&a).unwrap().len() > 1000, "no audio written");
assert_eq!(
std::fs::read(&a).unwrap().len(),
std::fs::read(&b).unwrap().len(),
"the seed must not change the utterance length"
);
let c = dir.join("c.wav");
run(&["-D", "-v", "en", "-w", c.to_str().unwrap(), "hello world"]);
assert_eq!(std::fs::read(&a).unwrap(), std::fs::read(&c).unwrap(), "not reproducible");
}