| mod prefix_entries; |
| mod top_entries; |
| |
| use std::{ |
| fmt::Write, |
| fs, |
| path::{Path, PathBuf}, |
| }; |
| |
| use expect_test::expect_file; |
| |
| use crate::{Edition, LexedStr, TopEntryPoint}; |
| |
| #[rustfmt::skip] |
| #[path = "../test_data/generated/runner.rs"] |
| mod runner; |
| |
| #[test] |
| fn lex_ok() { |
| for case in TestCase::list("lexer/ok") { |
| let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); |
| let actual = lex(&case.text); |
| expect_file![case.rast].assert_eq(&actual) |
| } |
| } |
| |
| #[test] |
| fn lex_err() { |
| for case in TestCase::list("lexer/err") { |
| let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); |
| let actual = lex(&case.text); |
| expect_file![case.rast].assert_eq(&actual) |
| } |
| } |
| |
| fn lex(text: &str) -> String { |
| let lexed = LexedStr::new(Edition::CURRENT, text); |
| |
| let mut res = String::new(); |
| for i in 0..lexed.len() { |
| let kind = lexed.kind(i); |
| let text = lexed.text(i); |
| let error = lexed.error(i); |
| |
| let error = error.map(|err| format!(" error: {err}")).unwrap_or_default(); |
| writeln!(res, "{kind:?} {text:?}{error}").unwrap(); |
| } |
| res |
| } |
| |
| #[test] |
| fn parse_ok() { |
| for case in TestCase::list("parser/ok") { |
| let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); |
| let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text, Edition::CURRENT); |
| assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display()); |
| expect_file![case.rast].assert_eq(&actual); |
| } |
| } |
| |
| #[test] |
| fn parse_err() { |
| for case in TestCase::list("parser/err") { |
| let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); |
| let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text, Edition::CURRENT); |
| assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display()); |
| expect_file![case.rast].assert_eq(&actual) |
| } |
| } |
| |
| fn parse(entry: TopEntryPoint, text: &str, edition: Edition) -> (String, bool) { |
| let lexed = LexedStr::new(edition, text); |
| let input = lexed.to_input(edition); |
| let output = entry.parse(&input, edition); |
| |
| let mut buf = String::new(); |
| let mut errors = Vec::new(); |
| let mut indent = String::new(); |
| let mut depth = 0; |
| let mut len = 0; |
| lexed.intersperse_trivia(&output, &mut |step| match step { |
| crate::StrStep::Token { kind, text } => { |
| assert!(depth > 0); |
| len += text.len(); |
| writeln!(buf, "{indent}{kind:?} {text:?}").unwrap(); |
| } |
| crate::StrStep::Enter { kind } => { |
| assert!(depth > 0 || len == 0); |
| depth += 1; |
| writeln!(buf, "{indent}{kind:?}").unwrap(); |
| indent.push_str(" "); |
| } |
| crate::StrStep::Exit => { |
| assert!(depth > 0); |
| depth -= 1; |
| indent.pop(); |
| indent.pop(); |
| } |
| crate::StrStep::Error { msg, pos } => { |
| assert!(depth > 0); |
| errors.push(format!("error {pos}: {msg}\n")) |
| } |
| }); |
| assert_eq!( |
| len, |
| text.len(), |
| "didn't parse all text.\nParsed:\n{}\n\nAll:\n{}\n", |
| &text[..len], |
| text |
| ); |
| |
| for (token, msg) in lexed.errors() { |
| let pos = lexed.text_start(token); |
| errors.push(format!("error {pos}: {msg}\n")); |
| } |
| |
| let has_errors = !errors.is_empty(); |
| for e in errors { |
| buf.push_str(&e); |
| } |
| (buf, has_errors) |
| } |
| |
| #[derive(PartialEq, Eq, PartialOrd, Ord)] |
| struct TestCase { |
| rs: PathBuf, |
| rast: PathBuf, |
| text: String, |
| } |
| |
| impl TestCase { |
| fn list(path: &'static str) -> Vec<TestCase> { |
| let crate_root_dir = Path::new(env!("CARGO_MANIFEST_DIR")); |
| let test_data_dir = crate_root_dir.join("test_data"); |
| let dir = test_data_dir.join(path); |
| |
| let mut res = Vec::new(); |
| let read_dir = fs::read_dir(&dir) |
| .unwrap_or_else(|err| panic!("can't `read_dir` {}: {err}", dir.display())); |
| for file in read_dir { |
| let file = file.unwrap(); |
| let path = file.path(); |
| if path.extension().unwrap_or_default() == "rs" { |
| let rs = path; |
| let rast = rs.with_extension("rast"); |
| let text = fs::read_to_string(&rs).unwrap(); |
| res.push(TestCase { rs, rast, text }); |
| } |
| } |
| res.sort(); |
| res |
| } |
| } |
| |
| #[track_caller] |
| fn run_and_expect_no_errors(path: &str) { |
| run_and_expect_no_errors_with_edition(path, Edition::CURRENT) |
| } |
| |
| #[track_caller] |
| fn run_and_expect_errors(path: &str) { |
| run_and_expect_errors_with_edition(path, Edition::CURRENT) |
| } |
| |
| #[track_caller] |
| fn run_and_expect_no_errors_with_edition(path: &str, edition: Edition) { |
| let path = PathBuf::from(path); |
| let text = std::fs::read_to_string(&path).unwrap(); |
| let (actual, errors) = parse(TopEntryPoint::SourceFile, &text, edition); |
| assert!(!errors, "errors in an OK file {}:\n{actual}", path.display()); |
| let mut p = PathBuf::from(".."); |
| p.push(path); |
| p.set_extension("rast"); |
| expect_file![p].assert_eq(&actual) |
| } |
| |
| #[track_caller] |
| fn run_and_expect_errors_with_edition(path: &str, edition: Edition) { |
| let path = PathBuf::from(path); |
| let text = std::fs::read_to_string(&path).unwrap(); |
| let (actual, errors) = parse(TopEntryPoint::SourceFile, &text, edition); |
| assert!(errors, "no errors in an ERR file {}:\n{actual}", path.display()); |
| let mut p = PathBuf::from(".."); |
| p.push(path); |
| p.set_extension("rast"); |
| expect_file![p].assert_eq(&actual) |
| } |