1use std::{assert_matches, iter};
5
6use rustc_ast::{self as ast, GenericParamKind, HasNodeId, 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::errors;
18use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute};
19
20pub(crate) fn expand_test_case(
28 ecx: &mut ExtCtxt<'_>,
29 attr_sp: Span,
30 meta_item: &ast::MetaItem,
31 anno_item: Annotatable,
32) -> Vec<Annotatable> {
33 check_builtin_macro_attribute(ecx, meta_item, sym::test_case);
34 warn_on_duplicate_attribute(ecx, &anno_item, sym::test_case);
35
36 let sp = ecx.with_def_site_ctxt(attr_sp);
37 let (mut item, is_stmt) = match anno_item {
38 Annotatable::Item(item) => (item, false),
39 Annotatable::Stmt(stmt) if let ast::StmtKind::Item(_) = stmt.kind => {
40 if let ast::StmtKind::Item(i) = stmt.kind {
41 (i, true)
42 } else {
43 ::core::panicking::panic("internal error: entered unreachable code")unreachable!()
44 }
45 }
46 _ => {
47 ecx.dcx().emit_err(errors::TestCaseNonItem { span: anno_item.span() });
48 return ::alloc::vec::Vec::new()vec![];
49 }
50 };
51
52 if !ecx.ecfg.should_test {
53 return ::alloc::vec::Vec::new()vec![];
54 }
55
56 match &mut item.kind {
59 ast::ItemKind::Fn(box ast::Fn { ident, .. })
60 | ast::ItemKind::Const(box ast::ConstItem { ident, .. })
61 | ast::ItemKind::Static(box ast::StaticItem { ident, .. }) => {
62 ident.span = ident.span.with_ctxt(sp.ctxt());
63 let test_path_symbol = Symbol::intern(&item_path(
64 &ecx.current_expansion.module.mod_path[1..],
66 ident,
67 ));
68 item.vis = ast::Visibility {
69 span: item.vis.span,
70 kind: ast::VisibilityKind::Public,
71 tokens: None,
72 };
73 item.attrs.push(ecx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, sp));
74 }
75 _ => {}
76 }
77
78 let ret = if is_stmt {
79 Annotatable::Stmt(Box::new(ecx.stmt_item(item.span, item)))
80 } else {
81 Annotatable::Item(item)
82 };
83
84 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[ret]))vec![ret]
85}
86
87pub(crate) fn expand_test(
88 cx: &mut ExtCtxt<'_>,
89 attr_sp: Span,
90 meta_item: &ast::MetaItem,
91 item: Annotatable,
92) -> Vec<Annotatable> {
93 check_builtin_macro_attribute(cx, meta_item, sym::test);
94 warn_on_duplicate_attribute(cx, &item, sym::test);
95 expand_test_or_bench(cx, attr_sp, item, false)
96}
97
98pub(crate) fn expand_bench(
99 cx: &mut ExtCtxt<'_>,
100 attr_sp: Span,
101 meta_item: &ast::MetaItem,
102 item: Annotatable,
103) -> Vec<Annotatable> {
104 check_builtin_macro_attribute(cx, meta_item, sym::bench);
105 warn_on_duplicate_attribute(cx, &item, sym::bench);
106 expand_test_or_bench(cx, attr_sp, item, true)
107}
108
109pub(crate) fn expand_test_or_bench(
110 cx: &ExtCtxt<'_>,
111 attr_sp: Span,
112 item: Annotatable,
113 is_bench: bool,
114) -> Vec<Annotatable> {
115 let (item, is_stmt) = match item {
116 Annotatable::Item(i) => (i, false),
117 Annotatable::Stmt(box ast::Stmt { kind: ast::StmtKind::Item(i), .. }) => (i, true),
118 other => {
119 not_testable_error(cx, is_bench, attr_sp, None);
120 return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[other]))vec![other];
121 }
122 };
123
124 let ast::ItemKind::Fn(fn_) = &item.kind else {
125 not_testable_error(cx, is_bench, attr_sp, Some(&item));
126 return if is_stmt {
127 ::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)))]
128 } else {
129 ::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)]
130 };
131 };
132
133 if !cx.ecfg.should_test {
135 return ::alloc::vec::Vec::new()vec![];
136 }
137
138 if let Some(attr) = attr::find_by_name(&item.attrs, sym::naked) {
139 cx.dcx().emit_err(errors::NakedFunctionTestingAttribute {
140 testing_span: attr_sp,
141 naked_span: attr.span,
142 });
143 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)];
144 }
145
146 let check_result = if is_bench {
150 check_bench_signature(cx, &item, fn_)
151 } else {
152 check_test_signature(cx, &item, fn_)
153 };
154 if check_result.is_err() {
155 return if is_stmt {
156 ::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)))]
157 } else {
158 ::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)]
159 };
160 }
161
162 let sp = cx.with_def_site_ctxt(item.span);
163 let ret_ty_sp = cx.with_def_site_ctxt(fn_.sig.decl.output.span());
164 let attr_sp = cx.with_def_site_ctxt(attr_sp);
165
166 let test_ident = Ident::new(sym::test, attr_sp);
167
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)]);
170
171 let should_panic_path = |name| {
173 cx.path(
174 sp,
175 ::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![
176 test_ident,
177 Ident::from_str_and_span("ShouldPanic", sp),
178 Ident::from_str_and_span(name, sp),
179 ],
180 )
181 };
182
183 let test_type_path = |name| {
185 cx.path(
186 sp,
187 ::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![
188 test_ident,
189 Ident::from_str_and_span("TestType", sp),
190 Ident::from_str_and_span(name, sp),
191 ],
192 )
193 };
194
195 let field = |name, expr| cx.field_imm(sp, Ident::from_str_and_span(name, sp), expr);
197
198 let coverage_off = |mut expr: Box<ast::Expr>| {
203 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(_));
204 expr.attrs.push(cx.attr_nested_word(sym::coverage, sym::off, sp));
205 expr
206 };
207
208 let test_fn = if is_bench {
209 let bencher_param =
211 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);
212 cx.expr_call(
213 sp,
214 cx.expr_path(test_path("StaticBenchFn")),
215 {
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![
216 coverage_off(cx.lambda1(
219 sp,
220 cx.expr_call(
221 sp,
222 cx.expr_path(test_path("assert_test_result")),
223 thin_vec![
224 cx.expr_call(
226 ret_ty_sp,
227 cx.expr_path(cx.path(sp, vec![fn_.ident])),
228 thin_vec![cx.expr_ident(sp, bencher_param)],
229 ),
230 ],
231 ),
232 bencher_param,
233 )), ],
235 )
236 } else {
237 cx.expr_call(
238 sp,
239 cx.expr_path(test_path("StaticTestFn")),
240 {
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![
241 coverage_off(cx.lambda0(
244 sp,
245 cx.expr_call(
247 sp,
248 cx.expr_path(test_path("assert_test_result")),
249 thin_vec![
250 cx.expr_call(
252 ret_ty_sp,
253 cx.expr_path(cx.path(sp, vec![fn_.ident])),
254 ThinVec::new(),
255 ), ],
257 ), )), ],
260 )
261 };
262
263 let test_path_symbol = Symbol::intern(&item_path(
264 &cx.current_expansion.module.mod_path[1..],
266 &fn_.ident,
267 ));
268
269 let location_info = get_location_info(cx, &fn_);
270
271 let mut test_const =
272 cx.item(
273 sp,
274 {
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![
275 cx.attr_nested_word(sym::cfg, sym::test, attr_sp),
277 cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp),
279 cx.attr_nested_word(sym::doc, sym::hidden, attr_sp),
281 ],
282 ast::ItemKind::Const(
284 ast::ConstItem {
285 defaultness: ast::Defaultness::Implicit,
286 ident: Ident::new(fn_.ident.name, sp),
287 generics: ast::Generics::default(),
288 ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
289 define_opaque: None,
290 rhs_kind: ast::ConstItemRhsKind::new_body(
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 field(
298 "desc",
299 cx.expr_struct(sp, test_path("TestDesc"), thin_vec![
300 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 field("ignore", cx.expr_bool(sp, should_ignore(&item)),),
311 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 field("source_file", cx.expr_str(sp, location_info.0)),
322 field("start_line", cx.expr_usize(sp, location_info.1)),
324 field("start_col", cx.expr_usize(sp, location_info.2)),
326 field("end_line", cx.expr_usize(sp, location_info.3)),
328 field("end_col", cx.expr_usize(sp, location_info.4)),
330 field("compile_fail", cx.expr_bool(sp, false)),
332 field("no_run", cx.expr_bool(sp, false)),
334 field("should_panic", match should_panic(cx, &item) {
336 ShouldPanic::No => {
338 cx.expr_path(should_panic_path("No"))
339 }
340 ShouldPanic::Yes(None) => {
342 cx.expr_path(should_panic_path("Yes"))
343 }
344 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 field("test_type", match test_type(cx) {
353 TestType::UnitTest => {
355 cx.expr_path(test_type_path("UnitTest"))
356 }
357 TestType::IntegrationTest => {
359 cx.expr_path(test_type_path("IntegrationTest"))
360 }
361 TestType::Unknown => {
363 cx.expr_path(test_type_path("Unknown"))
364 }
365 },),
366 ],),
368 ),
369 field("testfn", test_fn), ],
372 ), ),
374 }
375 .into(),
376 ),
377 );
378 test_const.vis.kind = ast::VisibilityKind::Public;
379
380 let test_extern =
382 cx.item(sp, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None, test_ident));
383
384 {
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:384",
"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(384u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("synthetic test item:\n{0}\n",
pprust::item_to_string(&test_const)) as &dyn Value))])
});
} else { ; }
};debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
385
386 if is_stmt {
387 ::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![
388 Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_extern))),
390 Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_const))),
392 Annotatable::Stmt(Box::new(cx.stmt_item(sp, item))),
394 ]
395 } else {
396 ::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![
397 Annotatable::Item(test_extern),
399 Annotatable::Item(test_const),
401 Annotatable::Item(item),
403 ]
404 }
405}
406
407fn not_testable_error(cx: &ExtCtxt<'_>, is_bench: bool, attr_sp: Span, item: Option<&ast::Item>) {
408 let dcx = cx.dcx();
409 let name = if is_bench { "bench" } else { "test" };
410 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");
411 let level = match item.map(|i| &i.kind) {
412 Some(ast::ItemKind::MacCall(_)) => Level::Warning,
415 _ => Level::Error,
416 };
417 let mut err = Diag::<()>::new(dcx, level, msg);
418 err.span(attr_sp);
419 if let Some(item) = item {
420 err.span_label(
421 item.span,
422 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected a non-associated function, found {0} {1}",
item.kind.article(), item.kind.descr()))
})format!(
423 "expected a non-associated function, found {} {}",
424 item.kind.article(),
425 item.kind.descr()
426 ),
427 );
428 }
429 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"));
430
431 if !is_bench {
432 err.with_span_suggestion(attr_sp,
433 "replace with conditional compilation to make the item only exist when tests are being run",
434 "#[cfg(test)]",
435 Applicability::MaybeIncorrect).emit();
436 } else {
437 err.emit();
438 }
439}
440
441fn get_location_info(cx: &ExtCtxt<'_>, fn_: &ast::Fn) -> (Symbol, usize, usize, usize, usize) {
442 let span = fn_.ident.span;
443 let (source_file, lo_line, lo_col, hi_line, hi_col) =
444 cx.sess.source_map().span_to_location_info(span);
445
446 let file_name = match source_file {
447 Some(sf) => sf.name.display(RemapPathScopeComponents::MACRO).to_string(),
448 None => "no-location".to_string(),
449 };
450
451 (Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col)
452}
453
454fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String {
455 join_path_idents(mod_path.iter().chain(iter::once(item_ident)))
456}
457
458enum ShouldPanic {
459 No,
460 Yes(Option<Symbol>),
461}
462
463fn should_ignore(i: &ast::Item) -> bool {
464 attr::contains_name(&i.attrs, sym::ignore)
465}
466
467fn should_ignore_message(i: &ast::Item) -> Option<Symbol> {
468 match attr::find_by_name(&i.attrs, sym::ignore) {
469 Some(attr) => {
470 match attr.meta_item_list() {
471 Some(_) => None,
473 None => attr.value_str(),
475 }
476 }
477 None => None,
478 }
479}
480
481fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
482 if let Some(Attribute::Parsed(AttributeKind::ShouldPanic { reason, .. })) =
483 AttributeParser::parse_limited(
484 cx.sess,
485 &i.attrs,
486 sym::should_panic,
487 i.span,
488 i.node_id(),
489 None,
490 )
491 {
492 ShouldPanic::Yes(reason)
493 } else {
494 ShouldPanic::No
495 }
496}
497
498enum TestType {
499 UnitTest,
500 IntegrationTest,
501 Unknown,
502}
503
504fn test_type(cx: &ExtCtxt<'_>) -> TestType {
508 let crate_path = cx.root_path.as_path();
513
514 if crate_path.ends_with("src") {
515 TestType::UnitTest
517 } else if crate_path.ends_with("tests") {
518 TestType::IntegrationTest
520 } else {
521 TestType::Unknown
523 }
524}
525
526fn check_test_signature(
527 cx: &ExtCtxt<'_>,
528 i: &ast::Item,
529 f: &ast::Fn,
530) -> Result<(), ErrorGuaranteed> {
531 let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic);
532 let dcx = cx.dcx();
533
534 if let ast::Safety::Unsafe(span) = f.sig.header.safety {
535 return Err(dcx.emit_err(errors::TestBadFn { span: i.span, cause: span, kind: "unsafe" }));
536 }
537
538 if let Some(coroutine_kind) = f.sig.header.coroutine_kind {
539 match coroutine_kind {
540 ast::CoroutineKind::Async { span, .. } => {
541 return Err(dcx.emit_err(errors::TestBadFn {
542 span: i.span,
543 cause: span,
544 kind: "async",
545 }));
546 }
547 ast::CoroutineKind::Gen { span, .. } => {
548 return Err(dcx.emit_err(errors::TestBadFn {
549 span: i.span,
550 cause: span,
551 kind: "gen",
552 }));
553 }
554 ast::CoroutineKind::AsyncGen { span, .. } => {
555 return Err(dcx.emit_err(errors::TestBadFn {
556 span: i.span,
557 cause: span,
558 kind: "async gen",
559 }));
560 }
561 }
562 }
563
564 let has_output = match &f.sig.decl.output {
567 ast::FnRetTy::Default(..) => false,
568 ast::FnRetTy::Ty(t) if t.kind.is_unit() => false,
569 _ => true,
570 };
571
572 if !f.sig.decl.inputs.is_empty() {
573 return Err(dcx.span_err(i.span, "functions used as tests can not have any arguments"));
574 }
575
576 if has_should_panic_attr && has_output {
577 return Err(dcx.span_err(i.span, "functions using `#[should_panic]` must return `()`"));
578 }
579
580 if f.generics.params.iter().any(|param| !#[allow(non_exhaustive_omitted_patterns)] match param.kind {
GenericParamKind::Lifetime => true,
_ => false,
}matches!(param.kind, GenericParamKind::Lifetime)) {
581 return Err(dcx.span_err(
582 i.span,
583 "functions used as tests can not have any non-lifetime generic parameters",
584 ));
585 }
586
587 Ok(())
588}
589
590fn check_bench_signature(
591 cx: &ExtCtxt<'_>,
592 i: &ast::Item,
593 f: &ast::Fn,
594) -> Result<(), ErrorGuaranteed> {
595 if f.sig.decl.inputs.len() != 1 {
598 return Err(cx.dcx().emit_err(errors::BenchSig { span: i.span }));
599 }
600 Ok(())
601}