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