use std::path::{Path, PathBuf};
use std::process::{Command, Output};
fn bins() -> &'static str {
env!("CARGO_BIN_EXE_engramdb")
}
fn run(args: &[&str], cwd: &Path, stdin_data: Option<&[u8]>) -> Output {
use std::io::Write;
let mut c = Command::new(bins());
c.args(args).current_dir(cwd);
let mut child = c
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("spawn engramdb");
if let Some(data) = stdin_data {
child.stdin.as_mut().unwrap().write_all(data).unwrap();
}
drop(child.stdin.take());
child.wait_with_output().expect("wait")
}
struct Temp(PathBuf);
impl Temp {
fn new(tag: &str) -> Self {
let p = std::env::temp_dir().join(format!(
"engramdb-cli-e2e-{}-{}-{}",
tag,
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&p).unwrap();
Temp(p)
}
}
impl Drop for Temp {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn row_value(i: u64) -> u8 {
((i % 110) + 1) as u8
}
fn write_shard_dir(dir: &Path) {
let mut data = vec![0u8; 110 * 160];
for i in 0..110u64 {
for c in 0..160 {
data[i as usize * 160 + c] = row_value(i);
}
}
std::fs::write(dir.join("shard_000.bin"), &data).unwrap();
}
fn stdout(s: &Output) -> String {
String::from_utf8_lossy(&s.stdout).into_owned()
}
fn expected_fnv(rowids: &[u64]) -> u64 {
let mut buf = vec![0u8; rowids.len() * 160];
for (i, &r) in rowids.iter().enumerate() {
if r < 110 {
let v = row_value(r);
for c in 0..160 {
buf[i * 160 + c] = v;
}
}
}
engramdb_core::fnv64(&buf)
}
#[test]
fn build_gather_verify_bench_chain() {
let tmp = Temp::new("chain");
let raw = tmp.0.join("raw");
let out = tmp.0.join("tbl");
std::fs::create_dir_all(&raw).unwrap();
write_shard_dir(&raw);
for i in 0..128 {
let d = raw.join(format!("shard_{:03}.bin", i));
if i > 0 {
std::fs::copy(raw.join("shard_000.bin"), d).unwrap();
}
}
let o = run(
&["build", raw.to_str().unwrap(), out.to_str().unwrap()],
&tmp.0,
None,
);
assert!(o.status.success(), "build: {}", stdout(&o));
assert!(stdout(&o).contains("built"), "build stdout");
let b = out.join("badge_000.bin");
assert!(b.exists(), "badge_000.bin 缺失");
assert!(out.join("manifest.json").exists(), "manifest.json 缺失");
let mut rowids: Vec<u64> = (0..10).collect();
rowids.extend([10, 11, 24, 30, 77]); let stdin: String = rowids.iter().map(|r| format!("{r}\n")).collect();
let o = run(
&["gather", out.to_str().unwrap()],
&tmp.0,
Some(stdin.as_bytes()),
);
assert!(o.status.success(), "gather: {}", stdout(&o));
let got: u64 = stdout(&o).trim().parse().unwrap();
let exp = expected_fnv(&rowids);
assert_eq!(got, exp, "gather fnv 与本地 fnv64 对拍失败");
let rowid_file = tmp.0.join("rowids.txt");
std::fs::write(&rowid_file, stdin.clone()).unwrap();
let o = run(
&[
"verify",
out.to_str().unwrap(),
rowid_file.to_str().unwrap(),
],
&tmp.0,
None,
);
assert!(o.status.success(), "verify: {}", stdout(&o));
assert!(
stdout(&o).contains(&format!("fnv={exp}")),
"verify fnv 不匹配"
);
let o = run(
&["warm", out.to_str().unwrap(), "--budget", "0.000000001"],
&tmp.0,
None,
);
assert!(o.status.success(), "warm: {}", stdout(&o));
assert!(stdout(&o).contains("warmed"), "warm stdout");
let repo = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap();
let stats = repo.join("probes/agent_workload_stats.json");
if stats.exists() {
let o = run(
&[
"bench-real",
out.to_str().unwrap(),
"--dist",
"agent",
"--stats",
stats.to_str().unwrap(),
"--reqs",
"4",
"--cap-token",
"500",
"--iters",
"1",
],
&tmp.0,
None,
);
assert!(o.status.success(), "bench-real: {}", stdout(&o));
assert!(stdout(&o).contains("rows/s="), "bench-real stdout");
}
let rb = tmp.0.join("rowids.bin");
let mut buf = Vec::new();
for r in &rowids {
buf.extend_from_slice(&r.to_le_bytes());
}
std::fs::write(&rb, &buf).unwrap();
let idx = tmp.0.join("index");
let o = run(
&["index", rb.to_str().unwrap(), idx.to_str().unwrap()],
&tmp.0,
None,
);
assert!(o.status.success(), "index: {}", stdout(&o));
assert!(idx.join("counts.bin").exists(), "counts.bin 缺失");
let dump = std::fs::read_to_string(idx.join("counts.dump.txt")).unwrap();
assert!(dump.lines().count() >= rowids.len() - 1, "dump 行数异常");
}