Skip to main content

rustc_builtin_macros/
test.rs

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
4use std::{assert_matches, iter};
5
6use rustc_ast::{self as ast, GenericParamKind, attr, join_path_idents};
7use rustc_ast_pretty::pprust;
8use rustc_attr_parsing::AttributeParser;
9use rustc_errors::{Applicability, Diag, Level};
10use rustc_expand::base::*;
11use rustc_hir::Attribute;
12use rustc_hir::attrs::AttributeKind;
13use rustc_span::{ErrorGuaranteed, Ident, RemapPathScopeComponents, Span, Symbol, sym};
14use thin_vec::{ThinVec, thin_vec};
15use tracing::debug;
16
17use crate::diagnostics;
18use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute};
19
20/// #[test_case] is used by custom test authors to mark tests
21/// When building for test, it needs to make the item public and gensym the name
22/// Otherwise, we'll omit the item. This behavior means that any item annotated
23/// with #[test_case] is never addressable.
24///
25/// We mark item with an inert attribute "rustc_test_marker" which the test generation
26/// logic will pick up on.
27///
28/// The test function also gains a `#[rustc_test_entrypoint_marker]` attribute for tools to pick up
29/// on. This behavior is *unstable*.
30pub(crate) fn expand_test_case(
31    ecx: &mut ExtCtxt<'_>,
32    attr_sp: Span,
33    meta_item: &ast::MetaItem,
34    anno_item: Annotatable,
35) -> Vec<Annotatable> {
36    check_builtin_macro_attribute(ecx, meta_item, sym::test_case);
37    warn_on_duplicate_attribute(ecx, &anno_item, sym::test_case);
38
39    let sp = ecx.with_def_site_ctxt(attr_sp);
40    let (mut item, is_stmt) = match anno_item {
41        Annotatable::Item(item) => (item, false),
42        Annotatable::Stmt(stmt) if let ast::StmtKind::Item(_) = stmt.kind => {
43            if let ast::StmtKind::Item(i) = stmt.kind {
44                (i, true)
45            } else {
46                ::core::panicking::panic("internal error: entered unreachable code")unreachable!()
47            }
48        }
49        _ => {
50            ecx.dcx().emit_err(diagnostics::TestCaseNonItem { span: anno_item.span() });
51            return ::alloc::vec::Vec::new()vec![];
52        }
53    };
54
55    if !ecx.ecfg.should_test {
56        return ::alloc::vec::Vec::new()vec![];
57    }
58
59    // `#[test_case]` is valid on functions, consts, and statics. Only modify
60    // the item in those cases.
61    match &mut item.kind {
62        ast::ItemKind::Fn(ast::Fn { ident, .. })
63        | ast::ItemKind::Const(ast::ConstItem { ident, .. })
64        | ast::ItemKind::Static(ast::StaticItem { ident, .. }) => {
65            ident.span = ident.span.with_ctxt(sp.ctxt());
66            let test_path_symbol = Symbol::intern(&item_path(
67                // skip the name of the root module
68                &ecx.current_expansion.module.mod_path[1..],
69                ident,
70            ));
71            item.vis = ast::Visibility { span: item.vis.span, kind: ast::VisibilityKind::Public };
72            item.attrs.push(ecx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, sp));
73        }
74        _ => {}
75    }
76
77    let ret = if is_stmt {
78        Annotatable::Stmt(Box::new(ecx.stmt_item(item.span, item)))
79    } else {
80        Annotatable::Item(item)
81    };
82
83    ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ret]))vec![ret]
84}
85
86pub(crate) fn expand_test(
87    cx: &mut ExtCtxt<'_>,
88    attr_sp: Span,
89    meta_item: &ast::MetaItem,
90    item: Annotatable,
91) -> Vec<Annotatable> {
92    check_builtin_macro_attribute(cx, meta_item, sym::test);
93    warn_on_duplicate_attribute(cx, &item, sym::test);
94    expand_test_or_bench(cx, attr_sp, item, false)
95}
96
97pub(crate) fn expand_bench(
98    cx: &mut ExtCtxt<'_>,
99    attr_sp: Span,
100    meta_item: &ast::MetaItem,
101    item: Annotatable,
102) -> Vec<Annotatable> {
103    check_builtin_macro_attribute(cx, meta_item, sym::bench);
104    warn_on_duplicate_attribute(cx, &item, sym::bench);
105    expand_test_or_bench(cx, attr_sp, item, true)
106}
107
108pub(crate) fn expand_test_or_bench(
109    cx: &ExtCtxt<'_>,
110    attr_sp: Span,
111    item: Annotatable,
112    is_bench: bool,
113) -> Vec<Annotatable> {
114    let (item, is_stmt) = match item {
115        Annotatable::Item(i) => (i, false),
116        Annotatable::Stmt(ast::Stmt { kind: ast::StmtKind::Item(i), .. }) => (i, true),
117        other => {
118            not_testable_error(cx, is_bench, attr_sp, None);
119            return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [other]))vec![other];
120        }
121    };
122
123    let ast::ItemKind::Fn(fn_) = &item.kind else {
124        not_testable_error(cx, is_bench, attr_sp, Some(&item));
125        return if is_stmt {
126            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Annotatable::Stmt(Box::new(cx.stmt_item(item.span, item)))]))vec![Annotatable::Stmt(Box::new(cx.stmt_item(item.span, item)))]
127        } else {
128            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Annotatable::Item(item)]))vec![Annotatable::Item(item)]
129        };
130    };
131
132    // If we're not in test configuration, remove the annotated item
133    if !cx.ecfg.should_test {
134        return ::alloc::vec::Vec::new()vec![];
135    }
136
137    if let Some(attr) = attr::find_by_name(&item.attrs, sym::naked) {
138        cx.dcx().emit_err(diagnostics::NakedFunctionTestingAttribute {
139            testing_span: attr_sp,
140            naked_span: attr.span,
141        });
142        return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Annotatable::Item(item)]))vec![Annotatable::Item(item)];
143    }
144
145    // check_*_signature will report any errors in the type so compilation
146    // will fail. We shouldn't try to expand in this case because the errors
147    // would be spurious.
148    let check_result = if is_bench {
149        check_bench_signature(cx, &item, fn_)
150    } else {
151        check_test_signature(cx, &item, fn_)
152    };
153    if check_result.is_err() {
154        return if is_stmt {
155            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Annotatable::Stmt(Box::new(cx.stmt_item(item.span, item)))]))vec![Annotatable::Stmt(Box::new(cx.stmt_item(item.span, item)))]
156        } else {
157            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Annotatable::Item(item)]))vec![Annotatable::Item(item)]
158        };
159    }
160
161    let sp = cx.with_def_site_ctxt(item.span);
162    let ret_ty_sp = cx.with_def_site_ctxt(fn_.sig.decl.output.span());
163    let attr_sp = cx.with_def_site_ctxt(attr_sp);
164
165    let test_ident = Ident::new(sym::test, attr_sp);
166
167    // creates test::$name
168    let test_path = |name| cx.path(ret_ty_sp, ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [test_ident, Ident::from_str_and_span(name, sp)]))vec![test_ident, Ident::from_str_and_span(name, sp)]);
169
170    // creates test::ShouldPanic::$name
171    let should_panic_path = |name| {
172        cx.path(
173            sp,
174            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [test_ident, Ident::from_str_and_span("ShouldPanic", sp),
                Ident::from_str_and_span(name, sp)]))vec![
175                test_ident,
176                Ident::from_str_and_span("ShouldPanic", sp),
177                Ident::from_str_and_span(name, sp),
178            ],
179        )
180    };
181
182    // creates test::TestType::$name
183    let test_type_path = |name| {
184        cx.path(
185            sp,
186            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [test_ident, Ident::from_str_and_span("TestType", sp),
                Ident::from_str_and_span(name, sp)]))vec![
187                test_ident,
188                Ident::from_str_and_span("TestType", sp),
189                Ident::from_str_and_span(name, sp),
190            ],
191        )
192    };
193
194    // creates $name: $expr
195    let field = |name, expr| cx.field_imm(sp, Ident::from_str_and_span(name, sp), expr);
196
197    // Adds `#[coverage(off)]` to a closure, so it won't be instrumented in
198    // `-Cinstrument-coverage` builds.
199    // This requires `#[allow_internal_unstable(coverage_attribute)]` on the
200    // corresponding macro declaration in `core::macros`.
201    let coverage_off = |mut expr: Box<ast::Expr>| {
202        {
    match expr.kind {
        ast::ExprKind::Closure(_) => {}
        ref left_val => {
            ::core::panicking::assert_matches_failed(left_val,
                "ast::ExprKind::Closure(_)", ::core::option::Option::None);
        }
    }
};assert_matches!(expr.kind, ast::ExprKind::Closure(_));
203        expr.attrs.push(cx.attr_nested_word(sym::coverage, sym::off, sp));
204        expr
205    };
206
207    let test_fn = if is_bench {
208        // avoid name collisions by using the function name within the identifier, see bug #148275
209        let bencher_param =
210            Ident::from_str_and_span(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__bench_{0}", fn_.ident.name))
    })format!("__bench_{}", fn_.ident.name), attr_sp);
