2022-07-23 15:23:13 +00:00
|
|
|
#[cfg(not(feature = "in-rust-tree"))]
|
2021-07-03 19:11:03 +00:00
|
|
|
mod ast_src;
|
2022-07-23 00:06:11 +00:00
|
|
|
#[cfg(not(feature = "in-rust-tree"))]
|
|
|
|
mod sourcegen_ast;
|
2021-07-03 19:11:03 +00:00
|
|
|
|
2018-07-30 12:25:52 +00:00
|
|
|
use std::{
|
2020-07-01 10:30:17 +00:00
|
|
|
fs,
|
2020-07-11 10:56:44 +00:00
|
|
|
path::{Path, PathBuf},
|
2018-07-30 12:25:52 +00:00
|
|
|
};
|
2018-01-07 11:56:08 +00:00
|
|
|
|
2021-09-27 10:54:24 +00:00
|
|
|
use ast::HasName;
|
2020-08-21 11:19:31 +00:00
|
|
|
use expect_test::expect_file;
|
2020-07-11 10:56:44 +00:00
|
|
|
use rayon::prelude::*;
|
2021-08-23 13:39:38 +00:00
|
|
|
use test_utils::{bench, bench_fixture, project_root};
|
2018-08-25 09:10:35 +00:00
|
|
|
|
2021-12-18 14:20:38 +00:00
|
|
|
use crate::{ast, fuzz, AstNode, SourceFile, SyntaxError};
|
2018-08-11 07:03:03 +00:00
|
|
|
|
2019-09-09 11:52:31 +00:00
|
|
|
#[test]
|
|
|
|
fn parse_smoke_test() {
|
2023-09-15 07:43:21 +00:00
|
|
|
let code = r#"
|
2019-09-09 11:52:31 +00:00
|
|
|
fn main() {
|
|
|
|
println!("Hello, world!")
|
|
|
|
}
|
2023-09-15 07:43:21 +00:00
|
|
|
"#;
|
2019-09-09 11:52:31 +00:00
|
|
|
|
|
|
|
let parse = SourceFile::parse(code);
|
2020-03-13 11:09:14 +00:00
|
|
|
// eprintln!("{:#?}", parse.syntax_node());
|
2019-09-09 11:52:31 +00:00
|
|
|
assert!(parse.ok().is_ok());
|
|
|
|
}
|
|
|
|
|
2021-02-09 18:52:34 +00:00
|
|
|
#[test]
|
|
|
|
fn benchmark_parser() {
|
2021-08-23 13:39:38 +00:00
|
|
|
if std::env::var("RUN_SLOW_BENCHES").is_err() {
|
2021-02-09 18:52:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-08-23 13:39:38 +00:00
|
|
|
|
2021-02-09 18:52:34 +00:00
|
|
|
let data = bench_fixture::glorious_old_parser();
|
|
|
|
let tree = {
|
|
|
|
let _b = bench("parsing");
|
|
|
|
let p = SourceFile::parse(&data);
|
2023-09-29 10:37:57 +00:00
|
|
|
assert!(p.errors.is_none());
|
2021-02-09 18:52:34 +00:00
|
|
|
assert_eq!(p.tree().syntax.text_range().len(), 352474.into());
|
|
|
|
p.tree()
|
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
let _b = bench("tree traversal");
|
|
|
|
let fn_names =
|
|
|
|
tree.syntax().descendants().filter_map(ast::Fn::cast).filter_map(|f| f.name()).count();
|
|
|
|
assert_eq!(fn_names, 268);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-10 18:14:09 +00:00
|
|
|
#[test]
|
2021-12-26 15:19:09 +00:00
|
|
|
fn validation_tests() {
|
|
|
|
dir_tests(&test_data_dir(), &["parser/validation"], "rast", |text, path| {
|
2019-05-28 14:34:28 +00:00
|
|
|
let parse = SourceFile::parse(text);
|
2019-07-12 16:41:13 +00:00
|
|
|
let errors = parse.errors();
|
2021-12-26 15:19:09 +00:00
|
|
|
assert_errors_are_present(errors, path);
|
2019-05-28 13:59:22 +00:00
|
|
|
parse.debug_dump()
|
2019-02-08 11:49:43 +00:00
|
|
|
});
|
2018-09-10 18:14:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parser_fuzz_tests() {
|
2020-04-06 11:04:26 +00:00
|
|
|
for (_, text) in collect_rust_files(&test_data_dir(), &["parser/fuzz-failures"]) {
|
2019-03-21 17:05:12 +00:00
|
|
|
fuzz::check_parser(&text)
|
2018-09-10 18:14:09 +00:00
|
|
|
}
|
2018-08-25 11:45:17 +00:00
|
|
|
}
|
|
|
|
|
2019-03-21 17:06:48 +00:00
|
|
|
#[test]
|
|
|
|
fn reparse_fuzz_tests() {
|
2020-04-06 11:04:26 +00:00
|
|
|
for (_, text) in collect_rust_files(&test_data_dir(), &["reparse/fuzz-failures"]) {
|
2019-03-21 17:06:48 +00:00
|
|
|
let check = fuzz::CheckReparse::from_data(text.as_bytes()).unwrap();
|
|
|
|
check.run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-11 14:15:20 +00:00
|
|
|
/// Test that Rust-analyzer can parse and validate the rust-analyzer
|
2018-12-09 19:19:23 +00:00
|
|
|
#[test]
|
|
|
|
fn self_hosting_parsing() {
|
2021-07-03 19:11:03 +00:00
|
|
|
let crates_dir = project_root().join("crates");
|
|
|
|
|
|
|
|
let mut files = ::sourcegen::list_rust_files(&crates_dir);
|
|
|
|
files.retain(|path| {
|
|
|
|
// Get all files which are not in the crates/syntax/test_data folder
|
|
|
|
!path.components().any(|component| component.as_os_str() == "test_data")
|
|
|
|
});
|
|
|
|
|
2018-12-19 21:19:32 +00:00
|
|
|
assert!(
|
2020-07-11 10:56:44 +00:00
|
|
|
files.len() > 100,
|
2018-12-19 21:19:32 +00:00
|
|
|
"self_hosting_parsing found too few files - is it running in the right directory?"
|
2020-07-11 10:56:44 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
let errors = files
|
|
|
|
.into_par_iter()
|
|
|
|
.filter_map(|file| {
|
|
|
|
let text = read_text(&file);
|
|
|
|
match SourceFile::parse(&text).ok() {
|
|
|
|
Ok(_) => None,
|
|
|
|
Err(err) => Some((file, err)),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
if !errors.is_empty() {
|
|
|
|
let errors = errors
|
|
|
|
.into_iter()
|
2021-07-04 08:20:31 +00:00
|
|
|
.map(|(path, err)| format!("{}: {:?}\n", path.display(), err[0]))
|
2020-07-11 10:56:44 +00:00
|
|
|
.collect::<String>();
|
2022-12-23 18:42:58 +00:00
|
|
|
panic!("Parsing errors:\n{errors}\n");
|
2020-07-11 10:56:44 +00:00
|
|
|
}
|
2018-12-09 19:19:23 +00:00
|
|
|
}
|
2018-01-07 11:56:08 +00:00
|
|
|
|
|
|
|
fn test_data_dir() -> PathBuf {
|
2021-03-08 17:22:33 +00:00
|
|
|
project_root().join("crates/syntax/test_data")
|
2018-08-11 07:03:03 +00:00
|
|
|
}
|
|
|
|
|
2020-02-01 20:25:01 +00:00
|
|
|
fn assert_errors_are_present(errors: &[SyntaxError], path: &Path) {
|
|
|
|
assert!(!errors.is_empty(), "There should be errors in the file {:?}", path.display());
|
|
|
|
}
|
|
|
|
|
2020-07-01 10:30:17 +00:00
|
|
|
/// Calls callback `f` with input code and file paths for each `.rs` file in `test_data_dir`
|
|
|
|
/// subdirectories defined by `paths`.
|
|
|
|
///
|
|
|
|
/// If the content of the matching output file differs from the output of `f()`
|
|
|
|
/// the test will fail.
|
|
|
|
///
|
|
|
|
/// If there is no matching output file it will be created and filled with the
|
|
|
|
/// output of `f()`, but the test will fail.
|
|
|
|
fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], outfile_extension: &str, f: F)
|
|
|
|
where
|
|
|
|
F: Fn(&str, &Path) -> String,
|
|
|
|
{
|
|
|
|
for (path, input_code) in collect_rust_files(test_data_dir, paths) {
|
|
|
|
let actual = f(&input_code, &path);
|
|
|
|
let path = path.with_extension(outfile_extension);
|
2020-07-09 09:47:27 +00:00
|
|
|
expect_file![path].assert_eq(&actual)
|
2020-07-01 10:30:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Collects all `.rs` files from `dir` subdirectories defined by `paths`.
|
|
|
|
fn collect_rust_files(root_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> {
|
|
|
|
paths
|
|
|
|
.iter()
|
|
|
|
.flat_map(|path| {
|
|
|
|
let path = root_dir.to_owned().join(path);
|
|
|
|
rust_files_in_dir(&path).into_iter()
|
|
|
|
})
|
|
|
|
.map(|path| {
|
|
|
|
let text = read_text(&path);
|
|
|
|
(path, text)
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Collects paths to all `.rs` files from `dir` in a sorted `Vec<PathBuf>`.
|
|
|
|
fn rust_files_in_dir(dir: &Path) -> Vec<PathBuf> {
|
|
|
|
let mut acc = Vec::new();
|
2022-12-30 08:05:03 +00:00
|
|
|
for file in fs::read_dir(dir).unwrap() {
|
2020-07-01 10:30:17 +00:00
|
|
|
let file = file.unwrap();
|
|
|
|
let path = file.path();
|
|
|
|
if path.extension().unwrap_or_default() == "rs" {
|
|
|
|
acc.push(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
acc.sort();
|
|
|
|
acc
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Read file and normalize newlines.
|
|
|
|
///
|
|
|
|
/// `rustc` seems to always normalize `\r\n` newlines to `\n`:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// let s = "
|
|
|
|
/// ";
|
|
|
|
/// assert_eq!(s.as_bytes(), &[10]);
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// so this should always be correct.
|
|
|
|
fn read_text(path: &Path) -> String {
|
|
|
|
fs::read_to_string(path)
|
2022-12-23 18:42:58 +00:00
|
|
|
.unwrap_or_else(|_| panic!("File at {path:?} should be valid"))
|
2020-07-01 10:30:17 +00:00
|
|
|
.replace("\r\n", "\n")
|
|
|
|
}
|