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)]
16 use self::HasTestSignature
::*;
22 use attr
::{self, HasAttrs}
;
23 use syntax_pos
::{self, DUMMY_SP, NO_EXPANSION, Span, FileMap, BytePos}
;
26 use codemap
::{self, CodeMap, ExpnInfo, NameAndSpan, MacroAttribute, dummy_spanned}
;
28 use errors
::snippet
::{SnippetData}
;
30 use entry
::{self, EntryPointType}
;
31 use ext
::base
::{ExtCtxt, Resolver}
;
32 use ext
::build
::AstBuilder
;
33 use ext
::expand
::ExpansionConfig
;
34 use ext
::hygiene
::{Mark, SyntaxContext}
;
36 use util
::move_map
::MoveMap
;
38 use parse
::{token, ParseSess}
;
40 use ast
::{self, Ident}
;
42 use symbol
::{self, Symbol, keywords}
;
43 use util
::small_vector
::SmallVector
;
55 should_panic
: ShouldPanic
60 span_diagnostic
: &'a errors
::Handler
,
64 reexport_test_harness_main
: Option
<Symbol
>,
68 // top-level re-export submodule, filled out after folding is finished
69 toplevel_reexport
: Option
<Ident
>,
72 // Traverse the crate, collecting all the test functions, eliding any
73 // existing main functions, and synthesizing a main test harness
74 pub fn modify_for_testing(sess
: &ParseSess
,
75 resolver
: &mut Resolver
,
78 span_diagnostic
: &errors
::Handler
) -> ast
::Crate
{
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
, resolver
, reexport_test_harness_main
, krate
, span_diagnostic
)
94 struct TestHarnessGenerator
<'a
> {
98 // submodule name, gensym'd identifier for re-exports
99 tested_submods
: Vec
<(Ident
, 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
);
109 if let Some(re
) = reexport
{
110 folded
.module
.items
.push(re
)
112 folded
.module
.items
.push(mod_
);
116 fn fold_item(&mut self, i
: P
<ast
::Item
>) -> SmallVector
<P
<ast
::Item
>> {
118 if ident
.name
!= keywords
::Invalid
.name() {
119 self.cx
.path
.push(ident
);
121 debug
!("current path: {}", path_name_i(&self.cx
.path
));
123 if is_test_fn(&self.cx
, &i
) || is_bench_fn(&self.cx
, &i
) {
125 ast
::ItemKind
::Fn(_
, ast
::Unsafety
::Unsafe
, _
, _
, _
, _
) => {
126 let diag
= self.cx
.span_diagnostic
;
127 panic
!(diag
.span_fatal(i
.span
, "unsafe functions cannot be used for tests"));
130 debug
!("this is a test function");
133 path
: self.cx
.path
.clone(),
134 bench
: is_bench_fn(&self.cx
, &i
),
135 ignore
: is_ignored(&i
),
136 should_panic
: should_panic(&i
, &self.cx
)
138 self.cx
.testfns
.push(test
);
139 self.tests
.push(i
.ident
);
144 let mut item
= i
.unwrap();
145 // We don't want to recurse into anything other than mods, since
146 // mods or tests inside of functions will break things
147 if let ast
::ItemKind
::Mod(module
) = item
.node
{
148 let tests
= mem
::replace(&mut self.tests
, Vec
::new());
149 let tested_submods
= mem
::replace(&mut self.tested_submods
, Vec
::new());
150 let mut mod_folded
= fold
::noop_fold_mod(module
, self);
151 let tests
= mem
::replace(&mut self.tests
, tests
);
152 let tested_submods
= mem
::replace(&mut self.tested_submods
, tested_submods
);
154 if !tests
.is_empty() || !tested_submods
.is_empty() {
155 let (it
, sym
) = mk_reexport_mod(&mut self.cx
, item
.id
, tests
, tested_submods
);
156 mod_folded
.items
.push(it
);
158 if !self.cx
.path
.is_empty() {
159 self.tested_submods
.push((self.cx
.path
[self.cx
.path
.len()-1], sym
));
161 debug
!("pushing nothing, sym: {:?}", sym
);
162 self.cx
.toplevel_reexport
= Some(sym
);
165 item
.node
= ast
::ItemKind
::Mod(mod_folded
);
167 if ident
.name
!= keywords
::Invalid
.name() {
170 SmallVector
::one(P(item
))
173 fn fold_mac(&mut self, mac
: ast
::Mac
) -> ast
::Mac { mac }
176 struct EntryPointCleaner
{
177 // Current depth in the ast
181 impl fold
::Folder
for EntryPointCleaner
{
182 fn fold_item(&mut self, i
: P
<ast
::Item
>) -> SmallVector
<P
<ast
::Item
>> {
184 let folded
= fold
::noop_fold_item(i
, self).expect_one("noop did something");
187 // Remove any #[main] or #[start] from the AST so it doesn't
188 // clash with the one we're going to add, but mark it as
189 // #[allow(dead_code)] to avoid printing warnings.
190 let folded
= match entry
::entry_point_type(&folded
, self.depth
) {
191 EntryPointType
::MainNamed
|
192 EntryPointType
::MainAttr
|
193 EntryPointType
::Start
=>
194 folded
.map(|ast
::Item {id, ident, attrs, node, vis, span}
| {
195 let allow_str
= Symbol
::intern("allow");
196 let dead_code_str
= Symbol
::intern("dead_code");
197 let word_vec
= vec
![attr
::mk_list_word_item(dead_code_str
)];
198 let allow_dead_code_item
= attr
::mk_list_item(allow_str
, word_vec
);
199 let allow_dead_code
= attr
::mk_attr_outer(DUMMY_SP
,
201 allow_dead_code_item
);
206 attrs
: attrs
.into_iter()
208 !attr
.check_name("main") && !attr
.check_name("start")
210 .chain(iter
::once(allow_dead_code
))
217 EntryPointType
::None
|
218 EntryPointType
::OtherMain
=> folded
,
221 SmallVector
::one(folded
)
224 fn fold_mac(&mut self, mac
: ast
::Mac
) -> ast
::Mac { mac }
227 fn mk_reexport_mod(cx
: &mut TestCtxt
,
230 tested_submods
: Vec
<(Ident
, Ident
)>)
231 -> (P
<ast
::Item
>, Ident
) {
232 let super_
= Ident
::from_str("super");
234 let items
= tests
.into_iter().map(|r
| {
235 cx
.ext_cx
.item_use_simple(DUMMY_SP
, ast
::Visibility
::Public
,
236 cx
.ext_cx
.path(DUMMY_SP
, vec
![super_
, r
]))
237 }).chain(tested_submods
.into_iter().map(|(r
, sym
)| {
238 let path
= cx
.ext_cx
.path(DUMMY_SP
, vec
![super_
, r
, sym
]);
239 cx
.ext_cx
.item_use_simple_(DUMMY_SP
, ast
::Visibility
::Public
, r
, path
)
242 let reexport_mod
= ast
::Mod
{
247 let sym
= Ident
::with_empty_ctxt(Symbol
::gensym("__test_reexports"));
248 let parent
= if parent
== ast
::DUMMY_NODE_ID { ast::CRATE_NODE_ID }
else { parent }
;
249 cx
.ext_cx
.current_expansion
.mark
= cx
.ext_cx
.resolver
.get_module_scope(parent
);
250 let it
= cx
.ext_cx
.monotonic_expander().fold_item(P(ast
::Item
{
253 id
: ast
::DUMMY_NODE_ID
,
254 node
: ast
::ItemKind
::Mod(reexport_mod
),
255 vis
: ast
::Visibility
::Public
,
262 fn generate_test_harness(sess
: &ParseSess
,
263 resolver
: &mut Resolver
,
264 reexport_test_harness_main
: Option
<Symbol
>,
266 sd
: &errors
::Handler
) -> ast
::Crate
{
267 // Remove the entry points
268 let mut cleaner
= EntryPointCleaner { depth: 0 }
;
269 let krate
= cleaner
.fold_crate(krate
);
271 let mark
= Mark
::fresh(Mark
::root());
272 let mut cx
: TestCtxt
= TestCtxt
{
275 ext_cx
: ExtCtxt
::new(sess
, ExpansionConfig
::default("test".to_string()), resolver
),
278 reexport_test_harness_main
: reexport_test_harness_main
,
279 is_test_crate
: is_test_crate(&krate
),
280 toplevel_reexport
: None
,
281 ctxt
: SyntaxContext
::empty().apply_mark(mark
),
283 cx
.ext_cx
.crate_root
= Some("std");
285 mark
.set_expn_info(ExpnInfo
{
287 callee
: NameAndSpan
{
288 format
: MacroAttribute(Symbol
::intern("test")),
290 allow_internal_unstable
: true,
294 TestHarnessGenerator
{
297 tested_submods
: Vec
::new(),
301 /// Craft a span that will be ignored by the stability lint's
302 /// call to codemap's `is_internal` check.
303 /// The expanded code calls some unstable functions in the test crate.
304 fn ignored_span(cx
: &TestCtxt
, sp
: Span
) -> Span
{
305 Span { ctxt: cx.ctxt, ..sp }
309 enum HasTestSignature
{
315 fn is_test_fn(cx
: &TestCtxt
, i
: &ast
::Item
) -> bool
{
316 let has_test_attr
= attr
::contains_name(&i
.attrs
, "test");
318 fn has_test_signature(i
: &ast
::Item
) -> HasTestSignature
{
320 ast
::ItemKind
::Fn(ref decl
, _
, _
, _
, ref generics
, _
) => {
321 let no_output
= match decl
.output
{
322 ast
::FunctionRetTy
::Default(..) => true,
323 ast
::FunctionRetTy
::Ty(ref t
) if t
.node
== ast
::TyKind
::Tup(vec
![]) => true,
326 if decl
.inputs
.is_empty()
328 && !generics
.is_parameterized() {
334 _
=> NotEvenAFunction
,
339 let diag
= cx
.span_diagnostic
;
340 match has_test_signature(i
) {
342 No
=> diag
.span_err(i
.span
, "functions used as tests must have signature fn() -> ()"),
343 NotEvenAFunction
=> diag
.span_err(i
.span
,
344 "only functions may be used as tests"),
348 has_test_attr
&& has_test_signature(i
) == Yes
351 fn is_bench_fn(cx
: &TestCtxt
, i
: &ast
::Item
) -> bool
{
352 let has_bench_attr
= attr
::contains_name(&i
.attrs
, "bench");
354 fn has_test_signature(i
: &ast
::Item
) -> bool
{
356 ast
::ItemKind
::Fn(ref decl
, _
, _
, _
, ref generics
, _
) => {
357 let input_cnt
= decl
.inputs
.len();
358 let no_output
= match decl
.output
{
359 ast
::FunctionRetTy
::Default(..) => true,
360 ast
::FunctionRetTy
::Ty(ref t
) if t
.node
== ast
::TyKind
::Tup(vec
![]) => true,
363 let tparm_cnt
= generics
.ty_params
.len();
364 // NB: inadequate check, but we're running
365 // well before resolve, can't get too deep.
367 && no_output
&& tparm_cnt
== 0
373 if has_bench_attr
&& !has_test_signature(i
) {
374 let diag
= cx
.span_diagnostic
;
375 diag
.span_err(i
.span
, "functions used as benches must have signature \
376 `fn(&mut Bencher) -> ()`");
379 has_bench_attr
&& has_test_signature(i
)
382 fn is_ignored(i
: &ast
::Item
) -> bool
{
383 i
.attrs
.iter().any(|attr
| attr
.check_name("ignore"))
386 fn should_panic(i
: &ast
::Item
, cx
: &TestCtxt
) -> ShouldPanic
{
387 match i
.attrs
.iter().find(|attr
| attr
.check_name("should_panic")) {
389 let sd
= cx
.span_diagnostic
;
390 if attr
.is_value_str() {
393 "attribute must be of the form: \
394 `#[should_panic]` or \
395 `#[should_panic(expected = \"error message\")]`"
396 ).note("Errors in this attribute were erroneously allowed \
397 and will become a hard error in a future release.")
399 return ShouldPanic
::Yes(None
);
401 match attr
.meta_item_list() {
402 // Handle #[should_panic]
403 None
=> ShouldPanic
::Yes(None
),
404 // Handle #[should_panic(expected = "foo")]
406 let msg
= list
.iter()
407 .find(|mi
| mi
.check_name("expected"))
408 .and_then(|mi
| mi
.meta_item())
409 .and_then(|mi
| mi
.value_str());
410 if list
.len() != 1 || msg
.is_none() {
413 "argument must be of the form: \
414 `expected = \"error message\"`"
415 ).note("Errors in this attribute were erroneously \
416 allowed and will become a hard error in a \
417 future release.").emit();
418 ShouldPanic
::Yes(None
)
420 ShouldPanic
::Yes(msg
)
425 None
=> ShouldPanic
::No
,
431 We're going to be building a module that looks more or less like:
434 extern crate test (name = "test", vers = "...");
436 test::test_main_static(&::os::args()[], tests, test::Options::new())
439 static tests : &'static [test::TestDescAndFn] = &[
440 ... the list of tests in the crate ...
446 fn mk_std(cx
: &TestCtxt
) -> P
<ast
::Item
> {
447 let id_test
= Ident
::from_str("test");
448 let sp
= ignored_span(cx
, DUMMY_SP
);
449 let (vi
, vis
, ident
) = if cx
.is_test_crate
{
451 P(nospan(ast
::ViewPathSimple(id_test
,
452 path_node(vec
![id_test
]))))),
453 ast
::Visibility
::Public
, keywords
::Invalid
.ident())
455 (ast
::ItemKind
::ExternCrate(None
), ast
::Visibility
::Inherited
, id_test
)
458 id
: ast
::DUMMY_NODE_ID
,
467 fn mk_main(cx
: &mut TestCtxt
) -> P
<ast
::Item
> {
468 // Writing this out by hand with 'ignored_span':
471 // use std::slice::AsSlice;
472 // test::test_main_static(::std::os::args().as_slice(), TESTS, test::Options::new());
475 let sp
= ignored_span(cx
, DUMMY_SP
);
476 let ecx
= &cx
.ext_cx
;
478 // test::test_main_static
480 ecx
.path(sp
, vec
![Ident
::from_str("test"), Ident
::from_str("test_main_static")]);
482 // test::test_main_static(...)
483 let test_main_path_expr
= ecx
.expr_path(test_main_path
);
484 let tests_ident_expr
= ecx
.expr_ident(sp
, Ident
::from_str("TESTS"));
485 let call_test_main
= ecx
.expr_call(sp
, test_main_path_expr
,
486 vec
![tests_ident_expr
]);
487 let call_test_main
= ecx
.stmt_expr(call_test_main
);
489 let main_meta
= ecx
.meta_word(sp
, Symbol
::intern("main"));
490 let main_attr
= ecx
.attribute(sp
, main_meta
);
491 // pub fn main() { ... }
492 let main_ret_ty
= ecx
.ty(sp
, ast
::TyKind
::Tup(vec
![]));
493 let main_body
= ecx
.block(sp
, vec
![call_test_main
]);
494 let main
= ast
::ItemKind
::Fn(ecx
.fn_decl(vec
![], main_ret_ty
),
495 ast
::Unsafety
::Normal
,
496 dummy_spanned(ast
::Constness
::NotConst
),
497 ::abi
::Abi
::Rust
, ast
::Generics
::default(), main_body
);
499 ident
: Ident
::from_str("main"),
500 attrs
: vec
![main_attr
],
501 id
: ast
::DUMMY_NODE_ID
,
503 vis
: ast
::Visibility
::Public
,
508 fn mk_test_module(cx
: &mut TestCtxt
) -> (P
<ast
::Item
>, Option
<P
<ast
::Item
>>) {
509 // Link to test crate
510 let import
= mk_std(cx
);
512 // A constant vector of test descriptors.
513 let tests
= mk_tests(cx
);
515 // The synthesized main function which will call the console test runner
516 // with our list of tests
517 let mainfn
= mk_main(cx
);
519 let testmod
= ast
::Mod
{
521 items
: vec
![import
, mainfn
, tests
],
523 let item_
= ast
::ItemKind
::Mod(testmod
);
524 let mod_ident
= Ident
::with_empty_ctxt(Symbol
::gensym("__test"));
526 let mut expander
= cx
.ext_cx
.monotonic_expander();
527 let item
= expander
.fold_item(P(ast
::Item
{
528 id
: ast
::DUMMY_NODE_ID
,
532 vis
: ast
::Visibility
::Public
,
535 let reexport
= cx
.reexport_test_harness_main
.map(|s
| {
536 // building `use <ident> = __test::main`
537 let reexport_ident
= Ident
::with_empty_ctxt(s
);
540 nospan(ast
::ViewPathSimple(reexport_ident
,
541 path_node(vec
![mod_ident
, Ident
::from_str("main")])));
543 expander
.fold_item(P(ast
::Item
{
544 id
: ast
::DUMMY_NODE_ID
,
545 ident
: keywords
::Invalid
.ident(),
547 node
: ast
::ItemKind
::Use(P(use_path
)),
548 vis
: ast
::Visibility
::Inherited
,
553 debug
!("Synthetic test module:\n{}\n", pprust
::item_to_string(&item
));
558 fn nospan
<T
>(t
: T
) -> codemap
::Spanned
<T
> {
559 codemap
::Spanned { node: t, span: DUMMY_SP }
562 fn path_node(ids
: Vec
<Ident
>) -> ast
::Path
{
565 segments
: ids
.into_iter().map(|id
| ast
::PathSegment
::from_ident(id
, DUMMY_SP
)).collect(),
569 fn path_name_i(idents
: &[Ident
]) -> String
{
570 // FIXME: Bad copies (#2543 -- same for everything else that says "bad")
571 idents
.iter().map(|i
| i
.to_string()).collect
::<Vec
<String
>>().join("::")
574 fn mk_tests(cx
: &TestCtxt
) -> P
<ast
::Item
> {
575 // The vector of test_descs for this crate
576 let test_descs
= mk_test_descs(cx
);
578 // FIXME #15962: should be using quote_item, but that stringifies
579 // __test_reexports, causing it to be reinterned, losing the
580 // gensym information.
581 let sp
= ignored_span(cx
, DUMMY_SP
);
582 let ecx
= &cx
.ext_cx
;
583 let struct_type
= ecx
.ty_path(ecx
.path(sp
, vec
![ecx
.ident_of("self"),
584 ecx
.ident_of("test"),
585 ecx
.ident_of("TestDescAndFn")]));
586 let static_lt
= ecx
.lifetime(sp
, keywords
::StaticLifetime
.ident());
587 // &'static [self::test::TestDescAndFn]
588 let static_type
= ecx
.ty_rptr(sp
,
589 ecx
.ty(sp
, ast
::TyKind
::Slice(struct_type
)),
591 ast
::Mutability
::Immutable
);
592 // static TESTS: $static_type = &[...];
594 ecx
.ident_of("TESTS"),
599 fn is_test_crate(krate
: &ast
::Crate
) -> bool
{
600 match attr
::find_crate_name(&krate
.attrs
) {
601 Some(s
) if "test" == s
.as_str() => true,
606 fn mk_test_descs(cx
: &TestCtxt
) -> P
<ast
::Expr
> {
607 debug
!("building test vector from {} tests", cx
.testfns
.len());
610 id
: ast
::DUMMY_NODE_ID
,
611 node
: ast
::ExprKind
::AddrOf(ast
::Mutability
::Immutable
,
613 id
: ast
::DUMMY_NODE_ID
,
614 node
: ast
::ExprKind
::Array(cx
.testfns
.iter().map(|test
| {
615 mk_test_desc_and_fn_rec(cx
, test
)
618 attrs
: ast
::ThinVec
::new(),
621 attrs
: ast
::ThinVec
::new(),
625 fn mk_test_desc_and_fn_rec(cx
: &TestCtxt
, test
: &Test
) -> P
<ast
::Expr
> {
626 // FIXME #15962: should be using quote_expr, but that stringifies
627 // __test_reexports, causing it to be reinterned, losing the
628 // gensym information.
630 let span
= ignored_span(cx
, test
.span
);
631 let path
= test
.path
.clone();
632 let ecx
= &cx
.ext_cx
;
633 let self_id
= ecx
.ident_of("self");
634 let test_id
= ecx
.ident_of("test");
636 // creates self::test::$name
637 let test_path
= |name
| {
638 ecx
.path(span
, vec
![self_id
, test_id
, ecx
.ident_of(name
)])
640 // creates $name: $expr
641 let field
= |name
, expr
| ecx
.field_imm(span
, ecx
.ident_of(name
), expr
);
643 debug
!("encoding {}", path_name_i(&path
[..]));
645 // path to the #[test] function: "foo::bar::baz"
646 let path_string
= path_name_i(&path
[..]);
647 let name_expr
= ecx
.expr_str(span
, Symbol
::intern(&path_string
));
649 // self::test::StaticTestName($name_expr)
650 let name_expr
= ecx
.expr_call(span
,
651 ecx
.expr_path(test_path("StaticTestName")),
654 let ignore_expr
= ecx
.expr_bool(span
, test
.ignore
);
655 let should_panic_path
= |name
| {
656 ecx
.path(span
, vec
![self_id
, test_id
, ecx
.ident_of("ShouldPanic"), ecx
.ident_of(name
)])
658 let fail_expr
= match test
.should_panic
{
659 ShouldPanic
::No
=> ecx
.expr_path(should_panic_path("No")),
660 ShouldPanic
::Yes(msg
) => {
663 let msg
= ecx
.expr_str(span
, msg
);
664 let path
= should_panic_path("YesWithMessage");
665 ecx
.expr_call(span
, ecx
.expr_path(path
), vec
![msg
])
667 None
=> ecx
.expr_path(should_panic_path("Yes")),
672 // self::test::TestDesc { ... }
673 let desc_expr
= ecx
.expr_struct(
675 test_path("TestDesc"),
676 vec
![field("name", name_expr
),
677 field("ignore", ignore_expr
),
678 field("should_panic", fail_expr
)]);
681 let mut visible_path
= match cx
.toplevel_reexport
{
682 Some(id
) => vec
![id
],
684 let diag
= cx
.span_diagnostic
;
685 diag
.bug("expected to find top-level re-export name, but found None");
688 visible_path
.extend(path
);
690 let fn_expr
= ecx
.expr_path(ecx
.path_global(span
, visible_path
));
692 let variant_name
= if test
.bench { "StaticBenchFn" }
else { "StaticTestFn" }
;
693 // self::test::$variant_name($fn_expr)
694 let testfn_expr
= ecx
.expr_call(span
, ecx
.expr_path(test_path(variant_name
)), vec
![fn_expr
]);
696 // self::test::TestDescAndFn { ... }
697 ecx
.expr_struct(span
,
698 test_path("TestDescAndFn"),
699 vec
![field("desc", desc_expr
),
700 field("testfn", testfn_expr
)])