Files
searchi/crates/searchi-speech/tests/native_cpu.rs
T

114 lines
3.8 KiB
Rust

//! Explicit real local speech proof; excluded from ordinary structural checks.
use anyhow::{ensure, Context, Result};
use searchi_speech::{Device, SpeechWorker, MAX_SAMPLES};
use std::{
fs,
path::{Path, PathBuf},
time::{SystemTime, UNIX_EPOCH},
};
fn required(name: &str) -> Result<PathBuf> {
std::env::var_os(name)
.map(PathBuf::from)
.with_context(|| format!("Set {name} for actual CPU speech proof"))
}
struct OwnedFixture(PathBuf);
impl Drop for OwnedFixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn link_or_copy(source: &Path, target: &Path) -> Result<()> {
if fs::hard_link(source, target).is_err() {
fs::copy(source, target)?;
}
Ok(())
}
#[test]
#[ignore = "Requires actual pinned native worker/models and a verified speech PCM fixture; CPU only"]
fn actual_speech_reuses_contexts_and_supports_unicode_runtime_and_model_paths() -> Result<()> {
let worker = required("SEARCHI_TEST_SPEECH_WORKER")?;
let base = required("SEARCHI_TEST_SPEECH_BASE")?;
let vad = required("SEARCHI_TEST_SPEECH_VAD")?;
let pcm_file = required("SEARCHI_TEST_SPEECH_PCM")?;
ensure!(
fs::metadata(&pcm_file)?.len() <= MAX_SAMPLES as u64 * 4,
"Proof PCM exceeds limit"
);
let bytes = fs::read(pcm_file)?;
ensure!(
!bytes.is_empty() && bytes.len() % 4 == 0,
"Invalid proof PCM bytes"
);
let pcm: Vec<f32> = bytes
.as_chunks::<4>()
.0
.iter()
.map(|b| f32::from_le_bytes(*b))
.collect();
let unique = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
let fixture = OwnedFixture(std::env::temp_dir().join(format!(
"searchi-sprachtest-München-測試-🚀-{}-{unique}",
std::process::id()
)));
fs::create_dir(&fixture.0)?;
let runtime = fixture.0.join("Laufzeit-測試-🚀");
fs::create_dir(&runtime)?;
let inventory = worker
.parent()
.context("Worker directory missing")?
.join("runtime-manifest.json");
let manifest: serde_json::Value = serde_json::from_slice(&fs::read(&inventory)?)?;
for record in manifest["files"]
.as_array()
.context("Missing runtime files")?
{
let name = record["path"].as_str().context("Invalid inventory name")?;
ensure!(
!name.contains(['/', '\\', ':']) && name != "..",
"Unsafe fixture artifact name"
);
fs::copy(worker.parent().unwrap().join(name), runtime.join(name))?;
}
fs::copy(inventory, runtime.join("runtime-manifest.json"))?;
let unicode_base = fixture.0.join("Modell-München-測試-🚀.bin");
let unicode_vad = fixture.0.join("Sprache-測試-🚀.bin");
link_or_copy(&base, &unicode_base)?;
link_or_copy(&vad, &unicode_vad)?;
ensure!(
std::env::var("SEARCHI_FORCE_CPU").ok().as_deref() == Some("1"),
"Actual VM inference proof requires forced CPU"
);
let mut client = SpeechWorker::load(
&runtime.join(worker.file_name().unwrap()),
&unicode_base,
&unicode_vad,
Device::Auto,
)?;
let first = client.transcribe(&pcm, 16000)?;
ensure!(
!first.passages.is_empty(),
"Actual speech fixture produced no passages"
);
let second = client.transcribe(&pcm, 16000)?;
ensure!(
first.passages == second.passages,
"Warm speech result differs"
);
ensure!(
second.metrics.load_ms == 0.0,
"Persistent contexts unexpectedly reloaded"
);
for passage in first.passages {
ensure!(
passage.start_ms <= passage.end_ms && passage.end_ms as usize <= pcm.len() / 16,
"Native passage outside source window"
);
}
println!(
"actual CPU speech: first={:?}; warm={:?}",
first.metrics, second.metrics
);
Ok(())
}