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.
3 use crate::util
::check_builtin_macro_attribute
;
8 use rustc_ast_pretty
::pprust
;
9 use rustc_expand
::base
::*;
10 use rustc_session
::Session
;
11 use rustc_span
::symbol
::{sym, Ident, Symbol}
;
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
{
35 let sp
= ecx
.with_def_site_ctxt(attr_sp
);
36 let mut item
= anno_item
.expect_item();
37 item
= item
.map(|mut item
| {
38 item
.vis
= ast
::Visibility
{
40 kind
: ast
::VisibilityKind
::Public
,
43 item
.ident
.span
= item
.ident
.span
.with_ctxt(sp
.ctxt());
44 item
.attrs
.push(ecx
.attribute(ecx
.meta_word(sp
, sym
::rustc_test_marker
)));
48 return vec
![Annotatable
::Item(item
)];
54 meta_item
: &ast
::MetaItem
,
56 ) -> Vec
<Annotatable
> {
57 check_builtin_macro_attribute(cx
, meta_item
, sym
::test
);
58 expand_test_or_bench(cx
, attr_sp
, item
, false)
64 meta_item
: &ast
::MetaItem
,
66 ) -> Vec
<Annotatable
> {
67 check_builtin_macro_attribute(cx
, meta_item
, sym
::bench
);
68 expand_test_or_bench(cx
, attr_sp
, item
, true)
71 pub fn expand_test_or_bench(
76 ) -> Vec
<Annotatable
> {
77 // If we're not in test configuration, remove the annotated item
78 if !cx
.ecfg
.should_test
{
82 let (item
, is_stmt
) = match item
{
83 Annotatable
::Item(i
) => (i
, false),
84 Annotatable
::Stmt(stmt
) if matches
!(stmt
.kind
, ast
::StmtKind
::Item(_
)) => {
85 // FIXME: Use an 'if let' guard once they are implemented
86 if let ast
::StmtKind
::Item(i
) = stmt
.into_inner().kind
{
95 "`#[test]` attribute is only allowed on non associated functions",
102 if let ast
::ItemKind
::MacCall(_
) = item
.kind
{
103 cx
.sess
.parse_sess
.span_diagnostic
.span_warn(
105 "`#[test]` attribute should not be used on macros. Use `#[cfg(test)]` instead.",
107 return vec
![Annotatable
::Item(item
)];
110 // has_*_signature will report any errors in the type so compilation
111 // will fail. We shouldn't try to expand in this case because the errors
112 // would be spurious.
113 if (!is_bench
&& !has_test_signature(cx
, &item
))
114 || (is_bench
&& !has_bench_signature(cx
, &item
))
116 return vec
![Annotatable
::Item(item
)];
119 let (sp
, attr_sp
) = (cx
.with_def_site_ctxt(item
.span
), cx
.with_def_site_ctxt(attr_sp
));
121 let test_id
= Ident
::new(sym
::test
, attr_sp
);
123 // creates test::$name
124 let test_path
= |name
| cx
.path(sp
, vec
![test_id
, Ident
::from_str_and_span(name
, sp
)]);
126 // creates test::ShouldPanic::$name
127 let should_panic_path
= |name
| {
132 Ident
::from_str_and_span("ShouldPanic", sp
),
133 Ident
::from_str_and_span(name
, sp
),
138 // creates test::TestType::$name
139 let test_type_path
= |name
| {
144 Ident
::from_str_and_span("TestType", sp
),
145 Ident
::from_str_and_span(name
, sp
),
150 // creates $name: $expr
151 let field
= |name
, expr
| cx
.field_imm(sp
, Ident
::from_str_and_span(name
, sp
), expr
);
153 let test_fn
= if is_bench
{
154 // A simple ident for a lambda
155 let b
= Ident
::from_str_and_span("b", attr_sp
);
159 cx
.expr_path(test_path("StaticBenchFn")),
161 // |b| self::test::assert_test_result(
166 cx
.expr_path(test_path("assert_test_result")),
168 // super::$test_fn(b)
171 cx
.expr_path(cx
.path(sp
, vec
![item
.ident
])),
172 vec
![cx
.expr_ident(sp
, b
)],
183 cx
.expr_path(test_path("StaticTestFn")),
188 // test::assert_test_result(
191 cx
.expr_path(test_path("assert_test_result")),
194 cx
.expr_call(sp
, cx
.expr_path(cx
.path(sp
, vec
![item
.ident
])), vec
![]), // )
202 let mut test_const
= cx
.item(
204 Ident
::new(item
.ident
.name
, sp
),
207 cx
.attribute(attr
::mk_list_item(
208 Ident
::new(sym
::cfg
, attr_sp
),
209 vec
![attr
::mk_nested_word_item(Ident
::new(sym
::test
, attr_sp
))],
211 // #[rustc_test_marker]
212 cx
.attribute(cx
.meta_word(attr_sp
, sym
::rustc_test_marker
)),
214 // const $ident: test::TestDescAndFn =
215 ast
::ItemKind
::Const(
216 ast
::Defaultness
::Final
,
217 cx
.ty(sp
, ast
::TyKind
::Path(None
, test_path("TestDescAndFn"))),
218 // test::TestDescAndFn {
222 test_path("TestDescAndFn"),
224 // desc: test::TestDesc {
229 test_path("TestDesc"),
231 // name: "path::to::test"
236 cx
.expr_path(test_path("StaticTestName")),
239 Symbol
::intern(&item_path(
240 // skip the name of the root module
241 &cx
.current_expansion
.module
.mod_path
[1..],
247 // ignore: true | false
250 cx
.expr_bool(sp
, should_ignore(&cx
.sess
, &item
)),
252 // allow_fail: true | false
255 cx
.expr_bool(sp
, should_fail(&cx
.sess
, &item
)),
260 match should_panic(cx
, &item
) {
261 // test::ShouldPanic::No
263 cx
.expr_path(should_panic_path("No"))
265 // test::ShouldPanic::Yes
266 ShouldPanic
::Yes(None
) => {
267 cx
.expr_path(should_panic_path("Yes"))
269 // test::ShouldPanic::YesWithMessage("...")
270 ShouldPanic
::Yes(Some(sym
)) => cx
.expr_call(
272 cx
.expr_path(should_panic_path("YesWithMessage")),
273 vec
![cx
.expr_str(sp
, sym
)],
280 match test_type(cx
) {
281 // test::TestType::UnitTest
282 TestType
::UnitTest
=> {
283 cx
.expr_path(test_type_path("UnitTest"))
285 // test::TestType::IntegrationTest
286 TestType
::IntegrationTest
=> {
287 cx
.expr_path(test_type_path("IntegrationTest"))
289 // test::TestPath::Unknown
290 TestType
::Unknown
=> {
291 cx
.expr_path(test_type_path("Unknown"))
299 // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
300 field("testfn", test_fn
), // }
306 test_const
= test_const
.map(|mut tc
| {
307 tc
.vis
.kind
= ast
::VisibilityKind
::Public
;
312 let test_extern
= cx
.item(sp
, test_id
, vec
![], ast
::ItemKind
::ExternCrate(None
));
314 tracing
::debug
!("synthetic test item:\n{}\n", pprust
::item_to_string(&test_const
));
318 // Access to libtest under a hygienic name
319 Annotatable
::Stmt(P(cx
.stmt_item(sp
, test_extern
))),
320 // The generated test case
321 Annotatable
::Stmt(P(cx
.stmt_item(sp
, test_const
))),
323 Annotatable
::Stmt(P(cx
.stmt_item(sp
, item
))),
327 // Access to libtest under a hygienic name
328 Annotatable
::Item(test_extern
),
329 // The generated test case
330 Annotatable
::Item(test_const
),
332 Annotatable
::Item(item
),
337 fn item_path(mod_path
: &[Ident
], item_ident
: &Ident
) -> String
{
340 .chain(iter
::once(item_ident
))
341 .map(|x
| x
.to_string())
342 .collect
::<Vec
<String
>>()
351 fn should_ignore(sess
: &Session
, i
: &ast
::Item
) -> bool
{
352 sess
.contains_name(&i
.attrs
, sym
::ignore
)
355 fn should_fail(sess
: &Session
, i
: &ast
::Item
) -> bool
{
356 sess
.contains_name(&i
.attrs
, sym
::allow_fail
)
359 fn should_panic(cx
: &ExtCtxt
<'_
>, i
: &ast
::Item
) -> ShouldPanic
{
360 match cx
.sess
.find_by_name(&i
.attrs
, sym
::should_panic
) {
362 let sd
= &cx
.sess
.parse_sess
.span_diagnostic
;
364 match attr
.meta_item_list() {
365 // Handle #[should_panic(expected = "foo")]
369 .find(|mi
| mi
.has_name(sym
::expected
))
370 .and_then(|mi
| mi
.meta_item())
371 .and_then(|mi
| mi
.value_str());
372 if list
.len() != 1 || msg
.is_none() {
375 "argument must be of the form: \
376 `expected = \"error message\"`",
379 "errors in this attribute were erroneously \
380 allowed and will become a hard error in a \
384 ShouldPanic
::Yes(None
)
386 ShouldPanic
::Yes(msg
)
389 // Handle #[should_panic] and #[should_panic = "expected"]
390 None
=> ShouldPanic
::Yes(attr
.value_str()),
393 None
=> ShouldPanic
::No
,
403 /// Attempts to determine the type of test.
404 /// Since doctests are created without macro expanding, only possible variants here
405 /// are `UnitTest`, `IntegrationTest` or `Unknown`.
406 fn test_type(cx
: &ExtCtxt
<'_
>) -> TestType
{
407 // Root path from context contains the topmost sources directory of the crate.
408 // I.e., for `project` with sources in `src` and tests in `tests` folders
409 // (no matter how many nested folders lie inside),
410 // there will be two different root paths: `/project/src` and `/project/tests`.
411 let crate_path
= cx
.root_path
.as_path();
413 if crate_path
.ends_with("src") {
414 // `/src` folder contains unit-tests.
416 } else if crate_path
.ends_with("tests") {
417 // `/tests` folder contains integration tests.
418 TestType
::IntegrationTest
420 // Crate layout doesn't match expected one, test type is unknown.
425 fn has_test_signature(cx
: &ExtCtxt
<'_
>, i
: &ast
::Item
) -> bool
{
426 let has_should_panic_attr
= cx
.sess
.contains_name(&i
.attrs
, sym
::should_panic
);
427 let sd
= &cx
.sess
.parse_sess
.span_diagnostic
;
428 if let ast
::ItemKind
::Fn(_
, ref sig
, ref generics
, _
) = i
.kind
{
429 if let ast
::Unsafe
::Yes(span
) = sig
.header
.unsafety
{
430 sd
.struct_span_err(i
.span
, "unsafe functions cannot be used for tests")
431 .span_label(span
, "`unsafe` because of this")
435 if let ast
::Async
::Yes { span, .. }
= sig
.header
.asyncness
{
436 sd
.struct_span_err(i
.span
, "async functions cannot be used for tests")
437 .span_label(span
, "`async` because of this")
442 // If the termination trait is active, the compiler will check that the output
443 // type implements the `Termination` trait as `libtest` enforces that.
444 let has_output
= match sig
.decl
.output
{
445 ast
::FnRetTy
::Default(..) => false,
446 ast
::FnRetTy
::Ty(ref t
) if t
.kind
.is_unit() => false,
450 if !sig
.decl
.inputs
.is_empty() {
451 sd
.span_err(i
.span
, "functions used as tests can not have any arguments");
455 match (has_output
, has_should_panic_attr
) {
457 sd
.span_err(i
.span
, "functions using `#[should_panic]` must return `()`");
461 if !generics
.params
.is_empty() {
462 sd
.span_err(i
.span
, "functions used as tests must have signature fn() -> ()");
471 sd
.span_err(i
.span
, "only functions may be used as tests");
476 fn has_bench_signature(cx
: &ExtCtxt
<'_
>, i
: &ast
::Item
) -> bool
{
477 let has_sig
= if let ast
::ItemKind
::Fn(_
, ref sig
, _
, _
) = i
.kind
{
478 // N.B., inadequate check, but we're running
479 // well before resolve, can't get too deep.
480 sig
.decl
.inputs
.len() == 1
486 cx
.sess
.parse_sess
.span_diagnostic
.span_err(
488 "functions used as benches must have \
489 signature `fn(&mut Bencher) -> impl Termination`",