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