]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/test.rs
Auto merge of #53824 - ljedrz:begone_onevector, r=michaelwoerister
[rust.git] / src / libsyntax / test.rs
1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // Code that generates a test runner to run all the tests in a crate
12
13 #![allow(dead_code)]
14 #![allow(unused_imports)]
15
16 use self::HasTestSignature::*;
17
18 use std::iter;
19 use std::slice;
20 use std::mem;
21 use std::vec;
22 use attr::{self, HasAttrs};
23 use syntax_pos::{self, DUMMY_SP, NO_EXPANSION, Span, SourceFile, BytePos};
24
25 use source_map::{self, SourceMap, ExpnInfo, MacroAttribute, dummy_spanned, respan};
26 use errors;
27 use config;
28 use entry::{self, EntryPointType};
29 use ext::base::{ExtCtxt, Resolver};
30 use ext::build::AstBuilder;
31 use ext::expand::ExpansionConfig;
32 use ext::hygiene::{self, Mark, SyntaxContext};
33 use fold::Folder;
34 use feature_gate::Features;
35 use util::move_map::MoveMap;
36 use fold::{self, ExpectOne};
37 use parse::{token, ParseSess};
38 use print::pprust;
39 use ast::{self, Ident};
40 use ptr::P;
41 use smallvec::SmallVec;
42 use symbol::{self, Symbol, keywords};
43 use ThinVec;
44
45 struct Test {
46     span: Span,
47     path: Vec<Ident>,
48 }
49
50 struct TestCtxt<'a> {
51     span_diagnostic: &'a errors::Handler,
52     path: Vec<Ident>,
53     ext_cx: ExtCtxt<'a>,
54     test_cases: Vec<Test>,
55     reexport_test_harness_main: Option<Symbol>,
56     is_libtest: bool,
57     ctxt: SyntaxContext,
58     features: &'a Features,
59     test_runner: Option<ast::Path>,
60
61     // top-level re-export submodule, filled out after folding is finished
62     toplevel_reexport: Option<Ident>,
63 }
64
65 // Traverse the crate, collecting all the test functions, eliding any
66 // existing main functions, and synthesizing a main test harness
67 pub fn modify_for_testing(sess: &ParseSess,
68                           resolver: &mut dyn Resolver,
69                           should_test: bool,
70                           krate: ast::Crate,
71                           span_diagnostic: &errors::Handler,
72                           features: &Features) -> ast::Crate {
73     // Check for #[reexport_test_harness_main = "some_name"] which
74     // creates a `use __test::main as some_name;`. This needs to be
75     // unconditional, so that the attribute is still marked as used in
76     // non-test builds.
77     let reexport_test_harness_main =
78         attr::first_attr_value_str_by_name(&krate.attrs,
79                                            "reexport_test_harness_main");
80
81     // Do this here so that the test_runner crate attribute gets marked as used
82     // even in non-test builds
83     let test_runner = get_test_runner(span_diagnostic, &krate);
84
85     if should_test {
86         generate_test_harness(sess, resolver, reexport_test_harness_main,
87                               krate, span_diagnostic, features, test_runner)
88     } else {
89         krate
90     }
91 }
92
93 struct TestHarnessGenerator<'a> {
94     cx: TestCtxt<'a>,
95     tests: Vec<Ident>,
96
97     // submodule name, gensym'd identifier for re-exports
98     tested_submods: Vec<(Ident, Ident)>,
99 }
100
101 impl<'a> fold::Folder for TestHarnessGenerator<'a> {
102     fn fold_crate(&mut self, c: ast::Crate) -> ast::Crate {
103         let mut folded = fold::noop_fold_crate(c, self);
104
105         // Create a main function to run our tests
106         let test_main = {
107             let unresolved = mk_main(&mut self.cx);
108             self.cx.ext_cx.monotonic_expander().fold_item(unresolved).pop().unwrap()
109         };
110
111         folded.module.items.push(test_main);
112         folded
113     }
114
115     fn fold_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
116         let ident = i.ident;
117         if ident.name != keywords::Invalid.name() {
118             self.cx.path.push(ident);
119         }
120         debug!("current path: {}", path_name_i(&self.cx.path));
121
122         let mut item = i.into_inner();
123         if is_test_case(&item) {
124             debug!("this is a test item");
125
126             let test = Test {
127                 span: item.span,
128                 path: self.cx.path.clone(),
129             };
130             self.cx.test_cases.push(test);
131             self.tests.push(item.ident);
132         }
133
134         // We don't want to recurse into anything other than mods, since
135         // mods or tests inside of functions will break things
136         if let ast::ItemKind::Mod(module) = item.node {
137             let tests = mem::replace(&mut self.tests, Vec::new());
138             let tested_submods = mem::replace(&mut self.tested_submods, Vec::new());
139             let mut mod_folded = fold::noop_fold_mod(module, self);
140             let tests = mem::replace(&mut self.tests, tests);
141             let tested_submods = mem::replace(&mut self.tested_submods, tested_submods);
142
143             if !tests.is_empty() || !tested_submods.is_empty() {
144                 let (it, sym) = mk_reexport_mod(&mut self.cx, item.id, tests, tested_submods);
145                 mod_folded.items.push(it);
146
147                 if !self.cx.path.is_empty() {
148                     self.tested_submods.push((self.cx.path[self.cx.path.len()-1], sym));
149                 } else {
150                     debug!("pushing nothing, sym: {:?}", sym);
151                     self.cx.toplevel_reexport = Some(sym);
152                 }
153             }
154             item.node = ast::ItemKind::Mod(mod_folded);
155         }
156         if ident.name != keywords::Invalid.name() {
157             self.cx.path.pop();
158         }
159         smallvec![P(item)]
160     }
161
162     fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac }
163 }
164
165 /// A folder used to remove any entry points (like fn main) because the harness
166 /// generator will provide its own
167 struct EntryPointCleaner {
168     // Current depth in the ast
169     depth: usize,
170 }
171
172 impl fold::Folder for EntryPointCleaner {
173     fn fold_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
174         self.depth += 1;
175         let folded = fold::noop_fold_item(i, self).expect_one("noop did something");
176         self.depth -= 1;
177
178         // Remove any #[main] or #[start] from the AST so it doesn't
179         // clash with the one we're going to add, but mark it as
180         // #[allow(dead_code)] to avoid printing warnings.
181         let folded = match entry::entry_point_type(&folded, self.depth) {
182             EntryPointType::MainNamed |
183             EntryPointType::MainAttr |
184             EntryPointType::Start =>
185                 folded.map(|ast::Item {id, ident, attrs, node, vis, span, tokens}| {
186                     let allow_ident = Ident::from_str("allow");
187                     let dc_nested = attr::mk_nested_word_item(Ident::from_str("dead_code"));
188                     let allow_dead_code_item = attr::mk_list_item(DUMMY_SP, allow_ident,
189                                                                   vec![dc_nested]);
190                     let allow_dead_code = attr::mk_attr_outer(DUMMY_SP,
191                                                               attr::mk_attr_id(),
192                                                               allow_dead_code_item);
193
194                     ast::Item {
195                         id,
196                         ident,
197                         attrs: attrs.into_iter()
198                             .filter(|attr| {
199                                 !attr.check_name("main") && !attr.check_name("start")
200                             })
201                             .chain(iter::once(allow_dead_code))
202                             .collect(),
203                         node,
204                         vis,
205                         span,
206                         tokens,
207                     }
208                 }),
209             EntryPointType::None |
210             EntryPointType::OtherMain => folded,
211         };
212
213         smallvec![folded]
214     }
215
216     fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac }
217 }
218
219 /// Creates an item (specifically a module) that "pub use"s the tests passed in.
220 /// Each tested submodule will contain a similar reexport module that we will export
221 /// under the name of the original module. That is, `submod::__test_reexports` is
222 /// reexported like so `pub use submod::__test_reexports as submod`.
223 fn mk_reexport_mod(cx: &mut TestCtxt,
224                    parent: ast::NodeId,
225                    tests: Vec<Ident>,
226                    tested_submods: Vec<(Ident, Ident)>)
227                    -> (P<ast::Item>, Ident) {
228     let super_ = Ident::from_str("super");
229
230     let items = tests.into_iter().map(|r| {
231         cx.ext_cx.item_use_simple(DUMMY_SP, dummy_spanned(ast::VisibilityKind::Public),
232                                   cx.ext_cx.path(DUMMY_SP, vec![super_, r]))
233     }).chain(tested_submods.into_iter().map(|(r, sym)| {
234         let path = cx.ext_cx.path(DUMMY_SP, vec![super_, r, sym]);
235         cx.ext_cx.item_use_simple_(DUMMY_SP, dummy_spanned(ast::VisibilityKind::Public),
236                                    Some(r), path)
237     })).collect();
238
239     let reexport_mod = ast::Mod {
240         inner: DUMMY_SP,
241         items,
242     };
243
244     let sym = Ident::with_empty_ctxt(Symbol::gensym("__test_reexports"));
245     let parent = if parent == ast::DUMMY_NODE_ID { ast::CRATE_NODE_ID } else { parent };
246     cx.ext_cx.current_expansion.mark = cx.ext_cx.resolver.get_module_scope(parent);
247     let it = cx.ext_cx.monotonic_expander().fold_item(P(ast::Item {
248         ident: sym,
249         attrs: Vec::new(),
250         id: ast::DUMMY_NODE_ID,
251         node: ast::ItemKind::Mod(reexport_mod),
252         vis: dummy_spanned(ast::VisibilityKind::Public),
253         span: DUMMY_SP,
254         tokens: None,
255     })).pop().unwrap();
256
257     (it, sym)
258 }
259
260 /// Crawl over the crate, inserting test reexports and the test main function
261 fn generate_test_harness(sess: &ParseSess,
262                          resolver: &mut dyn Resolver,
263                          reexport_test_harness_main: Option<Symbol>,
264                          krate: ast::Crate,
265                          sd: &errors::Handler,
266                          features: &Features,
267                          test_runner: Option<ast::Path>) -> ast::Crate {
268     // Remove the entry points
269     let mut cleaner = EntryPointCleaner { depth: 0 };
270     let krate = cleaner.fold_crate(krate);
271
272     let mark = Mark::fresh(Mark::root());
273
274     let mut econfig = ExpansionConfig::default("test".to_string());
275     econfig.features = Some(features);
276
277     let cx = TestCtxt {
278         span_diagnostic: sd,
279         ext_cx: ExtCtxt::new(sess, econfig, resolver),
280         path: Vec::new(),
281         test_cases: Vec::new(),
282         reexport_test_harness_main,
283         // NB: doesn't consider the value of `--crate-name` passed on the command line.
284         is_libtest: attr::find_crate_name(&krate.attrs).map(|s| s == "test").unwrap_or(false),
285         toplevel_reexport: None,
286         ctxt: SyntaxContext::empty().apply_mark(mark),
287         features,
288         test_runner
289     };
290
291     mark.set_expn_info(ExpnInfo {
292         call_site: DUMMY_SP,
293         def_site: None,
294         format: MacroAttribute(Symbol::intern("test_case")),
295         allow_internal_unstable: true,
296         allow_internal_unsafe: false,
297         local_inner_macros: false,
298         edition: hygiene::default_edition(),
299     });
300
301     TestHarnessGenerator {
302         cx,
303         tests: Vec::new(),
304         tested_submods: Vec::new(),
305     }.fold_crate(krate)
306 }
307
308 /// Craft a span that will be ignored by the stability lint's
309 /// call to source_map's `is_internal` check.
310 /// The expanded code calls some unstable functions in the test crate.
311 fn ignored_span(cx: &TestCtxt, sp: Span) -> Span {
312     sp.with_ctxt(cx.ctxt)
313 }
314
315 enum HasTestSignature {
316     Yes,
317     No(BadTestSignature),
318 }
319
320 #[derive(PartialEq)]
321 enum BadTestSignature {
322     NotEvenAFunction,
323     WrongTypeSignature,
324     NoArgumentsAllowed,
325     ShouldPanicOnlyWithNoArgs,
326 }
327
328 /// Creates a function item for use as the main function of a test build.
329 /// This function will call the `test_runner` as specified by the crate attribute
330 fn mk_main(cx: &mut TestCtxt) -> P<ast::Item> {
331     // Writing this out by hand with 'ignored_span':
332     //        pub fn main() {
333     //            #![main]
334     //            test::test_main_static(::std::os::args().as_slice(), &[..tests]);
335     //        }
336     let sp = ignored_span(cx, DUMMY_SP);
337     let ecx = &cx.ext_cx;
338     let test_id = ecx.ident_of("test").gensym();
339
340     // test::test_main_static(...)
341     let mut test_runner = cx.test_runner.clone().unwrap_or(
342         ecx.path(sp, vec![
343             test_id, ecx.ident_of("test_main_static")
344         ]));
345
346     test_runner.span = sp;
347
348     let test_main_path_expr = ecx.expr_path(test_runner.clone());
349     let call_test_main = ecx.expr_call(sp, test_main_path_expr,
350                                        vec![mk_tests_slice(cx)]);
351     let call_test_main = ecx.stmt_expr(call_test_main);
352
353     // #![main]
354     let main_meta = ecx.meta_word(sp, Symbol::intern("main"));
355     let main_attr = ecx.attribute(sp, main_meta);
356
357     // extern crate test as test_gensym
358     let test_extern_stmt = ecx.stmt_item(sp, ecx.item(sp,
359         test_id,
360         vec![],
361         ast::ItemKind::ExternCrate(Some(Symbol::intern("test")))
362     ));
363
364     // pub fn main() { ... }
365     let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![]));
366
367     // If no test runner is provided we need to import the test crate
368     let main_body = if cx.test_runner.is_none() {
369         ecx.block(sp, vec![test_extern_stmt, call_test_main])
370     } else {
371         ecx.block(sp, vec![call_test_main])
372     };
373
374     let main = ast::ItemKind::Fn(ecx.fn_decl(vec![], ast::FunctionRetTy::Ty(main_ret_ty)),
375                            ast::FnHeader::default(),
376                            ast::Generics::default(),
377                            main_body);
378
379     // Honor the reexport_test_harness_main attribute
380     let main_id = Ident::new(
381         cx.reexport_test_harness_main.unwrap_or(Symbol::gensym("main")),
382         sp);
383
384     P(ast::Item {
385         ident: main_id,
386         attrs: vec![main_attr],
387         id: ast::DUMMY_NODE_ID,
388         node: main,
389         vis: dummy_spanned(ast::VisibilityKind::Public),
390         span: sp,
391         tokens: None,
392     })
393
394 }
395
396 fn path_name_i(idents: &[Ident]) -> String {
397     let mut path_name = "".to_string();
398     let mut idents_iter = idents.iter().peekable();
399     while let Some(ident) = idents_iter.next() {
400         path_name.push_str(&ident.as_str());
401         if idents_iter.peek().is_some() {
402             path_name.push_str("::")
403         }
404     }
405     path_name
406 }
407
408 /// Creates a slice containing every test like so:
409 /// &[path::to::test1, path::to::test2]
410 fn mk_tests_slice(cx: &TestCtxt) -> P<ast::Expr> {
411     debug!("building test vector from {} tests", cx.test_cases.len());
412     let ref ecx = cx.ext_cx;
413
414     ecx.expr_vec_slice(DUMMY_SP,
415         cx.test_cases.iter().map(|test| {
416             ecx.expr_addr_of(test.span,
417                 ecx.expr_path(ecx.path(test.span, visible_path(cx, &test.path))))
418         }).collect())
419 }
420
421 /// Creates a path from the top-level __test module to the test via __test_reexports
422 fn visible_path(cx: &TestCtxt, path: &[Ident]) -> Vec<Ident>{
423     let mut visible_path = vec![];
424     match cx.toplevel_reexport {
425         Some(id) => visible_path.push(id),
426         None => {
427             cx.span_diagnostic.bug("expected to find top-level re-export name, but found None");
428         }
429     }
430     visible_path.extend_from_slice(path);
431     visible_path
432 }
433
434 fn is_test_case(i: &ast::Item) -> bool {
435     attr::contains_name(&i.attrs, "rustc_test_marker")
436 }
437
438 fn get_test_runner(sd: &errors::Handler, krate: &ast::Crate) -> Option<ast::Path> {
439     let test_attr = attr::find_by_name(&krate.attrs, "test_runner")?;
440     if let Some(meta_list) = test_attr.meta_item_list() {
441         if meta_list.len() != 1 {
442             sd.span_fatal(test_attr.span(),
443                 "#![test_runner(..)] accepts exactly 1 argument").raise()
444         }
445         Some(meta_list[0].word().as_ref().unwrap().ident.clone())
446     } else {
447         sd.span_fatal(test_attr.span(),
448             "test_runner must be of the form #[test_runner(..)]").raise()
449     }
450 }