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