• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #[cfg(not(feature = "in-rust-tree"))]
2 mod ast_src;
3 #[cfg(not(feature = "in-rust-tree"))]
4 mod sourcegen_ast;
5 
6 use std::{
7     fs,
8     path::{Path, PathBuf},
9 };
10 
11 use ast::HasName;
12 use expect_test::expect_file;
13 use rayon::prelude::*;
14 use test_utils::{bench, bench_fixture, project_root};
15 
16 use crate::{ast, fuzz, AstNode, SourceFile, SyntaxError};
17 
18 #[test]
parse_smoke_test()19 fn parse_smoke_test() {
20     let code = r##"
21 fn main() {
22     println!("Hello, world!")
23 }
24     "##;
25 
26     let parse = SourceFile::parse(code);
27     // eprintln!("{:#?}", parse.syntax_node());
28     assert!(parse.ok().is_ok());
29 }
30 
31 #[test]
benchmark_parser()32 fn benchmark_parser() {
33     if std::env::var("RUN_SLOW_BENCHES").is_err() {
34         return;
35     }
36 
37     let data = bench_fixture::glorious_old_parser();
38     let tree = {
39         let _b = bench("parsing");
40         let p = SourceFile::parse(&data);
41         assert!(p.errors.is_empty());
42         assert_eq!(p.tree().syntax.text_range().len(), 352474.into());
43         p.tree()
44     };
45 
46     {
47         let _b = bench("tree traversal");
48         let fn_names =
49             tree.syntax().descendants().filter_map(ast::Fn::cast).filter_map(|f| f.name()).count();
50         assert_eq!(fn_names, 268);
51     }
52 }
53 
54 #[test]
validation_tests()55 fn validation_tests() {
56     dir_tests(&test_data_dir(), &["parser/validation"], "rast", |text, path| {
57         let parse = SourceFile::parse(text);
58         let errors = parse.errors();
59         assert_errors_are_present(errors, path);
60         parse.debug_dump()
61     });
62 }
63 
64 #[test]
parser_fuzz_tests()65 fn parser_fuzz_tests() {
66     for (_, text) in collect_rust_files(&test_data_dir(), &["parser/fuzz-failures"]) {
67         fuzz::check_parser(&text)
68     }
69 }
70 
71 #[test]
reparse_fuzz_tests()72 fn reparse_fuzz_tests() {
73     for (_, text) in collect_rust_files(&test_data_dir(), &["reparse/fuzz-failures"]) {
74         let check = fuzz::CheckReparse::from_data(text.as_bytes()).unwrap();
75         check.run();
76     }
77 }
78 
79 /// Test that Rust-analyzer can parse and validate the rust-analyzer
80 #[test]
self_hosting_parsing()81 fn self_hosting_parsing() {
82     let crates_dir = project_root().join("crates");
83 
84     let mut files = ::sourcegen::list_rust_files(&crates_dir);
85     files.retain(|path| {
86         // Get all files which are not in the crates/syntax/test_data folder
87         !path.components().any(|component| component.as_os_str() == "test_data")
88     });
89 
90     assert!(
91         files.len() > 100,
92         "self_hosting_parsing found too few files - is it running in the right directory?"
93     );
94 
95     let errors = files
96         .into_par_iter()
97         .filter_map(|file| {
98             let text = read_text(&file);
99             match SourceFile::parse(&text).ok() {
100                 Ok(_) => None,
101                 Err(err) => Some((file, err)),
102             }
103         })
104         .collect::<Vec<_>>();
105 
106     if !errors.is_empty() {
107         let errors = errors
108             .into_iter()
109             .map(|(path, err)| format!("{}: {:?}\n", path.display(), err[0]))
110             .collect::<String>();
111         panic!("Parsing errors:\n{errors}\n");
112     }
113 }
114 
test_data_dir() -> PathBuf115 fn test_data_dir() -> PathBuf {
116     project_root().join("crates/syntax/test_data")
117 }
118 
assert_errors_are_present(errors: &[SyntaxError], path: &Path)119 fn assert_errors_are_present(errors: &[SyntaxError], path: &Path) {
120     assert!(!errors.is_empty(), "There should be errors in the file {:?}", path.display());
121 }
122 
123 /// Calls callback `f` with input code and file paths for each `.rs` file in `test_data_dir`
124 /// subdirectories defined by `paths`.
125 ///
126 /// If the content of the matching output file differs from the output of `f()`
127 /// the test will fail.
128 ///
129 /// If there is no matching output file it will be created and filled with the
130 /// output of `f()`, but the test will fail.
dir_tests<F>(test_data_dir: &Path, paths: &[&str], outfile_extension: &str, f: F) where F: Fn(&str, &Path) -> String,131 fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], outfile_extension: &str, f: F)
132 where
133     F: Fn(&str, &Path) -> String,
134 {
135     for (path, input_code) in collect_rust_files(test_data_dir, paths) {
136         let actual = f(&input_code, &path);
137         let path = path.with_extension(outfile_extension);
138         expect_file![path].assert_eq(&actual)
139     }
140 }
141 
142 /// Collects all `.rs` files from `dir` subdirectories defined by `paths`.
collect_rust_files(root_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)>143 fn collect_rust_files(root_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> {
144     paths
145         .iter()
146         .flat_map(|path| {
147             let path = root_dir.to_owned().join(path);
148             rust_files_in_dir(&path).into_iter()
149         })
150         .map(|path| {
151             let text = read_text(&path);
152             (path, text)
153         })
154         .collect()
155 }
156 
157 /// Collects paths to all `.rs` files from `dir` in a sorted `Vec<PathBuf>`.
rust_files_in_dir(dir: &Path) -> Vec<PathBuf>158 fn rust_files_in_dir(dir: &Path) -> Vec<PathBuf> {
159     let mut acc = Vec::new();
160     for file in fs::read_dir(dir).unwrap() {
161         let file = file.unwrap();
162         let path = file.path();
163         if path.extension().unwrap_or_default() == "rs" {
164             acc.push(path);
165         }
166     }
167     acc.sort();
168     acc
169 }
170 
171 /// Read file and normalize newlines.
172 ///
173 /// `rustc` seems to always normalize `\r\n` newlines to `\n`:
174 ///
175 /// ```
176 /// let s = "
177 /// ";
178 /// assert_eq!(s.as_bytes(), &[10]);
179 /// ```
180 ///
181 /// so this should always be correct.
read_text(path: &Path) -> String182 fn read_text(path: &Path) -> String {
183     fs::read_to_string(path)
184         .unwrap_or_else(|_| panic!("File at {path:?} should be valid"))
185         .replace("\r\n", "\n")
186 }
187