1 //! A set of high-level utility fixture methods to use in tests.
2 use std
::{mem, str::FromStr, sync::Arc}
;
5 use rustc_hash
::FxHashMap
;
7 extract_range_or_offset
, Fixture
, RangeOrOffset
, CURSOR_MARKER
, ESCAPED_CURSOR_MARKER
,
10 use vfs
::{file_set::FileSet, VfsPath}
;
13 input
::{CrateName, CrateOrigin, LangCrateOrigin}
,
14 Change
, CrateDisplayName
, CrateGraph
, CrateId
, Dependency
, Edition
, Env
, FileId
, FilePosition
,
15 FileRange
, ProcMacro
, ProcMacroExpander
, ProcMacroExpansionError
, SourceDatabaseExt
,
16 SourceRoot
, SourceRootId
,
19 pub const WORKSPACE
: SourceRootId
= SourceRootId(0);
21 pub trait WithFixture
: Default
+ SourceDatabaseExt
+ '
static {
22 fn with_single_file(ra_fixture
: &str) -> (Self, FileId
) {
23 let fixture
= ChangeFixture
::parse(ra_fixture
);
24 let mut db
= Self::default();
25 fixture
.change
.apply(&mut db
);
26 assert_eq
!(fixture
.files
.len(), 1);
27 (db
, fixture
.files
[0])
30 fn with_many_files(ra_fixture
: &str) -> (Self, Vec
<FileId
>) {
31 let fixture
= ChangeFixture
::parse(ra_fixture
);
32 let mut db
= Self::default();
33 fixture
.change
.apply(&mut db
);
34 assert
!(fixture
.file_position
.is_none());
38 fn with_files(ra_fixture
: &str) -> Self {
39 let fixture
= ChangeFixture
::parse(ra_fixture
);
40 let mut db
= Self::default();
41 fixture
.change
.apply(&mut db
);
42 assert
!(fixture
.file_position
.is_none());
46 fn with_files_extra_proc_macros(
48 proc_macros
: Vec
<(String
, ProcMacro
)>,
50 let fixture
= ChangeFixture
::parse_with_proc_macros(ra_fixture
, proc_macros
);
51 let mut db
= Self::default();
52 fixture
.change
.apply(&mut db
);
53 assert
!(fixture
.file_position
.is_none());
57 fn with_position(ra_fixture
: &str) -> (Self, FilePosition
) {
58 let (db
, file_id
, range_or_offset
) = Self::with_range_or_offset(ra_fixture
);
59 let offset
= range_or_offset
.expect_offset();
60 (db
, FilePosition { file_id, offset }
)
63 fn with_range(ra_fixture
: &str) -> (Self, FileRange
) {
64 let (db
, file_id
, range_or_offset
) = Self::with_range_or_offset(ra_fixture
);
65 let range
= range_or_offset
.expect_range();
66 (db
, FileRange { file_id, range }
)
69 fn with_range_or_offset(ra_fixture
: &str) -> (Self, FileId
, RangeOrOffset
) {
70 let fixture
= ChangeFixture
::parse(ra_fixture
);
71 let mut db
= Self::default();
72 fixture
.change
.apply(&mut db
);
73 let (file_id
, range_or_offset
) = fixture
75 .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
76 (db
, file_id
, range_or_offset
)
79 fn test_crate(&self) -> CrateId
{
80 let crate_graph
= self.crate_graph();
81 let mut it
= crate_graph
.iter();
82 let res
= it
.next().unwrap();
83 assert
!(it
.next().is_none());
88 impl<DB
: SourceDatabaseExt
+ Default
+ '
static> WithFixture
for DB {}
90 pub struct ChangeFixture
{
91 pub file_position
: Option
<(FileId
, RangeOrOffset
)>,
92 pub files
: Vec
<FileId
>,
97 pub fn parse(ra_fixture
: &str) -> ChangeFixture
{
98 Self::parse_with_proc_macros(ra_fixture
, Vec
::new())
101 pub fn parse_with_proc_macros(
103 mut proc_macros
: Vec
<(String
, ProcMacro
)>,
105 let (mini_core
, proc_macro_names
, fixture
) = Fixture
::parse(ra_fixture
);
106 let mut change
= Change
::new();
108 let mut files
= Vec
::new();
109 let mut crate_graph
= CrateGraph
::default();
110 let mut crates
= FxHashMap
::default();
111 let mut crate_deps
= Vec
::new();
112 let mut default_crate_root
: Option
<FileId
> = None
;
113 let mut default_cfg
= CfgOptions
::default();
115 let mut file_set
= FileSet
::default();
116 let mut current_source_root_kind
= SourceRootKind
::Local
;
117 let source_root_prefix
= "/".to_string();
118 let mut file_id
= FileId(0);
119 let mut roots
= Vec
::new();
121 let mut file_position
= None
;
123 for entry
in fixture
{
124 let text
= if entry
.text
.contains(CURSOR_MARKER
) {
125 if entry
.text
.contains(ESCAPED_CURSOR_MARKER
) {
126 entry
.text
.replace(ESCAPED_CURSOR_MARKER
, CURSOR_MARKER
)
128 let (range_or_offset
, text
) = extract_range_or_offset(&entry
.text
);
129 assert
!(file_position
.is_none());
130 file_position
= Some((file_id
, range_or_offset
));
137 let meta
= FileMeta
::from(entry
);
138 assert
!(meta
.path
.starts_with(&source_root_prefix
));
139 if !meta
.deps
.is_empty() {
140 assert
!(meta
.krate
.is_some(), "can't specify deps without naming the crate")
143 if let Some(kind
) = &meta
.introduce_new_source_root
{
144 let root
= match current_source_root_kind
{
145 SourceRootKind
::Local
=> SourceRoot
::new_local(mem
::take(&mut file_set
)),
146 SourceRootKind
::Library
=> SourceRoot
::new_library(mem
::take(&mut file_set
)),
149 current_source_root_kind
= *kind
;
152 if let Some((krate
, origin
, version
)) = meta
.krate
{
153 let crate_name
= CrateName
::normalize_dashes(&krate
);
154 let crate_id
= crate_graph
.add_crate_root(
157 Some(crate_name
.clone().into()),
165 meta
.target_data_layout
.as_deref().map(Arc
::from
),
167 let prev
= crates
.insert(crate_name
.clone(), crate_id
);
168 assert
!(prev
.is_none());
169 for dep
in meta
.deps
{
170 let prelude
= meta
.extern_prelude
.contains(&dep
);
171 let dep
= CrateName
::normalize_dashes(&dep
);
172 crate_deps
.push((crate_name
.clone(), dep
, prelude
))
174 } else if meta
.path
== "/main.rs" || meta
.path
== "/lib.rs" {
175 assert
!(default_crate_root
.is_none());
176 default_crate_root
= Some(file_id
);
177 default_cfg
= meta
.cfg
;
180 change
.change_file(file_id
, Some(Arc
::new(text
)));
181 let path
= VfsPath
::new_virtual_path(meta
.path
);
182 file_set
.insert(file_id
, path
);
187 if crates
.is_empty() {
188 let crate_root
= default_crate_root
189 .expect("missing default crate root, specify a main.rs or lib.rs");
190 crate_graph
.add_crate_root(
193 Some(CrateName
::new("test").unwrap().into()),
200 CrateOrigin
::CratesIo { repo: None, name: None }
,
204 for (from
, to
, prelude
) in crate_deps
{
205 let from_id
= crates
[&from
];
206 let to_id
= crates
[&to
];
210 Dependency
::with_prelude(CrateName
::new(&to
).unwrap(), to_id
, prelude
),
216 crate_graph
.iter().next().and_then(|it
| crate_graph
[it
].target_layout
.clone());
218 if let Some(mini_core
) = mini_core
{
219 let core_file
= file_id
;
222 let mut fs
= FileSet
::default();
223 fs
.insert(core_file
, VfsPath
::new_virtual_path("/sysroot/core/lib.rs".to_string()));
224 roots
.push(SourceRoot
::new_library(fs
));
226 change
.change_file(core_file
, Some(Arc
::new(mini_core
.source_code())));
228 let all_crates
= crate_graph
.crates_in_topological_order();
230 let core_crate
= crate_graph
.add_crate_root(
232 Edition
::Edition2021
,
233 Some(CrateDisplayName
::from_canonical_name("core".to_string())),
235 CfgOptions
::default(),
236 CfgOptions
::default(),
240 CrateOrigin
::Lang(LangCrateOrigin
::Core
),
241 target_layout
.clone(),
244 for krate
in all_crates
{
246 .add_dep(krate
, Dependency
::new(CrateName
::new("core").unwrap(), core_crate
))
251 if !proc_macro_names
.is_empty() {
252 let proc_lib_file
= file_id
;
255 proc_macros
.extend(default_test_proc_macros());
256 let (proc_macro
, source
) = filter_test_proc_macros(&proc_macro_names
, proc_macros
);
257 let mut fs
= FileSet
::default();
260 VfsPath
::new_virtual_path("/sysroot/proc_macros/lib.rs".to_string()),
262 roots
.push(SourceRoot
::new_library(fs
));
264 change
.change_file(proc_lib_file
, Some(Arc
::new(source
)));
266 let all_crates
= crate_graph
.crates_in_topological_order();
268 let proc_macros_crate
= crate_graph
.add_crate_root(
270 Edition
::Edition2021
,
271 Some(CrateDisplayName
::from_canonical_name("proc_macros".to_string())),
273 CfgOptions
::default(),
274 CfgOptions
::default(),
278 CrateOrigin
::CratesIo { repo: None, name: None }
,
282 for krate
in all_crates
{
286 Dependency
::new(CrateName
::new("proc_macros").unwrap(), proc_macros_crate
),
292 let root
= match current_source_root_kind
{
293 SourceRootKind
::Local
=> SourceRoot
::new_local(mem
::take(&mut file_set
)),
294 SourceRootKind
::Library
=> SourceRoot
::new_library(mem
::take(&mut file_set
)),
297 change
.set_roots(roots
);
298 change
.set_crate_graph(crate_graph
);
300 ChangeFixture { file_position, files, change }
304 fn default_test_proc_macros() -> [(String
, ProcMacro
); 4] {
308 #[proc_macro_attribute]
309 pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
315 name
: "identity".into(),
316 kind
: crate::ProcMacroKind
::Attr
,
317 expander
: Arc
::new(IdentityProcMacroExpander
),
322 #[proc_macro_derive(DeriveIdentity)]
323 pub fn derive_identity(item: TokenStream) -> TokenStream {
329 name
: "DeriveIdentity".into(),
330 kind
: crate::ProcMacroKind
::CustomDerive
,
331 expander
: Arc
::new(IdentityProcMacroExpander
),
336 #[proc_macro_attribute]
337 pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
343 name
: "input_replace".into(),
344 kind
: crate::ProcMacroKind
::Attr
,
345 expander
: Arc
::new(AttributeInputReplaceProcMacroExpander
),
351 pub fn mirror(input: TokenStream) -> TokenStream {
357 name
: "mirror".into(),
358 kind
: crate::ProcMacroKind
::FuncLike
,
359 expander
: Arc
::new(MirrorProcMacroExpander
),
365 fn filter_test_proc_macros(
366 proc_macro_names
: &[String
],
367 proc_macro_defs
: Vec
<(String
, ProcMacro
)>,
368 ) -> (Vec
<ProcMacro
>, String
) {
369 // The source here is only required so that paths to the macros exist and are resolvable.
370 let mut source
= String
::new();
371 let mut proc_macros
= Vec
::new();
373 for (c
, p
) in proc_macro_defs
{
374 if !proc_macro_names
.iter().any(|name
| name
== &stdx
::to_lower_snake_case(&p
.name
)) {
381 (proc_macros
, source
)
384 #[derive(Debug, Clone, Copy)]
385 enum SourceRootKind
{
393 krate
: Option
<(String
, CrateOrigin
, Option
<String
>)>,
395 extern_prelude
: Vec
<String
>,
399 introduce_new_source_root
: Option
<SourceRootKind
>,
400 target_data_layout
: Option
<String
>,
403 fn parse_crate(crate_str
: String
) -> (String
, CrateOrigin
, Option
<String
>) {
404 if let Some((a
, b
)) = crate_str
.split_once('@'
) {
405 let (version
, origin
) = match b
.split_once('
:'
) {
406 Some(("CratesIo", data
)) => match data
.split_once('
,'
) {
407 Some((version
, url
)) => {
408 (version
, CrateOrigin
::CratesIo { repo: Some(url.to_owned()), name: None }
)
410 _
=> panic
!("Bad crates.io parameter: {data}"),
412 _
=> panic
!("Bad string for crate origin: {b}"),
414 (a
.to_owned(), origin
, Some(version
.to_string()))
416 let crate_origin
= match &*crate_str
{
417 "std" => CrateOrigin
::Lang(LangCrateOrigin
::Std
),
418 "core" => CrateOrigin
::Lang(LangCrateOrigin
::Core
),
419 _
=> CrateOrigin
::CratesIo { repo: None, name: None }
,
421 (crate_str
, crate_origin
, None
)
425 impl From
<Fixture
> for FileMeta
{
426 fn from(f
: Fixture
) -> FileMeta
{
427 let mut cfg
= CfgOptions
::default();
428 f
.cfg_atoms
.iter().for_each(|it
| cfg
.insert_atom(it
.into()));
429 f
.cfg_key_values
.iter().for_each(|(k
, v
)| cfg
.insert_key_value(k
.into(), v
.into()));
433 krate
: f
.krate
.map(parse_crate
),
434 extern_prelude
: f
.extern_prelude
.unwrap_or_else(|| deps
.clone()),
437 edition
: f
.edition
.as_ref().map_or(Edition
::CURRENT
, |v
| Edition
::from_str(v
).unwrap()),
438 env
: f
.env
.into_iter().collect(),
439 introduce_new_source_root
: f
.introduce_new_source_root
.map(|kind
| match &*kind
{
440 "local" => SourceRootKind
::Local
,
441 "library" => SourceRootKind
::Library
,
442 invalid
=> panic
!("invalid source root kind '{invalid}'"),
444 target_data_layout
: f
.target_data_layout
,
451 struct IdentityProcMacroExpander
;
452 impl ProcMacroExpander
for IdentityProcMacroExpander
{
458 ) -> Result
<Subtree
, ProcMacroExpansionError
> {
463 // Pastes the attribute input as its output
465 struct AttributeInputReplaceProcMacroExpander
;
466 impl ProcMacroExpander
for AttributeInputReplaceProcMacroExpander
{
470 attrs
: Option
<&Subtree
>,
472 ) -> Result
<Subtree
, ProcMacroExpansionError
> {
475 .ok_or_else(|| ProcMacroExpansionError
::Panic("Expected attribute input".into()))
480 struct MirrorProcMacroExpander
;
481 impl ProcMacroExpander
for MirrorProcMacroExpander
{
487 ) -> Result
<Subtree
, ProcMacroExpansionError
> {
488 fn traverse(input
: &Subtree
) -> Subtree
{
489 let mut res
= Subtree
::default();
490 res
.delimiter
= input
.delimiter
;
491 for tt
in input
.token_trees
.iter().rev() {
493 tt
::TokenTree
::Leaf(leaf
) => tt
::TokenTree
::Leaf(leaf
.clone()),
494 tt
::TokenTree
::Subtree(sub
) => tt
::TokenTree
::Subtree(traverse(sub
)),
496 res
.token_trees
.push(tt
);