diff options
Diffstat (limited to 'crates/test_utils/src/lib.rs')
-rw-r--r-- | crates/test_utils/src/lib.rs | 296 |
1 files changed, 5 insertions, 291 deletions
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. |