]> git.lizzy.rs Git - rust.git/blob - src/librustc/front/test.rs
libsyntax: Update view_item_use/import to reflect actual usage
[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 core::prelude::*;
14
15 use driver::session;
16 use front::config;
17 use session::Session;
18
19 use core::dvec::DVec;
20 use core::option;
21 use core::vec;
22 use syntax::ast_util::*;
23 use syntax::attr;
24 use syntax::codemap::{dummy_sp, span, ExpandedFrom};
25 use syntax::codemap;
26 use syntax::fold;
27 use syntax::print::pprust;
28 use syntax::{ast, ast_util};
29 use syntax::attr::attrs_contains_name;
30
31 use syntax::ext::base::{mk_ctxt, ext_ctxt};
32
33 type node_id_gen = fn@() -> ast::node_id;
34
35 type test = {
36     span: span,
37     path: ~[ast::ident],
38     bench: bool,
39     ignore: bool,
40     should_fail: bool
41 };
42
43 struct TestCtxt {
44     sess: session::Session,
45     crate: @ast::crate,
46     path: ~[ast::ident],
47       ext_cx: ext_ctxt,
48     testfns: ~[test]
49 }
50
51 // Traverse the crate, collecting all the test functions, eliding any
52 // existing main functions, and synthesizing a main test harness
53 pub fn modify_for_testing(sess: session::Session,
54                           crate: @ast::crate)
55                        -> @ast::crate {
56     // We generate the test harness when building in the 'test'
57     // configuration, either with the '--test' or '--cfg test'
58     // command line options.
59     let should_test = attr::contains(crate.node.config,
60                                      attr::mk_word_item(~"test"));
61
62     if should_test {
63         generate_test_harness(sess, crate)
64     } else {
65         strip_test_functions(crate)
66     }
67 }
68
69 fn generate_test_harness(sess: session::Session,
70                          crate: @ast::crate)
71                       -> @ast::crate {
72     let cx: @mut TestCtxt = @mut TestCtxt {
73         sess: sess,
74         crate: crate,
75         ext_cx: mk_ctxt(sess.parse_sess, copy sess.opts.cfg),
76         path: ~[],
77         testfns: ~[]
78     };
79
80     cx.ext_cx.bt_push(ExpandedFrom({call_site: dummy_sp(),
81                                     callie: {name: ~"test",
82                                              span: None}}));
83
84     let precursor = @fold::AstFoldFns {
85         fold_crate: fold::wrap(|a,b| fold_crate(cx, a, b) ),
86         fold_item: |a,b| fold_item(cx, a, b),
87         fold_mod: |a,b| fold_mod(cx, a, b),.. *fold::default_ast_fold()};
88
89     let fold = fold::make_fold(precursor);
90     let res = @fold.fold_crate(*crate);
91     cx.ext_cx.bt_pop();
92     return res;
93 }
94
95 fn strip_test_functions(crate: @ast::crate) -> @ast::crate {
96     // When not compiling with --test we should not compile the
97     // #[test] functions
98     do config::strip_items(crate) |attrs| {
99         !attr::contains_name(attr::attr_metas(attrs), ~"test") &&
100         !attr::contains_name(attr::attr_metas(attrs), ~"bench")
101     }
102 }
103
104 fn fold_mod(cx: @mut TestCtxt,
105             m: ast::_mod,
106             fld: fold::ast_fold)
107          -> ast::_mod {
108     // Remove any #[main] from the AST so it doesn't clash with
109     // the one we're going to add. Only if compiling an executable.
110
111     fn nomain(cx: @mut TestCtxt, item: @ast::item) -> @ast::item {
112         if !*cx.sess.building_library {
113             @ast::item{attrs: item.attrs.filtered(|attr| {
114                                attr::get_attr_name(*attr) != ~"main"
115                            }),.. copy *item}
116         } else { item }
117     }
118
119     let mod_nomain = ast::_mod {
120         view_items: /*bad*/copy m.view_items,
121         items: vec::map(m.items, |i| nomain(cx, *i)),
122     };
123
124     fold::noop_fold_mod(mod_nomain, fld)
125 }
126
127 fn fold_crate(cx: @mut TestCtxt,
128               c: ast::crate_,
129               fld: fold::ast_fold)
130            -> ast::crate_ {
131     let folded = fold::noop_fold_crate(c, fld);
132
133     // Add a special __test module to the crate that will contain code
134     // generated for the test harness
135     ast::crate_ { module: add_test_module(cx, /*bad*/copy folded.module),
136                   .. folded }
137 }
138
139
140 fn fold_item(cx: @mut TestCtxt, &&i: @ast::item, fld: fold::ast_fold)
141           -> Option<@ast::item> {
142     cx.path.push(i.ident);
143     debug!("current path: %s",
144            ast_util::path_name_i(cx.path, cx.sess.parse_sess.interner));
145
146     if is_test_fn(i) || is_bench_fn(i) {
147         match i.node {
148           ast::item_fn(_, purity, _, _) if purity == ast::unsafe_fn => {
149             let sess = cx.sess;
150             sess.span_fatal(
151                 i.span,
152                 ~"unsafe functions cannot be used for tests");
153           }
154           _ => {
155             debug!("this is a test function");
156             let test = {span: i.span,
157                         path: /*bad*/copy cx.path,
158                         bench: is_bench_fn(i),
159                         ignore: is_ignored(cx, i),
160                         should_fail: should_fail(i)};
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 tps, _) => {
179             let no_output = match decl.output.node {
180                 ast::ty_nil => true,
181                 _ => false
182             };
183             decl.inputs.is_empty() && no_output && tps.is_empty()
184           }
185           _ => false
186         }
187     }
188
189     return has_test_attr && has_test_signature(i);
190 }
191
192 fn is_bench_fn(i: @ast::item) -> bool {
193     let has_bench_attr =
194         vec::len(attr::find_attrs_by_name(i.attrs, ~"bench")) > 0u;
195
196     fn has_test_signature(i: @ast::item) -> bool {
197         match /*bad*/copy i.node {
198             ast::item_fn(decl, _, tps, _) => {
199                 let input_cnt = vec::len(decl.inputs);
200                 let no_output = match decl.output.node {
201                     ast::ty_nil => true,
202                     _ => false
203                 };
204                 let tparm_cnt = vec::len(tps);
205                 // NB: inadequate check, but we're running
206                 // well before resolve, can't get too deep.
207                 input_cnt == 1u
208                     && no_output && tparm_cnt == 0u
209             }
210           _ => false
211         }
212     }
213
214     return has_bench_attr && has_test_signature(i);
215 }
216
217 fn is_ignored(cx: @mut TestCtxt, i: @ast::item) -> bool {
218     let ignoreattrs = attr::find_attrs_by_name(i.attrs, "ignore");
219     let ignoreitems = attr::attr_metas(ignoreattrs);
220     return if !ignoreitems.is_empty() {
221         let cfg_metas =
222             vec::concat(
223                 vec::filter_map(ignoreitems,
224                                 |i| attr::get_meta_item_list(i)));
225         config::metas_in_cfg(/*bad*/copy cx.crate.node.config, cfg_metas)
226     } else {
227         false
228     }
229 }
230
231 fn should_fail(i: @ast::item) -> bool {
232     vec::len(attr::find_attrs_by_name(i.attrs, ~"should_fail")) > 0u
233 }
234
235 fn add_test_module(cx: &TestCtxt, +m: ast::_mod) -> ast::_mod {
236     let testmod = mk_test_module(cx);
237     ast::_mod {
238         items: vec::append_one(/*bad*/copy m.items, testmod),
239         .. m
240     }
241 }
242
243 /*
244
245 We're going to be building a module that looks more or less like:
246
247 mod __test {
248   #[!resolve_unexported]
249   extern mod std (name = "std", vers = "...");
250   fn main() {
251     #[main];
252     std::test::test_main_static(::os::args(), tests)
253   }
254
255   const tests : &static/[std::test::TestDescAndFn] = &[
256     ... the list of tests in the crate ...
257   ];
258 }
259
260 */
261
262 fn mk_std(cx: &TestCtxt) -> @ast::view_item {
263     let vers = ast::lit_str(@~"0.6");
264     let vers = nospan(vers);
265     let mi = ast::meta_name_value(~"vers", vers);
266     let mi = nospan(mi);
267     let id_std = cx.sess.ident_of(~"std");
268     let vi = if is_std(cx) {
269         ast::view_item_use(
270             ~[@nospan(ast::view_path_simple(id_std,
271                                             path_node(~[id_std]),
272                                             ast::type_value_ns,
273                                             cx.sess.next_node_id()))])
274     } else {
275         ast::view_item_extern_mod(id_std, ~[@mi],
276                            cx.sess.next_node_id())
277     };
278     let vi = ast::view_item {
279         node: vi,
280         attrs: ~[],
281         vis: ast::private,
282         span: dummy_sp()
283     };
284     return @vi;
285 }
286
287 fn mk_test_module(cx: &TestCtxt) -> @ast::item {
288
289     // Link to std
290     let view_items = ~[mk_std(cx)];
291
292     // A constant vector of test descriptors.
293     let tests = mk_tests(cx);
294
295     // The synthesized main function which will call the console test runner
296     // with our list of tests
297     let ext_cx = cx.ext_cx;
298     let mainfn = (quote_item!(
299         pub fn main() {
300             #[main];
301             std::test::test_main_static(::os::args(), tests);
302         }
303     )).get();
304
305     let testmod = ast::_mod {
306         view_items: view_items,
307         items: ~[mainfn, tests],
308     };
309     let item_ = ast::item_mod(testmod);
310
311     // This attribute tells resolve to let us call unexported functions
312     let resolve_unexported_attr =
313         attr::mk_attr(attr::mk_word_item(~"!resolve_unexported"));
314
315     let item = ast::item {
316         ident: cx.sess.ident_of(~"__test"),
317         attrs: ~[resolve_unexported_attr],
318         id: cx.sess.next_node_id(),
319         node: item_,
320         vis: ast::public,
321         span: dummy_sp(),
322      };
323
324     debug!("Synthetic test module:\n%s\n",
325            pprust::item_to_str(@copy item, cx.sess.intr()));
326
327     return @item;
328 }
329
330 fn nospan<T: Copy>(t: T) -> codemap::spanned<T> {
331     codemap::spanned { node: t, span: dummy_sp() }
332 }
333
334 fn path_node(+ids: ~[ast::ident]) -> @ast::path {
335     @ast::path { span: dummy_sp(),
336                 global: false,
337                 idents: ids,
338                 rp: None,
339                 types: ~[] }
340 }
341
342 fn path_node_global(+ids: ~[ast::ident]) -> @ast::path {
343     @ast::path { span: dummy_sp(),
344                  global: true,
345                  idents: ids,
346                  rp: None,
347                  types: ~[] }
348 }
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 const 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: