]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_builtin_macros/src/test_harness.rs
New upstream version 1.75.0+dfsg1
[rustc.git] / compiler / rustc_builtin_macros / src / test_harness.rs
CommitLineData
1a4d82fc
JJ
1// Code that generates a test runner to run all the tests in a crate
2
3dfed10e 3use rustc_ast as ast;
3dfed10e 4use rustc_ast::entry::EntryPointType;
74b04a01
XL
5use rustc_ast::mut_visit::{ExpectOne, *};
6use rustc_ast::ptr::P;
add651ee 7use rustc_ast::visit::{walk_item, Visitor};
6a06907d 8use rustc_ast::{attr, ModKind};
f035d41b 9use rustc_expand::base::{ExtCtxt, ResolverExpand};
dfeec247 10use rustc_expand::expand::{AstFragment, ExpansionConfig};
60c5eb7d 11use rustc_feature::Features;
add651ee 12use rustc_session::lint::builtin::UNNAMEABLE_TEST_ITEMS;
3dfed10e 13use rustc_session::Session;
dfeec247 14use rustc_span::hygiene::{AstPass, SyntaxContext, Transparency};
f9f354fc 15use rustc_span::symbol::{sym, Ident, Symbol};
dfeec247 16use rustc_span::{Span, DUMMY_SP};
e74abb32 17use rustc_target::spec::PanicStrategy;
dfeec247 18use smallvec::{smallvec, SmallVec};
9ffffee4
FG
19use thin_vec::{thin_vec, ThinVec};
20use tracing::debug;
416331ca
XL
21
22use std::{iter, mem};
1a4d82fc 23
49aad941
FG
24use crate::errors;
25
2b03887a 26#[derive(Clone)]
1a4d82fc
JJ
27struct Test {
28 span: Span,
e1599b0c 29 ident: Ident,
2b03887a 30 name: Symbol,
1a4d82fc
JJ
31}
32
33struct TestCtxt<'a> {
1a4d82fc 34 ext_cx: ExtCtxt<'a>,
e74abb32 35 panic_strategy: PanicStrategy,
e1599b0c 36 def_site: Span,
b7449926 37 test_cases: Vec<Test>,
476ff2be 38 reexport_test_harness_main: Option<Symbol>,
b7449926 39 test_runner: Option<ast::Path>,
1a4d82fc
JJ
40}
41
487cf647
FG
42/// Traverse the crate, collecting all the test functions, eliding any
43/// existing main functions, and synthesizing a main test harness
add651ee
FG
44pub fn inject(
45 krate: &mut ast::Crate,
46 sess: &Session,
47 features: &Features,
48 resolver: &mut dyn ResolverExpand,
49) {
3dfed10e
XL
50 let span_diagnostic = sess.diagnostic();
51 let panic_strategy = sess.panic_strategy();
29967ef6 52 let platform_panic_strategy = sess.target.panic_strategy;
3dfed10e 53
e1599b0c
XL
54 // Check for #![reexport_test_harness_main = "some_name"] which gives the
55 // main test function the name `some_name` without hygiene. This needs to be
1a4d82fc
JJ
56 // unconditional, so that the attribute is still marked as used in
57 // non-test builds.
58 let reexport_test_harness_main =
353b0b11 59 attr::first_attr_value_str_by_name(&krate.attrs, sym::reexport_test_harness_main);
1a4d82fc 60
b7449926
XL
61 // Do this here so that the test_runner crate attribute gets marked as used
62 // even in non-test builds
353b0b11 63 let test_runner = get_test_runner(span_diagnostic, &krate);
b7449926 64
353b0b11 65 if sess.is_test_crate() {
064997fb 66 let panic_strategy = match (panic_strategy, sess.opts.unstable_opts.panic_abort_tests) {
dfeec247 67 (PanicStrategy::Abort, true) => PanicStrategy::Abort,
e74abb32 68 (PanicStrategy::Abort, false) => {
3dfed10e
XL
69 if panic_strategy == platform_panic_strategy {
70 // Silently allow compiling with panic=abort on these platforms,
71 // but with old behavior (abort if a test fails).
72 } else {
fe692bf9 73 span_diagnostic.emit_err(errors::TestsNotSupport {});
3dfed10e 74 }
e74abb32
XL
75 PanicStrategy::Unwind
76 }
77 (PanicStrategy::Unwind, _) => PanicStrategy::Unwind,
78 };
dfeec247
XL
79 generate_test_harness(
80 sess,
81 resolver,
82 reexport_test_harness_main,
83 krate,
add651ee 84 features,
dfeec247
XL
85 panic_strategy,
86 test_runner,
87 )
1a4d82fc
JJ
88 }
89}
90
91struct TestHarnessGenerator<'a> {
92 cx: TestCtxt<'a>,
e1599b0c 93 tests: Vec<Test>,
1a4d82fc
JJ
94}
95
a2a8927a
XL
96impl TestHarnessGenerator<'_> {
97 fn add_test_cases(&mut self, node_id: ast::NodeId, span: Span, prev_tests: Vec<Test>) {
98 let mut tests = mem::replace(&mut self.tests, prev_tests);
99
100 if !tests.is_empty() {
101 // Create an identifier that will hygienically resolve the test
102 // case name, even in another module.
103 let expn_id = self.cx.ext_cx.resolver.expansion_for_ast_pass(
104 span,
105 AstPass::TestHarness,
106 &[],
107 Some(node_id),
108 );
109 for test in &mut tests {
110 // See the comment on `mk_main` for why we're using
111 // `apply_mark` directly.
112 test.ident.span =
113 test.ident.span.apply_mark(expn_id.to_expn_id(), Transparency::Opaque);
114 }
115 self.cx.test_cases.extend(tests);
116 }
117 }
118}
119
9fa01778
XL
120impl<'a> MutVisitor for TestHarnessGenerator<'a> {
121 fn visit_crate(&mut self, c: &mut ast::Crate) {
a2a8927a 122 let prev_tests = mem::take(&mut self.tests);
9fa01778 123 noop_visit_crate(c, self);
5e7ed085 124 self.add_test_cases(ast::CRATE_NODE_ID, c.spans.inner_span, prev_tests);
1a4d82fc 125
b7449926 126 // Create a main function to run our tests
6a06907d 127 c.items.push(mk_main(&mut self.cx));
1a4d82fc
JJ
128 }
129
9fa01778 130 fn flat_map_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
b7449926 131 let mut item = i.into_inner();
353b0b11 132 if let Some(name) = get_test_name(&item) {
b7449926 133 debug!("this is a test item");
8faf50e0 134
2b03887a 135 let test = Test { span: item.span, ident: item.ident, name };
e1599b0c 136 self.tests.push(test);
c30ab7b3 137 }
1a4d82fc
JJ
138
139 // We don't want to recurse into anything other than mods, since
140 // mods or tests inside of functions will break things
487cf647
FG
141 if let ast::ItemKind::Mod(_, ModKind::Loaded(.., ast::ModSpans { inner_span: span, .. })) =
142 item.kind
143 {
a2a8927a 144 let prev_tests = mem::take(&mut self.tests);
6a06907d 145 noop_visit_item_kind(&mut item.kind, self);
a2a8927a 146 self.add_test_cases(item.id, span, prev_tests);
add651ee
FG
147 } else {
148 // But in those cases, we emit a lint to warn the user of these missing tests.
149 walk_item(&mut InnerItemLinter { sess: self.cx.ext_cx.sess }, &item);
c30ab7b3 150 }
b7449926 151 smallvec![P(item)]
1a4d82fc
JJ
152 }
153}
154
add651ee
FG
155struct InnerItemLinter<'a> {
156 sess: &'a Session,
157}
158
159impl<'a> Visitor<'a> for InnerItemLinter<'_> {
160 fn visit_item(&mut self, i: &'a ast::Item) {
161 if let Some(attr) = attr::find_by_name(&i.attrs, sym::rustc_test_marker) {
162 self.sess.parse_sess.buffer_lint(
163 UNNAMEABLE_TEST_ITEMS,
164 attr.span,
165 i.id,
166 crate::fluent_generated::builtin_macros_unnameable_test_items,
167 );
168 }
169 }
170}
171
ed00b5ec 172fn entry_point_type(item: &ast::Item, at_root: bool) -> EntryPointType {
3dfed10e
XL
173 match item.kind {
174 ast::ItemKind::Fn(..) => {
ed00b5ec 175 rustc_ast::entry::entry_point_type(&item.attrs, at_root, Some(item.ident.name))
3dfed10e
XL
176 }
177 _ => EntryPointType::None,
178 }
179}
ed00b5ec 180
b7449926 181/// A folder used to remove any entry points (like fn main) because the harness
ed00b5ec 182/// coroutine will provide its own
3dfed10e 183struct EntryPointCleaner<'a> {
e9174d1e 184 // Current depth in the ast
3dfed10e 185 sess: &'a Session,
e9174d1e 186 depth: usize,
e1599b0c 187 def_site: Span,
e9174d1e
SL
188}
189
3dfed10e 190impl<'a> MutVisitor for EntryPointCleaner<'a> {
9fa01778 191 fn flat_map_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
e9174d1e 192 self.depth += 1;
9fa01778 193 let item = noop_flat_map_item(i, self).expect_one("noop did something");
e9174d1e
SL
194 self.depth -= 1;
195
923072b8 196 // Remove any #[rustc_main] or #[start] from the AST so it doesn't
e9174d1e
SL
197 // clash with the one we're going to add, but mark it as
198 // #[allow(dead_code)] to avoid printing warnings.
ed00b5ec 199 let item = match entry_point_type(&item, self.depth == 0) {
923072b8
FG
200 EntryPointType::MainNamed | EntryPointType::RustcMainAttr | EntryPointType::Start => {
201 item.map(|ast::Item { id, ident, attrs, kind, vis, span, tokens }| {
487cf647 202 let allow_dead_code = attr::mk_attr_nested_word(
f2b60f7d 203 &self.sess.parse_sess.attr_id_generator,
487cf647
FG
204 ast::AttrStyle::Outer,
205 sym::allow,
206 sym::dead_code,
207 self.def_site,
f2b60f7d 208 );
74b04a01
XL
209 let attrs = attrs
210 .into_iter()
3dfed10e 211 .filter(|attr| {
94222f64 212 !attr.has_name(sym::rustc_main) && !attr.has_name(sym::start)
3dfed10e 213 })
74b04a01
XL
214 .chain(iter::once(allow_dead_code))
215 .collect();
e9174d1e 216
74b04a01 217 ast::Item { id, ident, attrs, kind, vis, span, tokens }
923072b8
FG
218 })
219 }
dfeec247 220 EntryPointType::None | EntryPointType::OtherMain => item,
e9174d1e
SL
221 };
222
9fa01778 223 smallvec![item]
e9174d1e
SL
224 }
225}
226
b7449926 227/// Crawl over the crate, inserting test reexports and the test main function
dfeec247 228fn generate_test_harness(
3dfed10e 229 sess: &Session,
f035d41b 230 resolver: &mut dyn ResolverExpand,
dfeec247
XL
231 reexport_test_harness_main: Option<Symbol>,
232 krate: &mut ast::Crate,
233 features: &Features,
234 panic_strategy: PanicStrategy,
235 test_runner: Option<ast::Path>,
236) {
add651ee 237 let econfig = ExpansionConfig::default("test".to_string(), features);
ba9703b0 238 let ext_cx = ExtCtxt::new(sess, econfig, resolver, None);
e1599b0c
XL
239
240 let expn_id = ext_cx.resolver.expansion_for_ast_pass(
241 DUMMY_SP,
242 AstPass::TestHarness,
781aab86 243 &[sym::test, sym::rustc_attrs, sym::coverage_attribute],
e1599b0c
XL
244 None,
245 );
136023e0 246 let def_site = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
e1599b0c
XL
247
248 // Remove the entry points
3dfed10e 249 let mut cleaner = EntryPointCleaner { sess, depth: 0, def_site };
e1599b0c
XL
250 cleaner.visit_crate(krate);
251
ff7c6d11 252 let cx = TestCtxt {
e1599b0c 253 ext_cx,
e74abb32 254 panic_strategy,
e1599b0c 255 def_site,
b7449926 256 test_cases: Vec::new(),
3b2f2976 257 reexport_test_harness_main,
dfeec247 258 test_runner,
1a4d82fc
JJ
259 };
260
dfeec247 261 TestHarnessGenerator { cx, tests: Vec::new() }.visit_crate(krate);
1a4d82fc
JJ
262}
263
b7449926
XL
264/// Creates a function item for use as the main function of a test build.
265/// This function will call the `test_runner` as specified by the crate attribute
e1599b0c
XL
266///
267/// By default this expands to
268///
04454e1e 269/// ```ignore UNSOLVED (I think I still need guidance for this one. Is it correct? Do we try to make it run? How do we nicely fill it out?)
cdc7bbd5 270/// #[rustc_main]
e1599b0c
XL
271/// pub fn main() {
272/// extern crate test;
273/// test::test_main_static(&[
274/// &test_const1,
275/// &test_const2,
276/// &test_const3,
277/// ]);
278/// }
f9f354fc 279/// ```
e1599b0c
XL
280///
281/// Most of the Ident have the usual def-site hygiene for the AST pass. The
282/// exception is the `test_const`s. These have a syntax context that has two
283/// opaque marks: one from the expansion of `test` or `test_case`, and one
284/// generated in `TestHarnessGenerator::flat_map_item`. When resolving this
285/// identifier after failing to find a matching identifier in the root module
286/// we remove the outer mark, and try resolving at its def-site, which will
287/// then resolve to `test_const`.
288///
289/// The expansion here can be controlled by two attributes:
290///
f9f354fc
XL
291/// [`TestCtxt::reexport_test_harness_main`] provides a different name for the `main`
292/// function and [`TestCtxt::test_runner`] provides a path that replaces
e1599b0c 293/// `test::test_main_static`.
9fa01778 294fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
e1599b0c 295 let sp = cx.def_site;
85aaf69f 296 let ecx = &cx.ext_cx;
e1599b0c 297 let test_id = Ident::new(sym::test, sp);
476ff2be 298
e74abb32
XL
299 let runner_name = match cx.panic_strategy {
300 PanicStrategy::Unwind => "test_main_static",
301 PanicStrategy::Abort => "test_main_static_abort",
302 };
303
85aaf69f 304 // test::test_main_static(...)
dfeec247
XL
305 let mut test_runner = cx
306 .test_runner
307 .clone()
29967ef6 308 .unwrap_or_else(|| ecx.path(sp, vec![test_id, Ident::from_str_and_span(runner_name, sp)]));
b7449926
XL
309
310 test_runner.span = sp;
311
0bf4aa26 312 let test_main_path_expr = ecx.expr_path(test_runner);
9ffffee4 313 let call_test_main = ecx.expr_call(sp, test_main_path_expr, thin_vec![mk_tests_slice(cx, sp)]);
85aaf69f 314 let call_test_main = ecx.stmt_expr(call_test_main);
b7449926 315
e1599b0c 316 // extern crate test
f2b60f7d
FG
317 let test_extern_stmt = ecx.stmt_item(
318 sp,
319 ecx.item(sp, test_id, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None)),
320 );
b7449926 321
cdc7bbd5 322 // #[rustc_main]
487cf647 323 let main_attr = ecx.attr_word(sym::rustc_main, sp);
781aab86
FG
324 // #[coverage(off)]
325 let coverage_attr = ecx.attr_nested_word(sym::coverage, sym::off, sp);
e1599b0c 326
85aaf69f 327 // pub fn main() { ... }
9ffffee4 328 let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(ThinVec::new()));
b7449926
XL
329
330 // If no test runner is provided we need to import the test crate
331 let main_body = if cx.test_runner.is_none() {
9ffffee4 332 ecx.block(sp, thin_vec![test_extern_stmt, call_test_main])
b7449926 333 } else {
9ffffee4 334 ecx.block(sp, thin_vec![call_test_main])
b7449926
XL
335 };
336
9ffffee4 337 let decl = ecx.fn_decl(ThinVec::new(), ast::FnRetTy::Ty(main_ret_ty));
3dfed10e 338 let sig = ast::FnSig { decl, header: ast::FnHeader::default(), span: sp };
3c0e092e
XL
339 let defaultness = ast::Defaultness::Final;
340 let main = ast::ItemKind::Fn(Box::new(ast::Fn {
341 defaultness,
94222f64 342 sig,
3c0e092e
XL
343 generics: ast::Generics::default(),
344 body: Some(main_body),
345 }));
b7449926
XL
346
347 // Honor the reexport_test_harness_main attribute
dc9dc135 348 let main_id = match cx.reexport_test_harness_main {
e1599b0c
XL
349 Some(sym) => Ident::new(sym, sp.with_ctxt(SyntaxContext::root())),
350 None => Ident::new(sym::main, sp),
dc9dc135 351 };
b7449926 352
e1599b0c 353 let main = P(ast::Item {
b7449926 354 ident: main_id,
781aab86 355 attrs: thin_vec![main_attr, coverage_attr],
85aaf69f 356 id: ast::DUMMY_NODE_ID,
e74abb32 357 kind: main,
1b1a35ee 358 vis: ast::Visibility { span: sp, kind: ast::VisibilityKind::Public, tokens: None },
3b2f2976
XL
359 span: sp,
360 tokens: None,
e1599b0c 361 });
1a4d82fc 362
e1599b0c
XL
363 // Integrate the new item into existing module structures.
364 let main = AstFragment::Items(smallvec![main]);
365 cx.ext_cx.monotonic_expander().fully_expand_fragment(main).make_items().pop().unwrap()
54a0048b
SL
366}
367
b7449926 368/// Creates a slice containing every test like so:
e1599b0c
XL
369/// &[&test1, &test2]
370fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P<ast::Expr> {
b7449926 371 debug!("building test vector from {} tests", cx.test_cases.len());
74b04a01 372 let ecx = &cx.ext_cx;
b7449926 373
2b03887a
FG
374 let mut tests = cx.test_cases.clone();
375 tests.sort_by(|a, b| a.name.as_str().cmp(&b.name.as_str()));
376
064997fb 377 ecx.expr_array_ref(
dfeec247 378 sp,
2b03887a 379 tests
dfeec247
XL
380 .iter()
381 .map(|test| {
382 ecx.expr_addr_of(test.span, ecx.expr_path(ecx.path(test.span, vec![test.ident])))
383 })
384 .collect(),
385 )
1a4d82fc
JJ
386}
387
353b0b11
FG
388fn get_test_name(i: &ast::Item) -> Option<Symbol> {
389 attr::first_attr_value_str_by_name(&i.attrs, sym::rustc_test_marker)
b7449926 390}
1a4d82fc 391
353b0b11
FG
392fn get_test_runner(sd: &rustc_errors::Handler, krate: &ast::Crate) -> Option<ast::Path> {
393 let test_attr = attr::find_by_name(&krate.attrs, sym::test_runner)?;
ba9703b0
XL
394 let meta_list = test_attr.meta_item_list()?;
395 let span = test_attr.span;
396 match &*meta_list {
397 [single] => match single.meta_item() {
398 Some(meta_item) if meta_item.is_word() => return Some(meta_item.path.clone()),
5e7ed085 399 _ => {
49aad941 400 sd.emit_err(errors::TestRunnerInvalid { span });
5e7ed085 401 }
ba9703b0 402 },
5e7ed085 403 _ => {
49aad941 404 sd.emit_err(errors::TestRunnerNargs { span });
5e7ed085 405 }
ba9703b0
XL
406 }
407 None
1a4d82fc 408}