#![allow(dead_code)]
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
pub const CORPUS_ROOT: &str = "tests/compatibility/files";
pub const MIN_FULL_CORPUS_SIZE: usize = 100;
pub const IN_TREE_FIXTURE_PREFIX: &str = "tests/compatibility/files/plugins/";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileFingerprint {
pub source: String,
pub parser: String,
}
pub fn repo_root() -> &'static Path {
static ROOT: OnceLock<PathBuf> = OnceLock::new();
ROOT.get_or_init(|| {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
loop {
if p.join("tests/compatibility/files").is_dir() && has_workspace_table(&p) {
return p;
}
assert!(
p.pop(),
"could not locate repo root from {}",
env!("CARGO_MANIFEST_DIR")
);
}
})
}
pub fn has_workspace_table(dir: &Path) -> bool {
let Ok(toml) = std::fs::read_to_string(dir.join("Cargo.toml")) else {
return false;
};
toml.lines()
.any(|line| line.trim_start().starts_with("[workspace]"))
}
pub fn discover_corpus_files() -> &'static [PathBuf] {
static DISCOVERED: OnceLock<Vec<PathBuf>> = OnceLock::new();
DISCOVERED.get_or_init(|| {
let corpus_dir = repo_root().join(CORPUS_ROOT);
let mut out = Vec::new();
if !corpus_dir.is_dir() {
return out;
}
walk(&corpus_dir, &mut out);
out.sort();
out
})
}
fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
let entries = std::fs::read_dir(dir)
.unwrap_or_else(|e| panic!("read_dir({}) failed: {e}", dir.display()));
for entry in entries {
let entry =
entry.unwrap_or_else(|e| panic!("read_dir entry under {} failed: {e}", dir.display()));
let path = entry.path();
if path.is_dir() {
walk(&path, out);
} else if path.extension().and_then(|s| s.to_str()) == Some("beancount") {
let rel = path
.strip_prefix(repo_root())
.expect("corpus paths are under repo_root")
.to_path_buf();
out.push(rel);
}
}
}
pub fn is_in_tree_fixture(rel: &Path) -> bool {
rel.starts_with(IN_TREE_FIXTURE_PREFIX)
}
pub fn validate_path(rel: &Path) {
let s = rel.to_string_lossy();
assert!(
!s.contains('\t'),
"corpus path contains a TAB character, which the manifest \
serializer cannot represent: {}",
rel.display(),
);
assert!(
!s.contains('\n'),
"corpus path contains a newline, which would split the \
manifest entry across lines: {}",
rel.display(),
);
assert!(
!s.contains('\r'),
"corpus path contains a carriage return, which would corrupt \
the line-oriented manifest format: {}",
rel.display(),
);
assert!(
!s.starts_with('#'),
"corpus path starts with `#`, which the manifest reader would \
treat as a comment: {}",
rel.display(),
);
}
pub fn panic_payload_hash(payload: &(dyn std::any::Any + Send)) -> String {
if let Some(s) = payload.downcast_ref::<&'static str>() {
return blake3::hash(s.as_bytes()).to_hex().to_string();
}
if let Some(s) = payload.downcast_ref::<String>() {
return blake3::hash(s.as_bytes()).to_hex().to_string();
}
let mut h = blake3::Hasher::new();
h.update(b"non-string-panic:");
h.update(format!("{:?}", payload.type_id()).as_bytes());
h.finalize().to_hex().to_string()
}
pub fn read_committed_manifest(manifest_path: &Path) -> BTreeMap<PathBuf, FileFingerprint> {
let contents = match std::fs::read_to_string(manifest_path) {
Ok(s) => s,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => String::new(),
Err(e) => panic!("failed to read {}: {e}", manifest_path.display()),
};
let mut out = BTreeMap::new();
for (lineno, line) in contents.lines().enumerate() {
let lineno = lineno + 1;
if line.is_empty() || line.starts_with('#') {
continue;
}
let mut parts = line.split('\t');
let (Some(path_str), Some(source), Some(parser), None) =
(parts.next(), parts.next(), parts.next(), parts.next())
else {
panic!(
"{}:{lineno}: malformed manifest line (expected \
`path<TAB>source<TAB>parser`): {line:?}",
manifest_path.display(),
);
};
out.insert(
PathBuf::from(path_str),
FileFingerprint {
source: source.to_string(),
parser: parser.to_string(),
},
);
}
out
}
pub fn render_manifest(
manifest: &BTreeMap<PathBuf, FileFingerprint>,
header_lines: &[&str],
) -> String {
let mut out = String::new();
for line in header_lines {
out.push_str(line);
out.push('\n');
}
for (path, fp) in manifest {
out.push_str(&path.to_string_lossy());
out.push('\t');
out.push_str(&fp.source);
out.push('\t');
out.push_str(&fp.parser);
out.push('\n');
}
out
}
pub fn write_manifest(
manifest_path: &Path,
manifest: &BTreeMap<PathBuf, FileFingerprint>,
header_lines: &[&str],
) {
if let Some(parent) = manifest_path.parent() {
std::fs::create_dir_all(parent).expect("create baseline dir");
}
std::fs::write(manifest_path, render_manifest(manifest, header_lines)).expect("write manifest");
eprintln!(
"wrote {} entries to {}",
manifest.len(),
manifest_path.display(),
);
}
pub fn compute_manifest<F>(fingerprint_fn: F) -> BTreeMap<PathBuf, FileFingerprint>
where
F: Fn(&Path) -> Option<FileFingerprint>,
{
let root = repo_root();
discover_corpus_files()
.iter()
.filter_map(|rel| {
validate_path(rel);
fingerprint_fn(&root.join(rel)).map(|fp| (rel.clone(), fp))
})
.collect()
}