194 lines
7.1 KiB
Rust
194 lines
7.1 KiB
Rust
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream};
|
|
use quote::{format_ident, quote, TokenStreamExt};
|
|
use std::env;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
struct ExpectedErrors(Vec<String>);
|
|
|
|
impl quote::ToTokens for ExpectedErrors {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
let mut inner = TokenStream::new();
|
|
for err in self.0.iter() {
|
|
inner.append(Literal::string(err));
|
|
inner.append(Punct::new(',', Spacing::Alone));
|
|
}
|
|
|
|
tokens.append(Ident::new("vec", Span::call_site()));
|
|
tokens.append(Punct::new('!', Spacing::Joint));
|
|
tokens.append(Group::new(Delimiter::Parenthesis, inner))
|
|
}
|
|
}
|
|
|
|
fn generate_test_for_file(path: PathBuf) -> String {
|
|
let contents = fs::read_to_string(&path)
|
|
.expect("Unable to read input")
|
|
.replace("\r\n", "\n");
|
|
let display_path = path.display().to_string();
|
|
|
|
// Start iterating over lines and processing directives....
|
|
let mut disabled = quote! {};
|
|
let mut assertions = Vec::new();
|
|
let mut lines = contents.lines();
|
|
while let Some(line) = lines.next() {
|
|
let line = match line.strip_prefix("//") {
|
|
Some(line) => line,
|
|
None => continue,
|
|
};
|
|
|
|
let line = line.trim();
|
|
if let Some(line) = line.strip_prefix("@ignore") {
|
|
let reason = line.trim();
|
|
assert_ne!(
|
|
reason, "",
|
|
"You need to provide at least some description for ignoring in {display_path}"
|
|
);
|
|
disabled = quote! { #[ignore = #reason] };
|
|
} else if line == "@concrete:" {
|
|
let mut concrete = String::new();
|
|
while let Some(line) = lines.next() {
|
|
let line = match line.strip_prefix("// | ") {
|
|
Some(line) => line,
|
|
None => break,
|
|
};
|
|
|
|
concrete.push_str(line);
|
|
concrete.push_str("\n");
|
|
}
|
|
|
|
assertions.push(quote! {
|
|
crate::assert_concrete(source.clone(), #concrete, #display_path);
|
|
});
|
|
} else if line == "@compiles-to:" {
|
|
let mut compiled = String::new();
|
|
while let Some(line) = lines.next() {
|
|
let line = match line.strip_prefix("// | ") {
|
|
Some(line) => line,
|
|
None => break,
|
|
};
|
|
|
|
compiled.push_str(line);
|
|
compiled.push_str("\n");
|
|
}
|
|
|
|
assertions.push(quote! {
|
|
crate::assert_compiles_to(_module.clone(), #compiled, #display_path);
|
|
});
|
|
} else if let Some(line) = line.strip_prefix("@type:") {
|
|
let (pos, expected) = line
|
|
.trim()
|
|
.split_once(' ')
|
|
.expect("Mal-formed type expectation");
|
|
let pos: usize = pos
|
|
.trim()
|
|
.parse()
|
|
.expect(&format!("Unable to parse position '{pos}'"));
|
|
let expected = expected.trim();
|
|
assertions.push(quote! {
|
|
crate::assert_type_at(_module.clone(), #pos, #expected, #display_path);
|
|
});
|
|
} else if let Some(line) = line.strip_prefix("@type-error:") {
|
|
let (pos, expected) = line
|
|
.trim()
|
|
.split_once(' ')
|
|
.expect("Mal-formed type-error expectation");
|
|
let pos: usize = pos
|
|
.trim()
|
|
.parse()
|
|
.expect(&format!("Unable to parse position '{pos}'"));
|
|
let expected = expected.trim();
|
|
assertions.push(quote! {
|
|
crate::assert_type_error_at(_module.clone(), &_errors, #pos, #expected, #display_path);
|
|
});
|
|
} else if line == "@no-errors" {
|
|
assertions.push(quote! {
|
|
crate::assert_no_errors(_module.clone(), &_errors);
|
|
});
|
|
} else if let Some(line) = line.strip_prefix("@eval:") {
|
|
let expected = line.trim();
|
|
assertions.push(quote! {
|
|
crate::assert_eval_ok(_module.clone(), #expected);
|
|
});
|
|
} else if let Some(line) = line.strip_prefix("@check-error:") {
|
|
let expected = line.trim();
|
|
assertions.push(quote! {
|
|
crate::assert_check_error(_module.clone(), &_errors, #expected);
|
|
});
|
|
} else if line == "@expect-errors:" {
|
|
let mut errors = Vec::new();
|
|
while let Some(line) = lines.next() {
|
|
let line = match line.strip_prefix("// | ") {
|
|
Some(line) => line,
|
|
None => break,
|
|
};
|
|
|
|
errors.push(line.to_string());
|
|
}
|
|
|
|
let errors = ExpectedErrors(errors);
|
|
assertions.push(quote! {
|
|
crate::assert_errors(_module.clone(), &_errors, #errors);
|
|
});
|
|
} else if line.starts_with("@") {
|
|
panic!("Test file {display_path} has unknown directive: {line}");
|
|
}
|
|
}
|
|
|
|
let name = format_ident!("{}", path.file_stem().unwrap().to_string_lossy());
|
|
let test_method = quote! {
|
|
#disabled
|
|
fn #name() {
|
|
let source : std::rc::Rc<str> = #contents.into();
|
|
let mut runtime = crate::test_runtime(source.clone());
|
|
let (_errors, _module) = runtime.load_module("__test__").unwrap();
|
|
|
|
#(#assertions)*
|
|
}
|
|
};
|
|
|
|
let syntax_tree = syn::parse2(test_method).unwrap();
|
|
prettyplease::unparse(&syntax_tree)
|
|
}
|
|
|
|
fn process_directory<T>(output: &mut String, path: T)
|
|
where
|
|
T: AsRef<Path>,
|
|
{
|
|
let fine_ext: std::ffi::OsString = "fine".into();
|
|
let path = path.as_ref();
|
|
for entry in std::fs::read_dir(path).expect("Unable to read directory") {
|
|
match entry {
|
|
Ok(dirent) => {
|
|
let file_type = dirent.file_type().unwrap();
|
|
if file_type.is_dir() {
|
|
let file_name = dirent.file_name();
|
|
let file_name = file_name.to_string_lossy().to_owned();
|
|
output.push_str(&format!("mod {file_name} {{\n"));
|
|
process_directory(output, dirent.path());
|
|
output.push_str("}\n\n");
|
|
} else if file_type.is_file() {
|
|
if dirent.path().extension() == Some(&fine_ext) {
|
|
output.push_str(&format!("// {}\n", dirent.path().display()));
|
|
output.push_str("#[test]\n");
|
|
output.push_str(&generate_test_for_file(dirent.path()));
|
|
output.push_str("\n\n");
|
|
}
|
|
} else {
|
|
eprintln!("Skipping symlink: {}", path.display());
|
|
}
|
|
}
|
|
Err(e) => eprintln!("Unable to read directory entry: {:?}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-changed=./tests");
|
|
|
|
let mut test_source = String::new();
|
|
process_directory(&mut test_source, "./tests");
|
|
|
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
|
let dest_path = Path::new(&out_dir).join("generated_tests.rs");
|
|
fs::write(dest_path, test_source).unwrap();
|
|
}
|