diff options
Diffstat (limited to 'crates/base_db/src/fixture.rs')
-rw-r--r-- | crates/base_db/src/fixture.rs | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs new file mode 100644 index 000000000..5ff8ead0e --- /dev/null +++ b/crates/base_db/src/fixture.rs | |||
@@ -0,0 +1,228 @@ | |||
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 | //! Simple 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 using two files in the same crate: | ||
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 | //! Example using two crates with one file each, with one crate depending on the other: | ||
33 | //! ``` | ||
34 | //! r#" | ||
35 | //! //- /main.rs crate:a deps:b | ||
36 | //! fn main() { | ||
37 | //! b::foo(); | ||
38 | //! } | ||
39 | //! //- /lib.rs crate:b | ||
40 | //! pub fn b() { | ||
41 | //! println!("Hello World") | ||
42 | //! } | ||
43 | //! "# | ||
44 | //! ``` | ||
45 | //! | ||
46 | //! Metadata allows specifying all settings and variables | ||
47 | //! that are available in a real rust project: | ||
48 | //! - crate names via `crate:cratename` | ||
49 | //! - dependencies via `deps:dep1,dep2` | ||
50 | //! - configuration settings via `cfg:dbg=false,opt_level=2` | ||
51 | //! - environment variables via `env:PATH=/bin,RUST_LOG=debug` | ||
52 | //! | ||
53 | //! Example using all available metadata: | ||
54 | //! ``` | ||
55 | //! " | ||
56 | //! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo | ||
57 | //! fn insert_source_code_here() {} | ||
58 | //! " | ||
59 | //! ``` | ||
60 | use std::{str::FromStr, sync::Arc}; | ||
61 | |||
62 | use cfg::CfgOptions; | ||
63 | use rustc_hash::FxHashMap; | ||
64 | use test_utils::{extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER}; | ||
65 | use vfs::{file_set::FileSet, VfsPath}; | ||
66 | |||
67 | use crate::{ | ||
68 | input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, SourceDatabaseExt, | ||
69 | SourceRoot, SourceRootId, | ||
70 | }; | ||
71 | |||
72 | pub const WORKSPACE: SourceRootId = SourceRootId(0); | ||
73 | |||
74 | pub trait WithFixture: Default + SourceDatabaseExt + 'static { | ||
75 | fn with_single_file(text: &str) -> (Self, FileId) { | ||
76 | let mut db = Self::default(); | ||
77 | let (_, files) = with_files(&mut db, text); | ||
78 | assert_eq!(files.len(), 1); | ||
79 | (db, files[0]) | ||
80 | } | ||
81 | |||
82 | fn with_files(ra_fixture: &str) -> Self { | ||
83 | let mut db = Self::default(); | ||
84 | let (pos, _) = with_files(&mut db, ra_fixture); | ||
85 | assert!(pos.is_none()); | ||
86 | db | ||
87 | } | ||
88 | |||
89 | fn with_position(ra_fixture: &str) -> (Self, FilePosition) { | ||
90 | let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); | ||
91 | let offset = match range_or_offset { | ||
92 | RangeOrOffset::Range(_) => panic!(), | ||
93 | RangeOrOffset::Offset(it) => it, | ||
94 | }; | ||
95 | (db, FilePosition { file_id, offset }) | ||
96 | } | ||
97 | |||
98 | fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) { | ||
99 | let mut db = Self::default(); | ||
100 | let (pos, _) = with_files(&mut db, ra_fixture); | ||
101 | let (file_id, range_or_offset) = pos.unwrap(); | ||
102 | (db, file_id, range_or_offset) | ||
103 | } | ||
104 | |||
105 | fn test_crate(&self) -> CrateId { | ||
106 | let crate_graph = self.crate_graph(); | ||
107 | let mut it = crate_graph.iter(); | ||
108 | let res = it.next().unwrap(); | ||
109 | assert!(it.next().is_none()); | ||
110 | res | ||
111 | } | ||
112 | } | ||
113 | |||
114 | impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {} | ||
115 | |||
116 | fn with_files( | ||
117 | db: &mut dyn SourceDatabaseExt, | ||
118 | fixture: &str, | ||
119 | ) -> (Option<(FileId, RangeOrOffset)>, Vec<FileId>) { | ||
120 | let fixture = Fixture::parse(fixture); | ||
121 | |||
122 | let mut files = Vec::new(); | ||
123 | let mut crate_graph = CrateGraph::default(); | ||
124 | let mut crates = FxHashMap::default(); | ||
125 | let mut crate_deps = Vec::new(); | ||
126 | let mut default_crate_root: Option<FileId> = None; | ||
127 | |||
128 | let mut file_set = FileSet::default(); | ||
129 | let source_root_id = WORKSPACE; | ||
130 | let source_root_prefix = "/".to_string(); | ||
131 | let mut file_id = FileId(0); | ||
132 | |||
133 | let mut file_position = None; | ||
134 | |||
135 | for entry in fixture { | ||
136 | let text = if entry.text.contains(CURSOR_MARKER) { | ||
137 | let (range_or_offset, text) = extract_range_or_offset(&entry.text); | ||
138 | assert!(file_position.is_none()); | ||
139 | file_position = Some((file_id, range_or_offset)); | ||
140 | text.to_string() | ||
141 | } else { | ||
142 | entry.text.clone() | ||
143 | }; | ||
144 | |||
145 | let meta = FileMeta::from(entry); | ||
146 | assert!(meta.path.starts_with(&source_root_prefix)); | ||
147 | |||
148 | if let Some(krate) = meta.krate { | ||
149 | let crate_id = crate_graph.add_crate_root( | ||
150 | file_id, | ||
151 | meta.edition, | ||
152 | Some(krate.clone()), | ||
153 | meta.cfg, | ||
154 | meta.env, | ||
155 | Default::default(), | ||
156 | ); | ||
157 | let crate_name = CrateName::new(&krate).unwrap(); | ||
158 | let prev = crates.insert(crate_name.clone(), crate_id); | ||
159 | assert!(prev.is_none()); | ||
160 | for dep in meta.deps { | ||
161 | let dep = CrateName::new(&dep).unwrap(); | ||
162 | crate_deps.push((crate_name.clone(), dep)) | ||
163 | } | ||
164 | } else if meta.path == "/main.rs" || meta.path == "/lib.rs" { | ||
165 | assert!(default_crate_root.is_none()); | ||
166 | default_crate_root = Some(file_id); | ||
167 | } | ||
168 | |||
169 | db.set_file_text(file_id, Arc::new(text)); | ||
170 | db.set_file_source_root(file_id, source_root_id); | ||
171 | let path = VfsPath::new_virtual_path(meta.path); | ||
172 | file_set.insert(file_id, path.into()); | ||
173 | files.push(file_id); | ||
174 | file_id.0 += 1; | ||
175 | } | ||
176 | |||
177 | if crates.is_empty() { | ||
178 | let crate_root = default_crate_root.unwrap(); | ||
179 | crate_graph.add_crate_root( | ||
180 | crate_root, | ||
181 | Edition::Edition2018, | ||
182 | None, | ||
183 | CfgOptions::default(), | ||
184 | Env::default(), | ||
185 | Default::default(), | ||
186 | ); | ||
187 | } else { | ||
188 | for (from, to) in crate_deps { | ||
189 | let from_id = crates[&from]; | ||
190 | let to_id = crates[&to]; | ||
191 | crate_graph.add_dep(from_id, CrateName::new(&to).unwrap(), to_id).unwrap(); | ||
192 | } | ||
193 | } | ||
194 | |||
195 | db.set_source_root(source_root_id, Arc::new(SourceRoot::new_local(file_set))); | ||
196 | db.set_crate_graph(Arc::new(crate_graph)); | ||
197 | |||
198 | (file_position, files) | ||
199 | } | ||
200 | |||
201 | struct FileMeta { | ||
202 | path: String, | ||
203 | krate: Option<String>, | ||
204 | deps: Vec<String>, | ||
205 | cfg: CfgOptions, | ||
206 | edition: Edition, | ||
207 | env: Env, | ||
208 | } | ||
209 | |||
210 | impl From<Fixture> for FileMeta { | ||
211 | fn from(f: Fixture) -> FileMeta { | ||
212 | let mut cfg = CfgOptions::default(); | ||
213 | f.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into())); | ||
214 | f.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into())); | ||
215 | |||
216 | FileMeta { | ||
217 | path: f.path, | ||
218 | krate: f.krate, | ||
219 | deps: f.deps, | ||
220 | cfg, | ||
221 | edition: f | ||
222 | .edition | ||
223 | .as_ref() | ||
224 | .map_or(Edition::Edition2018, |v| Edition::from_str(&v).unwrap()), | ||
225 | env: f.env.into_iter().collect(), | ||
226 | } | ||
227 | } | ||
228 | } | ||