Skip to content

rustc: gensym the module names for --test to avoid introducing names. #15964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 107 additions & 67 deletions src/librustc/front/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ struct TestCtxt<'a> {
ext_cx: ExtCtxt<'a>,
testfns: Vec<Test>,
reexport_mod_ident: ast::Ident,
reexport_test_harness_main: Option<InternedString>,
is_test_crate: bool,
config: ast::CrateConfig,
}
Expand All @@ -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)
}
Expand All @@ -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<ast::Item>) -> SmallVector<Gc<ast::Item>> {
Expand Down Expand Up @@ -196,7 +208,9 @@ fn mk_reexport_mod(cx: &mut TestCtxt, tests: Vec<ast::Ident>,
}
}

fn generate_test_harness(sess: &Session, krate: ast::Crate) -> ast::Crate {
fn generate_test_harness(sess: &Session,
reexport_test_harness_main: Option<InternedString>,
krate: ast::Crate) -> ast::Crate {
let mut cx: TestCtxt = TestCtxt {
sess: sess,
ext_cx: ExtCtxt::new(&sess.parse_sess, sess.opts.cfg.clone(),
Expand All @@ -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(),
};
Expand Down Expand Up @@ -314,14 +329,6 @@ fn should_fail(i: Gc<ast::Item>) -> 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:
Expand Down Expand Up @@ -359,7 +366,8 @@ fn mk_std(cx: &TestCtxt) -> ast::ViewItem {
}
}

fn mk_test_module(cx: &TestCtxt) -> Gc<ast::Item> {
fn mk_test_module(cx: &TestCtxt, reexport_test_harness_main: &Option<InternedString>)
-> (Gc<ast::Item>, Option<ast::ViewItem>) {
// Link to test crate
let view_items = vec!(mk_std(cx));

Expand All @@ -383,18 +391,35 @@ fn mk_test_module(cx: &TestCtxt) -> Gc<ast::Item> {
};
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 <ident> = __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: T) -> codemap::Spanned<T> {
Expand All @@ -417,11 +442,27 @@ fn mk_tests(cx: &TestCtxt) -> Gc<ast::Item> {
// 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 {
Expand All @@ -448,59 +489,58 @@ fn mk_test_descs(cx: &TestCtxt) -> Gc<ast::Expr> {
}

fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> Gc<ast::Expr> {
// 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)])
}
8 changes: 3 additions & 5 deletions src/librustuv/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 3 additions & 5 deletions src/libstd/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand Down
19 changes: 19 additions & 0 deletions src/test/compile-fail/inaccessible-test-modules.rs
Original file line number Diff line number Diff line change
@@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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() {}
7 changes: 7 additions & 0 deletions src/test/run-make/test-harness/Makefile
Original file line number Diff line number Diff line change
@@ -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'
19 changes: 19 additions & 0 deletions src/test/run-make/test-harness/test-ignore-cfg.rs
Original file line number Diff line number Diff line change
@@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[test]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linting may require that this need a license

#[ignore(cfg(ignorecfg))]
fn shouldignore() {
}

#[test]
#[ignore(cfg(noignorecfg))]
fn shouldnotignore() {
}
4 changes: 3 additions & 1 deletion src/test/run-pass/core-run-destroy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
// instead of in std.

#![feature(macro_rules)]
#![reexport_test_harness_main = "test_main"]

extern crate libc;

extern crate native;
Expand Down Expand Up @@ -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() {
Expand Down
Loading