diff options
-rw-r--r-- | crates/test_utils/src/lib.rs | 78 | ||||
-rw-r--r-- | crates/test_utils/src/marks.rs | 2 |
2 files changed, 56 insertions, 24 deletions
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 659f77b71..265fcf8da 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -21,6 +21,12 @@ pub use difference::Changeset as __Changeset; | |||
21 | 21 | ||
22 | pub const CURSOR_MARKER: &str = "<|>"; | 22 | pub const CURSOR_MARKER: &str = "<|>"; |
23 | 23 | ||
24 | /// Asserts that two strings are equal, otherwise displays a rich diff between them. | ||
25 | /// | ||
26 | /// The diff shows changes from the "original" left string to the "actual" right string. | ||
27 | /// | ||
28 | /// All arguments starting from and including the 3rd one are passed to | ||
29 | /// `eprintln!()` macro in case of text inequality. | ||
24 | #[macro_export] | 30 | #[macro_export] |
25 | macro_rules! assert_eq_text { | 31 | macro_rules! assert_eq_text { |
26 | ($left:expr, $right:expr) => { | 32 | ($left:expr, $right:expr) => { |
@@ -42,6 +48,7 @@ macro_rules! assert_eq_text { | |||
42 | }}; | 48 | }}; |
43 | } | 49 | } |
44 | 50 | ||
51 | /// Infallible version of `try_extract_offset()`. | ||
45 | pub fn extract_offset(text: &str) -> (TextUnit, String) { | 52 | pub fn extract_offset(text: &str) -> (TextUnit, String) { |
46 | match try_extract_offset(text) { | 53 | match try_extract_offset(text) { |
47 | None => panic!("text should contain cursor marker"), | 54 | None => panic!("text should contain cursor marker"), |
@@ -49,6 +56,8 @@ pub fn extract_offset(text: &str) -> (TextUnit, String) { | |||
49 | } | 56 | } |
50 | } | 57 | } |
51 | 58 | ||
59 | /// Returns the offset of the first occurence of `<|>` marker and the copy of `text` | ||
60 | /// without the marker. | ||
52 | fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> { | 61 | fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> { |
53 | let cursor_pos = text.find(CURSOR_MARKER)?; | 62 | let cursor_pos = text.find(CURSOR_MARKER)?; |
54 | let mut new_text = String::with_capacity(text.len() - CURSOR_MARKER.len()); | 63 | let mut new_text = String::with_capacity(text.len() - CURSOR_MARKER.len()); |
@@ -58,6 +67,7 @@ fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> { | |||
58 | Some((cursor_pos, new_text)) | 67 | Some((cursor_pos, new_text)) |
59 | } | 68 | } |
60 | 69 | ||
70 | /// Infallible version of `try_extract_range()`. | ||
61 | pub fn extract_range(text: &str) -> (TextRange, String) { | 71 | pub fn extract_range(text: &str) -> (TextRange, String) { |
62 | match try_extract_range(text) { | 72 | match try_extract_range(text) { |
63 | None => panic!("text should contain cursor marker"), | 73 | None => panic!("text should contain cursor marker"), |
@@ -65,6 +75,8 @@ pub fn extract_range(text: &str) -> (TextRange, String) { | |||
65 | } | 75 | } |
66 | } | 76 | } |
67 | 77 | ||
78 | /// Returns `TextRange` between the first two markers `<|>...<|>` and the copy | ||
79 | /// of `text` without both of these markers. | ||
68 | fn try_extract_range(text: &str) -> Option<(TextRange, String)> { | 80 | fn try_extract_range(text: &str) -> Option<(TextRange, String)> { |
69 | let (start, text) = try_extract_offset(text)?; | 81 | let (start, text) = try_extract_offset(text)?; |
70 | let (end, text) = try_extract_offset(&text)?; | 82 | let (end, text) = try_extract_offset(&text)?; |
@@ -85,6 +97,11 @@ impl From<RangeOrOffset> for TextRange { | |||
85 | } | 97 | } |
86 | } | 98 | } |
87 | 99 | ||
100 | /// Extracts `TextRange` or `TextUnit` depending on the amount of `<|>` markers | ||
101 | /// found in `text`. | ||
102 | /// | ||
103 | /// # Panics | ||
104 | /// Panics if no `<|>` marker is present in the `text`. | ||
88 | pub fn extract_range_or_offset(text: &str) -> (RangeOrOffset, String) { | 105 | pub fn extract_range_or_offset(text: &str) -> (RangeOrOffset, String) { |
89 | if let Some((range, text)) = try_extract_range(text) { | 106 | if let Some((range, text)) = try_extract_range(text) { |
90 | return (RangeOrOffset::Range(range), text); | 107 | return (RangeOrOffset::Range(range), text); |
@@ -93,7 +110,7 @@ pub fn extract_range_or_offset(text: &str) -> (RangeOrOffset, String) { | |||
93 | (RangeOrOffset::Offset(offset), text) | 110 | (RangeOrOffset::Offset(offset), text) |
94 | } | 111 | } |
95 | 112 | ||
96 | /// Extracts ranges, marked with `<tag> </tag>` paris from the `text` | 113 | /// Extracts ranges, marked with `<tag> </tag>` pairs from the `text` |
97 | pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec<TextRange>, String) { | 114 | pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec<TextRange>, String) { |
98 | let open = format!("<{}>", tag); | 115 | let open = format!("<{}>", tag); |
99 | let close = format!("</{}>", tag); | 116 | let close = format!("</{}>", tag); |
@@ -127,9 +144,9 @@ pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec<TextRange>, String) { | |||
127 | (ranges, res) | 144 | (ranges, res) |
128 | } | 145 | } |
129 | 146 | ||
147 | /// Inserts `<|>` marker into the `text` at `offset`. | ||
130 | pub fn add_cursor(text: &str, offset: TextUnit) -> String { | 148 | pub fn add_cursor(text: &str, offset: TextUnit) -> String { |
131 | let offset: u32 = offset.into(); | 149 | let offset: usize = offset.to_usize(); |
132 | let offset: usize = offset as usize; | ||
133 | let mut res = String::new(); | 150 | let mut res = String::new(); |
134 | res.push_str(&text[..offset]); | 151 | res.push_str(&text[..offset]); |
135 | res.push_str("<|>"); | 152 | res.push_str("<|>"); |
@@ -152,19 +169,6 @@ pub struct FixtureEntry { | |||
152 | /// // - other meta | 169 | /// // - other meta |
153 | /// ``` | 170 | /// ``` |
154 | pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { | 171 | pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { |
155 | let mut res = Vec::new(); | ||
156 | let mut buf = String::new(); | ||
157 | let mut meta: Option<&str> = None; | ||
158 | |||
159 | macro_rules! flush { | ||
160 | () => { | ||
161 | if let Some(meta) = meta { | ||
162 | res.push(FixtureEntry { meta: meta.to_string(), text: buf.clone() }); | ||
163 | buf.clear(); | ||
164 | } | ||
165 | }; | ||
166 | }; | ||
167 | |||
168 | let margin = fixture | 172 | let margin = fixture |
169 | .lines() | 173 | .lines() |
170 | .filter(|it| it.trim_start().starts_with("//-")) | 174 | .filter(|it| it.trim_start().starts_with("//-")) |
@@ -184,6 +188,19 @@ pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { | |||
184 | } | 188 | } |
185 | }); | 189 | }); |
186 | 190 | ||
191 | let mut res = Vec::new(); | ||
192 | let mut buf = String::new(); | ||
193 | let mut meta: Option<&str> = None; | ||
194 | |||
195 | macro_rules! flush { | ||
196 | () => { | ||
197 | if let Some(meta) = meta { | ||
198 | res.push(FixtureEntry { meta: meta.to_string(), text: buf.clone() }); | ||
199 | buf.clear(); | ||
200 | } | ||
201 | }; | ||
202 | }; | ||
203 | |||
187 | for line in lines { | 204 | for line in lines { |
188 | if line.starts_with("//-") { | 205 | if line.starts_with("//-") { |
189 | flush!(); | 206 | flush!(); |
@@ -236,11 +253,10 @@ fn lines_match_works() { | |||
236 | assert!(!lines_match("b", "cb")); | 253 | assert!(!lines_match("b", "cb")); |
237 | } | 254 | } |
238 | 255 | ||
239 | // Compares JSON object for approximate equality. | 256 | /// Compares JSON object for approximate equality. |
240 | // You can use `[..]` wildcard in strings (useful for OS dependent things such | 257 | /// You can use `[..]` wildcard in strings (useful for OS dependent things such |
241 | // as paths). You can use a `"{...}"` string literal as a wildcard for | 258 | /// as paths). You can use a `"{...}"` string literal as a wildcard for |
242 | // arbitrary nested JSON (useful for parts of object emitted by other programs | 259 | /// arbitrary nested JSON. Arrays are sorted before comparison. |
243 | // (e.g. rustc) rather than Cargo itself). Arrays are sorted before comparison. | ||
244 | pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> { | 260 | pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> { |
245 | use serde_json::Value::*; | 261 | use serde_json::Value::*; |
246 | match (expected, actual) { | 262 | match (expected, actual) { |
@@ -286,6 +302,14 @@ pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a | |||
286 | } | 302 | } |
287 | } | 303 | } |
288 | 304 | ||
305 | /// Calls callback `f` with input code and file paths of all `.rs` files from `test_data_dir` | ||
306 | /// subdirectories defined by `paths`. | ||
307 | /// | ||
308 | /// If the content of the matching `.txt` file differs from the output of `f()` | ||
309 | /// the test will fail. | ||
310 | /// | ||
311 | /// If there is no matching `.txt` file it will be created and filled with the | ||
312 | /// output of `f()`, but the test will fail. | ||
289 | pub fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], f: F) | 313 | pub fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], f: F) |
290 | where | 314 | where |
291 | F: Fn(&str, &Path) -> String, | 315 | F: Fn(&str, &Path) -> String, |
@@ -307,6 +331,7 @@ where | |||
307 | } | 331 | } |
308 | } | 332 | } |
309 | 333 | ||
334 | /// Collects all `.rs` files from `test_data_dir` subdirectories defined by `paths`. | ||
310 | pub fn collect_tests(test_data_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> { | 335 | pub fn collect_tests(test_data_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> { |
311 | paths | 336 | paths |
312 | .iter() | 337 | .iter() |
@@ -321,6 +346,7 @@ pub fn collect_tests(test_data_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, Stri | |||
321 | .collect() | 346 | .collect() |
322 | } | 347 | } |
323 | 348 | ||
349 | /// Collects paths to all `.rs` files from `dir` in a sorted `Vec<PathBuf>`. | ||
324 | fn test_from_dir(dir: &Path) -> Vec<PathBuf> { | 350 | fn test_from_dir(dir: &Path) -> Vec<PathBuf> { |
325 | let mut acc = Vec::new(); | 351 | let mut acc = Vec::new(); |
326 | for file in fs::read_dir(&dir).unwrap() { | 352 | for file in fs::read_dir(&dir).unwrap() { |
@@ -334,6 +360,7 @@ fn test_from_dir(dir: &Path) -> Vec<PathBuf> { | |||
334 | acc | 360 | acc |
335 | } | 361 | } |
336 | 362 | ||
363 | /// Returns the path to the root directory of `rust-analyzer` project. | ||
337 | pub fn project_dir() -> PathBuf { | 364 | pub fn project_dir() -> PathBuf { |
338 | let dir = env!("CARGO_MANIFEST_DIR"); | 365 | let dir = env!("CARGO_MANIFEST_DIR"); |
339 | PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned() | 366 | PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned() |
@@ -356,6 +383,9 @@ pub fn read_text(path: &Path) -> String { | |||
356 | .replace("\r\n", "\n") | 383 | .replace("\r\n", "\n") |
357 | } | 384 | } |
358 | 385 | ||
386 | /// Returns `false` if slow tests should not run, otherwise returns `true` and | ||
387 | /// also creates a file at `./target/.slow_tests_cookie` which serves as a flag | ||
388 | /// that slow tests did run. | ||
359 | pub fn skip_slow_tests() -> bool { | 389 | pub fn skip_slow_tests() -> bool { |
360 | let should_skip = std::env::var("CI").is_err() && std::env::var("RUN_SLOW_TESTS").is_err(); | 390 | let should_skip = std::env::var("CI").is_err() && std::env::var("RUN_SLOW_TESTS").is_err(); |
361 | if should_skip { | 391 | if should_skip { |
@@ -367,8 +397,9 @@ pub fn skip_slow_tests() -> bool { | |||
367 | should_skip | 397 | should_skip |
368 | } | 398 | } |
369 | 399 | ||
370 | const REWRITE: bool = false; | 400 | /// Asserts that `expected` and `actual` strings are equal. If they differ only |
371 | 401 | /// in trailing or leading whitespace the test won't fail and | |
402 | /// the contents of `actual` will be written to the file located at `path`. | ||
372 | fn assert_equal_text(expected: &str, actual: &str, path: &Path) { | 403 | fn assert_equal_text(expected: &str, actual: &str, path: &Path) { |
373 | if expected == actual { | 404 | if expected == actual { |
374 | return; | 405 | return; |
@@ -381,6 +412,7 @@ fn assert_equal_text(expected: &str, actual: &str, path: &Path) { | |||
381 | fs::write(path, actual).unwrap(); | 412 | fs::write(path, actual).unwrap(); |
382 | return; | 413 | return; |
383 | } | 414 | } |
415 | const REWRITE: bool = false; | ||
384 | if REWRITE { | 416 | if REWRITE { |
385 | println!("rewriting {}", pretty_path.display()); | 417 | println!("rewriting {}", pretty_path.display()); |
386 | fs::write(path, actual).unwrap(); | 418 | fs::write(path, actual).unwrap(); |
diff --git a/crates/test_utils/src/marks.rs b/crates/test_utils/src/marks.rs index fe1813947..f8fabfaff 100644 --- a/crates/test_utils/src/marks.rs +++ b/crates/test_utils/src/marks.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | //! This module implements manually tracked test coverage, which useful for | 1 | //! This module implements manually tracked test coverage, which is useful for |
2 | //! quickly finding a test responsible for testing a particular bit of code. | 2 | //! quickly finding a test responsible for testing a particular bit of code. |
3 | //! | 3 | //! |
4 | //! See <https://matklad.github.io/2018/06/18/a-trick-for-test-maintenance.html> | 4 | //! See <https://matklad.github.io/2018/06/18/a-trick-for-test-maintenance.html> |