diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_db/src/fixture.rs | 46 | ||||
-rw-r--r-- | crates/test_utils/src/lib.rs | 104 |
2 files changed, 140 insertions, 10 deletions
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs index 8248684ee..51d4c493e 100644 --- a/crates/ra_db/src/fixture.rs +++ b/crates/ra_db/src/fixture.rs | |||
@@ -1,4 +1,48 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! Fixtures are strings containing rust source code with optional metadata. |
2 | //! A fixture without metadata is parsed into a single source file. | ||
3 | //! Use this to test functionality local to one file. | ||
4 | //! | ||
5 | //! Example: | ||
6 | //! ``` | ||
7 | //! r#" | ||
8 | //! fn main() { | ||
9 | //! println!("Hello World") | ||
10 | //! } | ||
11 | //! "# | ||
12 | //! ``` | ||
13 | //! | ||
14 | //! Metadata can be added to a fixture after a `//-` comment. | ||
15 | //! The basic form is specifying filenames, | ||
16 | //! which is also how to define multiple files in a single test fixture | ||
17 | //! | ||
18 | //! Example: | ||
19 | //! ``` | ||
20 | //! " | ||
21 | //! //- /main.rs | ||
22 | //! mod foo; | ||
23 | //! fn main() { | ||
24 | //! foo::bar(); | ||
25 | //! } | ||
26 | //! | ||
27 | //! //- /foo.rs | ||
28 | //! pub fn bar() {} | ||
29 | //! " | ||
30 | //! ``` | ||
31 | //! | ||
32 | //! Metadata allows specifying all settings and variables | ||
33 | //! that are available in a real rust project: | ||
34 | //! - crate names via `crate:cratename` | ||
35 | //! - dependencies via `deps:dep1,dep2` | ||
36 | //! - configuration settings via `cfg:dbg=false,opt_level=2` | ||
37 | //! - environment variables via `env:PATH=/bin,RUST_LOG=debug` | ||
38 | //! | ||
39 | //! Example: | ||
40 | //! ``` | ||
41 | //! " | ||
42 | //! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo | ||
43 | //! fn insert_source_code_here() {} | ||
44 | //! " | ||
45 | //! ``` | ||
2 | 46 | ||
3 | use std::str::FromStr; | 47 | use std::str::FromStr; |
4 | use std::sync::Arc; | 48 | use std::sync::Arc; |
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index b1365444a..b13e13af2 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -155,7 +155,7 @@ pub fn add_cursor(text: &str, offset: TextSize) -> String { | |||
155 | res | 155 | res |
156 | } | 156 | } |
157 | 157 | ||
158 | #[derive(Debug)] | 158 | #[derive(Debug, Eq, PartialEq)] |
159 | pub struct FixtureEntry { | 159 | pub struct FixtureEntry { |
160 | pub meta: String, | 160 | pub meta: String, |
161 | pub text: String, | 161 | pub text: String, |
@@ -170,19 +170,26 @@ pub struct FixtureEntry { | |||
170 | /// // - other meta | 170 | /// // - other meta |
171 | /// ``` | 171 | /// ``` |
172 | pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { | 172 | pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { |
173 | let margin = fixture | 173 | let fixture = indent_first_line(fixture); |
174 | .lines() | 174 | let margin = fixture_margin(&fixture); |
175 | .filter(|it| it.trim_start().starts_with("//-")) | ||
176 | .map(|it| it.len() - it.trim_start().len()) | ||
177 | .next() | ||
178 | .expect("empty fixture"); | ||
179 | 175 | ||
180 | let mut lines = fixture | 176 | let mut lines = fixture |
181 | .split('\n') // don't use `.lines` to not drop `\r\n` | 177 | .split('\n') // don't use `.lines` to not drop `\r\n` |
182 | .filter_map(|line| { | 178 | .enumerate() |
179 | .filter_map(|(ix, line)| { | ||
183 | if line.len() >= margin { | 180 | if line.len() >= margin { |
184 | assert!(line[..margin].trim().is_empty()); | 181 | assert!(line[..margin].trim().is_empty()); |
185 | Some(&line[margin..]) | 182 | let line_content = &line[margin..]; |
183 | if !line_content.starts_with("//-") { | ||
184 | assert!( | ||
185 | !line_content.contains("//-"), | ||
186 | r#"Metadata line {} has invalid indentation. All metadata lines need to have the same indentation. | ||
187 | The offending line: {:?}"#, | ||
188 | ix, | ||
189 | line | ||
190 | ); | ||
191 | } | ||
192 | Some(line_content) | ||
186 | } else { | 193 | } else { |
187 | assert!(line.trim().is_empty()); | 194 | assert!(line.trim().is_empty()); |
188 | None | 195 | None |
@@ -202,6 +209,85 @@ pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { | |||
202 | res | 209 | res |
203 | } | 210 | } |
204 | 211 | ||
212 | /// 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 | ||
214 | /// the other lines visually: | ||
215 | /// ``` | ||
216 | /// let fixture = "//- /lib.rs | ||
217 | /// mod foo; | ||
218 | /// //- /foo.rs | ||
219 | /// fn bar() {} | ||
220 | /// "; | ||
221 | /// assert_eq!(fixture_margin(fixture), | ||
222 | /// " //- /lib.rs | ||
223 | /// mod foo; | ||
224 | /// //- /foo.rs | ||
225 | /// fn bar() {} | ||
226 | /// ") | ||
227 | /// ``` | ||
228 | fn indent_first_line(fixture: &str) -> String { | ||
229 | if fixture.is_empty() { | ||
230 | return String::new(); | ||
231 | } | ||
232 | let mut lines = fixture.lines(); | ||
233 | let first_line = lines.next().unwrap(); | ||
234 | if first_line.contains("//-") { | ||
235 | let rest = lines.collect::<Vec<_>>().join("\n"); | ||
236 | let fixed_margin = fixture_margin(&rest); | ||
237 | let fixed_indent = fixed_margin - indent_len(first_line); | ||
238 | format!("\n{}{}\n{}", " ".repeat(fixed_indent), first_line, rest) | ||
239 | } else { | ||
240 | fixture.to_owned() | ||
241 | } | ||
242 | } | ||
243 | |||
244 | fn fixture_margin(fixture: &str) -> usize { | ||
245 | fixture | ||
246 | .lines() | ||
247 | .filter(|it| it.trim_start().starts_with("//-")) | ||
248 | .map(indent_len) | ||
249 | .next() | ||
250 | .expect("empty fixture") | ||
251 | } | ||
252 | |||
253 | fn indent_len(s: &str) -> usize { | ||
254 | s.len() - s.trim_start().len() | ||
255 | } | ||
256 | |||
257 | #[test] | ||
258 | #[should_panic] | ||
259 | fn parse_fixture_checks_further_indented_metadata() { | ||
260 | parse_fixture( | ||
261 | r" | ||
262 | //- /lib.rs | ||
263 | mod bar; | ||
264 | |||
265 | fn foo() {} | ||
266 | //- /bar.rs | ||
267 | pub fn baz() {} | ||
268 | ", | ||
269 | ); | ||
270 | } | ||
271 | |||
272 | #[test] | ||
273 | fn parse_fixture_can_handle_unindented_first_line() { | ||
274 | let fixture = "//- /lib.rs | ||
275 | mod foo; | ||
276 | //- /foo.rs | ||
277 | struct Bar; | ||
278 | "; | ||
279 | assert_eq!( | ||
280 | parse_fixture(fixture), | ||
281 | parse_fixture( | ||
282 | "//- /lib.rs | ||
283 | mod foo; | ||
284 | //- /foo.rs | ||
285 | struct Bar; | ||
286 | " | ||
287 | ) | ||
288 | ) | ||
289 | } | ||
290 | |||
205 | /// Same as `parse_fixture`, except it allow empty fixture | 291 | /// Same as `parse_fixture`, except it allow empty fixture |
206 | pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> { | 292 | pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> { |
207 | if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) { | 293 | if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) { |