diff options
author | Aleksey Kladov <[email protected]> | 2020-05-24 14:32:52 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-05-24 14:32:52 +0100 |
commit | f26b7928e065f067d7181d3425fe17526dc3bcd1 (patch) | |
tree | 40dc4f23507c621ce80d0923a89c65faf947da2e /crates/test_utils | |
parent | ce7144a93d6fe290731cd018da3052d948df4d57 (diff) | |
parent | cd45c73b660f85acc9b564e536bc407f0836891d (diff) |
Merge pull request #4495 from vsrs/fixture_meta
Test fixtures parsing improvements
Diffstat (limited to 'crates/test_utils')
-rw-r--r-- | crates/test_utils/Cargo.toml | 4 | ||||
-rw-r--r-- | crates/test_utils/src/lib.rs | 160 |
2 files changed, 158 insertions, 6 deletions
diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml index 8ec986bcb..4d185b01c 100644 --- a/crates/test_utils/Cargo.toml +++ b/crates/test_utils/Cargo.toml | |||
@@ -11,3 +11,7 @@ doctest = false | |||
11 | difference = "2.0.0" | 11 | difference = "2.0.0" |
12 | text-size = "1.0.0" | 12 | text-size = "1.0.0" |
13 | serde_json = "1.0.48" | 13 | serde_json = "1.0.48" |
14 | relative-path = "1.0.0" | ||
15 | rustc-hash = "1.1.0" | ||
16 | |||
17 | ra_cfg = { path = "../ra_cfg" } \ No newline at end of file | ||
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index be2cfbaa2..1bd97215c 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -14,6 +14,10 @@ use std::{ | |||
14 | path::{Path, PathBuf}, | 14 | path::{Path, PathBuf}, |
15 | }; | 15 | }; |
16 | 16 | ||
17 | pub use ra_cfg::CfgOptions; | ||
18 | |||
19 | pub use relative_path::{RelativePath, RelativePathBuf}; | ||
20 | pub use rustc_hash::FxHashMap; | ||
17 | use serde_json::Value; | 21 | use serde_json::Value; |
18 | use text_size::{TextRange, TextSize}; | 22 | use text_size::{TextRange, TextSize}; |
19 | 23 | ||
@@ -157,10 +161,82 @@ pub fn add_cursor(text: &str, offset: TextSize) -> String { | |||
157 | 161 | ||
158 | #[derive(Debug, Eq, PartialEq)] | 162 | #[derive(Debug, Eq, PartialEq)] |
159 | pub struct FixtureEntry { | 163 | pub struct FixtureEntry { |
160 | pub meta: String, | 164 | pub meta: FixtureMeta, |
161 | pub text: String, | 165 | pub text: String, |
162 | } | 166 | } |
163 | 167 | ||
168 | #[derive(Debug, Eq, PartialEq)] | ||
169 | pub enum FixtureMeta { | ||
170 | Root { path: RelativePathBuf }, | ||
171 | File(FileMeta), | ||
172 | } | ||
173 | |||
174 | #[derive(Debug, Eq, PartialEq)] | ||
175 | pub struct FileMeta { | ||
176 | pub path: RelativePathBuf, | ||
177 | pub crate_name: Option<String>, | ||
178 | pub deps: Vec<String>, | ||
179 | pub cfg: CfgOptions, | ||
180 | pub edition: Option<String>, | ||
181 | pub env: FxHashMap<String, String>, | ||
182 | } | ||
183 | |||
184 | impl FixtureMeta { | ||
185 | pub fn path(&self) -> &RelativePath { | ||
186 | match self { | ||
187 | FixtureMeta::Root { path } => &path, | ||
188 | FixtureMeta::File(f) => &f.path, | ||
189 | } | ||
190 | } | ||
191 | |||
192 | pub fn crate_name(&self) -> Option<&String> { | ||
193 | match self { | ||
194 | FixtureMeta::File(f) => f.crate_name.as_ref(), | ||
195 | _ => None, | ||
196 | } | ||
197 | } | ||
198 | |||
199 | pub fn cfg_options(&self) -> Option<&CfgOptions> { | ||
200 | match self { | ||
201 | FixtureMeta::File(f) => Some(&f.cfg), | ||
202 | _ => None, | ||
203 | } | ||
204 | } | ||
205 | |||
206 | pub fn edition(&self) -> Option<&String> { | ||
207 | match self { | ||
208 | FixtureMeta::File(f) => f.edition.as_ref(), | ||
209 | _ => None, | ||
210 | } | ||
211 | } | ||
212 | |||
213 | pub fn env(&self) -> impl Iterator<Item = (&String, &String)> { | ||
214 | struct EnvIter<'a> { | ||
215 | iter: Option<std::collections::hash_map::Iter<'a, String, String>>, | ||
216 | } | ||
217 | |||
218 | impl<'a> EnvIter<'a> { | ||
219 | fn new(meta: &'a FixtureMeta) -> Self { | ||
220 | Self { | ||
221 | iter: match meta { | ||
222 | FixtureMeta::File(f) => Some(f.env.iter()), | ||
223 | _ => None, | ||
224 | }, | ||
225 | } | ||
226 | } | ||
227 | } | ||
228 | |||
229 | impl<'a> Iterator for EnvIter<'a> { | ||
230 | type Item = (&'a String, &'a String); | ||
231 | fn next(&mut self) -> Option<Self::Item> { | ||
232 | self.iter.as_mut().and_then(|i| i.next()) | ||
233 | } | ||
234 | } | ||
235 | |||
236 | EnvIter::new(self) | ||
237 | } | ||
238 | } | ||
239 | |||
164 | /// Parses text which looks like this: | 240 | /// Parses text which looks like this: |
165 | /// | 241 | /// |
166 | /// ```not_rust | 242 | /// ```not_rust |
@@ -169,8 +245,8 @@ pub struct FixtureEntry { | |||
169 | /// line 2 | 245 | /// line 2 |
170 | /// // - other meta | 246 | /// // - other meta |
171 | /// ``` | 247 | /// ``` |
172 | pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { | 248 | pub fn parse_fixture(ra_fixture: &str) -> Vec<FixtureEntry> { |
173 | let fixture = indent_first_line(fixture); | 249 | let fixture = indent_first_line(ra_fixture); |
174 | let margin = fixture_margin(&fixture); | 250 | let margin = fixture_margin(&fixture); |
175 | 251 | ||
176 | let mut lines = fixture | 252 | let mut lines = fixture |
@@ -200,6 +276,7 @@ The offending line: {:?}"#, | |||
200 | for line in lines.by_ref() { | 276 | for line in lines.by_ref() { |
201 | if line.starts_with("//-") { | 277 | if line.starts_with("//-") { |
202 | let meta = line["//-".len()..].trim().to_string(); | 278 | let meta = line["//-".len()..].trim().to_string(); |
279 | let meta = parse_meta(&meta); | ||
203 | res.push(FixtureEntry { meta, text: String::new() }) | 280 | res.push(FixtureEntry { meta, text: String::new() }) |
204 | } else if let Some(entry) = res.last_mut() { | 281 | } else if let Some(entry) = res.last_mut() { |
205 | entry.text.push_str(line); | 282 | entry.text.push_str(line); |
@@ -209,6 +286,57 @@ The offending line: {:?}"#, | |||
209 | res | 286 | res |
210 | } | 287 | } |
211 | 288 | ||
289 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo | ||
290 | fn parse_meta(meta: &str) -> FixtureMeta { | ||
291 | let components = meta.split_ascii_whitespace().collect::<Vec<_>>(); | ||
292 | |||
293 | if components[0] == "root" { | ||
294 | let path: RelativePathBuf = components[1].into(); | ||
295 | assert!(path.starts_with("/") && path.ends_with("/")); | ||
296 | return FixtureMeta::Root { path }; | ||
297 | } | ||
298 | |||
299 | let path: RelativePathBuf = components[0].into(); | ||
300 | assert!(path.starts_with("/")); | ||
301 | |||
302 | let mut krate = None; | ||
303 | let mut deps = Vec::new(); | ||
304 | let mut edition = None; | ||
305 | let mut cfg = CfgOptions::default(); | ||
306 | let mut env = FxHashMap::default(); | ||
307 | for component in components[1..].iter() { | ||
308 | let (key, value) = split1(component, ':').unwrap(); | ||
309 | match key { | ||
310 | "crate" => krate = Some(value.to_string()), | ||
311 | "deps" => deps = value.split(',').map(|it| it.to_string()).collect(), | ||
312 | "edition" => edition = Some(value.to_string()), | ||
313 | "cfg" => { | ||
314 | for key in value.split(',') { | ||
315 | match split1(key, '=') { | ||
316 | None => cfg.insert_atom(key.into()), | ||
317 | Some((k, v)) => cfg.insert_key_value(k.into(), v.into()), | ||
318 | } | ||
319 | } | ||
320 | } | ||
321 | "env" => { | ||
322 | for key in value.split(',') { | ||
323 | if let Some((k, v)) = split1(key, '=') { | ||
324 | env.insert(k.into(), v.into()); | ||
325 | } | ||
326 | } | ||
327 | } | ||
328 | _ => panic!("bad component: {:?}", component), | ||
329 | } | ||
330 | } | ||
331 | |||
332 | FixtureMeta::File(FileMeta { path, crate_name: krate, deps, edition, cfg, env }) | ||
333 | } | ||
334 | |||
335 | fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> { | ||
336 | let idx = haystack.find(delim)?; | ||
337 | Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..])) | ||
338 | } | ||
339 | |||
212 | /// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines. | 340 | /// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines. |
213 | /// This allows fixtures to start off in a different indentation, e.g. to align the first line with | 341 | /// This allows fixtures to start off in a different indentation, e.g. to align the first line with |
214 | /// the other lines visually: | 342 | /// the other lines visually: |
@@ -288,13 +416,33 @@ struct Bar; | |||
288 | ) | 416 | ) |
289 | } | 417 | } |
290 | 418 | ||
419 | #[test] | ||
420 | fn parse_fixture_gets_full_meta() { | ||
421 | let parsed = parse_fixture( | ||
422 | r" | ||
423 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo | ||
424 | mod m; | ||
425 | ", | ||
426 | ); | ||
427 | assert_eq!(1, parsed.len()); | ||
428 | |||
429 | let parsed = &parsed[0]; | ||
430 | assert_eq!("mod m;\n\n", parsed.text); | ||
431 | |||
432 | let meta = &parsed.meta; | ||
433 | assert_eq!("foo", meta.crate_name().unwrap()); | ||
434 | assert_eq!("/lib.rs", meta.path()); | ||
435 | assert!(meta.cfg_options().is_some()); | ||
436 | assert_eq!(2, meta.env().count()); | ||
437 | } | ||
438 | |||
291 | /// Same as `parse_fixture`, except it allow empty fixture | 439 | /// Same as `parse_fixture`, except it allow empty fixture |
292 | pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> { | 440 | pub fn parse_single_fixture(ra_fixture: &str) -> Option<FixtureEntry> { |
293 | if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) { | 441 | if !ra_fixture.lines().any(|it| it.trim_start().starts_with("//-")) { |
294 | return None; | 442 | return None; |
295 | } | 443 | } |
296 | 444 | ||
297 | let fixtures = parse_fixture(fixture); | 445 | let fixtures = parse_fixture(ra_fixture); |
298 | if fixtures.len() > 1 { | 446 | if fixtures.len() > 1 { |
299 | panic!("too many fixtures"); | 447 | panic!("too many fixtures"); |
300 | } | 448 | } |