1use std::{assert_matches, iter};
5
6use rustc_ast::{self as ast, GenericParamKind, 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
19pub(crate) fn expand_test_case(
30 ecx: &mut ExtCtxt<'_>,
31 attr_sp: Span,
32 meta_item: &ast::MetaItem,
33 anno_item: Annotatable,
34) -> Vec<Annotatable> {
35 check_builtin_macro_attribute(ecx, meta_item, sym::test_case);
36 warn_on_duplicate_attribute(ecx, &anno_item, sym::test_case);
37
38 let sp = ecx.with_def_site_ctxt(attr_sp);
39 let (mut item, is_stmt) = match anno_item {
40 Annotatable::Item(item) => (item, false),
41 Annotatable::Stmt(stmt) if let ast::StmtKind::Item(_) = stmt.kind => {
42 if let ast::StmtKind::Item(i) = stmt.kind {
43 (i, true)
44 } else {
45 ::core::panicking::panic("internal error: entered unreachable code")unreachable!()
46 }
47 }
48 _ => {
49 ecx.dcx().emit_err(diagnostics::TestCaseNonItem { span: anno_item.span() });
50 return ::alloc::vec::Vec::new()vec![];
51 }
52 };
53
54 if !ecx.ecfg.should_test {
55 return ::alloc::vec::Vec::new()vec![];
56 }
57
58 match &mut item.kind {
61 ast::ItemKind::Fn(ast::Fn { ident, .. })
62 | ast::ItemKind::Const(ast::ConstItem { ident, .. })
63 | ast::ItemKind::Static(ast::StaticItem { ident, .. }) => {
64 ident.span = ident.span.with_ctxt(sp.ctxt());
65 let test_path_symbol = Symbol::intern(&item_path(
66 &ecx.current_expansion.module.mod_path[1..],
68 ident,
69 ));
70 item.vis = ast::Visibility { span: item.vis.span, kind: ast::VisibilityKind::Public };
71 item.attrs.push(ecx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, sp));
72 }
73 _ => {}
74 }
75
76 let ret = if is_stmt {
77 Annotatable::Stmt(Box::new(ecx.stmt_item(item.span, item)))
78 } else {
79 Annotatable::Item(item)
80 };
81
82 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[ret]))vec![ret]
83}
84
85pub(crate) fn expand_test(
86 cx: &mut ExtCtxt<'_>,
87 attr_sp: Span,
88 meta_item: &ast::MetaItem,
89 item: Annotatable,
90) -> Vec<Annotatable> {
91 check_builtin_macro_attribute(cx, meta_item, sym::test);
92 warn_on_duplicate_attribute(cx, &item, sym::test);
93 expand_test_or_bench(cx, attr_sp, item, false)
94}
95
96pub(crate) fn expand_bench(
97 cx: &mut ExtCtxt<'_>,
98 attr_path_sp: Span,
99 meta_item: &ast::MetaItem,
100 item: Annotatable,
101) -> Vec<Annotatable> {
102 check_builtin_macro_attribute(cx, meta_item, sym::bench);
103 warn_on_duplicate_attribute(cx, &item, sym::bench);
104 expand_test_or_bench(cx, attr_path_sp, item, true)
105}
106
107pub(crate) fn expand_test_or_bench(
108 cx: &ExtCtxt<'_>,
109 attr_sp: Span,
110 item: Annotatable,
111 is_bench: bool,
112) -> Vec<Annotatable> {
113 let (item, is_stmt) = match item {
114 Annotatable::Item(i) => (i, false),
115 Annotatable::Stmt(ast::Stmt { kind: ast::StmtKind::Item(i), .. }) => (i, true),
116 other => {
117 not_testable_error(cx, is_bench, attr_sp, None);
118 return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[other]))vec![other];
119 }
120 };
121
122 let ast::ItemKind::Fn(fn_) = &item.kind else {
123 not_testable_error(cx, is_bench, attr_sp, Some(&item));
124 return if is_stmt {
125 ::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)))]
126 } else {
127 ::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)]
128 };
129 };
130
131 if !cx.ecfg.should_test {
133 return ::alloc::vec::Vec::new()vec![];
134 }
135
136 if let Some(attr) = attr::find_by_name(&item.attrs, sym::naked) {
137 cx.dcx().emit_err(diagnostics::NakedFunctionTestingAttribute {
138 testing_span: attr_sp,
139 naked_span: attr.span,
140 });
141 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)];
142 }
143
144 let check_result = if is_bench {
148 check_bench_signature(cx, &item, fn_)
149 } else {
150 check_test_signature(cx, &item, fn_)
151 };
152 if check_result.is_err() {
153 return if is_stmt {
154 ::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)))]
155 } else {
156 ::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)]
157 };
158 }
159
160 let sp = cx.with_def_site_ctxt(item.span);
161 let ret_ty_sp = cx.with_def_site_ctxt(fn_.sig.decl.output.span());
162 let attr_sp = cx.with_def_site_ctxt(attr_sp);
163
164 let test_ident = Ident::new(sym::test, attr_sp);
165
166 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)]);
168
169 let should_panic_path = |name| {
171 cx.path(
172 sp,
173 ::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![
174 test_ident,
175 Ident::from_str_and_span("ShouldPanic", sp),
176 Ident::from_str_and_span(name, sp),
177 ],
178 )
179 };
180
181 let test_type_path = |name| {
183 cx.path(
184 sp,
185 ::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![
186 test_ident,
187 Ident::from_str_and_span("TestType", sp),
188 Ident::from_str_and_span(name, sp),
189 ],
190 )
191 };
192
193 let field = |name, expr| cx.field_imm(sp, Ident::from_str_and_span(name, sp), expr);
195
196 let coverage_off = |mut expr: Box<ast::Expr>| {
201 {
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(_));
202 expr.attrs.push(cx.attr_nested_word(sym::coverage, sym::off, sp));
203 expr
204 };
205
206 let test_fn = if is_bench {
207 let bencher_param =
209 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);
210 cx.expr_call(
211 sp,
212 cx.expr_path(test_path("StaticBenchFn")),
213 {
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![
214 coverage_off(cx.lambda1(
217 sp,
218 cx.expr_call(
219 sp,
220 cx.expr_path(test_path("assert_test_result")),
221 thin_vec![
222 cx.expr_call(
224 ret_ty_sp,
225 cx.expr_path(cx.path(sp, vec![fn_.ident])),
226 thin_vec![cx.expr_ident(sp, bencher_param)],
227 ),
228 ],
229 ),
230 bencher_param,
231 )), ],
233 )
234 } else {
235 cx.expr_call(
236 sp,
237 cx.expr_path(test_path("StaticTestFn")),
238 {
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![
239 coverage_off(cx.lambda0(
242 sp,
243 cx.expr_call(
245 sp,
246 cx.expr_path(test_path("assert_test_result")),
247 thin_vec![
248 cx.expr_call(
250 ret_ty_sp,
251 cx.expr_path(cx.path(sp, vec![fn_.ident])),
252 ThinVec::new(),
253 ), ],
255 ), )), ],
258 )
259 };
260
261 let test_path_symbol = Symbol::intern(&item_path(
262 &cx.current_expansion.module.mod_path[1..],
264 &fn_.ident,
265 ));
266
267 let location_info = get_location_info(cx, &fn_);
268
269 let mut test_const =
270 cx.item(
271 sp,
272 {
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![
273 cx.attr_nested_word(sym::cfg, sym::test, attr_sp),
275 cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp),
277 cx.attr_nested_word(sym::doc, sym::hidden, attr_sp),
279 ],
280 ast::ItemKind::Const(
282 ast::ConstItem {
283 defaultness: ast::Defaultness::Implicit,
284 ident: Ident::new(fn_.ident.name, sp),
285 generics: ast::Generics::default(),
286 ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
287 define_opaque: None,
288 kind: ast::ConstItemKind::Body,
289 body: Some(
291 cx.expr_struct(
292 sp,
293 test_path("TestDescAndFn"),
294 {
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![
295 field(
297 "desc",
298 cx.expr_struct(sp, test_path("TestDesc"), thin_vec![
299 field(
301 "name",
302 cx.expr_call(
303 sp,
304 cx.expr_path(test_path("StaticTestName")),
305 thin_vec![cx.expr_str(sp, test_path_symbol)],
306 ),
307 ),
308 field("ignore", cx.expr_bool(sp, should_ignore(&item)),),
310 field(
312 "ignore_message",
313 if let Some(msg) = should_ignore_message(&item) {
314 cx.expr_some(sp, cx.expr_str(sp, msg))
315 } else {
316 cx.expr_none(sp)
317 },
318 ),
319 field("source_file", cx.expr_str(sp, location_info.0)),
321 field("start_line", cx.expr_usize(sp, location_info.1)),
323 field("start_col", cx.expr_usize(sp, location_info.2)),
325 field("end_line", cx.expr_usize(sp, location_info.3)),
327 field("end_col", cx.expr_usize(sp, location_info.4)),
329 field("compile_fail", cx.expr_bool(sp, false)),
331 field("no_run", cx.expr_bool(sp, false)),
333 field("should_panic", match should_panic(cx, &item) {
335 ShouldPanic::No => {
337 cx.expr_path(should_panic_path("No"))
338 }
339 ShouldPanic::Yes(None) => {
341 cx.expr_path(should_panic_path("Yes"))
342 }
343 ShouldPanic::Yes(Some(sym)) => cx.expr_call(
345 sp,
346 cx.expr_path(should_panic_path("YesWithMessage")),
347 thin_vec![cx.expr_str(sp, sym)],
348 ),
349 },),
350 field("test_type", match test_type(cx) {
352 TestType::UnitTest => {
354 cx.expr_path(test_type_path("UnitTest"))
355 }
356 TestType::IntegrationTest => {
358 cx.expr_path(test_type_path("IntegrationTest"))
359 }
360 TestType::Unknown => {
362 cx.expr_path(test_type_path("Unknown"))
363 }
364 },),
365 ],),
367 ),
368 field("testfn", test_fn), ],
371 ), ),
373 }
374 .into(),
375 ),
376 );
377 test_const.vis.kind = ast::VisibilityKind::Public;
378
379 let test_extern =
381 cx.item(sp, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None, test_ident));
382
383 let item = {
384 let mut item = item;
385 item.attrs.push(cx.attr_word(sym::rustc_test_entrypoint_marker, attr_sp));
386 item
387 };
388
389 {
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:389",
"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(389u32),
::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));
390
391 if is_stmt {
392 ::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![
393 Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_extern))),
395 Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_const))),
397 Annotatable::Stmt(Box::new(cx.stmt_item(sp, item))),
399 ]
400 } else {
401 ::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![
402 Annotatable::Item(test_extern),
404 Annotatable::Item(test_const),
406 Annotatable::Item(item),
408 ]
409 }
410}
411
412fn not_testable_error(cx: &ExtCtxt<'_>, is_bench: bool, attr_sp: Span, item: Option<&ast::Item>) {
413 let dcx = cx.dcx();
414 let name = if is_bench { "bench" } else { "test" };
415 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");
416 let level = match item.map(|i| &i.kind) {
417 Some(ast::ItemKind::MacCall(_)) => Level::Warning,
420 _ => Level::Error,
421 };
422 let mut err = Diag::<()>::new(dcx, level, msg);
423 err.span(attr_sp);
424 if let Some(item) = item {
425 err.span_label(
426 item.span,
427 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected a non-associated function, found {0} {1}",
item.kind.article(), item.kind.descr()))
})format!(
428 "expected a non-associated function, found {} {}",
429 item.kind.article(),
430 item.kind.descr()
431 ),
432 );
433 }
434 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"));
435
436 if !is_bench {
437 err.with_span_suggestion(attr_sp,
438 "replace with conditional compilation to make the item only exist when tests are being run",
439 "#[cfg(test)]",
440 Applicability::MaybeIncorrect).emit();
441 } else {
442 err.emit();
443 }
444}
445
446fn get_location_info(cx: &ExtCtxt<'_>, fn_: &ast::Fn) -> (Symbol, usize, usize, usize, usize) {
447 let span = fn_.ident.span;
448 let (source_file, lo_line, lo_col, hi_line, hi_col) =
449 cx.sess.source_map().span_to_location_info(span);
450
451 let file_name = match source_file {
452 Some(sf) => sf.name.display(RemapPathScopeComponents::MACRO).to_string(),
453 None => "no-location".to_string(),
454 };
455
456 (Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col)
457}
458
459fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String {
460 join_path_idents(mod_path.iter().chain(iter::once(item_ident)))
461}
462
463enum ShouldPanic {
464 No,
465 Yes(Option<Symbol>),
466}
467
468fn should_ignore(i: &ast::Item) -> bool {
469 attr::contains_name(&i.attrs, sym::ignore)
470}
471
472fn should_ignore_message(i: &ast::Item) -> Option<Symbol> {
473 match attr::find_by_name(&i.attrs, sym::ignore) {
474 Some(attr) => {
475 match attr.meta_item_list() {
476 Some(_) => None,
478 None => attr.value_str(),
480 }
481 }
482 None => None,
483 }
484}
485
486fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
487 if let Some(Attribute::Parsed(AttributeKind::ShouldPanic { reason, .. })) =
488 AttributeParser::parse_limited_sym(cx.sess, &i.attrs, &[sym::should_panic])
489 {
490 ShouldPanic::Yes(reason)
491 } else {
492 ShouldPanic::No
493 }
494}
495
496enum TestType {
497 UnitTest,
498 IntegrationTest,
499 Unknown,
500}
501
502fn test_type(cx: &ExtCtxt<'_>) -> TestType {
506 let crate_path = cx.root_path.as_path();
511
512 if crate_path.ends_with("src") {
513 TestType::UnitTest
515 } else if crate_path.ends_with("tests") {
516 TestType::IntegrationTest
518 } else {
519 TestType::Unknown
521 }
522}
523
524fn check_test_signature(
525 cx: &ExtCtxt<'_>,
526 i: &ast::Item,
527 f: &ast::Fn,
528) -> Result<(), ErrorGuaranteed> {
529 let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic);
530 let dcx = cx.dcx();
531
532 if let ast::Safety::Unsafe(span) = f.sig.header.safety {
533 return Err(dcx.emit_err(diagnostics::TestBadFn {
534 span: i.span,
535 cause: span,
536 kind: "unsafe",
537 }));
538 }
539
540 if let Some(coroutine_kind) = f.sig.header.coroutine_kind {
541 match coroutine_kind {
542 ast::CoroutineKind::Async { span, .. } => {
543 return Err(dcx.emit_err(diagnostics::TestBadFn {
544 span: i.span,
545 cause: span,
546 kind: "async",
547 }));
548 }
549 ast::CoroutineKind::Gen { span, .. } => {
550 return Err(dcx.emit_err(diagnostics::TestBadFn {
551 span: i.span,
552 cause: span,
553 kind: "gen",
554 }));
555 }
556 ast::CoroutineKind::AsyncGen { span, .. } => {
557 return Err(dcx.emit_err(diagnostics::TestBadFn {
558 span: i.span,
559 cause: span,
560 kind: "async gen",
561 }));
562 }
563 }
564 }
565
566 let has_output = match &f.sig.decl.output {
569 ast::FnRetTy::Default(..) => false,
570 ast::FnRetTy::Ty(t) if t.kind.is_unit() => false,
571 _ => true,
572 };
573
574 if !f.sig.decl.inputs.is_empty() {
575 return Err(dcx.span_err(i.span, "functions used as tests can not have any arguments"));
576 }
577
578 if has_should_panic_attr && has_output {
579 return Err(dcx.span_err(i.span, "functions using `#[should_panic]` must return `()`"));
580 }
581
582 if f.generics.params.iter().any(|param| !#[allow(non_exhaustive_omitted_patterns)] match param.kind {
GenericParamKind::Lifetime => true,
_ => false,
}matches!(param.kind, GenericParamKind::Lifetime)) {
583 return Err(dcx.span_err(
584 i.span,
585 "functions used as tests can not have any non-lifetime generic parameters",
586 ));
587 }
588
589 Ok(())
590}
591
592fn check_bench_signature(
593 cx: &ExtCtxt<'_>,
594 i: &ast::Item,
595 f: &ast::Fn,
596) -> Result<(), ErrorGuaranteed> {
597 if f.sig.decl.inputs.len() != 1 {
600 return Err(cx.dcx().emit_err(diagnostics::BenchSig { span: i.span }));
601 }
602 Ok(())
603}