oden/fine/tests/example_tests.rs
John Doty 618e0028d3 [fine] Type testing with probes and reporting
I'm proud of the test harness here actually. Also fix a bug in
checking!
2024-01-05 17:10:15 -08:00

137 lines
4.2 KiB
Rust

use fine::parser::SyntaxTree;
use fine::semantics::{Semantics, Type};
use fine::tokens::Lines;
use pretty_assertions::assert_eq;
fn rebase_concrete(source_path: &str, dump: &str) {
let contents = std::fs::read_to_string(source_path)
.expect(&format!("unable to read input file {}", source_path));
let mut result = String::new();
let mut lines = contents.lines();
// Search for the "concrete:" section.
let mut found_concrete_section = false;
while let Some(line) = lines.next() {
result.push_str(line);
result.push_str("\n");
if line == "// concrete:" {
found_concrete_section = true;
break;
}
}
if !found_concrete_section {
panic!(
"unable to locate the concrete section in {}. Is there a line that starts with '// concrete:'?",
source_path
);
}
// We've found the section we care about, replace all the lines we care
// about with the actual lines.
let mut replaced_output = false;
while let Some(line) = lines.next() {
if line.starts_with("// | ") {
// Skip copying lines here, because we're skipping
// the existing concrete syntax tree.
} else {
// OK we're out of concrete syntax tree; copy in the
// new CST. (We do this inline so we don't lose
// `line`.)
for expected_line in dump.lines() {
result.push_str("// | ");
result.push_str(expected_line);
result.push_str("\n");
}
// (Make sure not to drop this line.)
result.push_str(line);
result.push_str("\n");
replaced_output = true;
break;
}
}
if !replaced_output {
panic!(
"didn't actually replace the output section in {}",
source_path
);
}
// Now just copy the rest of the lines.
while let Some(line) = lines.next() {
result.push_str(line);
result.push_str("\n");
}
// ... and re-write the file.
std::fs::write(source_path, result).expect("unable to write the new file!");
}
fn assert_concrete(tree: &SyntaxTree, expected: &str, source_path: &str) {
let dump = tree.dump(false);
let rebase = std::env::var("FINE_TEST_REBASE")
.unwrap_or(String::new())
.to_lowercase();
match rebase.as_str() {
"1" | "true" | "yes" | "y" => {
if dump != expected {
rebase_concrete(source_path, &dump)
}
}
_ => assert_eq!(expected, dump, "concrete syntax trees did not match (set FINE_TEST_REBASE=1 to auto-rebase if the diff is expected)"),
}
}
fn assert_type_at(
tree: &SyntaxTree,
lines: &Lines,
pos: usize,
expected: &str,
_source_path: &str,
) {
let tree_ref = match tree.find_tree_at(pos) {
Some(t) => t,
None => {
println!("Unable to find the subtee at position {pos}! Parsed the tree as:");
println!("\n{}", tree.dump(true));
panic!("Cannot find tree at position {pos}");
}
};
let semantics = Semantics::new(tree, lines);
let tree_type = semantics.type_of(tree_ref, true);
let actual = format!("{}", tree_type.unwrap_or(Type::Error));
if actual != expected {
println!(
"The type of the {:?} tree at position {pos} had the wrong type! Parsed the tree as:",
tree[tree_ref].kind
);
println!("\n{}", tree.dump(true));
let errors = semantics.snapshot_errors();
if errors.len() == 0 {
println!("There were no errors reported during type checking.\n");
} else {
println!(
"{} error{} reported during type checking:",
errors.len(),
if errors.len() == 1 { "" } else { "s" }
);
for error in &errors {
println!(" Error: {error}");
}
println!();
}
assert_eq!(
expected, actual,
"The type of the tree at position {pos} was incorrect"
);
}
}
include!(concat!(env!("OUT_DIR"), "/generated_tests.rs"));