diff options
Diffstat (limited to 'crates/test_utils/src/fixture.rs')
-rw-r--r-- | crates/test_utils/src/fixture.rs | 192 |
1 files changed, 180 insertions, 12 deletions
diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs index d0bddf7d8..005a5c092 100644 --- a/crates/test_utils/src/fixture.rs +++ b/crates/test_utils/src/fixture.rs | |||
@@ -77,6 +77,11 @@ pub struct Fixture { | |||
77 | pub introduce_new_source_root: bool, | 77 | pub introduce_new_source_root: bool, |
78 | } | 78 | } |
79 | 79 | ||
80 | pub struct MiniCore { | ||
81 | activated_flags: Vec<String>, | ||
82 | valid_flags: Vec<String>, | ||
83 | } | ||
84 | |||
80 | impl Fixture { | 85 | impl Fixture { |
81 | /// Parses text which looks like this: | 86 | /// Parses text which looks like this: |
82 | /// | 87 | /// |
@@ -86,12 +91,28 @@ impl Fixture { | |||
86 | /// line 2 | 91 | /// line 2 |
87 | /// //- other meta | 92 | /// //- other meta |
88 | /// ``` | 93 | /// ``` |
89 | pub fn parse(ra_fixture: &str) -> Vec<Fixture> { | 94 | /// |
95 | /// Fixture can also start with a minicore declaration: | ||
96 | /// | ||
97 | /// ``` | ||
98 | /// //- minicore: sized | ||
99 | /// ``` | ||
100 | /// | ||
101 | /// That will include a subset of `libcore` into the fixture, see | ||
102 | /// `minicore.rs` for what's available. | ||
103 | pub fn parse(ra_fixture: &str) -> (Option<MiniCore>, Vec<Fixture>) { | ||
90 | let fixture = trim_indent(ra_fixture); | 104 | let fixture = trim_indent(ra_fixture); |
91 | 105 | let mut fixture = fixture.as_str(); | |
106 | let mut mini_core = None; | ||
92 | let mut res: Vec<Fixture> = Vec::new(); | 107 | let mut res: Vec<Fixture> = Vec::new(); |
93 | 108 | ||
94 | let default = if ra_fixture.contains("//-") { None } else { Some("//- /main.rs") }; | 109 | if fixture.starts_with("//- minicore:") { |
110 | let first_line = fixture.split_inclusive('\n').next().unwrap(); | ||
111 | mini_core = Some(MiniCore::parse(first_line)); | ||
112 | fixture = &fixture[first_line.len()..]; | ||
113 | } | ||
114 | |||
115 | let default = if fixture.contains("//-") { None } else { Some("//- /main.rs") }; | ||
95 | 116 | ||
96 | for (ix, line) in default.into_iter().chain(fixture.split_inclusive('\n')).enumerate() { | 117 | for (ix, line) in default.into_iter().chain(fixture.split_inclusive('\n')).enumerate() { |
97 | if line.contains("//-") { | 118 | if line.contains("//-") { |
@@ -108,12 +129,22 @@ impl Fixture { | |||
108 | if line.starts_with("//-") { | 129 | if line.starts_with("//-") { |
109 | let meta = Fixture::parse_meta_line(line); | 130 | let meta = Fixture::parse_meta_line(line); |
110 | res.push(meta) | 131 | res.push(meta) |
111 | } else if let Some(entry) = res.last_mut() { | 132 | } else { |
112 | entry.text.push_str(line); | 133 | if line.starts_with("// ") |
134 | && line.contains(":") | ||
135 | && !line.contains("::") | ||
136 | && line.chars().all(|it| !it.is_uppercase()) | ||
137 | { | ||
138 | panic!("looks like invalid metadata line: {:?}", line) | ||
139 | } | ||
140 | |||
141 | if let Some(entry) = res.last_mut() { | ||
142 | entry.text.push_str(line); | ||
143 | } | ||
113 | } | 144 | } |
114 | } | 145 | } |
115 | 146 | ||
116 | res | 147 | (mini_core, res) |
117 | } | 148 | } |
118 | 149 | ||
119 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo | 150 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo |
@@ -133,7 +164,9 @@ impl Fixture { | |||
133 | let mut env = FxHashMap::default(); | 164 | let mut env = FxHashMap::default(); |
134 | let mut introduce_new_source_root = false; | 165 | let mut introduce_new_source_root = false; |
135 | for component in components[1..].iter() { | 166 | for component in components[1..].iter() { |
136 | let (key, value) = component.split_once(':').unwrap(); | 167 | let (key, value) = component |
168 | .split_once(':') | ||
169 | .unwrap_or_else(|| panic!("invalid meta line: {:?}", meta)); | ||
137 | match key { | 170 | match key { |
138 | "crate" => krate = Some(value.to_string()), | 171 | "crate" => krate = Some(value.to_string()), |
139 | "deps" => deps = value.split(',').map(|it| it.to_string()).collect(), | 172 | "deps" => deps = value.split(',').map(|it| it.to_string()).collect(), |
@@ -172,6 +205,139 @@ impl Fixture { | |||
172 | } | 205 | } |
173 | } | 206 | } |
174 | 207 | ||
208 | impl MiniCore { | ||
209 | fn has_flag(&self, flag: &str) -> bool { | ||
210 | self.activated_flags.iter().any(|it| it == flag) | ||
211 | } | ||
212 | |||
213 | #[track_caller] | ||
214 | fn assert_valid_flag(&self, flag: &str) { | ||
215 | if !self.valid_flags.iter().any(|it| it == flag) { | ||
216 | panic!("invalid flag: {:?}, valid flags: {:?}", flag, self.valid_flags); | ||
217 | } | ||
218 | } | ||
219 | |||
220 | fn parse(line: &str) -> MiniCore { | ||
221 | let mut res = MiniCore { activated_flags: Vec::new(), valid_flags: Vec::new() }; | ||
222 | |||
223 | let line = line.strip_prefix("//- minicore:").unwrap().trim(); | ||
224 | for entry in line.split(", ") { | ||
225 | if res.has_flag(entry) { | ||
226 | panic!("duplicate minicore flag: {:?}", entry) | ||
227 | } | ||
228 | res.activated_flags.push(entry.to_string()) | ||
229 | } | ||
230 | |||
231 | res | ||
232 | } | ||
233 | |||
234 | /// Strips parts of minicore.rs which are flagged by inactive flags. | ||
235 | /// | ||
236 | /// This is probably over-engineered to support flags dependencies. | ||
237 | pub fn source_code(mut self) -> String { | ||
238 | let mut buf = String::new(); | ||
239 | let raw_mini_core = include_str!("./minicore.rs"); | ||
240 | let mut lines = raw_mini_core.split_inclusive('\n'); | ||
241 | |||
242 | let mut parsing_flags = false; | ||
243 | let mut implications = Vec::new(); | ||
244 | |||
245 | // Parse `//!` preamble and extract flags and dependencies. | ||
246 | for line in lines.by_ref() { | ||
247 | let line = match line.strip_prefix("//!") { | ||
248 | Some(it) => it, | ||
249 | None => { | ||
250 | assert!(line.trim().is_empty()); | ||
251 | break; | ||
252 | } | ||
253 | }; | ||
254 | |||
255 | if parsing_flags { | ||
256 | let (flag, deps) = line.split_once(':').unwrap(); | ||
257 | let flag = flag.trim(); | ||
258 | self.valid_flags.push(flag.to_string()); | ||
259 | for dep in deps.split(", ") { | ||
260 | let dep = dep.trim(); | ||
261 | if !dep.is_empty() { | ||
262 | self.assert_valid_flag(dep); | ||
263 | implications.push((flag, dep)); | ||
264 | } | ||
265 | } | ||
266 | } | ||
267 | |||
268 | if line.contains("Available flags:") { | ||
269 | parsing_flags = true; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | for flag in &self.activated_flags { | ||
274 | self.assert_valid_flag(flag); | ||
275 | } | ||
276 | |||
277 | // Fixed point loop to compute transitive closure of flags. | ||
278 | loop { | ||
279 | let mut changed = false; | ||
280 | for &(u, v) in implications.iter() { | ||
281 | if self.has_flag(u) && !self.has_flag(v) { | ||
282 | self.activated_flags.push(v.to_string()); | ||
283 | changed = true; | ||
284 | } | ||
285 | } | ||
286 | if !changed { | ||
287 | break; | ||
288 | } | ||
289 | } | ||
290 | |||
291 | let mut active_regions = Vec::new(); | ||
292 | let mut seen_regions = Vec::new(); | ||
293 | for line in lines { | ||
294 | let trimmed = line.trim(); | ||
295 | if let Some(region) = trimmed.strip_prefix("// region:") { | ||
296 | active_regions.push(region); | ||
297 | continue; | ||
298 | } | ||
299 | if let Some(region) = trimmed.strip_prefix("// endregion:") { | ||
300 | let prev = active_regions.pop().unwrap(); | ||
301 | assert_eq!(prev, region); | ||
302 | continue; | ||
303 | } | ||
304 | |||
305 | let mut line_region = false; | ||
306 | if let Some(idx) = trimmed.find("// :") { | ||
307 | line_region = true; | ||
308 | active_regions.push(&trimmed[idx + "// :".len()..]); | ||
309 | } | ||
310 | |||
311 | let mut keep = true; | ||
312 | for ®ion in &active_regions { | ||
313 | assert!( | ||
314 | !region.starts_with(' '), | ||
315 | "region marker starts with a space: {:?}", | ||
316 | region | ||
317 | ); | ||
318 | self.assert_valid_flag(region); | ||
319 | seen_regions.push(region); | ||
320 | keep &= self.has_flag(region); | ||
321 | } | ||
322 | |||
323 | if keep { | ||
324 | buf.push_str(line) | ||
325 | } | ||
326 | if line_region { | ||
327 | active_regions.pop().unwrap(); | ||
328 | } | ||
329 | } | ||
330 | |||
331 | for flag in &self.valid_flags { | ||
332 | if !seen_regions.iter().any(|it| it == flag) { | ||
333 | panic!("unused minicore flag: {:?}", flag); | ||
334 | } | ||
335 | } | ||
336 | format!("{}", buf); | ||
337 | buf | ||
338 | } | ||
339 | } | ||
340 | |||
175 | #[test] | 341 | #[test] |
176 | #[should_panic] | 342 | #[should_panic] |
177 | fn parse_fixture_checks_further_indented_metadata() { | 343 | fn parse_fixture_checks_further_indented_metadata() { |
@@ -189,12 +355,14 @@ fn parse_fixture_checks_further_indented_metadata() { | |||
189 | 355 | ||
190 | #[test] | 356 | #[test] |
191 | fn parse_fixture_gets_full_meta() { | 357 | fn parse_fixture_gets_full_meta() { |
192 | let parsed = Fixture::parse( | 358 | let (mini_core, parsed) = Fixture::parse( |
193 | r" | 359 | r#" |
194 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo | 360 | //- minicore: coerce_unsized |
195 | mod m; | 361 | //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo |
196 | ", | 362 | mod m; |
363 | "#, | ||
197 | ); | 364 | ); |
365 | assert_eq!(mini_core.unwrap().activated_flags, vec!["coerce_unsized".to_string()]); | ||
198 | assert_eq!(1, parsed.len()); | 366 | assert_eq!(1, parsed.len()); |
199 | 367 | ||
200 | let meta = &parsed[0]; | 368 | let meta = &parsed[0]; |