1 | // vim: tw=80 |
2 | use super::*; |
3 | |
4 | /// Performs transformations on a function to make it mockable |
5 | fn mockable_fn(mut item_fn: ItemFn) -> ItemFn { |
6 | demutify(&mut item_fn.sig.inputs); |
7 | deimplify(&mut item_fn.sig.output); |
8 | item_fn |
9 | } |
10 | |
11 | /// Performs transformations on an Item to make it mockable |
12 | fn mockable_item(item: Item) -> Item { |
13 | match item { |
14 | Item::Fn(item_fn) => Item::Fn(mockable_fn(item_fn)), |
15 | x => x |
16 | } |
17 | } |
18 | |
19 | /// An item that's ready to be mocked. |
20 | /// |
21 | /// It should be functionally identical or near-identical to the original item, |
22 | /// but with minor alterations that make it suitable for mocking, such as |
23 | /// altered lifetimes. |
24 | pub(crate) enum MockableItem { |
25 | Module(MockableModule), |
26 | Struct(MockableStruct) |
27 | } |
28 | |
29 | impl From<(Attrs, Item)> for MockableItem { |
30 | fn from((attrs, item): (Attrs, Item)) -> MockableItem { |
31 | match item { |
32 | Item::Impl(item_impl) => |
33 | MockableItem::Struct(MockableStruct::from(item_impl)), |
34 | Item::ForeignMod(item_foreign_mod) => |
35 | MockableItem::Module( |
36 | MockableModule::from((attrs, item_foreign_mod)) |
37 | ), |
38 | Item::Mod(item_mod) => |
39 | MockableItem::Module(MockableModule::from(item_mod)), |
40 | Item::Trait(trait_) => |
41 | MockableItem::Struct(MockableStruct::from((attrs, trait_))), |
42 | _ => panic!("automock does not support this item type" ) |
43 | } |
44 | } |
45 | } |
46 | |
47 | impl From<MockableStruct> for MockableItem { |
48 | fn from(mock: MockableStruct) -> MockableItem { |
49 | MockableItem::Struct(mock) |
50 | } |
51 | } |
52 | |
53 | pub(crate) struct MockableModule { |
54 | pub attrs: TokenStream, |
55 | pub vis: Visibility, |
56 | pub mock_ident: Ident, |
57 | /// Ident of the original module, if any |
58 | pub orig_ident: Option<Ident>, |
59 | pub content: Vec<Item> |
60 | } |
61 | |
62 | impl From<(Attrs, ItemForeignMod)> for MockableModule { |
63 | fn from((attrs, foreign): (Attrs, ItemForeignMod)) -> MockableModule { |
64 | let orig_ident = None; |
65 | let mock_ident = attrs.modname.expect(concat!( |
66 | "module name is required when mocking foreign functions," , |
67 | " like `#[automock(mod mock_ffi)]`" |
68 | )); |
69 | let vis = Visibility::Public(VisPublic{ |
70 | pub_token: <Token![pub]>::default() |
71 | }); |
72 | let attrs = quote!( |
73 | #[deprecated(since = "0.9.0" , note = "Using automock directly on an extern block is deprecated. Instead, wrap the extern block in a module, and automock that, like #[automock] mod ffi { extern \"C \" { fn foo ... } }" )] |
74 | ); |
75 | let mut content = vec![ |
76 | // When mocking extern blocks, we pretend that they're modules, so |
77 | // we need a "use super::*;" to ensure that types can resolve |
78 | Item::Use(ItemUse { |
79 | attrs: Vec::new(), |
80 | vis: Visibility::Inherited, |
81 | use_token: token::Use::default(), |
82 | leading_colon: None, |
83 | tree: UseTree::Path(UsePath { |
84 | ident: Ident::new("super" , Span::call_site()), |
85 | colon2_token: token::Colon2::default(), |
86 | tree: Box::new(UseTree::Glob(UseGlob { |
87 | star_token: token::Star::default() |
88 | })) |
89 | }), |
90 | semi_token: token::Semi::default() |
91 | }) |
92 | ]; |
93 | content.extend(foreign.items.into_iter() |
94 | .map(|foreign_item| { |
95 | match foreign_item { |
96 | ForeignItem::Fn(f) => { |
97 | let span = f.sig.span(); |
98 | let mut sig = f.sig; |
99 | |
100 | // When mocking extern blocks, we pretend that they're |
101 | // modules. So we must supersuperfy everything by one |
102 | // level. |
103 | let vis = expectation_visibility(&f.vis, 1); |
104 | |
105 | for arg in sig.inputs.iter_mut() { |
106 | if let FnArg::Typed(pt) = arg { |
107 | *pt.ty = supersuperfy(pt.ty.as_ref(), 1); |
108 | } |
109 | } |
110 | if let ReturnType::Type(_, ty) = &mut sig.output { |
111 | **ty = supersuperfy(&*ty, 1); |
112 | } |
113 | |
114 | // Foreign functions are always unsafe. Mock foreign |
115 | // functions should be unsafe too, to prevent "warning: |
116 | // unused unsafe" messages. |
117 | sig.unsafety = Some(Token![unsafe](span)); |
118 | let block = Box::new(Block { |
119 | brace_token: token::Brace::default(), |
120 | stmts: Vec::new() |
121 | }); |
122 | |
123 | Item::Fn( |
124 | ItemFn { |
125 | attrs: f.attrs, |
126 | vis, |
127 | sig, |
128 | block |
129 | } |
130 | ) |
131 | }, |
132 | _ => { |
133 | compile_error(foreign_item.span(), |
134 | "Unsupported foreign item type" |
135 | ); |
136 | Item::Verbatim(TokenStream::default()) |
137 | } |
138 | } |
139 | })); |
140 | MockableModule { |
141 | attrs, |
142 | vis, |
143 | mock_ident, |
144 | orig_ident, |
145 | content |
146 | } |
147 | } |
148 | } |
149 | |
150 | impl From<ItemMod> for MockableModule { |
151 | fn from(mod_: ItemMod) -> MockableModule { |
152 | let span = mod_.span(); |
153 | let vis = mod_.vis; |
154 | let mock_ident = format_ident!("mock_{}" , mod_.ident); |
155 | let orig_ident = Some(mod_.ident); |
156 | let content = if let Some((_, content)) = mod_.content { |
157 | content.into_iter() |
158 | .map(mockable_item) |
159 | .collect() |
160 | } else { |
161 | compile_error(span, |
162 | "automock can only mock inline modules, not modules from another file" ); |
163 | Vec::new() |
164 | }; |
165 | MockableModule { |
166 | attrs: TokenStream::new(), |
167 | vis, |
168 | mock_ident, |
169 | orig_ident, |
170 | content |
171 | } |
172 | } |
173 | } |
174 | |