blob: 5a71bfd82b1a37ce626ad2934a841b897c5540bc [file] [log] [blame]
//! This module greps parser's code for specially formatted comments and turns
//! them into tests.
#![allow(clippy::disallowed_types, clippy::print_stdout)]
use std::{
collections::HashMap,
fs, iter,
path::{Path, PathBuf},
};
#[test]
fn sourcegen_parser_tests() {
let grammar_dir = sourcegen::project_root().join(Path::new("crates/parser/src/grammar"));
let tests = tests_from_dir(&grammar_dir);
install_tests(&tests.ok, "crates/parser/test_data/parser/inline/ok");
install_tests(&tests.err, "crates/parser/test_data/parser/inline/err");
fn install_tests(tests: &HashMap<String, Test>, into: &str) {
let tests_dir = sourcegen::project_root().join(into);
if !tests_dir.is_dir() {
fs::create_dir_all(&tests_dir).unwrap();
}
// ok is never actually read, but it needs to be specified to create a Test in existing_tests
let existing = existing_tests(&tests_dir, true);
if let Some(t) = existing.keys().find(|&t| !tests.contains_key(t)) {
panic!("Test is deleted: {t}");
}
let mut new_idx = existing.len() + 1;
for (name, test) in tests {
let path = match existing.get(name) {
Some((path, _test)) => path.clone(),
None => {
let file_name = format!("{new_idx:04}_{name}.rs");
new_idx += 1;
tests_dir.join(file_name)
}
};
sourcegen::ensure_file_contents(&path, &test.text);
}
}
}
#[derive(Debug)]
struct Test {
name: String,
text: String,
ok: bool,
}
#[derive(Default, Debug)]
struct Tests {
ok: HashMap<String, Test>,
err: HashMap<String, Test>,
}
fn collect_tests(s: &str) -> Vec<Test> {
let mut res = Vec::new();
for comment_block in sourcegen::CommentBlock::extract_untagged(s) {
let first_line = &comment_block.contents[0];
let (name, ok) = if let Some(name) = first_line.strip_prefix("test ") {
(name.to_owned(), true)
} else if let Some(name) = first_line.strip_prefix("test_err ") {
(name.to_owned(), false)
} else {
continue;
};
let text: String = comment_block.contents[1..]
.iter()
.cloned()
.chain(iter::once(String::new()))
.collect::<Vec<_>>()
.join("\n");
assert!(!text.trim().is_empty() && text.ends_with('\n'));
res.push(Test { name, text, ok })
}
res
}
fn tests_from_dir(dir: &Path) -> Tests {
let mut res = Tests::default();
for entry in sourcegen::list_rust_files(dir) {
process_file(&mut res, entry.as_path());
}
let grammar_rs = dir.parent().unwrap().join("grammar.rs");
process_file(&mut res, &grammar_rs);
return res;
fn process_file(res: &mut Tests, path: &Path) {
let text = fs::read_to_string(path).unwrap();
for test in collect_tests(&text) {
if test.ok {
if let Some(old_test) = res.ok.insert(test.name.clone(), test) {
panic!("Duplicate test: {}", old_test.name);
}
} else if let Some(old_test) = res.err.insert(test.name.clone(), test) {
panic!("Duplicate test: {}", old_test.name);
}
}
}
}
fn existing_tests(dir: &Path, ok: bool) -> HashMap<String, (PathBuf, Test)> {
let mut res = HashMap::default();
for file in fs::read_dir(dir).unwrap() {
let file = file.unwrap();
let path = file.path();
if path.extension().unwrap_or_default() != "rs" {
continue;
}
let name = {
let file_name = path.file_name().unwrap().to_str().unwrap();
file_name[5..file_name.len() - 3].to_string()
};
let text = fs::read_to_string(&path).unwrap();
let test = Test { name: name.clone(), text, ok };
if let Some(old) = res.insert(name, (path, test)) {
println!("Duplicate test: {old:?}");
}
}
res
}