use std::path::PathBuf;
use std::process::{Command, Output};
fn repo() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("..")
}
fn corpus(sub: &str, name: &str) -> PathBuf {
repo()
.join("engine")
.join("tests")
.join("files")
.join(sub)
.join(format!("{name}.verit"))
}
fn verit(args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_verit"))
.args(args)
.output()
.expect("failed to run verit")
}
fn stdout(args: &[&str]) -> String {
let out = verit(args);
assert!(
out.status.success(),
"verit {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout).unwrap()
}
fn scratch(name: &str) -> PathBuf {
let p = std::env::temp_dir().join(format!("verit-cli-{}-{name}", std::process::id()));
let _ = std::fs::remove_file(&p);
let _ = std::fs::remove_dir_all(&p);
p
}
#[test]
fn dump_handles_both_a_file_and_a_message() {
let file = stdout(&["dump", corpus("good", "mixed").to_str().unwrap()]);
assert_eq!(file.lines().count(), 4);
assert!(file.lines().all(|l| l.starts_with('{') && l.ends_with('}')));
let msg = repo().join("engine/tests/vectors/scalars.bin");
let out = stdout(&["dump", msg.to_str().unwrap()]);
assert_eq!(out.lines().count(), 1);
assert!(out.contains("\"name\":\"veritate\""));
}
#[test]
fn a_vertc_container_is_refused_with_an_explanation() {
let path = scratch("old.vertc");
std::fs::write(&path, b"VRTC\x01\x00\x00\x00").unwrap();
let out = verit(&["ls", path.to_str().unwrap()]);
assert!(!out.status.success());
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("deprecated"), "unhelpful error: {err}");
assert!(
err.contains("ADR-0002"),
"error should point somewhere: {err}"
);
}
#[test]
fn a_non_veritate_file_is_refused_clearly() {
let path = scratch("random.bin");
std::fs::write(&path, b"this is not a veritate artifact at all").unwrap();
let out = verit(&["dump", path.to_str().unwrap()]);
assert!(!out.status.success());
assert!(String::from_utf8_lossy(&out.stderr).contains("not a Veritate artifact"));
}
#[test]
fn ls_reports_identity_generation_and_reclaimable_space() {
let out = stdout(&["ls", corpus("good", "removed").to_str().unwrap()]);
assert!(out.contains("generation: 3"));
assert!(out.contains("records: 3"));
assert!(
out.contains("next id: 6"),
"the retired id counter must be visible"
);
for id in ["\n 1", "\n 3", "\n 5"] {
assert!(out.contains(id), "missing id row {id:?} in:\n{out}");
}
assert!(out.contains("dead"), "no dead-space accounting:\n{out}");
assert!(out.contains("verit compact"), "no remedy offered:\n{out}");
}
#[test]
fn ls_does_not_nag_about_alignment_padding() {
let out = stdout(&["ls", corpus("good", "mixed").to_str().unwrap()]);
assert!(
!out.contains("verit compact"),
"nagged over padding:\n{out}"
);
assert!(
out.contains("schemas: 3"),
"should list all three schemas"
);
}
#[test]
fn verify_reports_a_rolled_back_commit_loudly() {
let out = stdout(&["verify", corpus("torn", "torn-in-index").to_str().unwrap()]);
assert!(
out.contains("RECOVERED"),
"rollback was not surfaced:\n{out}"
);
assert!(
out.contains("rolled back"),
"no explanation of what happened:\n{out}"
);
assert!(out.contains("generation 2"));
}
#[test]
fn verify_fails_with_a_typed_error_on_every_bad_file() {
let dir = repo().join("engine/tests/files/bad");
let manifest = std::fs::read_to_string(dir.join("manifest.tsv")).unwrap();
let mut checked = 0;
for line in manifest
.lines()
.filter(|l| !l.starts_with('#') && !l.trim().is_empty())
{
let name = line.split('\t').next().unwrap();
let out = verit(&["verify", corpus("bad", name).to_str().unwrap()]);
assert!(!out.status.success(), "bad/{name} was accepted by verify");
let err = String::from_utf8_lossy(&out.stderr);
assert!(
err.starts_with("verit: "),
"bad/{name}: unformatted error {err:?}"
);
assert!(!err.contains("panicked"), "bad/{name} panicked: {err}");
checked += 1;
}
assert!(checked >= 15, "only {checked} bad files checked");
}
#[test]
fn pack_builds_a_readable_file_from_self_describing_messages() {
let out_path = scratch("packed.verit");
let vectors = repo().join("engine/tests/vectors");
stdout(&[
"pack",
vectors.join("scalars.bin").to_str().unwrap(),
vectors.join("nested.bin").to_str().unwrap(),
"-o",
out_path.to_str().unwrap(),
]);
let listed = stdout(&["ls", out_path.to_str().unwrap()]);
assert!(listed.contains("records: 2"));
assert!(listed.contains("schemas: 2"));
assert!(stdout(&["verify", out_path.to_str().unwrap()]).contains("ok:"));
}
#[test]
fn pack_refuses_hash_only_messages_with_a_reason() {
let msg = scratch("hashonly.bin");
let path = repo().join("engine/tests/files/good/single.verit");
let image = std::fs::read(&path).unwrap();
let f = verit::FileView::open(&image).unwrap();
std::fs::write(&msg, f.get(0).unwrap()).unwrap();
let out = verit(&[
"pack",
msg.to_str().unwrap(),
"-o",
scratch("x.verit").to_str().unwrap(),
]);
assert!(!out.status.success());
assert!(
String::from_utf8_lossy(&out.stderr).contains("inline schema"),
"error should name the cause: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn unpack_names_records_by_id_and_keeps_the_schemas() {
let dir = scratch("unpacked");
stdout(&[
"unpack",
corpus("good", "removed").to_str().unwrap(),
"-o",
dir.to_str().unwrap(),
]);
for id in [1u64, 3, 5] {
assert!(
dir.join(format!("{id:06}.bin")).exists(),
"missing record {id}"
);
}
assert!(!dir.join("000002.bin").exists(), "record 2 was removed");
assert!(dir.join("schemas.vrsb").exists(), "unpack must be lossless");
}
#[test]
fn compact_reclaims_space_preserves_ids_and_warns_about_erasure() {
let path = scratch("compact-me.verit");
std::fs::copy(corpus("good", "removed"), &path).unwrap();
let before = std::fs::metadata(&path).unwrap().len();
let out = stdout(&["compact", path.to_str().unwrap()]);
let after = std::fs::metadata(&path).unwrap().len();
assert!(
after < before / 2,
"reclaimed too little: {before} -> {after}"
);
assert!(out.contains("ERASED"), "no erasure warning:\n{out}");
assert!(
out.contains("ids"),
"no word on what happens to ids:\n{out}"
);
let ids = stdout(&["id", path.to_str().unwrap()]);
let got: Vec<&str> = ids.lines().map(|l| l.split('\t').next().unwrap()).collect();
assert_eq!(got, vec!["1", "3", "5"]);
assert!(stdout(&["ls", path.to_str().unwrap()]).contains("next id: 6"));
}
#[test]
fn dump_selects_a_record_by_id_not_by_position() {
let file = corpus("good", "removed");
let by_id = stdout(&["dump", file.to_str().unwrap(), "--id", "3"]);
let by_pos = stdout(&["dump", file.to_str().unwrap(), "--record", "1"]);
assert_eq!(by_id, by_pos);
let out = verit(&["dump", file.to_str().unwrap(), "--id", "2"]);
assert!(!out.status.success());
assert!(String::from_utf8_lossy(&out.stderr).contains("no live record"));
}