]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/test.rs
8ff4b0d025c8d340c5e82bc05d2e8ad7b03f61a6
[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         inline: true,
241         inner: DUMMY_SP,
242         items,
243     };
244
245     let sym = Ident::with_empty_ctxt(Symbol::gensym("__test_reexports"));
246     let parent = if parent == ast::DUMMY_NODE_ID { ast::CRATE_NODE_ID } else { parent };
247     cx.ext_cx.current_expansion.mark = cx.ext_cx.resolver.get_module_scope(parent);
248     let it = cx.ext_cx.monotonic_expander().fold_item(P(ast::Item {
249         ident: sym,
250         attrs: Vec::new(),
251         id: ast::DUMMY_NODE_ID,
252         node: ast::ItemKind::Mod(reexport_mod),
253         vis: dummy_spanned(ast::VisibilityKind::Public),
254         span: DUMMY_SP,
255         tokens: None,
256     })).pop().unwrap();
257
258     (it, sym)
259 }
260
261 /// Crawl over the crate, inserting test reexports and the test main function
262 fn generate_test_harness(sess: &ParseSess,
263                          resolver: &mut dyn Resolver,
264                          reexport_test_harness_main: Option<Symbol>,
265                          krate: ast::Crate,
266                          sd: &errors::Handler,
267                          features: &Features,
268                          test_runner: Option<ast::Path>) -> ast::Crate {
269     // Remove the entry points
270     let mut cleaner = EntryPointCleaner { depth: 0 };
271     let krate = cleaner.fold_crate(krate);
272
273     let mark = Mark::fresh(Mark::root());
274
275     let mut econfig = ExpansionConfig::default("test".to_string());
276     econfig.features = Some(features);
277
278     let cx = TestCtxt {
279         span_diagnostic: sd,
280         ext_cx: ExtCtxt::new(sess, econfig, resolver),
281         path: Vec::new(),
282         test_cases: Vec::new(),
283         reexport_test_harness_main,
284         // NB: doesn't consider the value of `--crate-name` passed on the command line.
285         is_libtest: attr::find_crate_name(&krate.attrs).map(|s| s == "test").unwrap_or(false),
286         toplevel_reexport: None,
287         ctxt: SyntaxContext::empty().apply_mark(mark),
288         features,
289         test_runner
290     };
291
292     mark.set_expn_info(ExpnInfo {
293         call_site: DUMMY_SP,
294         def_site: None,
295         format: MacroAttribute(Symbol::intern("test_case")),
296         allow_internal_unstable: true,
297         allow_internal_unsafe: false,
298         local_inner_macros: false,
299         edition: hygiene::default_edition(),
300     });
301
302     TestHarnessGenerator {
303         cx,
304         tests: Vec::new(),
305         tested_submods: Vec::new(),
306     }.fold_crate(krate)
307 }
308
309 /// Craft a span that will be ignored by the stability lint's
310 /// call to source_map's `is_internal` check.
311 /// The expanded code calls some unstable functions in the test crate.
312 fn ignored_span(cx: &TestCtxt, sp: Span) -> Span {
313     sp.with_ctxt(cx.ctxt)
314 }
315
316 enum HasTestSignature {
317     Yes,
318     No(BadTestSignature),
319 }
320
321 #[derive(PartialEq)]
322 enum BadTestSignature {
323     NotEvenAFunction,
324     WrongTypeSignature,
325     NoArgumentsAllowed,
326     ShouldPanicOnlyWithNoArgs,
327 }
328
329 /// Creates a function item for use as the main function of a test build.
330 /// This function will call the `test_runner` as specified by the crate attribute
331 fn mk_main(cx: &mut TestCtxt) -> P<ast::Item> {
332     // Writing this out by hand with 'ignored_span':
333     //        pub fn main() {
334     //            #![main]
335     //            test::test_main_static(::std::os::args().as_slice(), &[..tests]);
336     //        }
337     let sp = ignored_span(cx, DUMMY_SP);
338     let ecx = &cx.ext_cx;
339     let test_id = ecx.ident_of("test").gensym();
340
341     // test::test_main_static(...)
342     let mut test_runner = cx.test_runner.clone().unwrap_or(
343         ecx.path(sp, vec![
344             test_id, ecx.ident_of("test_main_static")
345         ]));
346
347     test_runner.span = sp;
348
349     let test_main_path_expr = ecx.expr_path(test_runner);
350     let call_test_main = ecx.expr_call(sp, test_main_path_expr,
351                                        vec![mk_tests_slice(cx)]);
352     let call_test_main = ecx.stmt_expr(call_test_main);
353
354     // #![main]
355     let main_meta = ecx.meta_word(sp, Symbol::intern("main"));
356     let main_attr = ecx.attribute(sp, main_meta);
357
358     // extern crate test as test_gensym
359     let test_extern_stmt = ecx.stmt_item(sp, ecx.item(sp,
360         test_id,
361         vec![],
362         ast::ItemKind::ExternCrate(Some(Symbol::intern("test")))
363     ));
364
365     // pub fn main() { ... }
366     let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![]));
367
368     // If no test runner is provided we need to import the test crate
369     let main_body = if cx.test_runner.is_none() {
370         ecx.block(sp, vec![test_extern_stmt, call_test_main])
371     } else {
372         ecx.block(sp, vec![call_test_main])
373     };
374
375     let main = ast::ItemKind::Fn(ecx.fn_decl(vec![], ast::FunctionRetTy::Ty(main_ret_ty)),
376                            ast::FnHeader::default(),
377                            ast::Generics::default(),
378                            main_body);
379
380     // Honor the reexport_test_harness_main attribute
381     let main_id = Ident::new(
382         cx.reexport_test_harness_main.unwrap_or(Symbol::gensym("main")),
383         sp);
384
385     P(ast::Item {
386         ident: main_id,
387         attrs: vec![main_attr],
388         id: ast::DUMMY_NODE_ID,
389         node: main,
390         vis: dummy_spanned(ast::VisibilityKind::Public),
391         span: sp,
392         tokens: None,
393     })
394
395 }
396
397 fn path_name_i(idents: &[Ident]) -> String {
398     let mut path_name = "".to_string();
399     let mut idents_iter = idents.iter().peekable();
400     while let Some(ident) = idents_iter.next() {
401         path_name.push_str(&ident.as_str());
402         if idents_iter.peek().is_some() {
403             path_name.push_str("::")
404         }
405     }
406     path_name
407 }
408
409 /// Creates a slice containing every test like so:
410 /// &[path::to::test1, path::to::test2]
411 fn mk_tests_slice(cx: &TestCtxt) -> P<ast::Expr> {
412     debug!("building test vector from {} tests", cx.test_cases.len());
413     let ref ecx = cx.ext_cx;
414
415     ecx.expr_vec_slice(DUMMY_SP,
416         cx.test_cases.iter().map(|test| {
417             ecx.expr_addr_of(test.span,
418                 ecx.expr_path(ecx.path(test.span, visible_path(cx, &test.path))))
419         }).collect())
420 }
421
422 /// Creates a path from the top-level __test module to the test via __test_reexports
423 fn visible_path(cx: &TestCtxt, path: &[Ident]) -> Vec<Ident>{
424     let mut visible_path = vec![];
425     match cx.toplevel_reexport {
426         Some(id) => visible_path.push(id),
427         None => {
428             cx.span_diagnostic.bug("expected to find top-level re-export name, but found None");
429         }
430     }
431     visible_path.extend_from_slice(path);
432     visible_path
433 }
434
435 fn is_test_case(i: &ast::Item) -> bool {
436     attr::contains_name(&i.attrs, "rustc_test_marker")
437 }
438
439 fn get_test_runner(sd: &errors::Handler, krate: &ast::Crate) -> Option<ast::Path> {
440     let test_attr = attr::find_by_name(&krate.attrs, "test_runner")?;
441     if let Some(meta_list) = test_attr.meta_item_list() {
442         if meta_list.len() != 1 {
443             sd.span_fatal(test_attr.span(),
444                 "#![test_runner(..)] accepts exactly 1 argument").raise()
445         }
446         Some(meta_list[0].word().as_ref().unwrap().ident.clone())
447     } else {
448         sd.span_fatal(test_attr.span(),
449             "test_runner must be of the form #[test_runner(..)]").raise()
450     }
451 }