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
)),
257 // compile_fail: true | false
258 field("compile_fail", cx
.expr_bool(sp
, false)),
259 // no_run: true | false
260 field("no_run", cx
.expr_bool(sp
, false)),
264 match should_panic(cx
, &item
) {
265 // test::ShouldPanic::No
267 cx
.expr_path(should_panic_path("No"))
269 // test::ShouldPanic::Yes
270 ShouldPanic
::Yes(None
) => {
271 cx
.expr_path(should_panic_path("Yes"))
273 // test::ShouldPanic::YesWithMessage("...")
274 ShouldPanic
::Yes(Some(sym
)) => cx
.expr_call(
276 cx
.expr_path(should_panic_path("YesWithMessage")),
277 vec
![cx
.expr_str(sp
, sym
)],
284 match test_type(cx
) {
285 // test::TestType::UnitTest
286 TestType
::UnitTest
=> {
287 cx
.expr_path(test_type_path("UnitTest"))
289 // test::TestType::IntegrationTest
290 TestType
::IntegrationTest
=> {
291 cx
.expr_path(test_type_path("IntegrationTest"))
293 // test::TestPath::Unknown
294 TestType
::Unknown
=> {
295 cx
.expr_path(test_type_path("Unknown"))
303 // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
304 field("testfn", test_fn
), // }
310 test_const
= test_const
.map(|mut tc
| {
311 tc
.vis
.kind
= ast
::VisibilityKind
::Public
;
316 let test_extern
= cx
.item(sp
, test_id
, vec
![], ast
::ItemKind
::ExternCrate(None
));
318 tracing
::debug
!("synthetic test item:\n{}\n", pprust
::item_to_string(&test_const
));
322 // Access to libtest under a hygienic name
323 Annotatable
::Stmt(P(cx
.stmt_item(sp
, test_extern
))),
324 // The generated test case
325 Annotatable
::Stmt(P(cx
.stmt_item(sp
, test_const
))),
327 Annotatable
::Stmt(P(cx
.stmt_item(sp
, item
))),
331 // Access to libtest under a hygienic name
332 Annotatable
::Item(test_extern
),
333 // The generated test case
334 Annotatable
::Item(test_const
),
336 Annotatable
::Item(item
),
341 fn item_path(mod_path
: &[Ident
], item_ident
: &Ident
) -> String
{
344 .chain(iter
::once(item_ident
))
345 .map(|x
| x
.to_string())
346 .collect
::<Vec
<String
>>()
355 fn should_ignore(sess
: &Session
, i
: &ast
::Item
) -> bool
{
356 sess
.contains_name(&i
.attrs
, sym
::ignore
)
359 fn should_fail(sess
: &Session
, i
: &ast
::Item
) -> bool
{
360 sess
.contains_name(&i
.attrs
, sym
::allow_fail
)
363 fn should_panic(cx
: &ExtCtxt
<'_
>, i
: &ast
::Item
) -> ShouldPanic
{
364 match cx
.sess
.find_by_name(&i
.attrs
, sym
::should_panic
) {
366 let sd
= &cx
.sess
.parse_sess
.span_diagnostic
;
368 match attr
.meta_item_list() {
369 // Handle #[should_panic(expected = "foo")]
373 .find(|mi
| mi
.has_name(sym
::expected
))
374 .and_then(|mi
| mi
.meta_item())
375 .and_then(|mi
| mi
.value_str());
376 if list
.len() != 1 || msg
.is_none() {
379 "argument must be of the form: \
380 `expected = \"error message\"`",
383 "errors in this attribute were erroneously \
384 allowed and will become a hard error in a \
388 ShouldPanic
::Yes(None
)
390 ShouldPanic
::Yes(msg
)
393 // Handle #[should_panic] and #[should_panic = "expected"]
394 None
=> ShouldPanic
::Yes(attr
.value_str()),
397 None
=> ShouldPanic
::No
,
407 /// Attempts to determine the type of test.
408 /// Since doctests are created without macro expanding, only possible variants here
409 /// are `UnitTest`, `IntegrationTest` or `Unknown`.
410 fn test_type(cx
: &ExtCtxt
<'_
>) -> TestType
{
411 // Root path from context contains the topmost sources directory of the crate.
412 // I.e., for `project` with sources in `src` and tests in `tests` folders
413 // (no matter how many nested folders lie inside),
414 // there will be two different root paths: `/project/src` and `/project/tests`.
415 let crate_path
= cx
.root_path
.as_path();
417 if crate_path
.ends_with("src") {
418 // `/src` folder contains unit-tests.
420 } else if crate_path
.ends_with("tests") {
421 // `/tests` folder contains integration tests.
422 TestType
::IntegrationTest
424 // Crate layout doesn't match expected one, test type is unknown.
429 fn has_test_signature(cx
: &ExtCtxt
<'_
>, i
: &ast
::Item
) -> bool
{
430 let has_should_panic_attr
= cx
.sess
.contains_name(&i
.attrs
, sym
::should_panic
);
431 let sd
= &cx
.sess
.parse_sess
.span_diagnostic
;
432 if let ast
::ItemKind
::Fn(box ast
::Fn { ref sig, ref generics, .. }
) = i
.kind
{
433 if let ast
::Unsafe
::Yes(span
) = sig
.header
.unsafety
{
434 sd
.struct_span_err(i
.span
, "unsafe functions cannot be used for tests")
435 .span_label(span
, "`unsafe` because of this")
439 if let ast
::Async
::Yes { span, .. }
= sig
.header
.asyncness
{
440 sd
.struct_span_err(i
.span
, "async functions cannot be used for tests")
441 .span_label(span
, "`async` because of this")
446 // If the termination trait is active, the compiler will check that the output
447 // type implements the `Termination` trait as `libtest` enforces that.
448 let has_output
= match sig
.decl
.output
{
449 ast
::FnRetTy
::Default(..) => false,
450 ast
::FnRetTy
::Ty(ref t
) if t
.kind
.is_unit() => false,
454 if !sig
.decl
.inputs
.is_empty() {
455 sd
.span_err(i
.span
, "functions used as tests can not have any arguments");
459 match (has_output
, has_should_panic_attr
) {
461 sd
.span_err(i
.span
, "functions using `#[should_panic]` must return `()`");
465 if !generics
.params
.is_empty() {
466 sd
.span_err(i
.span
, "functions used as tests must have signature fn() -> ()");
475 sd
.span_err(i
.span
, "only functions may be used as tests");
480 fn has_bench_signature(cx
: &ExtCtxt
<'_
>, i
: &ast
::Item
) -> bool
{
481 let has_sig
= if let ast
::ItemKind
::Fn(box ast
::Fn { ref sig, .. }
) = i
.kind
{
482 // N.B., inadequate check, but we're running
483 // well before resolve, can't get too deep.
484 sig
.decl
.inputs
.len() == 1
490 cx
.sess
.parse_sess
.span_diagnostic
.span_err(
492 "functions used as benches must have \
493 signature `fn(&mut Bencher) -> impl Termination`",