aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/expect/src/lib.rs97
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs22
-rw-r--r--crates/ra_ide/test_data/highlight_doctest.html (renamed from crates/ra_ide/src/snapshots/highlight_doctest.html)0
-rw-r--r--crates/ra_ide/test_data/highlight_injection.html (renamed from crates/ra_ide/src/snapshots/highlight_injection.html)0
-rw-r--r--crates/ra_ide/test_data/highlight_strings.html (renamed from crates/ra_ide/src/snapshots/highlight_strings.html)0
-rw-r--r--crates/ra_ide/test_data/highlight_unsafe.html (renamed from crates/ra_ide/src/snapshots/highlight_unsafe.html)0
-rw-r--r--crates/ra_ide/test_data/highlighting.html (renamed from crates/ra_ide/src/snapshots/highlighting.html)0
-rw-r--r--crates/ra_ide/test_data/rainbow_highlighting.html (renamed from crates/ra_ide/src/snapshots/rainbow_highlighting.html)0
-rw-r--r--crates/ra_syntax/src/tests.rs100
-rw-r--r--crates/rust-analyzer/src/handlers.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop.rs34
-rw-r--r--crates/test_utils/src/lib.rs106
12 files changed, 209 insertions, 152 deletions
diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs
index dd7b96aab..a5e26fade 100644
--- a/crates/expect/src/lib.rs
+++ b/crates/expect/src/lib.rs
@@ -2,7 +2,7 @@
2//! https://github.com/rust-analyzer/rust-analyzer/pull/5101 2//! https://github.com/rust-analyzer/rust-analyzer/pull/5101
3use std::{ 3use std::{
4 collections::HashMap, 4 collections::HashMap,
5 env, fmt, fs, 5 env, fmt, fs, mem,
6 ops::Range, 6 ops::Range,
7 panic, 7 panic,
8 path::{Path, PathBuf}, 8 path::{Path, PathBuf},
@@ -14,7 +14,7 @@ use once_cell::sync::Lazy;
14use stdx::{lines_with_ends, trim_indent}; 14use stdx::{lines_with_ends, trim_indent};
15 15
16const HELP: &str = " 16const HELP: &str = "
17You can update all `expect![[]]` tests by: 17You can update all `expect![[]]` tests by running:
18 18
19 env UPDATE_EXPECT=1 cargo test 19 env UPDATE_EXPECT=1 cargo test
20 20
@@ -25,24 +25,48 @@ fn update_expect() -> bool {
25 env::var("UPDATE_EXPECT").is_ok() 25 env::var("UPDATE_EXPECT").is_ok()
26} 26}
27 27
28/// expect![[""]] 28/// expect![[r#"inline snapshot"#]]
29#[macro_export] 29#[macro_export]
30macro_rules! expect { 30macro_rules! expect {
31 [[$lit:literal]] => {$crate::Expect { 31 [[$data:literal]] => {$crate::Expect {
32 file: file!(), 32 position: $crate::Position {
33 line: line!(), 33 file: file!(),
34 column: column!(), 34 line: line!(),
35 data: $lit, 35 column: column!(),
36 },
37 data: $data,
36 }}; 38 }};
37 [[]] => { $crate::expect![[""]] }; 39 [[]] => { $crate::expect![[""]] };
38} 40}
39 41
42/// expect_file!["/crates/foo/test_data/bar.html"]
43#[macro_export]
44macro_rules! expect_file {
45 [$path:literal] => {$crate::ExpectFile { path: $path }};
46}
47
40#[derive(Debug)] 48#[derive(Debug)]
41pub struct Expect { 49pub struct Expect {
50 pub position: Position,
51 pub data: &'static str,
52}
53
54#[derive(Debug)]
55pub struct ExpectFile {
56 pub path: &'static str,
57}
58
59#[derive(Debug)]
60pub struct Position {
42 pub file: &'static str, 61 pub file: &'static str,
43 pub line: u32, 62 pub line: u32,
44 pub column: u32, 63 pub column: u32,
45 pub data: &'static str, 64}
65
66impl fmt::Display for Position {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "{}:{}:{}", self.file, self.line, self.column)
69 }
46} 70}
47 71
48impl Expect { 72impl Expect {
@@ -51,7 +75,7 @@ impl Expect {
51 if &trimmed == actual { 75 if &trimmed == actual {
52 return; 76 return;
53 } 77 }
54 Runtime::fail(self, &trimmed, actual); 78 Runtime::fail_expect(self, &trimmed, actual);
55 } 79 }
56 pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { 80 pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) {
57 let actual = format!("{:#?}\n", actual); 81 let actual = format!("{:#?}\n", actual);
@@ -69,7 +93,7 @@ impl Expect {
69 let mut target_line = None; 93 let mut target_line = None;
70 let mut line_start = 0; 94 let mut line_start = 0;
71 for (i, line) in lines_with_ends(file).enumerate() { 95 for (i, line) in lines_with_ends(file).enumerate() {
72 if i == self.line as usize - 1 { 96 if i == self.position.line as usize - 1 {
73 let pat = "expect![["; 97 let pat = "expect![[";
74 let offset = line.find(pat).unwrap(); 98 let offset = line.find(pat).unwrap();
75 let literal_start = line_start + offset + pat.len(); 99 let literal_start = line_start + offset + pat.len();
@@ -87,6 +111,25 @@ impl Expect {
87 } 111 }
88} 112}
89 113
114impl ExpectFile {
115 pub fn assert_eq(&self, actual: &str) {
116 let expected = self.read();
117 if actual == expected {
118 return;
119 }
120 Runtime::fail_file(self, &expected, actual);
121 }
122 fn read(&self) -> String {
123 fs::read_to_string(self.abs_path()).unwrap_or_default().replace("\r\n", "\n")
124 }
125 fn write(&self, contents: &str) {
126 fs::write(self.abs_path(), contents).unwrap()
127 }
128 fn abs_path(&self) -> PathBuf {
129 workspace_root().join(self.path)
130 }
131}
132
90#[derive(Default)] 133#[derive(Default)]
91struct Runtime { 134struct Runtime {
92 help_printed: bool, 135 help_printed: bool,
@@ -95,27 +138,39 @@ struct Runtime {
95static RT: Lazy<Mutex<Runtime>> = Lazy::new(Default::default); 138static RT: Lazy<Mutex<Runtime>> = Lazy::new(Default::default);
96 139
97impl Runtime { 140impl Runtime {
98 fn fail(expect: &Expect, expected: &str, actual: &str) { 141 fn fail_expect(expect: &Expect, expected: &str, actual: &str) {
99 let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); 142 let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
100 let mut updated = "";
101 if update_expect() { 143 if update_expect() {
102 updated = " (updated)"; 144 println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.position);
103 rt.per_file 145 rt.per_file
104 .entry(expect.file) 146 .entry(expect.position.file)
105 .or_insert_with(|| FileRuntime::new(expect)) 147 .or_insert_with(|| FileRuntime::new(expect))
106 .update(expect, actual); 148 .update(expect, actual);
149 return;
107 } 150 }
108 let print_help = !rt.help_printed && !update_expect(); 151 rt.panic(expect.position.to_string(), expected, actual);
109 rt.help_printed = true; 152 }
153
154 fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) {
155 let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
156 if update_expect() {
157 println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path);
158 expect.write(actual);
159 return;
160 }
161 rt.panic(expect.path.to_string(), expected, actual);
162 }
110 163
164 fn panic(&mut self, position: String, expected: &str, actual: &str) {
165 let print_help = !mem::replace(&mut self.help_printed, true);
111 let help = if print_help { HELP } else { "" }; 166 let help = if print_help { HELP } else { "" };
112 167
113 let diff = Changeset::new(actual, expected, "\n"); 168 let diff = Changeset::new(actual, expected, "\n");
114 169
115 println!( 170 println!(
116 "\n 171 "\n
117\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m{} 172\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m
118 \x1b[1m\x1b[34m-->\x1b[0m {}:{}:{} 173 \x1b[1m\x1b[34m-->\x1b[0m {}
119{} 174{}
120\x1b[1mExpect\x1b[0m: 175\x1b[1mExpect\x1b[0m:
121---- 176----
@@ -132,7 +187,7 @@ impl Runtime {
132{} 187{}
133---- 188----
134", 189",
135 updated, expect.file, expect.line, expect.column, help, expected, actual, diff 190 position, help, expected, actual, diff
136 ); 191 );
137 // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. 192 // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise.
138 panic::resume_unwind(Box::new(())); 193 panic::resume_unwind(Box::new(()));
@@ -147,7 +202,7 @@ struct FileRuntime {
147 202
148impl FileRuntime { 203impl FileRuntime {
149 fn new(expect: &Expect) -> FileRuntime { 204 fn new(expect: &Expect) -> FileRuntime {
150 let path = workspace_root().join(expect.file); 205 let path = workspace_root().join(expect.position.file);
151 let original_text = fs::read_to_string(&path).unwrap(); 206 let original_text = fs::read_to_string(&path).unwrap();
152 let patchwork = Patchwork::new(original_text.clone()); 207 let patchwork = Patchwork::new(original_text.clone());
153 FileRuntime { path, original_text, patchwork } 208 FileRuntime { path, original_text, patchwork }
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index b7fad9719..aa7c887d6 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -1,6 +1,7 @@
1use std::fs; 1use std::fs;
2 2
3use test_utils::{assert_eq_text, project_dir, read_text}; 3use expect::{expect_file, ExpectFile};
4use test_utils::project_dir;
4 5
5use crate::{mock_analysis::single_file, FileRange, TextRange}; 6use crate::{mock_analysis::single_file, FileRange, TextRange};
6 7
@@ -91,7 +92,7 @@ impl<T> Option<T> {
91} 92}
92"# 93"#
93 .trim(), 94 .trim(),
94 "crates/ra_ide/src/snapshots/highlighting.html", 95 expect_file!["crates/ra_ide/test_data/highlighting.html"],
95 false, 96 false,
96 ); 97 );
97} 98}
@@ -114,7 +115,7 @@ fn bar() {
114} 115}
115"# 116"#
116 .trim(), 117 .trim(),
117 "crates/ra_ide/src/snapshots/rainbow_highlighting.html", 118 expect_file!["crates/ra_ide/test_data/rainbow_highlighting.html"],
118 true, 119 true,
119 ); 120 );
120} 121}
@@ -167,7 +168,7 @@ fn main() {
167 ); 168 );
168}"## 169}"##
169 .trim(), 170 .trim(),
170 "crates/ra_ide/src/snapshots/highlight_injection.html", 171 expect_file!["crates/ra_ide/test_data/highlight_injection.html"],
171 false, 172 false,
172 ); 173 );
173} 174}
@@ -250,7 +251,7 @@ fn main() {
250 println!("{ничоси}", ничоси = 92); 251 println!("{ничоси}", ничоси = 92);
251}"# 252}"#
252 .trim(), 253 .trim(),
253 "crates/ra_ide/src/snapshots/highlight_strings.html", 254 expect_file!["crates/ra_ide/test_data/highlight_strings.html"],
254 false, 255 false,
255 ); 256 );
256} 257}
@@ -278,7 +279,7 @@ fn main() {
278} 279}
279"# 280"#
280 .trim(), 281 .trim(),
281 "crates/ra_ide/src/snapshots/highlight_unsafe.html", 282 expect_file!["crates/ra_ide/test_data/highlight_unsafe.html"],
282 false, 283 false,
283 ); 284 );
284} 285}
@@ -354,7 +355,7 @@ macro_rules! noop {
354} 355}
355"# 356"#
356 .trim(), 357 .trim(),
357 "crates/ra_ide/src/snapshots/highlight_doctest.html", 358 expect_file!["crates/ra_ide/test_data/highlight_doctest.html"],
358 false, 359 false,
359 ); 360 );
360} 361}
@@ -362,11 +363,8 @@ macro_rules! noop {
362/// Highlights the code given by the `ra_fixture` argument, renders the 363/// Highlights the code given by the `ra_fixture` argument, renders the
363/// result as HTML, and compares it with the HTML file given as `snapshot`. 364/// result as HTML, and compares it with the HTML file given as `snapshot`.
364/// Note that the `snapshot` file is overwritten by the rendered HTML. 365/// Note that the `snapshot` file is overwritten by the rendered HTML.
365fn check_highlighting(ra_fixture: &str, snapshot: &str, rainbow: bool) { 366fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) {
366 let (analysis, file_id) = single_file(ra_fixture); 367 let (analysis, file_id) = single_file(ra_fixture);
367 let dst_file = project_dir().join(snapshot);
368 let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap(); 368 let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
369 let expected_html = &read_text(&dst_file); 369 expect.assert_eq(actual_html)
370 fs::write(dst_file, &actual_html).unwrap();
371 assert_eq_text!(expected_html, actual_html);
372} 370}
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/test_data/highlight_doctest.html
index e8155def7..e8155def7 100644
--- a/crates/ra_ide/src/snapshots/highlight_doctest.html
+++ b/crates/ra_ide/test_data/highlight_doctest.html
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/test_data/highlight_injection.html
index 1b0349bae..1b0349bae 100644
--- a/crates/ra_ide/src/snapshots/highlight_injection.html
+++ b/crates/ra_ide/test_data/highlight_injection.html
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/test_data/highlight_strings.html
index d184b5691..d184b5691 100644
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ b/crates/ra_ide/test_data/highlight_strings.html
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/test_data/highlight_unsafe.html
index 6936e949f..6936e949f 100644
--- a/crates/ra_ide/src/snapshots/highlight_unsafe.html
+++ b/crates/ra_ide/test_data/highlight_unsafe.html
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/test_data/highlighting.html
index 8d0b38f95..8d0b38f95 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/test_data/highlighting.html
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/test_data/rainbow_highlighting.html
index 9516c7441..9516c7441 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/test_data/rainbow_highlighting.html
diff --git a/crates/ra_syntax/src/tests.rs b/crates/ra_syntax/src/tests.rs
index 959967b79..7b4232497 100644
--- a/crates/ra_syntax/src/tests.rs
+++ b/crates/ra_syntax/src/tests.rs
@@ -1,9 +1,11 @@
1use std::{ 1use std::{
2 env,
2 fmt::Write, 3 fmt::Write,
4 fs,
3 path::{Component, Path, PathBuf}, 5 path::{Component, Path, PathBuf},
4}; 6};
5 7
6use test_utils::{collect_rust_files, dir_tests, project_dir, read_text}; 8use test_utils::{assert_eq_text, project_dir};
7 9
8use crate::{fuzz, tokenize, SourceFile, SyntaxError, TextRange, TextSize, Token}; 10use crate::{fuzz, tokenize, SourceFile, SyntaxError, TextRange, TextSize, Token};
9 11
@@ -200,3 +202,99 @@ where
200 } 202 }
201 }); 203 });
202} 204}
205
206/// Calls callback `f` with input code and file paths for each `.rs` file in `test_data_dir`
207/// subdirectories defined by `paths`.
208///
209/// If the content of the matching output file differs from the output of `f()`
210/// the test will fail.
211///
212/// If there is no matching output file it will be created and filled with the
213/// output of `f()`, but the test will fail.
214fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], outfile_extension: &str, f: F)
215where
216 F: Fn(&str, &Path) -> String,
217{
218 for (path, input_code) in collect_rust_files(test_data_dir, paths) {
219 let actual = f(&input_code, &path);
220 let path = path.with_extension(outfile_extension);
221 if !path.exists() {
222 println!("\nfile: {}", path.display());
223 println!("No .txt file with expected result, creating...\n");
224 println!("{}\n{}", input_code, actual);
225 fs::write(&path, &actual).unwrap();
226 panic!("No expected result");
227 }
228 let expected = read_text(&path);
229 assert_equal_text(&expected, &actual, &path);
230 }
231}
232
233/// Collects all `.rs` files from `dir` subdirectories defined by `paths`.
234fn collect_rust_files(root_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> {
235 paths
236 .iter()
237 .flat_map(|path| {
238 let path = root_dir.to_owned().join(path);
239 rust_files_in_dir(&path).into_iter()
240 })
241 .map(|path| {
242 let text = read_text(&path);
243 (path, text)
244 })
245 .collect()
246}
247
248/// Collects paths to all `.rs` files from `dir` in a sorted `Vec<PathBuf>`.
249fn rust_files_in_dir(dir: &Path) -> Vec<PathBuf> {
250 let mut acc = Vec::new();
251 for file in fs::read_dir(&dir).unwrap() {
252 let file = file.unwrap();
253 let path = file.path();
254 if path.extension().unwrap_or_default() == "rs" {
255 acc.push(path);
256 }
257 }
258 acc.sort();
259 acc
260}
261
262/// Asserts that `expected` and `actual` strings are equal. If they differ only
263/// in trailing or leading whitespace the test won't fail and
264/// the contents of `actual` will be written to the file located at `path`.
265fn assert_equal_text(expected: &str, actual: &str, path: &Path) {
266 if expected == actual {
267 return;
268 }
269 let dir = project_dir();
270 let pretty_path = path.strip_prefix(&dir).unwrap_or_else(|_| path);
271 if expected.trim() == actual.trim() {
272 println!("whitespace difference, rewriting");
273 println!("file: {}\n", pretty_path.display());
274 fs::write(path, actual).unwrap();
275 return;
276 }
277 if env::var("UPDATE_EXPECT").is_ok() {
278 println!("rewriting {}", pretty_path.display());
279 fs::write(path, actual).unwrap();
280 return;
281 }
282 assert_eq_text!(expected, actual, "file: {}", pretty_path.display());
283}
284
285/// Read file and normalize newlines.
286///
287/// `rustc` seems to always normalize `\r\n` newlines to `\n`:
288///
289/// ```
290/// let s = "
291/// ";
292/// assert_eq!(s.as_bytes(), &[10]);
293/// ```
294///
295/// so this should always be correct.
296fn read_text(path: &Path) -> String {
297 fs::read_to_string(path)
298 .unwrap_or_else(|_| panic!("File at {:?} should be valid", path))
299 .replace("\r\n", "\n")
300}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 25bcd80af..607a95682 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -415,7 +415,7 @@ pub(crate) fn handle_runnables(
415 let source_file = snap.analysis.parse(file_id)?; 415 let source_file = snap.analysis.parse(file_id)?;
416 algo::find_node_at_offset::<ast::MacroCall>(source_file.syntax(), offset) 416 algo::find_node_at_offset::<ast::MacroCall>(source_file.syntax(), offset)
417 .and_then(|it| it.path()?.segment()?.name_ref()) 417 .and_then(|it| it.path()?.segment()?.name_ref())
418 .map_or(false, |it| it.text() == "expect") 418 .map_or(false, |it| it.text() == "expect" || it.text() == "expect_file")
419 } 419 }
420 None => false, 420 None => false,
421 }; 421 };
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 9fd16ef3b..2ce1441b6 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -168,22 +168,24 @@ impl GlobalState {
168 } 168 }
169 } 169 }
170 vfs::loader::Message::Progress { n_total, n_done } => { 170 vfs::loader::Message::Progress { n_total, n_done } => {
171 let state = if n_done == 0 { 171 if n_total > 0 {
172 Progress::Begin 172 let state = if n_done == 0 {
173 } else if n_done < n_total { 173 Progress::Begin
174 Progress::Report 174 } else if n_done < n_total {
175 } else { 175 Progress::Report
176 assert_eq!(n_done, n_total); 176 } else {
177 self.status = Status::Ready; 177 assert_eq!(n_done, n_total);
178 became_ready = true; 178 self.status = Status::Ready;
179 Progress::End 179 became_ready = true;
180 }; 180 Progress::End
181 self.report_progress( 181 };
182 "roots scanned", 182 self.report_progress(
183 state, 183 "roots scanned",
184 Some(format!("{}/{}", n_done, n_total)), 184 state,
185 Some(Progress::percentage(n_done, n_total)), 185 Some(format!("{}/{}", n_done, n_total)),
186 ) 186 Some(Progress::percentage(n_done, n_total)),
187 )
188 }
187 } 189 }
188 }, 190 },
189 Event::Flycheck(task) => match task { 191 Event::Flycheck(task) => match task {
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index e32a0a0c3..fba5f4281 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -13,7 +13,7 @@ mod fixture;
13use std::{ 13use std::{
14 convert::{TryFrom, TryInto}, 14 convert::{TryFrom, TryInto},
15 env, fs, 15 env, fs,
16 path::{Path, PathBuf}, 16 path::PathBuf,
17}; 17};
18 18
19use serde_json::Value; 19use serde_json::Value;
@@ -299,85 +299,6 @@ pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a
299 } 299 }
300} 300}
301 301
302/// Calls callback `f` with input code and file paths for each `.rs` file in `test_data_dir`
303/// subdirectories defined by `paths`.
304///
305/// If the content of the matching output file differs from the output of `f()`
306/// the test will fail.
307///
308/// If there is no matching output file it will be created and filled with the
309/// output of `f()`, but the test will fail.
310pub fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], outfile_extension: &str, f: F)
311where
312 F: Fn(&str, &Path) -> String,
313{
314 for (path, input_code) in collect_rust_files(test_data_dir, paths) {
315 let actual = f(&input_code, &path);
316 let path = path.with_extension(outfile_extension);
317 if !path.exists() {
318 println!("\nfile: {}", path.display());
319 println!("No .txt file with expected result, creating...\n");
320 println!("{}\n{}", input_code, actual);
321 fs::write(&path, &actual).unwrap();
322 panic!("No expected result");
323 }
324 let expected = read_text(&path);
325 assert_equal_text(&expected, &actual, &path);
326 }
327}
328
329/// Collects all `.rs` files from `dir` subdirectories defined by `paths`.
330pub fn collect_rust_files(root_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> {
331 paths
332 .iter()
333 .flat_map(|path| {
334 let path = root_dir.to_owned().join(path);
335 rust_files_in_dir(&path).into_iter()
336 })
337 .map(|path| {
338 let text = read_text(&path);
339 (path, text)
340 })
341 .collect()
342}
343
344/// Collects paths to all `.rs` files from `dir` in a sorted `Vec<PathBuf>`.
345fn rust_files_in_dir(dir: &Path) -> Vec<PathBuf> {
346 let mut acc = Vec::new();
347 for file in fs::read_dir(&dir).unwrap() {
348 let file = file.unwrap();
349 let path = file.path();
350 if path.extension().unwrap_or_default() == "rs" {
351 acc.push(path);
352 }
353 }
354 acc.sort();
355 acc
356}
357
358/// Returns the path to the root directory of `rust-analyzer` project.
359pub fn project_dir() -> PathBuf {
360 let dir = env!("CARGO_MANIFEST_DIR");
361 PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned()
362}
363
364/// Read file and normalize newlines.
365///
366/// `rustc` seems to always normalize `\r\n` newlines to `\n`:
367///
368/// ```
369/// let s = "
370/// ";
371/// assert_eq!(s.as_bytes(), &[10]);
372/// ```
373///
374/// so this should always be correct.
375pub fn read_text(path: &Path) -> String {
376 fs::read_to_string(path)
377 .unwrap_or_else(|_| panic!("File at {:?} should be valid", path))
378 .replace("\r\n", "\n")
379}
380
381/// Returns `false` if slow tests should not run, otherwise returns `true` and 302/// Returns `false` if slow tests should not run, otherwise returns `true` and
382/// also creates a file at `./target/.slow_tests_cookie` which serves as a flag 303/// also creates a file at `./target/.slow_tests_cookie` which serves as a flag
383/// that slow tests did run. 304/// that slow tests did run.
@@ -392,25 +313,8 @@ pub fn skip_slow_tests() -> bool {
392 should_skip 313 should_skip
393} 314}
394 315
395/// Asserts that `expected` and `actual` strings are equal. If they differ only 316/// Returns the path to the root directory of `rust-analyzer` project.
396/// in trailing or leading whitespace the test won't fail and 317pub fn project_dir() -> PathBuf {
397/// the contents of `actual` will be written to the file located at `path`. 318 let dir = env!("CARGO_MANIFEST_DIR");
398fn assert_equal_text(expected: &str, actual: &str, path: &Path) { 319 PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned()
399 if expected == actual {
400 return;
401 }
402 let dir = project_dir();
403 let pretty_path = path.strip_prefix(&dir).unwrap_or_else(|_| path);
404 if expected.trim() == actual.trim() {
405 println!("whitespace difference, rewriting");
406 println!("file: {}\n", pretty_path.display());
407 fs::write(path, actual).unwrap();
408 return;
409 }
410 if env::var("UPDATE_EXPECTATIONS").is_ok() {
411 println!("rewriting {}", pretty_path.display());
412 fs::write(path, actual).unwrap();
413 return;
414 }
415 assert_eq_text!(expected, actual, "file: {}", pretty_path.display());
416} 320}