| 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; | |
| fn infer_edition(file_path: &Path) -> Edition { | |
| let file_content = std::fs::read_to_string(file_path).unwrap(); | |
| if let Some(edition) = file_content.strip_prefix("//@ edition: ") { | |
| edition[..4].parse().expect("invalid edition directive") | |
| } else { | |
| Edition::CURRENT | |
| } | |
| } | |
| #[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, infer_edition(&case.rs)); | |
| 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, infer_edition(&case.rs)); | |
| expect_file![case.rast].assert_eq(&actual) | |
| } | |
| } | |
| fn lex(text: &str, edition: Edition) -> String { | |
| let lexed = LexedStr::new(edition, 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); | |
| 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) | |
| } |