aboutsummaryrefslogtreecommitdiff
path: root/crates/test_utils
diff options
context:
space:
mode:
Diffstat (limited to 'crates/test_utils')
-rw-r--r--crates/test_utils/src/fixture.rs192
-rw-r--r--crates/test_utils/src/lib.rs52
-rw-r--r--crates/test_utils/src/minicore.rs234
3 files changed, 456 insertions, 22 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
80pub struct MiniCore {
81 activated_flags: Vec<String>,
82 valid_flags: Vec<String>,
83}
84
80impl Fixture { 85impl 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
208impl 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 &region 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]
177fn parse_fixture_checks_further_indented_metadata() { 343fn parse_fixture_checks_further_indented_metadata() {
@@ -189,12 +355,14 @@ fn parse_fixture_checks_further_indented_metadata() {
189 355
190#[test] 356#[test]
191fn parse_fixture_gets_full_meta() { 357fn 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 ", 362mod 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];
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index ac5a9509d..d55bae62a 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -23,7 +23,10 @@ use text_size::{TextRange, TextSize};
23pub use dissimilar::diff as __diff; 23pub use dissimilar::diff as __diff;
24pub use rustc_hash::FxHashMap; 24pub use rustc_hash::FxHashMap;
25 25
26pub use crate::{assert_linear::AssertLinear, fixture::Fixture}; 26pub use crate::{
27 assert_linear::AssertLinear,
28 fixture::{Fixture, MiniCore},
29};
27 30
28pub const CURSOR_MARKER: &str = "$0"; 31pub const CURSOR_MARKER: &str = "$0";
29pub const ESCAPED_CURSOR_MARKER: &str = "\\$0"; 32pub const ESCAPED_CURSOR_MARKER: &str = "\\$0";
@@ -190,10 +193,21 @@ pub fn add_cursor(text: &str, offset: TextSize) -> String {
190 res 193 res
191} 194}
192 195
193/// Extracts `//^ some text` annotations 196/// Extracts `//^^^ some text` annotations.
197///
198/// A run of `^^^` can be arbitrary long and points to the corresponding range
199/// in the line above.
200///
201/// The `// ^file text` syntax can be used to attach `text` to the entirety of
202/// the file.
203///
204/// Multiline string values are supported:
205///
206/// // ^^^ first line
207/// // | second line
194pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> { 208pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> {
195 let mut res = Vec::new(); 209 let mut res = Vec::new();
196 let mut prev_line_start: Option<TextSize> = None; 210 let mut prev_line_start: Option<TextSize> = Some(0.into());
197 let mut line_start: TextSize = 0.into(); 211 let mut line_start: TextSize = 0.into();
198 let mut prev_line_annotations: Vec<(TextSize, usize)> = Vec::new(); 212 let mut prev_line_annotations: Vec<(TextSize, usize)> = Vec::new();
199 for line in text.split_inclusive('\n') { 213 for line in text.split_inclusive('\n') {
@@ -202,10 +216,15 @@ pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> {
202 let annotation_offset = TextSize::of(&line[..idx + "//".len()]); 216 let annotation_offset = TextSize::of(&line[..idx + "//".len()]);
203 for annotation in extract_line_annotations(&line[idx + "//".len()..]) { 217 for annotation in extract_line_annotations(&line[idx + "//".len()..]) {
204 match annotation { 218 match annotation {
205 LineAnnotation::Annotation { mut range, content } => { 219 LineAnnotation::Annotation { mut range, content, file } => {
206 range += annotation_offset; 220 range += annotation_offset;
207 this_line_annotations.push((range.end(), res.len())); 221 this_line_annotations.push((range.end(), res.len()));
208 res.push((range + prev_line_start.unwrap(), content)) 222 let range = if file {
223 TextRange::up_to(TextSize::of(text))
224 } else {
225 range + prev_line_start.unwrap()
226 };
227 res.push((range, content))
209 } 228 }
210 LineAnnotation::Continuation { mut offset, content } => { 229 LineAnnotation::Continuation { mut offset, content } => {
211 offset += annotation_offset; 230 offset += annotation_offset;
@@ -226,11 +245,12 @@ pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> {
226 245
227 prev_line_annotations = this_line_annotations; 246 prev_line_annotations = this_line_annotations;
228 } 247 }
248
229 res 249 res
230} 250}
231 251
232enum LineAnnotation { 252enum LineAnnotation {
233 Annotation { range: TextRange, content: String }, 253 Annotation { range: TextRange, content: String, file: bool },
234 Continuation { offset: TextSize, content: String }, 254 Continuation { offset: TextSize, content: String },
235} 255}
236 256
@@ -251,12 +271,20 @@ fn extract_line_annotations(mut line: &str) -> Vec<LineAnnotation> {
251 } 271 }
252 let range = TextRange::at(offset, len.try_into().unwrap()); 272 let range = TextRange::at(offset, len.try_into().unwrap());
253 let next = line[len..].find(marker).map_or(line.len(), |it| it + len); 273 let next = line[len..].find(marker).map_or(line.len(), |it| it + len);
254 let content = line[len..][..next - len].trim().to_string(); 274 let mut content = &line[len..][..next - len];
275
276 let mut file = false;
277 if !continuation && content.starts_with("file") {
278 file = true;
279 content = &content["file".len()..]
280 }
281
282 let content = content.trim().to_string();
255 283
256 let annotation = if continuation { 284 let annotation = if continuation {
257 LineAnnotation::Continuation { offset: range.end(), content } 285 LineAnnotation::Continuation { offset: range.end(), content }
258 } else { 286 } else {
259 LineAnnotation::Annotation { range, content } 287 LineAnnotation::Annotation { range, content, file }
260 }; 288 };
261 res.push(annotation); 289 res.push(annotation);
262 290
@@ -277,16 +305,20 @@ fn main() {
277 zoo + 1 305 zoo + 1
278} //^^^ type: 306} //^^^ type:
279 // | i32 307 // | i32
308
309// ^file
280 "#, 310 "#,
281 ); 311 );
282 let res = extract_annotations(&text) 312 let res = extract_annotations(&text)
283 .into_iter() 313 .into_iter()
284 .map(|(range, ann)| (&text[range], ann)) 314 .map(|(range, ann)| (&text[range], ann))
285 .collect::<Vec<_>>(); 315 .collect::<Vec<_>>();
316
286 assert_eq!( 317 assert_eq!(
287 res, 318 res[..3],
288 vec![("x", "def".into()), ("y", "def".into()), ("zoo", "type:\ni32\n".into()),] 319 [("x", "def".into()), ("y", "def".into()), ("zoo", "type:\ni32\n".into())]
289 ); 320 );
321 assert_eq!(res[3].0.len(), 115);
290} 322}
291 323
292/// Returns `false` if slow tests should not run, otherwise returns `true` and 324/// Returns `false` if slow tests should not run, otherwise returns `true` and
diff --git a/crates/test_utils/src/minicore.rs b/crates/test_utils/src/minicore.rs
new file mode 100644
index 000000000..e04ca58d2
--- /dev/null
+++ b/crates/test_utils/src/minicore.rs
@@ -0,0 +1,234 @@
1//! This is a fixture we use for tests that need lang items.
2//!
3//! We want to include the minimal subset of core for each test, so this file
4//! supports "conditional compilation". Tests use the following syntax to include minicore:
5//!
6//! //- minicore: flag1, flag2
7//!
8//! We then strip all the code marked with other flags.
9//!
10//! Available flags:
11//! sized:
12//! unsize: sized
13//! coerce_unsized: unsize
14//! slice:
15//! range:
16//! deref: sized
17//! deref_mut: deref
18//! fn:
19//! pin:
20//! future: pin
21//! option:
22//! result:
23
24pub mod marker {
25 // region:sized
26 #[lang = "sized"]
27 #[fundamental]
28 #[rustc_specialization_trait]
29 pub trait Sized {}
30 // endregion:sized
31
32 // region:unsize
33 #[lang = "unsize"]
34 pub trait Unsize<T: ?Sized> {}
35 // endregion:unsize
36}
37
38pub mod ops {
39 // region:coerce_unsized
40 mod unsize {
41 use crate::marker::Unsize;
42
43 #[lang = "coerce_unsized"]
44 pub trait CoerceUnsized<T: ?Sized> {}
45
46 impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a mut U> for &'a mut T {}
47 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b mut T {}
48 impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for &'a mut T {}
49 impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for &'a mut T {}
50
51 impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {}
52 impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for &'a T {}
53
54 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {}
55 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *mut T {}
56 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *const T {}
57 }
58 pub use self::unsize::CoerceUnsized;
59 // endregion:coerce_unsized
60
61 // region:deref
62 mod deref {
63 #[lang = "deref"]
64 pub trait Deref {
65 #[lang = "deref_target"]
66 type Target: ?Sized;
67 fn deref(&self) -> &Self::Target;
68 }
69 // region:deref_mut
70 #[lang = "deref_mut"]
71 pub trait DerefMut: Deref {
72 fn deref_mut(&mut self) -> &mut Self::Target;
73 }
74 // endregion:deref_mut
75 }
76 pub use self::deref::Deref;
77 pub use self::deref::DerefMut; //:deref_mut
78 // endregion:deref
79
80 // region:range
81 mod range {
82 #[lang = "RangeFull"]
83 pub struct RangeFull;
84
85 #[lang = "Range"]
86 pub struct Range<Idx> {
87 pub start: Idx,
88 pub end: Idx,
89 }
90
91 #[lang = "RangeFrom"]
92 pub struct RangeFrom<Idx> {
93 pub start: Idx,
94 }
95
96 #[lang = "RangeTo"]
97 pub struct RangeTo<Idx> {
98 pub end: Idx,
99 }
100
101 #[lang = "RangeInclusive"]
102 pub struct RangeInclusive<Idx> {
103 pub(crate) start: Idx,
104 pub(crate) end: Idx,
105 pub(crate) exhausted: bool,
106 }
107
108 #[lang = "RangeToInclusive"]
109 pub struct RangeToInclusive<Idx> {
110 pub end: Idx,
111 }
112 }
113 pub use self::range::{Range, RangeFrom, RangeFull, RangeTo};
114 pub use self::range::{RangeInclusive, RangeToInclusive};
115 // endregion:range
116
117 // region:fn
118 mod function {
119 #[lang = "fn"]
120 #[fundamental]
121 pub trait Fn<Args>: FnMut<Args> {}
122
123 #[lang = "fn_mut"]
124 #[fundamental]
125 pub trait FnMut<Args>: FnOnce<Args> {}
126
127 #[lang = "fn_once"]
128 #[fundamental]
129 pub trait FnOnce<Args> {
130 #[lang = "fn_once_output"]
131 type Output;
132 }
133 }
134 pub use self::function::{Fn, FnMut, FnOnce};
135 // endregion:fn
136}
137
138// region:slice
139pub mod slice {
140 #[lang = "slice"]
141 impl<T> [T] {
142 pub fn len(&self) -> usize {
143 loop {}
144 }
145 }
146}
147// endregion:slice
148
149// region:option
150pub mod option {
151 pub enum Option<T> {
152 #[lang = "None"]
153 None,
154 #[lang = "Some"]
155 Some(T),
156 }
157}
158// endregion:option
159
160// region:result
161pub mod result {
162 pub enum Result<T, E> {
163 #[lang = "Ok"]
164 Ok(T),
165 #[lang = "Err"]
166 Err(E),
167 }
168}
169// endregion:result
170
171// region:pin
172pub mod pin {
173 #[lang = "pin"]
174 #[fundamental]
175 pub struct Pin<P> {
176 pointer: P,
177 }
178}
179// endregion:pin
180
181// region:future
182pub mod future {
183 use crate::{
184 pin::Pin,
185 task::{Context, Poll},
186 };
187
188 #[lang = "future_trait"]
189 pub trait Future {
190 type Output;
191 #[lang = "poll"]
192 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
193 }
194}
195pub mod task {
196 pub enum Poll<T> {
197 #[lang = "Ready"]
198 Ready(T),
199 #[lang = "Pending"]
200 Pending,
201 }
202
203 pub struct Context<'a> {
204 waker: &'a (),
205 }
206}
207// endregion:future
208
209pub mod prelude {
210 pub mod v1 {
211 pub use crate::{
212 marker::Sized, // :sized
213 ops::{Fn, FnMut, FnOnce}, // :fn
214 option::Option::{self, None, Some}, // :option
215 result::Result::{self, Err, Ok}, // :result
216 };
217 }
218
219 pub mod rust_2015 {
220 pub use super::v1::*;
221 }
222
223 pub mod rust_2018 {
224 pub use super::v1::*;
225 }
226
227 pub mod rust_2021 {
228 pub use super::v1::*;
229 }
230}
231
232#[prelude_import]
233#[allow(unused)]
234use prelude::v1::*;