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.
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.
11 // Code that generates a test runner to run all the tests in a crate
14 #![allow(unused_imports)]
15 use self::HasTestSignature
::*;
21 use attr
::AttrMetaMethods
;
23 use codemap
::{DUMMY_SP, Span, ExpnInfo, NameAndSpan, MacroAttribute}
;
27 use entry
::{self, EntryPointType}
;
28 use ext
::base
::ExtCtxt
;
29 use ext
::build
::AstBuilder
;
30 use ext
::expand
::ExpansionConfig
;
32 use util
::move_map
::MoveMap
;
34 use parse
::token
::{intern, InternedString}
;
35 use parse
::{token, ParseSess}
;
39 use util
::small_vector
::SmallVector
;
43 Yes(Option
<InternedString
>),
48 path
: Vec
<ast
::Ident
> ,
51 should_panic
: ShouldPanic
56 span_diagnostic
: &'a errors
::Handler
,
57 path
: Vec
<ast
::Ident
>,
60 reexport_test_harness_main
: Option
<InternedString
>,
62 config
: ast
::CrateConfig
,
64 // top-level re-export submodule, filled out after folding is finished
65 toplevel_reexport
: Option
<ast
::Ident
>,
68 // Traverse the crate, collecting all the test functions, eliding any
69 // existing main functions, and synthesizing a main test harness
70 pub fn modify_for_testing(sess
: &ParseSess
,
71 cfg
: &ast
::CrateConfig
,
73 span_diagnostic
: &errors
::Handler
) -> ast
::Crate
{
74 // We generate the test harness when building in the 'test'
75 // configuration, either with the '--test' or '--cfg test'
76 // command line options.
77 let should_test
= attr
::contains_name(&krate
.config
, "test");
79 // Check for #[reexport_test_harness_main = "some_name"] which
80 // creates a `use some_name = __test::main;`. This needs to be
81 // unconditional, so that the attribute is still marked as used in
83 let reexport_test_harness_main
=
84 attr
::first_attr_value_str_by_name(&krate
.attrs
,
85 "reexport_test_harness_main");
88 generate_test_harness(sess
, reexport_test_harness_main
, krate
, cfg
, span_diagnostic
)
90 strip_test_functions(span_diagnostic
, krate
)
94 struct TestHarnessGenerator
<'a
> {
96 tests
: Vec
<ast
::Ident
>,
98 // submodule name, gensym'd identifier for re-exports
99 tested_submods
: Vec
<(ast
::Ident
, ast
::Ident
)>,
102 impl<'a
> fold
::Folder
for TestHarnessGenerator
<'a
> {
103 fn fold_crate(&mut self, c
: ast
::Crate
) -> ast
::Crate
{
104 let mut folded
= fold
::noop_fold_crate(c
, self);
106 // Add a special __test module to the crate that will contain code
107 // generated for the test harness
108 let (mod_
, reexport
) = mk_test_module(&mut self.cx
);
110 Some(re
) => folded
.module
.items
.push(re
),
113 folded
.module
.items
.push(mod_
);
117 fn fold_item(&mut self, i
: P
<ast
::Item
>) -> SmallVector
<P
<ast
::Item
>> {
119 if ident
.name
!= token
::special_idents
::invalid
.name
{
120 self.cx
.path
.push(ident
);
122 debug
!("current path: {}", path_name_i(&self.cx
.path
));
124 let i
= if is_test_fn(&self.cx
, &i
) || is_bench_fn(&self.cx
, &i
) {
126 ast
::ItemKind
::Fn(_
, ast
::Unsafety
::Unsafe
, _
, _
, _
, _
) => {
127 let diag
= self.cx
.span_diagnostic
;
128 panic
!(diag
.span_fatal(i
.span
, "unsafe functions cannot be used for tests"));
131 debug
!("this is a test function");
134 path
: self.cx
.path
.clone(),
135 bench
: is_bench_fn(&self.cx
, &i
),
136 ignore
: is_ignored(&i
),
137 should_panic
: should_panic(&i
)
139 self.cx
.testfns
.push(test
);
140 self.tests
.push(i
.ident
);
141 // debug!("have {} test/bench functions",
142 // cx.testfns.len());
144 // Make all tests public so we can call them from outside
145 // the module (note that the tests are re-exported and must
146 // be made public themselves to avoid privacy errors).
148 i
.vis
= ast
::Visibility
::Public
;
157 // We don't want to recurse into anything other than mods, since
158 // mods or tests inside of functions will break things
159 let res
= match i
.node
{
160 ast
::ItemKind
::Mod(..) => fold
::noop_fold_item(i
, self),
161 _
=> SmallVector
::one(i
),
163 if ident
.name
!= token
::special_idents
::invalid
.name
{
169 fn fold_mod(&mut self, m
: ast
::Mod
) -> ast
::Mod
{
170 let tests
= mem
::replace(&mut self.tests
, Vec
::new());
171 let tested_submods
= mem
::replace(&mut self.tested_submods
, Vec
::new());
172 let mut mod_folded
= fold
::noop_fold_mod(m
, self);
173 let tests
= mem
::replace(&mut self.tests
, tests
);
174 let tested_submods
= mem
::replace(&mut self.tested_submods
, tested_submods
);
176 if !tests
.is_empty() || !tested_submods
.is_empty() {
177 let (it
, sym
) = mk_reexport_mod(&mut self.cx
, tests
, tested_submods
);
178 mod_folded
.items
.push(it
);
180 if !self.cx
.path
.is_empty() {
181 self.tested_submods
.push((self.cx
.path
[self.cx
.path
.len()-1], sym
));
183 debug
!("pushing nothing, sym: {:?}", sym
);
184 self.cx
.toplevel_reexport
= Some(sym
);
192 struct EntryPointCleaner
{
193 // Current depth in the ast
197 impl fold
::Folder
for EntryPointCleaner
{
198 fn fold_item(&mut self, i
: P
<ast
::Item
>) -> SmallVector
<P
<ast
::Item
>> {
200 let folded
= fold
::noop_fold_item(i
, self).expect_one("noop did something");
203 // Remove any #[main] or #[start] from the AST so it doesn't
204 // clash with the one we're going to add, but mark it as
205 // #[allow(dead_code)] to avoid printing warnings.
206 let folded
= match entry
::entry_point_type(&folded
, self.depth
) {
207 EntryPointType
::MainNamed
|
208 EntryPointType
::MainAttr
|
209 EntryPointType
::Start
=>
210 folded
.map(|ast
::Item {id, ident, attrs, node, vis, span}
| {
211 let allow_str
= InternedString
::new("allow");
212 let dead_code_str
= InternedString
::new("dead_code");
213 let allow_dead_code_item
=
214 attr
::mk_list_item(allow_str
,
215 vec
![attr
::mk_word_item(dead_code_str
)]);
216 let allow_dead_code
= attr
::mk_attr_outer(attr
::mk_attr_id(),
217 allow_dead_code_item
);
222 attrs
: attrs
.into_iter()
224 !attr
.check_name("main") && !attr
.check_name("start")
226 .chain(iter
::once(allow_dead_code
))
233 EntryPointType
::None
|
234 EntryPointType
::OtherMain
=> folded
,
237 SmallVector
::one(folded
)
241 fn mk_reexport_mod(cx
: &mut TestCtxt
, tests
: Vec
<ast
::Ident
>,
242 tested_submods
: Vec
<(ast
::Ident
, ast
::Ident
)>) -> (P
<ast
::Item
>, ast
::Ident
) {
243 let super_
= token
::str_to_ident("super");
245 let items
= tests
.into_iter().map(|r
| {
246 cx
.ext_cx
.item_use_simple(DUMMY_SP
, ast
::Visibility
::Public
,
247 cx
.ext_cx
.path(DUMMY_SP
, vec
![super_
, r
]))
248 }).chain(tested_submods
.into_iter().map(|(r
, sym
)| {
249 let path
= cx
.ext_cx
.path(DUMMY_SP
, vec
![super_
, r
, sym
]);
250 cx
.ext_cx
.item_use_simple_(DUMMY_SP
, ast
::Visibility
::Public
, r
, path
)
253 let reexport_mod
= ast
::Mod
{
255 items
: items
.collect(),
258 let sym
= token
::gensym_ident("__test_reexports");
259 let it
= P(ast
::Item
{
262 id
: ast
::DUMMY_NODE_ID
,
263 node
: ast
::ItemKind
::Mod(reexport_mod
),
264 vis
: ast
::Visibility
::Public
,
271 fn generate_test_harness(sess
: &ParseSess
,
272 reexport_test_harness_main
: Option
<InternedString
>,
274 cfg
: &ast
::CrateConfig
,
275 sd
: &errors
::Handler
) -> ast
::Crate
{
276 // Remove the entry points
277 let mut cleaner
= EntryPointCleaner { depth: 0 }
;
278 let krate
= cleaner
.fold_crate(krate
);
280 let mut feature_gated_cfgs
= vec
![];
281 let mut cx
: TestCtxt
= TestCtxt
{
284 ext_cx
: ExtCtxt
::new(sess
, cfg
.clone(),
285 ExpansionConfig
::default("test".to_string()),
286 &mut feature_gated_cfgs
),
289 reexport_test_harness_main
: reexport_test_harness_main
,
290 is_test_crate
: is_test_crate(&krate
),
291 config
: krate
.config
.clone(),
292 toplevel_reexport
: None
,
294 cx
.ext_cx
.crate_root
= Some("std");
296 cx
.ext_cx
.bt_push(ExpnInfo
{
298 callee
: NameAndSpan
{
299 format
: MacroAttribute(intern("test")),
301 allow_internal_unstable
: false,
305 let mut fold
= TestHarnessGenerator
{
308 tested_submods
: Vec
::new(),
310 let res
= fold
.fold_crate(krate
);
311 fold
.cx
.ext_cx
.bt_pop();
315 fn strip_test_functions(diagnostic
: &errors
::Handler
, krate
: ast
::Crate
)
317 // When not compiling with --test we should not compile the
319 config
::strip_items(diagnostic
, krate
, |attrs
| {
320 !attr
::contains_name(&attrs
[..], "test") &&
321 !attr
::contains_name(&attrs
[..], "bench")
325 /// Craft a span that will be ignored by the stability lint's
326 /// call to codemap's is_internal check.
327 /// The expanded code calls some unstable functions in the test crate.
328 fn ignored_span(cx
: &TestCtxt
, sp
: Span
) -> Span
{
329 let info
= ExpnInfo
{
331 callee
: NameAndSpan
{
332 format
: MacroAttribute(intern("test")),
334 allow_internal_unstable
: true,
337 let expn_id
= cx
.sess
.codemap().record_expansion(info
);
339 sp
.expn_id
= expn_id
;
344 enum HasTestSignature
{
350 fn is_test_fn(cx
: &TestCtxt
, i
: &ast
::Item
) -> bool
{
351 let has_test_attr
= attr
::contains_name(&i
.attrs
, "test");
353 fn has_test_signature(i
: &ast
::Item
) -> HasTestSignature
{
355 ast
::ItemKind
::Fn(ref decl
, _
, _
, _
, ref generics
, _
) => {
356 let no_output
= match decl
.output
{
357 ast
::FunctionRetTy
::Default(..) => true,
358 ast
::FunctionRetTy
::Ty(ref t
) if t
.node
== ast
::TyKind
::Tup(vec
![]) => true,
361 if decl
.inputs
.is_empty()
363 && !generics
.is_parameterized() {
369 _
=> NotEvenAFunction
,
374 let diag
= cx
.span_diagnostic
;
375 match has_test_signature(i
) {
377 No
=> diag
.span_err(i
.span
, "functions used as tests must have signature fn() -> ()"),
378 NotEvenAFunction
=> diag
.span_err(i
.span
,
379 "only functions may be used as tests"),
383 return has_test_attr
&& has_test_signature(i
) == Yes
;
386 fn is_bench_fn(cx
: &TestCtxt
, i
: &ast
::Item
) -> bool
{
387 let has_bench_attr
= attr
::contains_name(&i
.attrs
, "bench");
389 fn has_test_signature(i
: &ast
::Item
) -> bool
{
391 ast
::ItemKind
::Fn(ref decl
, _
, _
, _
, ref generics
, _
) => {
392 let input_cnt
= decl
.inputs
.len();
393 let no_output
= match decl
.output
{
394 ast
::FunctionRetTy
::Default(..) => true,
395 ast
::FunctionRetTy
::Ty(ref t
) if t
.node
== ast
::TyKind
::Tup(vec
![]) => true,
398 let tparm_cnt
= generics
.ty_params
.len();
399 // NB: inadequate check, but we're running
400 // well before resolve, can't get too deep.
402 && no_output
&& tparm_cnt
== 0
408 if has_bench_attr
&& !has_test_signature(i
) {
409 let diag
= cx
.span_diagnostic
;
410 diag
.span_err(i
.span
, "functions used as benches must have signature \
411 `fn(&mut Bencher) -> ()`");
414 return has_bench_attr
&& has_test_signature(i
);
417 fn is_ignored(i
: &ast
::Item
) -> bool
{
418 i
.attrs
.iter().any(|attr
| attr
.check_name("ignore"))
421 fn should_panic(i
: &ast
::Item
) -> ShouldPanic
{
422 match i
.attrs
.iter().find(|attr
| attr
.check_name("should_panic")) {
424 let msg
= attr
.meta_item_list()
425 .and_then(|list
| list
.iter().find(|mi
| mi
.check_name("expected")))
426 .and_then(|mi
| mi
.value_str());
427 ShouldPanic
::Yes(msg
)
429 None
=> ShouldPanic
::No
,
435 We're going to be building a module that looks more or less like:
438 extern crate test (name = "test", vers = "...");
440 test::test_main_static(&::os::args()[], tests)
443 static tests : &'static [test::TestDescAndFn] = &[
444 ... the list of tests in the crate ...
450 fn mk_std(cx
: &TestCtxt
) -> P
<ast
::Item
> {
451 let id_test
= token
::str_to_ident("test");
452 let (vi
, vis
, ident
) = if cx
.is_test_crate
{
454 P(nospan(ast
::ViewPathSimple(id_test
,
455 path_node(vec
!(id_test
)))))),
456 ast
::Visibility
::Public
, token
::special_idents
::invalid
)
458 (ast
::ItemKind
::ExternCrate(None
), ast
::Visibility
::Inherited
, id_test
)
461 id
: ast
::DUMMY_NODE_ID
,
470 fn mk_main(cx
: &mut TestCtxt
) -> P
<ast
::Item
> {
471 // Writing this out by hand with 'ignored_span':
474 // use std::slice::AsSlice;
475 // test::test_main_static(::std::os::args().as_slice(), TESTS);
478 let sp
= ignored_span(cx
, DUMMY_SP
);
479 let ecx
= &cx
.ext_cx
;
481 // test::test_main_static
482 let test_main_path
= ecx
.path(sp
, vec
![token
::str_to_ident("test"),
483 token
::str_to_ident("test_main_static")]);
484 // test::test_main_static(...)
485 let test_main_path_expr
= ecx
.expr_path(test_main_path
);
486 let tests_ident_expr
= ecx
.expr_ident(sp
, token
::str_to_ident("TESTS"));
487 let call_test_main
= ecx
.expr_call(sp
, test_main_path_expr
,
488 vec
![tests_ident_expr
]);
489 let call_test_main
= ecx
.stmt_expr(call_test_main
);
491 let main_meta
= ecx
.meta_word(sp
, token
::intern_and_get_ident("main"));
492 let main_attr
= ecx
.attribute(sp
, main_meta
);
493 // pub fn main() { ... }
494 let main_ret_ty
= ecx
.ty(sp
, ast
::TyKind
::Tup(vec
![]));
495 let main_body
= ecx
.block_all(sp
, vec
![call_test_main
], None
);
496 let main
= ast
::ItemKind
::Fn(ecx
.fn_decl(vec
![], main_ret_ty
),
497 ast
::Unsafety
::Normal
,
498 ast
::Constness
::NotConst
,
499 ::abi
::Abi
::Rust
, ast
::Generics
::default(), main_body
);
500 let main
= P(ast
::Item
{
501 ident
: token
::str_to_ident("main"),
502 attrs
: vec
![main_attr
],
503 id
: ast
::DUMMY_NODE_ID
,
505 vis
: ast
::Visibility
::Public
,
512 fn mk_test_module(cx
: &mut TestCtxt
) -> (P
<ast
::Item
>, Option
<P
<ast
::Item
>>) {
513 // Link to test crate
514 let import
= mk_std(cx
);
516 // A constant vector of test descriptors.
517 let tests
= mk_tests(cx
);
519 // The synthesized main function which will call the console test runner
520 // with our list of tests
521 let mainfn
= mk_main(cx
);
523 let testmod
= ast
::Mod
{
525 items
: vec
![import
, mainfn
, tests
],
527 let item_
= ast
::ItemKind
::Mod(testmod
);
529 let mod_ident
= token
::gensym_ident("__test");
530 let item
= P(ast
::Item
{
531 id
: ast
::DUMMY_NODE_ID
,
535 vis
: ast
::Visibility
::Public
,
538 let reexport
= cx
.reexport_test_harness_main
.as_ref().map(|s
| {
539 // building `use <ident> = __test::main`
540 let reexport_ident
= token
::str_to_ident(&s
);
543 nospan(ast
::ViewPathSimple(reexport_ident
,
544 path_node(vec
![mod_ident
, token
::str_to_ident("main")])));
547 id
: ast
::DUMMY_NODE_ID
,
548 ident
: token
::special_idents
::invalid
,
550 node
: ast
::ItemKind
::Use(P(use_path
)),
551 vis
: ast
::Visibility
::Inherited
,
556 debug
!("Synthetic test module:\n{}\n", pprust
::item_to_string(&item
));
561 fn nospan
<T
>(t
: T
) -> codemap
::Spanned
<T
> {
562 codemap
::Spanned { node: t, span: DUMMY_SP }
565 fn path_node(ids
: Vec
<ast
::Ident
> ) -> ast
::Path
{
569 segments
: ids
.into_iter().map(|identifier
| ast
::PathSegment
{
570 identifier
: identifier
,
571 parameters
: ast
::PathParameters
::none(),
576 fn path_name_i(idents
: &[ast
::Ident
]) -> String
{
577 // FIXME: Bad copies (#2543 -- same for everything else that says "bad")
578 idents
.iter().map(|i
| i
.to_string()).collect
::<Vec
<String
>>().join("::")
581 fn mk_tests(cx
: &TestCtxt
) -> P
<ast
::Item
> {
582 // The vector of test_descs for this crate
583 let test_descs
= mk_test_descs(cx
);
585 // FIXME #15962: should be using quote_item, but that stringifies
586 // __test_reexports, causing it to be reinterned, losing the
587 // gensym information.
589 let ecx
= &cx
.ext_cx
;
590 let struct_type
= ecx
.ty_path(ecx
.path(sp
, vec
![ecx
.ident_of("self"),
591 ecx
.ident_of("test"),
592 ecx
.ident_of("TestDescAndFn")]));
593 let static_lt
= ecx
.lifetime(sp
, token
::special_idents
::static_lifetime
.name
);
594 // &'static [self::test::TestDescAndFn]
595 let static_type
= ecx
.ty_rptr(sp
,
596 ecx
.ty(sp
, ast
::TyKind
::Vec(struct_type
)),
598 ast
::Mutability
::Immutable
);
599 // static TESTS: $static_type = &[...];
601 ecx
.ident_of("TESTS"),
606 fn is_test_crate(krate
: &ast
::Crate
) -> bool
{
607 match attr
::find_crate_name(&krate
.attrs
) {
608 Some(ref s
) if "test" == &s
[..] => true,
613 fn mk_test_descs(cx
: &TestCtxt
) -> P
<ast
::Expr
> {
614 debug
!("building test vector from {} tests", cx
.testfns
.len());
617 id
: ast
::DUMMY_NODE_ID
,
618 node
: ast
::ExprKind
::AddrOf(ast
::Mutability
::Immutable
,
620 id
: ast
::DUMMY_NODE_ID
,
621 node
: ast
::ExprKind
::Vec(cx
.testfns
.iter().map(|test
| {
622 mk_test_desc_and_fn_rec(cx
, test
)
632 fn mk_test_desc_and_fn_rec(cx
: &TestCtxt
, test
: &Test
) -> P
<ast
::Expr
> {
633 // FIXME #15962: should be using quote_expr, but that stringifies
634 // __test_reexports, causing it to be reinterned, losing the
635 // gensym information.
637 let span
= ignored_span(cx
, test
.span
);
638 let path
= test
.path
.clone();
639 let ecx
= &cx
.ext_cx
;
640 let self_id
= ecx
.ident_of("self");
641 let test_id
= ecx
.ident_of("test");
643 // creates self::test::$name
644 let test_path
= |name
| {
645 ecx
.path(span
, vec
![self_id
, test_id
, ecx
.ident_of(name
)])
647 // creates $name: $expr
648 let field
= |name
, expr
| ecx
.field_imm(span
, ecx
.ident_of(name
), expr
);
650 debug
!("encoding {}", path_name_i(&path
[..]));
652 // path to the #[test] function: "foo::bar::baz"
653 let path_string
= path_name_i(&path
[..]);
654 let name_expr
= ecx
.expr_str(span
, token
::intern_and_get_ident(&path_string
[..]));
656 // self::test::StaticTestName($name_expr)
657 let name_expr
= ecx
.expr_call(span
,
658 ecx
.expr_path(test_path("StaticTestName")),
661 let ignore_expr
= ecx
.expr_bool(span
, test
.ignore
);
662 let should_panic_path
= |name
| {
663 ecx
.path(span
, vec
![self_id
, test_id
, ecx
.ident_of("ShouldPanic"), ecx
.ident_of(name
)])
665 let fail_expr
= match test
.should_panic
{
666 ShouldPanic
::No
=> ecx
.expr_path(should_panic_path("No")),
667 ShouldPanic
::Yes(ref msg
) => {
670 let msg
= ecx
.expr_str(span
, msg
.clone());
671 let path
= should_panic_path("YesWithMessage");
672 ecx
.expr_call(span
, ecx
.expr_path(path
), vec
![msg
])
674 None
=> ecx
.expr_path(should_panic_path("Yes")),
679 // self::test::TestDesc { ... }
680 let desc_expr
= ecx
.expr_struct(
682 test_path("TestDesc"),
683 vec
![field("name", name_expr
),
684 field("ignore", ignore_expr
),
685 field("should_panic", fail_expr
)]);
688 let mut visible_path
= match cx
.toplevel_reexport
{
689 Some(id
) => vec
![id
],
691 let diag
= cx
.span_diagnostic
;
692 diag
.bug("expected to find top-level re-export name, but found None");
695 visible_path
.extend(path
);
697 let fn_expr
= ecx
.expr_path(ecx
.path_global(span
, visible_path
));
699 let variant_name
= if test
.bench { "StaticBenchFn" }
else { "StaticTestFn" }
;
700 // self::test::$variant_name($fn_expr)
701 let testfn_expr
= ecx
.expr_call(span
, ecx
.expr_path(test_path(variant_name
)), vec
![fn_expr
]);
703 // self::test::TestDescAndFn { ... }
704 ecx
.expr_struct(span
,
705 test_path("TestDescAndFn"),
706 vec
![field("desc", desc_expr
),
707 field("testfn", testfn_expr
)])