1 /// The expansion from a test function to the appropriate test struct for libtest
2 /// Ideally, this code would be in libtest but for efficiency and error messages it lives here.
4 use crate::util
::check_builtin_macro_attribute
;
8 use syntax_expand
::base
::*;
9 use syntax
::print
::pprust
;
10 use syntax
::source_map
::respan
;
11 use syntax
::symbol
::{Symbol, sym}
;
16 // #[test_case] is used by custom test authors to mark tests
17 // When building for test, it needs to make the item public and gensym the name
18 // Otherwise, we'll omit the item. This behavior means that any item annotated
19 // with #[test_case] is never addressable.
21 // We mark item with an inert attribute "rustc_test_marker" which the test generation
22 // logic will pick up on.
23 pub fn expand_test_case(
24 ecx
: &mut ExtCtxt
<'_
>,
26 meta_item
: &ast
::MetaItem
,
27 anno_item
: Annotatable
28 ) -> Vec
<Annotatable
> {
29 check_builtin_macro_attribute(ecx
, meta_item
, sym
::test_case
);
31 if !ecx
.ecfg
.should_test { return vec![]; }
33 let sp
= ecx
.with_def_site_ctxt(attr_sp
);
34 let mut item
= anno_item
.expect_item();
35 item
= item
.map(|mut item
| {
36 item
.vis
= respan(item
.vis
.span
, ast
::VisibilityKind
::Public
);
37 item
.ident
.span
= item
.ident
.span
.with_ctxt(sp
.ctxt());
39 ecx
.attribute(ecx
.meta_word(sp
, sym
::rustc_test_marker
))
44 return vec
![Annotatable
::Item(item
)]
50 meta_item
: &ast
::MetaItem
,
52 ) -> Vec
<Annotatable
> {
53 check_builtin_macro_attribute(cx
, meta_item
, sym
::test
);
54 expand_test_or_bench(cx
, attr_sp
, item
, false)
60 meta_item
: &ast
::MetaItem
,
62 ) -> Vec
<Annotatable
> {
63 check_builtin_macro_attribute(cx
, meta_item
, sym
::bench
);
64 expand_test_or_bench(cx
, attr_sp
, item
, true)
67 pub fn expand_test_or_bench(
72 ) -> Vec
<Annotatable
> {
73 // If we're not in test configuration, remove the annotated item
74 if !cx
.ecfg
.should_test { return vec![]; }
77 if let Annotatable
::Item(i
) = item { i }
79 cx
.parse_sess
.span_diagnostic
.span_fatal(item
.span(),
80 "`#[test]` attribute is only allowed on non associated functions").raise();
83 if let ast
::ItemKind
::Mac(_
) = item
.kind
{
84 cx
.parse_sess
.span_diagnostic
.span_warn(item
.span
,
85 "`#[test]` attribute should not be used on macros. Use `#[cfg(test)]` instead.");
86 return vec
![Annotatable
::Item(item
)];
89 // has_*_signature will report any errors in the type so compilation
90 // will fail. We shouldn't try to expand in this case because the errors
92 if (!is_bench
&& !has_test_signature(cx
, &item
)) ||
93 (is_bench
&& !has_bench_signature(cx
, &item
)) {
94 return vec
![Annotatable
::Item(item
)];
97 let (sp
, attr_sp
) = (cx
.with_def_site_ctxt(item
.span
), cx
.with_def_site_ctxt(attr_sp
));
99 let test_id
= ast
::Ident
::new(sym
::test
, attr_sp
);
101 // creates test::$name
102 let test_path
= |name
| {
103 cx
.path(sp
, vec
![test_id
, cx
.ident_of(name
, sp
)])
106 // creates test::ShouldPanic::$name
107 let should_panic_path
= |name
| {
108 cx
.path(sp
, vec
![test_id
, cx
.ident_of("ShouldPanic", sp
), cx
.ident_of(name
, sp
)])
111 // creates test::TestType::$name
112 let test_type_path
= |name
| {
113 cx
.path(sp
, vec
![test_id
, cx
.ident_of("TestType", sp
), cx
.ident_of(name
, sp
)])
116 // creates $name: $expr
117 let field
= |name
, expr
| cx
.field_imm(sp
, cx
.ident_of(name
, sp
), expr
);
119 let test_fn
= if is_bench
{
120 // A simple ident for a lambda
121 let b
= cx
.ident_of("b", attr_sp
);
123 cx
.expr_call(sp
, cx
.expr_path(test_path("StaticBenchFn")), vec
![
124 // |b| self::test::assert_test_result(
126 cx
.expr_call(sp
, cx
.expr_path(test_path("assert_test_result")), vec
![
127 // super::$test_fn(b)
129 cx
.expr_path(cx
.path(sp
, vec
![item
.ident
])),
130 vec
![cx
.expr_ident(sp
, b
)])
137 cx
.expr_call(sp
, cx
.expr_path(test_path("StaticTestFn")), vec
![
140 // test::assert_test_result(
141 cx
.expr_call(sp
, cx
.expr_path(test_path("assert_test_result")), vec
![
143 cx
.expr_call(sp
, cx
.expr_path(cx
.path(sp
, vec
![item
.ident
])), vec
![])
152 let mut test_const
= cx
.item(sp
, ast
::Ident
::new(item
.ident
.name
, sp
),
155 cx
.attribute(attr
::mk_list_item(ast
::Ident
::new(sym
::cfg
, attr_sp
), vec
![
156 attr
::mk_nested_word_item(ast
::Ident
::new(sym
::test
, attr_sp
))
158 // #[rustc_test_marker]
159 cx
.attribute(cx
.meta_word(attr_sp
, sym
::rustc_test_marker
)),
161 // const $ident: test::TestDescAndFn =
162 ast
::ItemKind
::Const(cx
.ty(sp
, ast
::TyKind
::Path(None
, test_path("TestDescAndFn"))),
163 // test::TestDescAndFn {
164 cx
.expr_struct(sp
, test_path("TestDescAndFn"), vec
![
165 // desc: test::TestDesc {
166 field("desc", cx
.expr_struct(sp
, test_path("TestDesc"), vec
![
167 // name: "path::to::test"
168 field("name", cx
.expr_call(sp
, cx
.expr_path(test_path("StaticTestName")),
170 cx
.expr_str(sp
, Symbol
::intern(&item_path(
171 // skip the name of the root module
172 &cx
.current_expansion
.module
.mod_path
[1..],
176 // ignore: true | false
177 field("ignore", cx
.expr_bool(sp
, should_ignore(&item
))),
178 // allow_fail: true | false
179 field("allow_fail", cx
.expr_bool(sp
, should_fail(&item
))),
181 field("should_panic", match should_panic(cx
, &item
) {
182 // test::ShouldPanic::No
183 ShouldPanic
::No
=> cx
.expr_path(should_panic_path("No")),
184 // test::ShouldPanic::Yes
185 ShouldPanic
::Yes(None
) => cx
.expr_path(should_panic_path("Yes")),
186 // test::ShouldPanic::YesWithMessage("...")
187 ShouldPanic
::Yes(Some(sym
)) => cx
.expr_call(sp
,
188 cx
.expr_path(should_panic_path("YesWithMessage")),
189 vec
![cx
.expr_str(sp
, sym
)]),
192 field("test_type", match test_type(cx
) {
193 // test::TestType::UnitTest
194 TestType
::UnitTest
=> cx
.expr_path(test_type_path("UnitTest")),
195 // test::TestType::IntegrationTest
196 TestType
::IntegrationTest
=> cx
.expr_path(
197 test_type_path("IntegrationTest")
199 // test::TestPath::Unknown
200 TestType
::Unknown
=> cx
.expr_path(test_type_path("Unknown")),
204 // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
205 field("testfn", test_fn
)
210 test_const
= test_const
.map(|mut tc
| { tc.vis.node = ast::VisibilityKind::Public; tc}
);
213 let test_extern
= cx
.item(sp
,
216 ast
::ItemKind
::ExternCrate(None
)
219 log
::debug
!("synthetic test item:\n{}\n", pprust
::item_to_string(&test_const
));
222 // Access to libtest under a hygienic name
223 Annotatable
::Item(test_extern
),
224 // The generated test case
225 Annotatable
::Item(test_const
),
227 Annotatable
::Item(item
)
231 fn item_path(mod_path
: &[ast
::Ident
], item_ident
: &ast
::Ident
) -> String
{
232 mod_path
.iter().chain(iter
::once(item_ident
))
233 .map(|x
| x
.to_string()).collect
::<Vec
<String
>>().join("::")
241 fn should_ignore(i
: &ast
::Item
) -> bool
{
242 attr
::contains_name(&i
.attrs
, sym
::ignore
)
245 fn should_fail(i
: &ast
::Item
) -> bool
{
246 attr
::contains_name(&i
.attrs
, sym
::allow_fail
)
249 fn should_panic(cx
: &ExtCtxt
<'_
>, i
: &ast
::Item
) -> ShouldPanic
{
250 match attr
::find_by_name(&i
.attrs
, sym
::should_panic
) {
252 let ref sd
= cx
.parse_sess
.span_diagnostic
;
254 match attr
.meta_item_list() {
255 // Handle #[should_panic(expected = "foo")]
257 let msg
= list
.iter()
258 .find(|mi
| mi
.check_name(sym
::expected
))
259 .and_then(|mi
| mi
.meta_item())
260 .and_then(|mi
| mi
.value_str());
261 if list
.len() != 1 || msg
.is_none() {
264 "argument must be of the form: \
265 `expected = \"error message\"`"
266 ).note("Errors in this attribute were erroneously \
267 allowed and will become a hard error in a \
268 future release.").emit();
269 ShouldPanic
::Yes(None
)
271 ShouldPanic
::Yes(msg
)
274 // Handle #[should_panic] and #[should_panic = "expected"]
275 None
=> ShouldPanic
::Yes(attr
.value_str())
278 None
=> ShouldPanic
::No
,
288 /// Attempts to determine the type of test.
289 /// Since doctests are created without macro expanding, only possible variants here
290 /// are `UnitTest`, `IntegrationTest` or `Unknown`.
291 fn test_type(cx
: &ExtCtxt
<'_
>) -> TestType
{
292 // Root path from context contains the topmost sources directory of the crate.
293 // I.e., for `project` with sources in `src` and tests in `tests` folders
294 // (no matter how many nested folders lie inside),
295 // there will be two different root paths: `/project/src` and `/project/tests`.
296 let crate_path
= cx
.root_path
.as_path();
298 if crate_path
.ends_with("src") {
299 // `/src` folder contains unit-tests.
301 } else if crate_path
.ends_with("tests") {
302 // `/tests` folder contains integration tests.
303 TestType
::IntegrationTest
305 // Crate layout doesn't match expected one, test type is unknown.
310 fn has_test_signature(cx
: &ExtCtxt
<'_
>, i
: &ast
::Item
) -> bool
{
311 let has_should_panic_attr
= attr
::contains_name(&i
.attrs
, sym
::should_panic
);
312 let ref sd
= cx
.parse_sess
.span_diagnostic
;
313 if let ast
::ItemKind
::Fn(ref sig
, ref generics
, _
) = i
.kind
{
314 if sig
.header
.unsafety
== ast
::Unsafety
::Unsafe
{
317 "unsafe functions cannot be used for tests"
321 if sig
.header
.asyncness
.node
.is_async() {
324 "async functions cannot be used for tests"
330 // If the termination trait is active, the compiler will check that the output
331 // type implements the `Termination` trait as `libtest` enforces that.
332 let has_output
= match sig
.decl
.output
{
333 ast
::FunctionRetTy
::Default(..) => false,
334 ast
::FunctionRetTy
::Ty(ref t
) if t
.kind
.is_unit() => false,
338 if !sig
.decl
.inputs
.is_empty() {
339 sd
.span_err(i
.span
, "functions used as tests can not have any arguments");
343 match (has_output
, has_should_panic_attr
) {
345 sd
.span_err(i
.span
, "functions using `#[should_panic]` must return `()`");
348 (true, false) => if !generics
.params
.is_empty() {
350 "functions used as tests must have signature fn() -> ()");
358 sd
.span_err(i
.span
, "only functions may be used as tests");
363 fn has_bench_signature(cx
: &ExtCtxt
<'_
>, i
: &ast
::Item
) -> bool
{
364 let has_sig
= if let ast
::ItemKind
::Fn(ref sig
, _
, _
) = i
.kind
{
365 // N.B., inadequate check, but we're running
366 // well before resolve, can't get too deep.
367 sig
.decl
.inputs
.len() == 1
373 cx
.parse_sess
.span_diagnostic
.span_err(i
.span
, "functions used as benches must have \
374 signature `fn(&mut Bencher) -> impl Termination`");