1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// Copyright 2014-2016 The Stainless Developers. See the LICENSE file at the top-level directory of
// this distrubution.
//
// Licensed under the MIT license. This file may not be copied, modified, or distributed except
// according to those terms.
use std::ops::Deref;
use syntax::{ast, abi, codemap};
use syntax::ptr::P;
use syntax::ext::base;
use syntax::symbol::Symbol;
use syntax::ext::build::AstBuilder;
use test::Test;
use bench::Bench;
use describe::{DescribeState, SubBlock};
/// Trait meaning something can be turned into an ast::Item with configuration.
pub trait Generate<Cfg> {
/// Turn Self into an ast::Item with a configuration object.
fn generate(self, codemap::Span, &mut base::ExtCtxt, Cfg) -> P<ast::Item>;
}
impl<'a> Generate<&'a DescribeState> for Test {
fn generate(self, sp: codemap::Span, cx: &mut base::ExtCtxt, state: &'a DescribeState) -> P<ast::Item> {
let Test { description, block, test_config } = self;
// Create the #[test] attribute.
let test_attribute = cx.attribute(sp, cx.meta_word(sp, Symbol::intern("test")));
// Create the #[should_panic] attribute.
let should_panic = cx.attribute(sp, cx.meta_word(sp, Symbol::intern("should_panic")));
// Create the #[ignore] attribute.
let ignore = cx.attribute(sp, cx.meta_word(sp, Symbol::intern("ignore")));
let non_snake_word = cx.meta_list_item_word(sp, Symbol::intern("non_snake_case"));
let allow_non_snake_case = cx.meta_list(sp, Symbol::intern("allow"),
vec![non_snake_word]);
let allow_non_snake_case = cx.attribute(sp, allow_non_snake_case);
// Create the full test body by splicing in the statements and view items of the before and
// after blocks if they are present.
let test_body = match (&state.before_each, &state.after_each) {
(&None, &None) => block,
(&Some(ref before), &None) => {
P(ast::Block {
stmts: before.stmts.iter()
.chain(block.stmts.iter())
.cloned().collect(),
..block.deref().clone()
})
},
(&None, &Some(ref after)) => {
P(ast::Block {
stmts: block.stmts.iter()
.chain(after.stmts.iter())
.cloned().collect(),
..block.deref().clone()
})
},
(&Some(ref before), &Some(ref after)) => {
P(ast::Block {
stmts: before.stmts.iter()
.chain(block.stmts.iter())
.chain(after.stmts.iter())
.cloned().collect(),
..block.deref().clone()
})
}
};
// Constructing attributes:
// #[test] - no way without it
// #[allow(non_snake_case_attr)] as description may contain upper case
// #[should_panic] or #[should_panic(expected = "...")] if specified
// #[ignore] if specified
let mut attrs = vec![test_attribute, allow_non_snake_case];
if test_config.failing {
match test_config.failing_msg {
Some(msg) => {
// Create #[should_panic(expected = "...")] attribute
let should_panic_str = Symbol::intern("should_panic");
let expected_str = Symbol::intern("expected");
let expected_name_value = cx.meta_name_value(
sp,
expected_str,
ast::LitKind::Str(msg.0, msg.1)
);
let nested_expected_name_value = codemap::respan(
sp,
ast::NestedMetaItemKind::MetaItem(expected_name_value));
attrs.push(cx.attribute(sp, cx.meta_list(
sp,
should_panic_str,
vec![nested_expected_name_value]
)));
},
None => attrs.push(should_panic)
};
}
if test_config.ignored {
attrs.push(ignore);
}
// Create the final Item that represents the test.
P(ast::Item {
// Name it with a snake_case version of the description.
ident: cx.ident_of(&description.replace(" ", "_")),
attrs: attrs,
id: ast::DUMMY_NODE_ID,
node: ast::ItemKind::Fn(
// Takes no arguments and returns ()
P(ast::FnDecl {
inputs: vec![],
output: ast::FunctionRetTy::Default(sp),
variadic: false
}),
// All the usual types.
ast::Unsafety::Normal,
codemap::respan(sp, ast::Constness::NotConst),
abi::Abi::Rust,
ast::Generics::default(),
// Add the body of the function.
test_body
),
// Inherited visibility (not pub)
vis: ast::Visibility::Inherited,
span: sp,
tokens: None,
})
}
}
impl Generate<()> for Bench {
fn generate(self, sp: codemap::Span, cx: &mut base::ExtCtxt, _: ()) -> P<ast::Item> {
let Bench { bench, description, block } = self;
// Create the #[bench] attribute.
let bench_attribute = cx.attribute(sp, cx.meta_word(sp, Symbol::intern("bench")));
// Create the final Item that represents the benchmark.
P(ast::Item {
// Name it with a snake_case version of the description.
ident: cx.ident_of(&description.replace(" ", "_")),
// Add #[test] and possibly #[should_panic]
attrs: vec![bench_attribute],
id: ast::DUMMY_NODE_ID,
node: ast::ItemKind::Fn(
// Takes one argument of &mut Bencher
P(ast::FnDecl {
inputs: vec![ast::Arg {
ty: quote_ty!(cx, &mut ::test::Bencher),
pat: quote_pat!(cx, $bench),
id: ast::DUMMY_NODE_ID
}],
output: ast::FunctionRetTy::Default(sp),
variadic: false
}),
// All the usual types.
ast::Unsafety::Normal,
codemap::respan(sp, ast::Constness::NotConst),
abi::Abi::Rust,
ast::Generics::default(),
// Add the body of the function.
block
),
// Inherited visibility (not pub)
vis: ast::Visibility::Inherited,
span: sp,
tokens: None,
})
}
}
impl<'a> Generate<&'a DescribeState> for SubBlock {
fn generate(self, sp: codemap::Span, cx: &mut base::ExtCtxt, state: &'a DescribeState) -> P<ast::Item> {
match self {
SubBlock::Test(test) => test.generate(sp, cx, state),
SubBlock::Bench(bench) => bench.generate(sp, cx, ()),
SubBlock::Describe(item) => item.generate(sp, cx, Some(state))
}
}
}
impl<'a> Generate<Option<&'a DescribeState>> for DescribeState {
fn generate(mut self, sp: codemap::Span, cx: &mut base::ExtCtxt,
state: Option<&'a DescribeState>) -> P<ast::Item> {
// Get the name of this mod.
let name = self.name.clone().unwrap();
if let Some(state) = state {
if let Some(ref parent) = state.before_each {
self.before_each = match self.before_each {
Some(ref now) => Some(P(ast::Block {
stmts: parent.stmts.iter().chain(&*now.stmts).cloned().collect(),
..now.deref().clone()
})),
None => Some(P(parent.deref().clone()))
};
}
if let Some(ref parent) = state.after_each {
self.after_each = match self.after_each {
Some(ref now) => Some(P(ast::Block {
stmts: now.stmts.iter().chain(&*parent.stmts).cloned().collect(),
..now.deref().clone()
})),
None => Some(P(parent.deref().clone()))
};
}
}
// Get a glob import of all items in scope to the module that `describe!` is called from.
//
// This glob is `pub use super::*` so that nested `describe!` blocks (which will also contain
// this glob) will be able to see all the symbols.
let super_glob = cx.item_use_glob(sp, ast::Visibility::Public, vec![cx.ident_of("super")]);
let mut items = vec![super_glob];
// Create subblocks from a full DescribeState
items.extend(self.subblocks.clone().into_iter().map(|block| {
block.generate(sp, cx, &self)
}));
// Generate the new module.
cx.item_mod(sp, sp, name, vec![], items)
}
}