use std::fs;
use std::path::{Path, PathBuf};
pub struct Row {
pub file: String,
pub line: usize,
pub input: String,
pub expected: String,
}
fn unescape(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut chars = text.chars();
while let Some(c) = chars.next() {
if c != '\\' {
out.push(c);
continue;
}
match chars.next() {
Some('n') => out.push('\n'),
Some('r') => out.push('\r'),
Some('t') => out.push('\t'),
Some('\\') => out.push('\\'),
Some(other) => {
out.push('\\');
out.push(other);
}
None => out.push('\\'),
}
}
out
}
pub fn spec_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("rs/ has a parent")
.join("test")
.join("spec")
}
pub fn rows(file: &str) -> Vec<Row> {
let path = spec_dir().join(file);
let body =
fs::read_to_string(&path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let mut out = Vec::new();
for (index, raw) in body.lines().enumerate() {
let line = index + 1;
if raw.trim().is_empty() {
continue;
}
if raw.starts_with('#') && !raw.contains('\t') {
continue;
}
let mut cols = raw.split('\t');
let input = cols.next().unwrap_or_default();
let expected = cols.next().unwrap_or_default();
if line == 1 && input == "input" {
continue;
}
out.push(Row {
file: file.to_string(),
line,
input: unescape(input),
expected: expected.to_string(),
});
}
assert!(!out.is_empty(), "{} has no data rows", path.display());
out
}
pub fn files() -> Vec<String> {
let mut names: Vec<String> = fs::read_dir(spec_dir())
.expect("the spec directory exists")
.filter_map(|entry| entry.ok())
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.filter(|name| name.ends_with(".tsv"))
.collect();
names.sort();
assert!(
!names.is_empty(),
"{} holds no .tsv fixtures; the parity suite would test nothing",
spec_dir().display()
);
names
}
pub fn report(failures: Vec<String>) {
if failures.is_empty() {
return;
}
panic!(
"{} spec row(s) failed:\n{}",
failures.len(),
failures.join("\n")
);
}