diff --git a/src/librustc/front/test.rs b/src/librustc/front/test.rs index 0fce75c8369bc..14cda7d62c35d 100644 --- a/src/librustc/front/test.rs +++ b/src/librustc/front/test.rs @@ -51,6 +51,7 @@ struct TestCtxt<'a> { ext_cx: ExtCtxt<'a>, testfns: Vec, reexport_mod_ident: ast::Ident, + reexport_test_harness_main: Option, is_test_crate: bool, config: ast::CrateConfig, } @@ -64,8 +65,16 @@ pub fn modify_for_testing(sess: &Session, // command line options. let should_test = attr::contains_name(krate.config.as_slice(), "test"); + // Check for #[reexport_test_harness_main = "some_name"] which + // creates a `use some_name = __test::main;`. This needs to be + // unconditional, so that the attribute is still marked as used in + // non-test builds. + let reexport_test_harness_main = + attr::first_attr_value_str_by_name(krate.attrs.as_slice(), + "reexport_test_harness_main"); + if should_test { - generate_test_harness(sess, krate) + generate_test_harness(sess, reexport_test_harness_main, krate) } else { strip_test_functions(krate) } @@ -79,14 +88,17 @@ struct TestHarnessGenerator<'a> { impl<'a> fold::Folder for TestHarnessGenerator<'a> { fn fold_crate(&mut self, c: ast::Crate) -> ast::Crate { - let folded = fold::noop_fold_crate(c, self); + let mut folded = fold::noop_fold_crate(c, self); // Add a special __test module to the crate that will contain code // generated for the test harness - ast::Crate { - module: add_test_module(&self.cx, &folded.module), - .. folded + let (mod_, reexport) = mk_test_module(&self.cx, &self.cx.reexport_test_harness_main); + folded.module.items.push(mod_); + match reexport { + Some(re) => folded.module.view_items.push(re), + None => {} } + folded } fn fold_item(&mut self, i: Gc) -> SmallVector> { @@ -196,7 +208,9 @@ fn mk_reexport_mod(cx: &mut TestCtxt, tests: Vec, } } -fn generate_test_harness(sess: &Session, krate: ast::Crate) -> ast::Crate { +fn generate_test_harness(sess: &Session, + reexport_test_harness_main: Option, + krate: ast::Crate) -> ast::Crate { let mut cx: TestCtxt = TestCtxt { sess: sess, ext_cx: ExtCtxt::new(&sess.parse_sess, sess.opts.cfg.clone(), @@ -206,7 +220,8 @@ fn generate_test_harness(sess: &Session, krate: ast::Crate) -> ast::Crate { }), path: Vec::new(), testfns: Vec::new(), - reexport_mod_ident: token::str_to_ident("__test_reexports"), + reexport_mod_ident: token::gensym_ident("__test_reexports"), + reexport_test_harness_main: reexport_test_harness_main, is_test_crate: is_test_crate(&krate), config: krate.config.clone(), }; @@ -314,14 +329,6 @@ fn should_fail(i: Gc) -> bool { attr::contains_name(i.attrs.as_slice(), "should_fail") } -fn add_test_module(cx: &TestCtxt, m: &ast::Mod) -> ast::Mod { - let testmod = mk_test_module(cx); - ast::Mod { - items: m.items.clone().append_one(testmod), - ..(*m).clone() - } -} - /* We're going to be building a module that looks more or less like: @@ -359,7 +366,8 @@ fn mk_std(cx: &TestCtxt) -> ast::ViewItem { } } -fn mk_test_module(cx: &TestCtxt) -> Gc { +fn mk_test_module(cx: &TestCtxt, reexport_test_harness_main: &Option) + -> (Gc, Option) { // Link to test crate let view_items = vec!(mk_std(cx)); @@ -383,18 +391,35 @@ fn mk_test_module(cx: &TestCtxt) -> Gc { }; let item_ = ast::ItemMod(testmod); + let mod_ident = token::gensym_ident("__test"); let item = ast::Item { - ident: token::str_to_ident("__test"), + ident: mod_ident, attrs: Vec::new(), id: ast::DUMMY_NODE_ID, node: item_, vis: ast::Public, span: DUMMY_SP, - }; + }; + let reexport = reexport_test_harness_main.as_ref().map(|s| { + // building `use = __test::main` + let reexport_ident = token::str_to_ident(s.get()); + + let use_path = + nospan(ast::ViewPathSimple(reexport_ident, + path_node(vec![mod_ident, token::str_to_ident("main")]), + ast::DUMMY_NODE_ID)); + + ast::ViewItem { + node: ast::ViewItemUse(box(GC) use_path), + attrs: vec![], + vis: ast::Inherited, + span: DUMMY_SP + } + }); debug!("Synthetic test module:\n{}\n", pprust::item_to_string(&item)); - box(GC) item + (box(GC) item, reexport) } fn nospan(t: T) -> codemap::Spanned { @@ -417,11 +442,27 @@ fn mk_tests(cx: &TestCtxt) -> Gc { // The vector of test_descs for this crate let test_descs = mk_test_descs(cx); - (quote_item!(&cx.ext_cx, - pub static TESTS : &'static [self::test::TestDescAndFn] = - $test_descs - ; - )).unwrap() + // FIXME #15962: should be using quote_item, but that stringifies + // __test_reexports, causing it to be reinterned, losing the + // gensym information. + let sp = DUMMY_SP; + let ecx = &cx.ext_cx; + let struct_type = ecx.ty_path(ecx.path(sp, vec![ecx.ident_of("self"), + ecx.ident_of("test"), + ecx.ident_of("TestDescAndFn")]), + None); + let static_lt = ecx.lifetime(sp, token::special_idents::static_lifetime.name); + // &'static [self::test::TestDescAndFn] + let static_type = ecx.ty_rptr(sp, + ecx.ty(sp, ast::TyVec(struct_type)), + Some(static_lt), + ast::MutImmutable); + // static TESTS: $static_type = &[...]; + ecx.item_static(sp, + ecx.ident_of("TESTS"), + static_type, + ast::MutImmutable, + test_descs) } fn is_test_crate(krate: &ast::Crate) -> bool { @@ -448,59 +489,58 @@ fn mk_test_descs(cx: &TestCtxt) -> Gc { } fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> Gc { + // FIXME #15962: should be using quote_expr, but that stringifies + // __test_reexports, causing it to be reinterned, losing the + // gensym information. + let span = test.span; let path = test.path.clone(); + let ecx = &cx.ext_cx; + let self_id = ecx.ident_of("self"); + let test_id = ecx.ident_of("test"); + + // creates self::test::$name + let test_path = |name| { + ecx.path(span, vec![self_id, test_id, ecx.ident_of(name)]) + }; + // creates $name: $expr + let field = |name, expr| ecx.field_imm(span, ecx.ident_of(name), expr); debug!("encoding {}", ast_util::path_name_i(path.as_slice())); - let name_lit: ast::Lit = - nospan(ast::LitStr(token::intern_and_get_ident( - ast_util::path_name_i(path.as_slice()).as_slice()), - ast::CookedStr)); + // path to the #[test] function: "foo::bar::baz" + let path_string = ast_util::path_name_i(path.as_slice()); + let name_expr = ecx.expr_str(span, token::intern_and_get_ident(path_string.as_slice())); - let name_expr = box(GC) ast::Expr { - id: ast::DUMMY_NODE_ID, - node: ast::ExprLit(box(GC) name_lit), - span: span - }; + // self::test::StaticTestName($name_expr) + let name_expr = ecx.expr_call(span, + ecx.expr_path(test_path("StaticTestName")), + vec![name_expr]); - let mut visible_path = vec![cx.reexport_mod_ident.clone()]; - visible_path.extend(path.move_iter()); - let fn_path = cx.ext_cx.path_global(DUMMY_SP, visible_path); + let ignore_expr = ecx.expr_bool(span, test.ignore); + let fail_expr = ecx.expr_bool(span, test.should_fail); - let fn_expr = box(GC) ast::Expr { - id: ast::DUMMY_NODE_ID, - node: ast::ExprPath(fn_path), - span: span, - }; + // self::test::TestDesc { ... } + let desc_expr = ecx.expr_struct( + span, + test_path("TestDesc"), + vec![field("name", name_expr), + field("ignore", ignore_expr), + field("should_fail", fail_expr)]); - let t_expr = if test.bench { - quote_expr!(&cx.ext_cx, self::test::StaticBenchFn($fn_expr) ) - } else { - quote_expr!(&cx.ext_cx, self::test::StaticTestFn($fn_expr) ) - }; - let ignore_expr = if test.ignore { - quote_expr!(&cx.ext_cx, true ) - } else { - quote_expr!(&cx.ext_cx, false ) - }; + let mut visible_path = vec![cx.reexport_mod_ident.clone()]; + visible_path.extend(path.move_iter()); - let fail_expr = if test.should_fail { - quote_expr!(&cx.ext_cx, true ) - } else { - quote_expr!(&cx.ext_cx, false ) - }; + let fn_expr = ecx.expr_path(ecx.path_global(span, visible_path)); - let e = quote_expr!(&cx.ext_cx, - self::test::TestDescAndFn { - desc: self::test::TestDesc { - name: self::test::StaticTestName($name_expr), - ignore: $ignore_expr, - should_fail: $fail_expr - }, - testfn: $t_expr, - } - ); - e + let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" }; + // self::test::$variant_name($fn_expr) + let testfn_expr = ecx.expr_call(span, ecx.expr_path(test_path(variant_name)), vec![fn_expr]); + + // self::test::TestDescAndFn { ... } + ecx.expr_struct(span, + test_path("TestDescAndFn"), + vec![field("desc", desc_expr), + field("testfn", testfn_expr)]) } diff --git a/src/librustuv/lib.rs b/src/librustuv/lib.rs index 24b8c29785804..dd80ab3ee78a1 100644 --- a/src/librustuv/lib.rs +++ b/src/librustuv/lib.rs @@ -48,6 +48,8 @@ via `close` and `delete` methods. #![deny(unused_result, unused_must_use)] #![allow(visible_private_types)] +#![reexport_test_harness_main = "test_main"] + #[cfg(test)] extern crate green; #[cfg(test)] extern crate debug; #[cfg(test)] extern crate realrustuv = "rustuv"; @@ -76,13 +78,9 @@ pub use self::timer::TimerWatcher; pub use self::tty::TtyWatcher; // Run tests with libgreen instead of libnative. -// -// FIXME: This egregiously hacks around starting the test runner in a different -// threading mode than the default by reaching into the auto-generated -// '__test' module. #[cfg(test)] #[start] fn start(argc: int, argv: *const *const u8) -> int { - green::start(argc, argv, event_loop, __test::main) + green::start(argc, argv, event_loop, test_main) } mod macros; diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 125c3fdf5d90c..20fc7efeb574f 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -114,6 +114,8 @@ #![allow(deprecated)] #![deny(missing_doc)] +#![reexport_test_harness_main = "test_main"] + // When testing libstd, bring in libuv as the I/O backend so tests can print // things and all of the std::io tests have an I/O interface to run on top // of @@ -186,13 +188,9 @@ pub use unicode::char; pub use core_sync::comm; // Run tests with libgreen instead of libnative. -// -// FIXME: This egregiously hacks around starting the test runner in a different -// threading mode than the default by reaching into the auto-generated -// '__test' module. #[cfg(test)] #[start] fn start(argc: int, argv: *const *const u8) -> int { - green::start(argc, argv, rustuv::event_loop, __test::main) + green::start(argc, argv, rustuv::event_loop, test_main) } /* Exported macros */ diff --git a/src/test/compile-fail/inaccessible-test-modules.rs b/src/test/compile-fail/inaccessible-test-modules.rs new file mode 100644 index 0000000000000..b646f8083b8d5 --- /dev/null +++ b/src/test/compile-fail/inaccessible-test-modules.rs @@ -0,0 +1,19 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags:--test + +// the `--test` harness creates modules with these textual names, but +// they should be inaccessible from normal code. +use x = __test; //~ ERROR unresolved import `__test` +use y = __test_reexports; //~ ERROR unresolved import `__test_reexports` + +#[test] +fn baz() {} diff --git a/src/test/run-make/test-harness/Makefile b/src/test/run-make/test-harness/Makefile new file mode 100644 index 0000000000000..4517af8e24be5 --- /dev/null +++ b/src/test/run-make/test-harness/Makefile @@ -0,0 +1,7 @@ +-include ../tools.mk + +all: + # check that #[ignore(cfg(...))] does the right thing. + $(RUSTC) --test test-ignore-cfg.rs --cfg ignorecfg + $(call RUN,test-ignore-cfg) | grep 'shouldnotignore ... ok' + $(call RUN,test-ignore-cfg) | grep 'shouldignore ... ignored' diff --git a/src/test/run-make/test-harness/test-ignore-cfg.rs b/src/test/run-make/test-harness/test-ignore-cfg.rs new file mode 100644 index 0000000000000..a8f88cc8544a9 --- /dev/null +++ b/src/test/run-make/test-harness/test-ignore-cfg.rs @@ -0,0 +1,19 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[test] +#[ignore(cfg(ignorecfg))] +fn shouldignore() { +} + +#[test] +#[ignore(cfg(noignorecfg))] +fn shouldnotignore() { +} diff --git a/src/test/run-pass/core-run-destroy.rs b/src/test/run-pass/core-run-destroy.rs index 8e84278c10e02..d187a6a8afebb 100644 --- a/src/test/run-pass/core-run-destroy.rs +++ b/src/test/run-pass/core-run-destroy.rs @@ -16,6 +16,8 @@ // instead of in std. #![feature(macro_rules)] +#![reexport_test_harness_main = "test_main"] + extern crate libc; extern crate native; @@ -55,7 +57,7 @@ macro_rules! iotest ( #[cfg(test)] #[start] fn start(argc: int, argv: *const *const u8) -> int { - green::start(argc, argv, rustuv::event_loop, __test::main) + green::start(argc, argv, rustuv::event_loop, test_main) } iotest!(fn test_destroy_once() { diff --git a/src/test/run-pass/reexport-test-harness-main.rs b/src/test/run-pass/reexport-test-harness-main.rs new file mode 100644 index 0000000000000..309ae1bcc56ec --- /dev/null +++ b/src/test/run-pass/reexport-test-harness-main.rs @@ -0,0 +1,21 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-pretty +// compile-flags:--test + +#![reexport_test_harness_main = "test_main"] + +#[cfg(test)] +fn _unused() { + // should resolve to the entry point function the --test harness + // creates. + test_main(); +} diff --git a/src/test/run-pass/tcp-connect-timeouts.rs b/src/test/run-pass/tcp-connect-timeouts.rs index d2408509fc582..6f6fff15814d5 100644 --- a/src/test/run-pass/tcp-connect-timeouts.rs +++ b/src/test/run-pass/tcp-connect-timeouts.rs @@ -18,6 +18,7 @@ #![feature(macro_rules, globs)] #![allow(experimental)] +#![reexport_test_harness_main = "test_main"] extern crate native; extern crate green; @@ -25,7 +26,7 @@ extern crate rustuv; #[cfg(test)] #[start] fn start(argc: int, argv: *const *const u8) -> int { - green::start(argc, argv, rustuv::event_loop, __test::main) + green::start(argc, argv, rustuv::event_loop, test_main) } macro_rules! iotest ( diff --git a/src/test/run-pass/test-ignore-cfg.rs b/src/test/run-pass/test-ignore-cfg.rs deleted file mode 100644 index b36fbca2da048..0000000000000 --- a/src/test/run-pass/test-ignore-cfg.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// compile-flags: --test --cfg ignorecfg -// ignore-pretty: does not work well with `--test` - -#[test] -#[ignore(cfg(ignorecfg))] -fn shouldignore() { -} - -#[test] -#[ignore(cfg(noignorecfg))] -fn shouldnotignore() { -} - -#[test] -fn checktests() { - // Pull the tests out of the secreturn test module - let tests = __test::TESTS; - - assert!( - tests.iter().any(|t| t.desc.name.to_string().as_slice() == "shouldignore" && - t.desc.ignore)); - - assert!( - tests.iter().any(|t| t.desc.name.to_string().as_slice() == "shouldnotignore" && - !t.desc.ignore)); -}