211        cx.expr_call(
212            sp,
213            cx.expr_path(test_path("StaticBenchFn")),
214            {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(coverage_off(cx.lambda1(sp,
                cx.expr_call(sp,
                    cx.expr_path(test_path("assert_test_result")),
                    {
                        let len = [()].len();
                        let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                        vec.push(cx.expr_call(ret_ty_sp,
                                cx.expr_path(cx.path(sp,
                                        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                                                [fn_.ident])))),
                                {
                                    let len = [()].len();
                                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                                    vec.push(cx.expr_ident(sp, bencher_param));
                                    vec
                                }));
                        vec
                    }), bencher_param)));
    vec
}thin_vec![
215                // #[coverage(off)]
216                // |__bench_fn_name| self::test::assert_test_result(
217                coverage_off(cx.lambda1(
218                    sp,
219                    cx.expr_call(
220                        sp,
221                        cx.expr_path(test_path("assert_test_result")),
222                        thin_vec![
223                            // super::$test_fn(__bench_fn_name)
224                            cx.expr_call(
225                                ret_ty_sp,
226                                cx.expr_path(cx.path(sp, vec![fn_.ident])),
227                                thin_vec![cx.expr_ident(sp, bencher_param)],
228                            ),
229                        ],
230                    ),
231                    bencher_param,
232                )), // )
233            ],
234        )
235    } else {
236        cx.expr_call(
237            sp,
238            cx.expr_path(test_path("StaticTestFn")),
239            {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(coverage_off(cx.lambda0(sp,
                cx.expr_call(sp,
                    cx.expr_path(test_path("assert_test_result")),
                    {
                        let len = [()].len();
                        let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                        vec.push(cx.expr_call(ret_ty_sp,
                                cx.expr_path(cx.path(sp,
                                        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                                                [fn_.ident])))), ThinVec::new()));
                        vec
                    }))));
    vec
}thin_vec![
240                // #[coverage(off)]
241                // || {
242                coverage_off(cx.lambda0(
243                    sp,
244                    // test::assert_test_result(
245                    cx.expr_call(
246                        sp,
247                        cx.expr_path(test_path("assert_test_result")),
248                        thin_vec![
249                            // $test_fn()
250                            cx.expr_call(
251                                ret_ty_sp,
252                                cx.expr_path(cx.path(sp, vec![fn_.ident])),
253                                ThinVec::new(),
254                            ), // )
255                        ],
256                    ), // }
257                )), // )
258            ],
259        )
260    };
261
262    let test_path_symbol = Symbol::intern(&item_path(
263        // skip the name of the root module
264        &cx.current_expansion.module.mod_path[1..],
265        &fn_.ident,
266    ));
267
268    let location_info = get_location_info(cx, &fn_);
269
270    let mut test_const =
271        cx.item(
272            sp,
273            {
    let len = [(), (), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(cx.attr_nested_word(sym::cfg, sym::test, attr_sp));
    vec.push(cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol,
            attr_sp));
    vec.push(cx.attr_nested_word(sym::doc, sym::hidden, attr_sp));
    vec
}thin_vec![
274                // #[cfg(test)]
275                cx.attr_nested_word(sym::cfg, sym::test, attr_sp),
276                // #[rustc_test_marker = "test_case_sort_key"]
277                cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp),
278                // #[doc(hidden)]
279                cx.attr_nested_word(sym::doc, sym::hidden, attr_sp),
280            ],
281            // const $ident: test::TestDescAndFn =
282            ast::ItemKind::Const(
283                ast::ConstItem {
284                    defaultness: ast::Defaultness::Implicit,
285                    ident: Ident::new(fn_.ident.name, sp),
286                    generics: ast::Generics::default(),
287                    ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
288                    define_opaque: None,
289                    kind: ast::ConstItemKind::Body,
290                    // test::TestDescAndFn {
291                    body: Some(
292                        cx.expr_struct(
293                            sp,
294                            test_path("TestDescAndFn"),
295                            {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(field("desc",
            cx.expr_struct(sp, test_path("TestDesc"),
                {
                    let len =
                        [(), (), (), (), (), (), (), (), (), (), (), ()].len();
                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                    vec.push(field("name",
                            cx.expr_call(sp, cx.expr_path(test_path("StaticTestName")),
                                {
                                    let len = [()].len();
                                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                                    vec.push(cx.expr_str(sp, test_path_symbol));
                                    vec
                                })));
                    vec.push(field("ignore",
                            cx.expr_bool(sp, should_ignore(&item))));
                    vec.push(field("ignore_message",
                            if let Some(msg) = should_ignore_message(&item) {
                                cx.expr_some(sp, cx.expr_str(sp, msg))
                            } else { cx.expr_none(sp) }));
                    vec.push(field("source_file",
                            cx.expr_str(sp, location_info.0)));
                    vec.push(field("start_line",
                            cx.expr_usize(sp, location_info.1)));
                    vec.push(field("start_col",
                            cx.expr_usize(sp, location_info.2)));
                    vec.push(field("end_line",
                            cx.expr_usize(sp, location_info.3)));
                    vec.push(field("end_col",
                            cx.expr_usize(sp, location_info.4)));
                    vec.push(field("compile_fail", cx.expr_bool(sp, false)));
                    vec.push(field("no_run", cx.expr_bool(sp, false)));
                    vec.push(field("should_panic",
                            match should_panic(cx, &item) {
                                ShouldPanic::No => { cx.expr_path(should_panic_path("No")) }
                                ShouldPanic::Yes(None) => {
                                    cx.expr_path(should_panic_path("Yes"))
                                }
                                ShouldPanic::Yes(Some(sym)) =>
                                    cx.expr_call(sp,
                                        cx.expr_path(should_panic_path("YesWithMessage")),
                                        {
                                            let len = [()].len();
                                            let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                                            vec.push(cx.expr_str(sp, sym));
                                            vec
                                        }),
                            }));
                    vec.push(field("test_type",
                            match test_type(cx) {
                                TestType::UnitTest => {
                                    cx.expr_path(test_type_path("UnitTest"))
                                }
                                TestType::IntegrationTest => {
                                    cx.expr_path(test_type_path("IntegrationTest"))
                                }
                                TestType::Unknown => {
                                    cx.expr_path(test_type_path("Unknown"))
                                }
                            }));
                    vec
                })));
    vec.push(field("testfn", test_fn));
    vec
}thin_vec![
296                        // desc: test::TestDesc {
297                        field(
298                            "desc",
299                            cx.expr_struct(sp, test_path("TestDesc"), thin_vec![
300                                // name: "path::to::test"
301                                field(
302                                    "name",
303                                    cx.expr_call(
304                                        sp,
305                                        cx.expr_path(test_path("StaticTestName")),
306                                        thin_vec![cx.expr_str(sp, test_path_symbol)],
307                                    ),
308                                ),
309                                // ignore: true | false
310                                field("ignore", cx.expr_bool(sp, should_ignore(&item)),),
311                                // ignore_message: Some("...") | None
312                                field(
313                                    "ignore_message",
314                                    if let Some(msg) = should_ignore_message(&item) {
315                                        cx.expr_some(sp, cx.expr_str(sp, msg))
316                                    } else {
317                                        cx.expr_none(sp)
318                                    },
319                                ),
320                                // source_file: <relative_path_of_source_file>
321                                field("source_file", cx.expr_str(sp, location_info.0)),
322                                // start_line: start line of the test fn identifier.
323                                field("start_line", cx.expr_usize(sp, location_info.1)),
324                                // start_col: start column of the test fn identifier.
325                                field("start_col", cx.expr_usize(sp, location_info.2)),
326                                // end_line: end line of the test fn identifier.
327                                field("end_line", cx.expr_usize(sp, location_info.3)),
328                                // end_col: end column of the test fn identifier.
329                                field("end_col", cx.expr_usize(sp, location_info.4)),
330                                // compile_fail: true | false
331                                field("compile_fail", cx.expr_bool(sp, false)),
332                                // no_run: true | false
333                                field("no_run", cx.expr_bool(sp, false)),
334                                // should_panic: ...
335                                field("should_panic", match should_panic(cx, &item) {
336                                    // test::ShouldPanic::No
337                                    ShouldPanic::No => {
338                                        cx.expr_path(should_panic_path("No"))
339                                    }
340                                    // test::ShouldPanic::Yes
341                                    ShouldPanic::Yes(None) => {
342                                        cx.expr_path(should_panic_path("Yes"))
343                                    }
344                                    // test::ShouldPanic::YesWithMessage("...")
345                                    ShouldPanic::Yes(Some(sym)) => cx.expr_call(
346                                        sp,
347                                        cx.expr_path(should_panic_path("YesWithMessage")),
348                                        thin_vec![cx.expr_str(sp, sym)],
349                                    ),
350                                },),
351                                // test_type: ...
352                                field("test_type", match test_type(cx) {
353                                    // test::TestType::UnitTest
354                                    TestType::UnitTest => {
355                                        cx.expr_path(test_type_path("UnitTest"))
356                                    }
357                                    // test::TestType::IntegrationTest
358                                    TestType::IntegrationTest => {
359                                        cx.expr_path(test_type_path("IntegrationTest"))
360                                    }
361                                    // test::TestPath::Unknown
362                                    TestType::Unknown => {
363                                        cx.expr_path(test_type_path("Unknown"))
364                                    }
365                                },),
366                                // },
367                            ],),
368                        ),
369                        // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
370                        field("testfn", test_fn), // }
371                    ],
372                        ), // }
373                    ),
374                }
375                .into(),
376            ),
377        );
378    test_const.vis.kind = ast::VisibilityKind::Public;
379
380    // extern crate test
381    let test_extern =
382        cx.item(sp, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None, test_ident));
383
384    let item = {
385        let mut item = item;
386        item.attrs.push(cx.attr_word(sym::rustc_test_entrypoint_marker, attr_sp));
387        item
388    };
389
390    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_builtin_macros/src/test.rs:390",
                        "rustc_builtin_macros::test", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_builtin_macros/src/test.rs"),
                        ::tracing_core::__macro_support::Option::Some(390u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_builtin_macros::test"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("synthetic test item:\n{0}\n",
                                                    pprust::item_to_string(&test_const)) as
                                            &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
391
392    if is_stmt {
393        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_extern))),
                Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_const))),
                Annotatable::Stmt(Box::new(cx.stmt_item(sp, item)))]))vec![
394            // Access to libtest under a hygienic name
395            Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_extern))),
396            // The generated test case
397            Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_const))),
398            // The original item
399            Annotatable::Stmt(Box::new(cx.stmt_item(sp, item))),
400        ]
401    } else {
402        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Annotatable::Item(test_extern), Annotatable::Item(test_const),
                Annotatable::Item(item)]))vec![
403            // Access to libtest under a hygienic name
404            Annotatable::Item(test_extern),
405            // The generated test case
406            Annotatable::Item(test_const),
407            // The original item
408            Annotatable::Item(item),
409        ]
410    }
411}
412
413fn not_testable_error(cx: &ExtCtxt<'_>, is_bench: bool, attr_sp: Span, item: Option<&ast::Item>) {
414    let dcx = cx.dcx();
415    let name = if is_bench { "bench" } else { "test" };
416    let msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the `#[{0}]` attribute may only be used on a free function",
                name))
    })format!("the `#[{name}]` attribute may only be used on a free function");
417    let level = match item.map(|i| &i.kind) {
418        // These were a warning before #92959 and need to continue being that to avoid breaking
419        // stable user code (#94508).
420        Some(ast::ItemKind::MacCall(_)) => Level::Warning,
421        _ => Level::Error,
422    };
423    let mut err = Diag::<()>::new(dcx, level, msg);
424    err.span(attr_sp);
425    if let Some(item) = item {
426        err.span_label(
427            item.span,
428            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected a non-associated function, found {0} {1}",
                item.kind.article(), item.kind.descr()))
    })format!(
429                "expected a non-associated function, found {} {}",
430                item.kind.article(),
431                item.kind.descr()
432            ),
433        );
434    }
435    err.span_label(attr_sp, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the `#[{0}]` macro causes a function to be run as a test and has no effect on non-functions",
                name))
    })format!("the `#[{name}]` macro causes a function to be run as a test and has no effect on non-functions"));
436
437    if !is_bench {
438        err.with_span_suggestion(attr_sp,
439            "replace with conditional compilation to make the item only exist when tests are being run",
440            "#[cfg(test)]",
441            Applicability::MaybeIncorrect).emit();
442    } else {
443        err.emit();
444    }
445}
446
447fn get_location_info(cx: &ExtCtxt<'_>, fn_: &ast::Fn) -> (Symbol, usize, usize, usize, usize) {
448    let span = fn_.ident.span;
449    let (source_file, lo_line, lo_col, hi_line, hi_col) =
450        cx.sess.source_map().span_to_location_info(span);
451
452    let file_name = match source_file {
453        Some(sf) => sf.name.display(RemapPathScopeComponents::MACRO).to_string(),
454        None => "no-location".to_string(),
455    };
456
457    (Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col)
458}
459
460fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String {
461    join_path_idents(mod_path.iter().chain(iter::once(item_ident)))
462}
463
464enum ShouldPanic {
465    No,
466    Yes(Option<Symbol>),
467}
468
469fn should_ignore(i: &ast::Item) -> bool {
470    attr::contains_name(&i.attrs, sym::ignore)
471}
472
473fn should_ignore_message(i: &ast::Item) -> Option<Symbol> {
474    match attr::find_by_name(&i.attrs, sym::ignore) {
475        Some(attr) => {
476            match attr.meta_item_list() {
477                // Handle #[ignore(bar = "foo")]
478                Some(_) => None,
479                // Handle #[ignore] and #[ignore = "message"]
480                None => attr.value_str(),
481            }
482        }
483        None => None,
484    }
485}
486
487fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
488    if let Some(Attribute::Parsed(AttributeKind::ShouldPanic { reason, .. })) =
489        AttributeParser::parse_limited(cx.sess, &i.attrs, &[sym::should_panic])
490    {
491        ShouldPanic::Yes(reason)
492    } else {
493        ShouldPanic::No
494    }
495}
496
497enum TestType {
498    UnitTest,
499    IntegrationTest,
500    Unknown,
501}
502
503/// Attempts to determine the type of test.
504/// Since doctests are created without macro expanding, only possible variants here
505/// are `UnitTest`, `IntegrationTest` or `Unknown`.
506fn test_type(cx: &ExtCtxt<'_>) -> TestType {
507    // Root path from context contains the topmost sources directory of the crate.
508    // I.e., for `project` with sources in `src` and tests in `tests` folders
509    // (no matter how many nested folders lie inside),
510    // there will be two different root paths: `/project/src` and `/project/tests`.
511    let crate_path = cx.root_path.as_path();
512
513    if crate_path.ends_with("src") {
514        // `/src` folder contains unit-tests.
515        TestType::UnitTest
516    } else if crate_path.ends_with("tests") {
517        // `/tests` folder contains integration tests.
518        TestType::IntegrationTest
519    } else {
520        // Crate layout doesn't match expected one, test type is unknown.
521        TestType::Unknown
522    }
523}
524
525fn check_test_signature(
526    cx: &ExtCtxt<'_>,
527    i: &ast::Item,
528    f: &ast::Fn,
529) -> Result<(), ErrorGuaranteed> {
530    let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic);
531    let dcx = cx.dcx();
532
533    if let ast::Safety::Unsafe(span) = f.sig.header.safety {
534        return Err(dcx.emit_err(diagnostics::TestBadFn {
535            span: i.span,
536            cause: span,
537            kind: "unsafe",
538        }));
539    }
540
541    if let Some(coroutine_kind) = f.sig.header.coroutine_kind {
542        match coroutine_kind {
543            ast::CoroutineKind::Async { span, .. } => {
544                return Err(dcx.emit_err(diagnostics::TestBadFn {
545                    span: i.span,
546                    cause: span,
547                    kind: "async",
548                }));
549            }
550            ast::CoroutineKind::Gen { span, .. } => {
551                return Err(dcx.emit_err(diagnostics::TestBadFn {
552                    span: i.span,
553                    cause: span,
554                    kind: "gen",
555                }));
556            }
557            ast::CoroutineKind::AsyncGen { span, .. } => {
558                return Err(dcx.emit_err(diagnostics::TestBadFn {
559                    span: i.span,
560                    cause: span,
561                    kind: "async gen",
562                }));
563            }
564        }
565    }
566
567    // If the termination trait is active, the compiler will check that the output
568    // type implements the `Termination` trait as `libtest` enforces that.
569    let has_output = match &f.sig.decl.output {
570        ast::FnRetTy::Default(..) => false,
571        ast::FnRetTy::Ty(t) if t.kind.is_unit() => false,
572        _ => true,
573    };
574
575    if !f.sig.decl.inputs.is_empty() {
576        return Err(dcx.span_err(i.span, "functions used as tests can not have any arguments"));
577    }
578
579    if has_should_panic_attr && has_output {
580        return Err(dcx.span_err(i.span, "functions using `#[should_panic]` must return `()`"));
581    }
582
583    if f.generics.params.iter().any(|param| !#[allow(non_exhaustive_omitted_patterns)] match param.kind {
    GenericParamKind::Lifetime => true,
    _ => false,
}matches!(param.kind, GenericParamKind::Lifetime)) {
584        return Err(dcx.span_err(
585            i.span,
586            "functions used as tests can not have any non-lifetime generic parameters",
587        ));
588    }
589
590    Ok(())
591}
592
593fn check_bench_signature(
594    cx: &ExtCtxt<'_>,
595    i: &ast::Item,
596    f: &ast::Fn,
597) -> Result<(), ErrorGuaranteed> {
598    // N.B., inadequate check, but we're running
599    // well before resolve, can't get too deep.
600    if f.sig.decl.inputs.len() != 1 {
601        return Err(cx.dcx().emit_err(diagnostics::BenchSig { span: i.span }));
602    }
603    Ok(())
604}