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