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