]> git.lizzy.rs Git - rust.git/blob - src/librustc/front/test.rs
02e2a4c8734f8b55635c82e1237cfd95bb462c88
[rust.git] / src / librustc / front / test.rs
1 // Copyright 2012 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 use driver::session;
14 use front::config;
15
16 use syntax::ast_util::*;
17 use syntax::attr;
18 use syntax::codemap::{dummy_sp, span, ExpandedFrom, CallInfo, NameAndSpan};
19 use syntax::codemap;
20 use syntax::ext::base::{mk_ctxt, ext_ctxt};
21 use syntax::fold;
22 use syntax::print::pprust;
23 use syntax::{ast, ast_util};
24
25 type node_id_gen = @fn() -> ast::node_id;
26
27 struct Test {
28     span: span,
29     path: ~[ast::ident],
30     bench: bool,
31     ignore: bool,
32     should_fail: bool
33 }
34
35 struct TestCtxt {
36     sess: session::Session,
37     crate: @ast::crate,
38     path: ~[ast::ident],
39     ext_cx: @ext_ctxt,
40     testfns: ~[Test]
41 }
42
43 // Traverse the crate, collecting all the test functions, eliding any
44 // existing main functions, and synthesizing a main test harness
45 pub fn modify_for_testing(sess: session::Session,
46                           crate: @ast::crate)
47                        -> @ast::crate {
48     // We generate the test harness when building in the 'test'
49     // configuration, either with the '--test' or '--cfg test'
50     // command line options.
51     let should_test = attr::contains(crate.node.config,
52                                      attr::mk_word_item(@~"test"));
53
54     if should_test {
55         generate_test_harness(sess, crate)
56     } else {
57         strip_test_functions(crate)
58     }
59 }
60
61 fn generate_test_harness(sess: session::Session,
62                          crate: @ast::crate)
63                       -> @ast::crate {
64     let cx: @mut TestCtxt = @mut TestCtxt {
65         sess: sess,
66         crate: crate,
67         ext_cx: mk_ctxt(sess.parse_sess, copy sess.opts.cfg),
68         path: ~[],
69         testfns: ~[]
70     };
71
72     cx.ext_cx.bt_push(ExpandedFrom(CallInfo {
73         call_site: dummy_sp(),
74         callee: NameAndSpan {
75             name: ~"test",
76             span: None
77         }
78     }));
79
80     let precursor = @fold::AstFoldFns {
81         fold_crate: fold::wrap(|a,b| fold_crate(cx, a, b) ),
82         fold_item: |a,b| fold_item(cx, a, b),
83         fold_mod: |a,b| fold_mod(cx, a, b),.. *fold::default_ast_fold()};
84
85     let fold = fold::make_fold(precursor);
86     let res = @fold.fold_crate(&*crate);
87     cx.ext_cx.bt_pop();
88     return res;
89 }
90
91 fn strip_test_functions(crate: @ast::crate) -> @ast::crate {
92     // When not compiling with --test we should not compile the
93     // #[test] functions
94     do config::strip_items(crate) |attrs| {
95         !attr::contains_name(attr::attr_metas(attrs), ~"test") &&
96         !attr::contains_name(attr::attr_metas(attrs), ~"bench")
97     }
98 }
99
100 fn fold_mod(cx: @mut TestCtxt,
101             m: &ast::_mod,
102             fld: @fold::ast_fold)
103          -> ast::_mod {
104     // Remove any #[main] from the AST so it doesn't clash with
105     // the one we're going to add. Only if compiling an executable.
106
107     fn nomain(cx: @mut TestCtxt, item: @ast::item) -> @ast::item {
108         if !*cx.sess.building_library {
109             @ast::item{attrs: item.attrs.filtered(|attr| {
110                                *attr::get_attr_name(attr) != ~"main"
111                            }),.. copy *item}
112         } else { item }
113     }
114
115     let mod_nomain = ast::_mod {
116         view_items: /*bad*/copy m.view_items,
117         items: vec::map(m.items, |i| nomain(cx, *i)),
118     };
119
120     fold::noop_fold_mod(&mod_nomain, fld)
121 }
122
123 fn fold_crate(cx: @mut TestCtxt,
124               c: &ast::crate_,
125               fld: @fold::ast_fold)
126            -> ast::crate_ {
127     let folded = fold::noop_fold_crate(c, fld);
128
129     // Add a special __test module to the crate that will contain code
130     // generated for the test harness
131     ast::crate_ {
132         module: add_test_module(cx, &folded.module),
133         .. folded
134     }
135 }
136
137
138 fn fold_item(cx: @mut TestCtxt, i: @ast::item, fld: @fold::ast_fold)
139           -> Option<@ast::item> {
140     cx.path.push(i.ident);
141     debug!("current path: %s",
142            ast_util::path_name_i(copy cx.path, cx.sess.parse_sess.interner));
143
144     if is_test_fn(i) || is_bench_fn(i) {
145         match i.node {
146           ast::item_fn(_, purity, _, _, _) if purity == ast::unsafe_fn => {
147             let sess = cx.sess;
148             sess.span_fatal(
149                 i.span,
150                 ~"unsafe functions cannot be used for tests");
151           }
152           _ => {
153             debug!("this is a test function");
154             let test = Test {
155                 span: i.span,
156                 path: /*bad*/copy cx.path,
157                 bench: is_bench_fn(i),
158                 ignore: is_ignored(cx, i),
159                 should_fail: should_fail(i)
160             };
161             cx.testfns.push(test);
162             // debug!("have %u test/bench functions", cx.testfns.len());
163           }
164         }
165     }
166
167     let res = fold::noop_fold_item(i, fld);
168     cx.path.pop();
169     return res;
170 }
171
172 fn is_test_fn(i: @ast::item) -> bool {
173     let has_test_attr = !attr::find_attrs_by_name(i.attrs,
174                                                   ~"test").is_empty();
175
176     fn has_test_signature(i: @ast::item) -> bool {
177         match &i.node {
178           &ast::item_fn(ref decl, _, _, ref generics, _) => {
179             let no_output = match decl.output.node {
180                 ast::ty_nil => true,
181                 _ => false
182             };
183             decl.inputs.is_empty()
184                 && no_output
185                 && !generics.is_parameterized()
186           }
187           _ => false
188         }
189     }
190
191     return has_test_attr && has_test_signature(i);
192 }
193
194 fn is_bench_fn(i: @ast::item) -> bool {
195     let has_bench_attr =
196         vec::len(attr::find_attrs_by_name(i.attrs, ~"bench")) > 0u;
197
198     fn has_test_signature(i: @ast::item) -> bool {
199         match i.node {
200             ast::item_fn(ref decl, _, _, ref generics, _) => {
201                 let input_cnt = vec::len(decl.inputs);
202                 let no_output = match decl.output.node {
203                     ast::ty_nil => true,
204                     _ => false
205                 };
206                 let tparm_cnt = generics.ty_params.len();
207                 // NB: inadequate check, but we're running
208                 // well before resolve, can't get too deep.
209                 input_cnt == 1u
210                     && no_output && tparm_cnt == 0u
211             }
212           _ => false
213         }
214     }
215
216     return has_bench_attr && has_test_signature(i);
217 }
218
219 fn is_ignored(cx: @mut TestCtxt, i: @ast::item) -> bool {
220     let ignoreattrs = attr::find_attrs_by_name(i.attrs, "ignore");
221     let ignoreitems = attr::attr_metas(ignoreattrs);
222     return if !ignoreitems.is_empty() {
223         let cfg_metas =
224             vec::concat(
225                 vec::filter_map(ignoreitems,
226                                 |i| attr::get_meta_item_list(i)));
227         config::metas_in_cfg(/*bad*/copy cx.crate.node.config, cfg_metas)
228     } else {
229         false
230     }
231 }
232
233 fn should_fail(i: @ast::item) -> bool {
234     vec::len(attr::find_attrs_by_name(i.attrs, ~"should_fail")) > 0u
235 }
236
237 fn add_test_module(cx: &TestCtxt, m: &ast::_mod) -> ast::_mod {
238     let testmod = mk_test_module(cx);
239     ast::_mod {
240         items: vec::append_one(/*bad*/copy m.items, testmod),
241         .. /*bad*/ copy *m
242     }
243 }
244
245 /*
246
247 We're going to be building a module that looks more or less like:
248
249 mod __test {
250   #[!resolve_unexported]
251   extern mod std (name = "std", vers = "...");
252   fn main() {
253     #[main];
254     std::test::test_main_static(::os::args(), tests)
255   }
256
257   static tests : &'static [std::test::TestDescAndFn] = &[
258     ... the list of tests in the crate ...
259   ];
260 }
261
262 */
263
264 fn mk_std(cx: &TestCtxt) -> @ast::view_item {
265     let vers = ast::lit_str(@~"0.7-pre");
266     let vers = nospan(vers);
267     let mi = ast::meta_name_value(@~"vers", vers);
268     let mi = nospan(mi);
269     let id_std = cx.sess.ident_of(~"std");
270     let vi = if is_std(cx) {
271         ast::view_item_use(
272             ~[@nospan(ast::view_path_simple(id_std,
273                                             path_node(~[id_std]),
274                                             cx.sess.next_node_id()))])
275     } else {
276         ast::view_item_extern_mod(id_std, ~[@mi],
277                            cx.sess.next_node_id())
278     };
279     let vi = ast::view_item {
280         node: vi,
281         attrs: ~[],
282         vis: ast::public,
283         span: dummy_sp()
284     };
285     return @vi;
286 }
287
288 fn mk_test_module(cx: &TestCtxt) -> @ast::item {
289
290     // Link to std
291     let view_items = ~[mk_std(cx)];
292
293     // A constant vector of test descriptors.
294     let tests = mk_tests(cx);
295
296     // The synthesized main function which will call the console test runner
297     // with our list of tests
298     let ext_cx = cx.ext_cx;
299     let mainfn = (quote_item!(
300         pub fn main() {
301             #[main];
302             std::test::test_main_static(::os::args(), tests);
303         }
304     )).get();
305
306     let testmod = ast::_mod {
307         view_items: view_items,
308         items: ~[mainfn, tests],
309     };
310     let item_ = ast::item_mod(testmod);
311
312     // This attribute tells resolve to let us call unexported functions
313     let resolve_unexported_attr =
314         attr::mk_attr(attr::mk_word_item(@~"!resolve_unexported"));
315
316     let item = ast::item {
317         ident: cx.sess.ident_of(~"__test"),
318         attrs: ~[resolve_unexported_attr],
319         id: cx.sess.next_node_id(),
320         node: item_,
321         vis: ast::public,
322         span: dummy_sp(),
323      };
324
325     debug!("Synthetic test module:\n%s\n",
326            pprust::item_to_str(@copy item, cx.sess.intr()));
327
328     return @item;
329 }
330
331 fn nospan<T:Copy>(t: T) -> codemap::spanned<T> {
332     codemap::spanned { node: t, span: dummy_sp() }
333 }
334
335 fn path_node(ids: ~[ast::ident]) -> @ast::Path {
336     @ast::Path { span: dummy_sp(),
337                 global: false,
338                 idents: ids,
339                 rp: None,
340                 types: ~[] }
341 }
342
343 fn path_node_global(ids: ~[ast::ident]) -> @ast::Path {
344     @ast::Path { span: dummy_sp(),
345                  global: true,
346                  idents: ids,
347                  rp: None,
348                  types: ~[] }
349 }
350
351 fn mk_tests(cx: &TestCtxt) -> @ast::item {
352
353     let ext_cx = cx.ext_cx;
354
355     // The vector of test_descs for this crate
356     let test_descs = mk_test_descs(cx);
357
358     (quote_item!(
359         pub static tests : &'static [self::std::test::TestDescAndFn] =
360             $test_descs
361         ;
362     )).get()
363 }
364
365 fn is_std(cx: &TestCtxt) -> bool {
366     let is_std = {
367         let items = attr::find_linkage_metas(cx.crate.node.attrs);
368         match attr::last_meta_item_value_str_by_name(items, ~"name") {
369           Some(@~"std") => true,
370           _ => false
371         }
372     };
373     return is_std;
374 }
375
376 fn mk_test_descs(cx: &TestCtxt) -> @ast::expr {
377     debug!("building test vector from %u tests", cx.testfns.len());
378     let mut descs = ~[];
379     for cx.testfns.each |test| {
380         descs.push(mk_test_desc_and_fn_rec(cx, test));
381     }
382
383     let sess = cx.sess;
384     let inner_expr = @ast::expr {
385         id: sess.next_node_id(),
386         callee_id: sess.next_node_id(),
387         node: ast::expr_vec(descs, ast::m_imm),
388         span: dummy_sp(),
389     };
390
391     @ast::expr {
392         id: sess.next_node_id(),
393         callee_id: sess.next_node_id(),
394         node: ast::expr_vstore(inner_expr, ast::expr_vstore_slice),
395         span: dummy_sp(),
396     }
397 }
398
399 fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> @ast::expr {
400     let span = test.span;
401     let path = /*bad*/copy test.path;
402
403     let ext_cx = cx.ext_cx;
404
405     debug!("encoding %s", ast_util::path_name_i(path,
406                                                 cx.sess.parse_sess.interner));
407
408     let name_lit: ast::lit =
409         nospan(ast::lit_str(@ast_util::path_name_i(
410             path,
411             cx.sess.parse_sess.interner)));
412
413     let name_expr = @ast::expr {
414           id: cx.sess.next_node_id(),
415           callee_id: cx.sess.next_node_id(),
416           node: ast::expr_lit(@name_lit),
417           span: span
418     };
419
420     let fn_path = path_node_global(path);
421
422     let fn_expr = @ast::expr {
423         id: cx.sess.next_node_id(),
424         callee_id: cx.sess.next_node_id(),
425         node: ast::expr_path(fn_path),
426         span: span,
427     };
428
429     let t_expr = if test.bench {
430         quote_expr!( self::std::test::StaticBenchFn($fn_expr) )
431     } else {
432         quote_expr!( self::std::test::StaticTestFn($fn_expr) )
433     };
434
435     let ignore_expr = if test.ignore {
436         quote_expr!( true )
437     } else {
438         quote_expr!( false )
439     };
440
441     let fail_expr = if test.should_fail {
442         quote_expr!( true )
443     } else {
444         quote_expr!( false )
445     };
446
447     let e = quote_expr!(
448         self::std::test::TestDescAndFn {
449             desc: self::std::test::TestDesc {
450                 name: self::std::test::StaticTestName($name_expr),
451                 ignore: $ignore_expr,
452                 should_fail: $fail_expr
453             },
454             testfn: $t_expr,
455         }
456     );
457     e
458 }
459
460 // Local Variables:
461 // mode: rust
462 // fill-column: 78;
463 // indent-tabs-mode: nil
464 // c-basic-offset: 4
465 // buffer-file-coding-system: utf-8-unix
466 // End: