diff options
Diffstat (limited to 'crates/test_utils/src')
-rw-r--r-- | crates/test_utils/src/fixture.rs | 142 | ||||
-rw-r--r-- | crates/test_utils/src/lib.rs | 296 |
2 files changed, 147 insertions, 291 deletions
diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs new file mode 100644 index 000000000..fad8f7e2c --- /dev/null +++ b/crates/test_utils/src/fixture.rs | |||
@@ -0,0 +1,142 @@ | |||
1 | //! Defines `Fixture` -- a convenient way to describe the initial state of | ||
2 | //! rust-analyzer database from a single string. | ||
3 | |||
4 | use rustc_hash::FxHashMap; | ||
5 | use stdx::{lines_with_ends, split_delim, trim_indent}; | ||
6 | |||
7 | #[derive(Debug, Eq, PartialEq)] | ||
8 | pub struct Fixture { | ||
9 | pub path: String, | ||
10 | pub text: String, | ||
11 | pub krate: Option<String>, | ||
12 | pub deps: Vec<String>, | ||
13 | pub cfg_atoms: Vec<String>, | ||
14 | pub cfg_key_values: Vec<(String, String)>, | ||
15 | pub edition: Option<String>, | ||
16 | pub env: FxHashMap<String, String>, | ||
17 | } | ||
18 | |||
19 | impl Fixture { | ||
20 | /// Parses text which looks like this: | ||
21 | /// | ||
22 | /// ```not_rust | ||
23 | /// //- some meta | ||
24 | /// line 1 | ||
25 | /// line 2 | ||
26 | /// // - other meta | ||
27 | /// ``` | ||
28 | pub fn parse(ra_fixture: &str) -> Vec<Fixture> { | ||
29 | let fixture = trim_indent(ra_fixture); | ||
30 | |||
31 | let mut res: Vec<Fixture> = Vec::new(); | ||
32 | |||
33 | let default = if ra_fixture.contains("//-") { None } else { Some("//- /main.rs") }; | ||
34 | |||
35 | for (ix, line) in default.into_iter().chain(lines_with_ends(&fixture)).enumerate() { | ||
36 | if line.contains("//-") { | ||
37 | assert!( | ||
38 | line.starts_with("//-"), | ||
39 | "Metadata line {} has invalid indentation. \ | ||
40 | All metadata lines need to have the same indentation.\n\ | ||
41 | The offending line: {:?}", | ||
42 | ix, | ||
43 | line | ||
44 | ); | ||
45 | } | ||
46 | |||
47 | if line.starts_with("//-") { | ||
48 | let meta = Fixture::parse_meta_line(line); | ||
49 | res.push(meta) | ||
50 | } else if let Some(entry) = res.last_mut() { | ||
51 | entry.text.push_str(line); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | res | ||
56 | } | ||
57 | |||
58 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo | ||
59 | fn parse_meta_line(meta: &str) -> Fixture { | ||
60 | assert!(meta.starts_with("//-")); | ||
61 | let meta = meta["//-".len()..].trim(); | ||
62 | let components = meta.split_ascii_whitespace().collect::<Vec<_>>(); | ||
63 | |||
64 | let path = components[0].to_string(); | ||
65 | assert!(path.starts_with("/")); | ||
66 | |||
67 | let mut krate = None; | ||
68 | let mut deps = Vec::new(); | ||
69 | let mut edition = None; | ||
70 | let mut cfg_atoms = Vec::new(); | ||
71 | let mut cfg_key_values = Vec::new(); | ||
72 | let mut env = FxHashMap::default(); | ||
73 | for component in components[1..].iter() { | ||
74 | let (key, value) = split_delim(component, ':').unwrap(); | ||
75 | match key { | ||
76 | "crate" => krate = Some(value.to_string()), | ||
77 | "deps" => deps = value.split(',').map(|it| it.to_string()).collect(), | ||
78 | "edition" => edition = Some(value.to_string()), | ||
79 | "cfg" => { | ||
80 | for entry in value.split(',') { | ||
81 | match split_delim(entry, '=') { | ||
82 | Some((k, v)) => cfg_key_values.push((k.to_string(), v.to_string())), | ||
83 | None => cfg_atoms.push(entry.to_string()), | ||
84 | } | ||
85 | } | ||
86 | } | ||
87 | "env" => { | ||
88 | for key in value.split(',') { | ||
89 | if let Some((k, v)) = split_delim(key, '=') { | ||
90 | env.insert(k.into(), v.into()); | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | _ => panic!("bad component: {:?}", component), | ||
95 | } | ||
96 | } | ||
97 | |||
98 | Fixture { | ||
99 | path, | ||
100 | text: String::new(), | ||
101 | krate: krate, | ||
102 | deps, | ||
103 | cfg_atoms, | ||
104 | cfg_key_values, | ||
105 | edition, | ||
106 | env, | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | |||
111 | #[test] | ||
112 | #[should_panic] | ||
113 | fn parse_fixture_checks_further_indented_metadata() { | ||
114 | Fixture::parse( | ||
115 | r" | ||
116 | //- /lib.rs | ||
117 | mod bar; | ||
118 | |||
119 | fn foo() {} | ||
120 | //- /bar.rs | ||
121 | pub fn baz() {} | ||
122 | ", | ||
123 | ); | ||
124 | } | ||
125 | |||
126 | #[test] | ||
127 | fn parse_fixture_gets_full_meta() { | ||
128 | let parsed = Fixture::parse( | ||
129 | r" | ||
130 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo | ||
131 | mod m; | ||
132 | ", | ||
133 | ); | ||
134 | assert_eq!(1, parsed.len()); | ||
135 | |||
136 | let meta = &parsed[0]; | ||
137 | assert_eq!("mod m;\n", meta.text); | ||
138 | |||
139 | assert_eq!("foo", meta.krate.as_ref().unwrap()); | ||
140 | assert_eq!("/lib.rs", meta.path); | ||
141 | assert_eq!(2, meta.env.len()); | ||
142 | } | ||
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 981565cd7..eaeeeb97b 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -8,6 +8,7 @@ | |||
8 | 8 | ||
9 | #[macro_use] | 9 | #[macro_use] |
10 | pub mod mark; | 10 | pub mod mark; |
11 | mod fixture; | ||
11 | 12 | ||
12 | use std::{ | 13 | use std::{ |
13 | env, fs, | 14 | env, fs, |
@@ -15,14 +16,12 @@ use std::{ | |||
15 | }; | 16 | }; |
16 | 17 | ||
17 | use serde_json::Value; | 18 | use serde_json::Value; |
18 | use stdx::split1; | ||
19 | use text_size::{TextRange, TextSize}; | 19 | use text_size::{TextRange, TextSize}; |
20 | 20 | ||
21 | pub use ra_cfg::CfgOptions; | 21 | pub use difference::Changeset as __Changeset; |
22 | pub use relative_path::{RelativePath, RelativePathBuf}; | ||
23 | pub use rustc_hash::FxHashMap; | 22 | pub use rustc_hash::FxHashMap; |
24 | 23 | ||
25 | pub use difference::Changeset as __Changeset; | 24 | pub use crate::fixture::Fixture; |
26 | 25 | ||
27 | pub const CURSOR_MARKER: &str = "<|>"; | 26 | pub const CURSOR_MARKER: &str = "<|>"; |
28 | 27 | ||
@@ -44,7 +43,7 @@ macro_rules! assert_eq_text { | |||
44 | if left.trim() == right.trim() { | 43 | if left.trim() == right.trim() { |
45 | eprintln!("Left:\n{:?}\n\nRight:\n{:?}\n\nWhitespace difference\n", left, right); | 44 | eprintln!("Left:\n{:?}\n\nRight:\n{:?}\n\nWhitespace difference\n", left, right); |
46 | } else { | 45 | } else { |
47 | let changeset = $crate::__Changeset::new(right, left, "\n"); | 46 | let changeset = $crate::__Changeset::new(left, right, "\n"); |
48 | eprintln!("Left:\n{}\n\nRight:\n{}\n\nDiff:\n{}\n", left, right, changeset); | 47 | eprintln!("Left:\n{}\n\nRight:\n{}\n\nDiff:\n{}\n", left, right, changeset); |
49 | } | 48 | } |
50 | eprintln!($($tt)*); | 49 | eprintln!($($tt)*); |
@@ -98,7 +97,7 @@ impl From<RangeOrOffset> for TextRange { | |||
98 | fn from(selection: RangeOrOffset) -> Self { | 97 | fn from(selection: RangeOrOffset) -> Self { |
99 | match selection { | 98 | match selection { |
100 | RangeOrOffset::Range(it) => it, | 99 | RangeOrOffset::Range(it) => it, |
101 | RangeOrOffset::Offset(it) => TextRange::new(it, it), | 100 | RangeOrOffset::Offset(it) => TextRange::empty(it), |
102 | } | 101 | } |
103 | } | 102 | } |
104 | } | 103 | } |
@@ -160,291 +159,6 @@ pub fn add_cursor(text: &str, offset: TextSize) -> String { | |||
160 | res | 159 | res |
161 | } | 160 | } |
162 | 161 | ||
163 | #[derive(Debug, Eq, PartialEq)] | ||
164 | pub struct FixtureEntry { | ||
165 | pub meta: FixtureMeta, | ||
166 | pub text: String, | ||
167 | } | ||
168 | |||
169 | #[derive(Debug, Eq, PartialEq)] | ||
170 | pub enum FixtureMeta { | ||
171 | Root { path: RelativePathBuf }, | ||
172 | File(FileMeta), | ||
173 | } | ||
174 | |||
175 | #[derive(Debug, Eq, PartialEq)] | ||
176 | pub struct FileMeta { | ||
177 | pub path: RelativePathBuf, | ||
178 | pub crate_name: Option<String>, | ||
179 | pub deps: Vec<String>, | ||
180 | pub cfg: CfgOptions, | ||
181 | pub edition: Option<String>, | ||
182 | pub env: FxHashMap<String, String>, | ||
183 | } | ||
184 | |||
185 | impl FixtureMeta { | ||
186 | pub fn path(&self) -> &RelativePath { | ||
187 | match self { | ||
188 | FixtureMeta::Root { path } => &path, | ||
189 | FixtureMeta::File(f) => &f.path, | ||
190 | } | ||
191 | } | ||
192 | |||
193 | pub fn crate_name(&self) -> Option<&String> { | ||
194 | match self { | ||
195 | FixtureMeta::File(f) => f.crate_name.as_ref(), | ||
196 | _ => None, | ||
197 | } | ||
198 | } | ||
199 | |||
200 | pub fn cfg_options(&self) -> Option<&CfgOptions> { | ||
201 | match self { | ||
202 | FixtureMeta::File(f) => Some(&f.cfg), | ||
203 | _ => None, | ||
204 | } | ||
205 | } | ||
206 | |||
207 | pub fn edition(&self) -> Option<&String> { | ||
208 | match self { | ||
209 | FixtureMeta::File(f) => f.edition.as_ref(), | ||
210 | _ => None, | ||
211 | } | ||
212 | } | ||
213 | |||
214 | pub fn env(&self) -> impl Iterator<Item = (&String, &String)> { | ||
215 | struct EnvIter<'a> { | ||
216 | iter: Option<std::collections::hash_map::Iter<'a, String, String>>, | ||
217 | } | ||
218 | |||
219 | impl<'a> EnvIter<'a> { | ||
220 | fn new(meta: &'a FixtureMeta) -> Self { | ||
221 | Self { | ||
222 | iter: match meta { | ||
223 | FixtureMeta::File(f) => Some(f.env.iter()), | ||
224 | _ => None, | ||
225 | }, | ||
226 | } | ||
227 | } | ||
228 | } | ||
229 | |||
230 | impl<'a> Iterator for EnvIter<'a> { | ||
231 | type Item = (&'a String, &'a String); | ||
232 | fn next(&mut self) -> Option<Self::Item> { | ||
233 | self.iter.as_mut().and_then(|i| i.next()) | ||
234 | } | ||
235 | } | ||
236 | |||
237 | EnvIter::new(self) | ||
238 | } | ||
239 | } | ||
240 | |||
241 | /// Parses text which looks like this: | ||
242 | /// | ||
243 | /// ```not_rust | ||
244 | /// //- some meta | ||
245 | /// line 1 | ||
246 | /// line 2 | ||
247 | /// // - other meta | ||
248 | /// ``` | ||
249 | pub fn parse_fixture(ra_fixture: &str) -> Vec<FixtureEntry> { | ||
250 | let fixture = indent_first_line(ra_fixture); | ||
251 | let margin = fixture_margin(&fixture); | ||
252 | |||
253 | let mut lines = fixture | ||
254 | .split('\n') // don't use `.lines` to not drop `\r\n` | ||
255 | .enumerate() | ||
256 | .filter_map(|(ix, line)| { | ||
257 | if line.len() >= margin { | ||
258 | assert!(line[..margin].trim().is_empty()); | ||
259 | let line_content = &line[margin..]; | ||
260 | if !line_content.starts_with("//-") { | ||
261 | assert!( | ||
262 | !line_content.contains("//-"), | ||
263 | r#"Metadata line {} has invalid indentation. All metadata lines need to have the same indentation. | ||
264 | The offending line: {:?}"#, | ||
265 | ix, | ||
266 | line | ||
267 | ); | ||
268 | } | ||
269 | Some(line_content) | ||
270 | } else { | ||
271 | assert!(line.trim().is_empty()); | ||
272 | None | ||
273 | } | ||
274 | }); | ||
275 | |||
276 | let mut res: Vec<FixtureEntry> = Vec::new(); | ||
277 | for line in lines.by_ref() { | ||
278 | if line.starts_with("//-") { | ||
279 | let meta = line["//-".len()..].trim().to_string(); | ||
280 | let meta = parse_meta(&meta); | ||
281 | res.push(FixtureEntry { meta, text: String::new() }) | ||
282 | } else if let Some(entry) = res.last_mut() { | ||
283 | entry.text.push_str(line); | ||
284 | entry.text.push('\n'); | ||
285 | } | ||
286 | } | ||
287 | res | ||
288 | } | ||
289 | |||
290 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo | ||
291 | fn parse_meta(meta: &str) -> FixtureMeta { | ||
292 | let components = meta.split_ascii_whitespace().collect::<Vec<_>>(); | ||
293 | |||
294 | if components[0] == "root" { | ||
295 | let path: RelativePathBuf = components[1].into(); | ||
296 | assert!(path.starts_with("/") && path.ends_with("/")); | ||
297 | return FixtureMeta::Root { path }; | ||
298 | } | ||
299 | |||
300 | let path: RelativePathBuf = components[0].into(); | ||
301 | assert!(path.starts_with("/")); | ||
302 | |||
303 | let mut krate = None; | ||
304 | let mut deps = Vec::new(); | ||
305 | let mut edition = None; | ||
306 | let mut cfg = CfgOptions::default(); | ||
307 | let mut env = FxHashMap::default(); | ||
308 | for component in components[1..].iter() { | ||
309 | let (key, value) = split1(component, ':').unwrap(); | ||
310 | match key { | ||
311 | "crate" => krate = Some(value.to_string()), | ||
312 | "deps" => deps = value.split(',').map(|it| it.to_string()).collect(), | ||
313 | "edition" => edition = Some(value.to_string()), | ||
314 | "cfg" => { | ||
315 | for key in value.split(',') { | ||
316 | match split1(key, '=') { | ||
317 | None => cfg.insert_atom(key.into()), | ||
318 | Some((k, v)) => cfg.insert_key_value(k.into(), v.into()), | ||
319 | } | ||
320 | } | ||
321 | } | ||
322 | "env" => { | ||
323 | for key in value.split(',') { | ||
324 | if let Some((k, v)) = split1(key, '=') { | ||
325 | env.insert(k.into(), v.into()); | ||
326 | } | ||
327 | } | ||
328 | } | ||
329 | _ => panic!("bad component: {:?}", component), | ||
330 | } | ||
331 | } | ||
332 | |||
333 | FixtureMeta::File(FileMeta { path, crate_name: krate, deps, edition, cfg, env }) | ||
334 | } | ||
335 | |||
336 | /// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines. | ||
337 | /// This allows fixtures to start off in a different indentation, e.g. to align the first line with | ||
338 | /// the other lines visually: | ||
339 | /// ``` | ||
340 | /// let fixture = "//- /lib.rs | ||
341 | /// mod foo; | ||
342 | /// //- /foo.rs | ||
343 | /// fn bar() {} | ||
344 | /// "; | ||
345 | /// assert_eq!(fixture_margin(fixture), | ||
346 | /// " //- /lib.rs | ||
347 | /// mod foo; | ||
348 | /// //- /foo.rs | ||
349 | /// fn bar() {} | ||
350 | /// ") | ||
351 | /// ``` | ||
352 | fn indent_first_line(fixture: &str) -> String { | ||
353 | if fixture.is_empty() { | ||
354 | return String::new(); | ||
355 | } | ||
356 | let mut lines = fixture.lines(); | ||
357 | let first_line = lines.next().unwrap(); | ||
358 | if first_line.contains("//-") { | ||
359 | let rest = lines.collect::<Vec<_>>().join("\n"); | ||
360 | let fixed_margin = fixture_margin(&rest); | ||
361 | let fixed_indent = fixed_margin - indent_len(first_line); | ||
362 | format!("\n{}{}\n{}", " ".repeat(fixed_indent), first_line, rest) | ||
363 | } else { | ||
364 | fixture.to_owned() | ||
365 | } | ||
366 | } | ||
367 | |||
368 | fn fixture_margin(fixture: &str) -> usize { | ||
369 | fixture | ||
370 | .lines() | ||
371 | .filter(|it| it.trim_start().starts_with("//-")) | ||
372 | .map(indent_len) | ||
373 | .next() | ||
374 | .expect("empty fixture") | ||
375 | } | ||
376 | |||
377 | fn indent_len(s: &str) -> usize { | ||
378 | s.len() - s.trim_start().len() | ||
379 | } | ||
380 | |||
381 | #[test] | ||
382 | #[should_panic] | ||
383 | fn parse_fixture_checks_further_indented_metadata() { | ||
384 | parse_fixture( | ||
385 | r" | ||
386 | //- /lib.rs | ||
387 | mod bar; | ||
388 | |||
389 | fn foo() {} | ||
390 | //- /bar.rs | ||
391 | pub fn baz() {} | ||
392 | ", | ||
393 | ); | ||
394 | } | ||
395 | |||
396 | #[test] | ||
397 | fn parse_fixture_can_handle_dedented_first_line() { | ||
398 | let fixture = "//- /lib.rs | ||
399 | mod foo; | ||
400 | //- /foo.rs | ||
401 | struct Bar; | ||
402 | "; | ||
403 | assert_eq!( | ||
404 | parse_fixture(fixture), | ||
405 | parse_fixture( | ||
406 | "//- /lib.rs | ||
407 | mod foo; | ||
408 | //- /foo.rs | ||
409 | struct Bar; | ||
410 | " | ||
411 | ) | ||
412 | ) | ||
413 | } | ||
414 | |||
415 | #[test] | ||
416 | fn parse_fixture_gets_full_meta() { | ||
417 | let parsed = parse_fixture( | ||
418 | r" | ||
419 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo | ||
420 | mod m; | ||
421 | ", | ||
422 | ); | ||
423 | assert_eq!(1, parsed.len()); | ||
424 | |||
425 | let parsed = &parsed[0]; | ||
426 | assert_eq!("mod m;\n\n", parsed.text); | ||
427 | |||
428 | let meta = &parsed.meta; | ||
429 | assert_eq!("foo", meta.crate_name().unwrap()); | ||
430 | assert_eq!("/lib.rs", meta.path()); | ||
431 | assert!(meta.cfg_options().is_some()); | ||
432 | assert_eq!(2, meta.env().count()); | ||
433 | } | ||
434 | |||
435 | /// Same as `parse_fixture`, except it allow empty fixture | ||
436 | pub fn parse_single_fixture(ra_fixture: &str) -> Option<FixtureEntry> { | ||
437 | if !ra_fixture.lines().any(|it| it.trim_start().starts_with("//-")) { | ||
438 | return None; | ||
439 | } | ||
440 | |||
441 | let fixtures = parse_fixture(ra_fixture); | ||
442 | if fixtures.len() > 1 { | ||
443 | panic!("too many fixtures"); | ||
444 | } | ||
445 | fixtures.into_iter().nth(0) | ||
446 | } | ||
447 | |||
448 | // Comparison functionality borrowed from cargo: | 162 | // Comparison functionality borrowed from cargo: |
449 | 163 | ||
450 | /// Compare a line with an expected pattern. | 164 | /// Compare a line with an expected pattern. |