diff options
| -rw-r--r-- | src/librustc_driver/driver.rs | 3 | ||||
| -rw-r--r-- | src/libstd/termination.rs | 7 | ||||
| -rw-r--r-- | src/libsyntax/lib.rs | 1 | ||||
| -rw-r--r-- | src/libsyntax/test.rs | 170 | ||||
| -rw-r--r-- | src/test/run-pass/termination-trait-in-test.rs | 28 |
5 files changed, 166 insertions, 43 deletions
diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index b8a1fe99105..e4600f25ea7 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -816,7 +816,8 @@ pub fn phase_2_configure_and_expand_inner<'a, F>(sess: &'a Session, &mut resolver, sess.opts.test, krate, - sess.diagnostic()) + sess.diagnostic(), + &sess.features.borrow()) }); // If we're actually rustdoc then there's no need to actually compile diff --git a/src/libstd/termination.rs b/src/libstd/termination.rs index dc7fa53aab6..f02fad009d8 100644 --- a/src/libstd/termination.rs +++ b/src/libstd/termination.rs @@ -37,6 +37,13 @@ pub trait Termination { /// Is called to get the representation of the value as status code. /// This status code is returned to the operating system. fn report(self) -> i32; + + /// Invoked when unit tests terminate. Should panic if the unit + /// test is considered a failure. By default, invokes `report()` + /// and checks for a `0` result. + fn assert_unit_test_successful(self) where Self: Sized { + assert_eq!(self.report(), 0); + } } #[unstable(feature = "termination_trait", issue = "43301")] diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs index 9181cca215c..53ff3ccd48a 100644 --- a/src/libsyntax/lib.rs +++ b/src/libsyntax/lib.rs @@ -105,6 +105,7 @@ pub mod syntax { pub use ext; pub use parse; pub use ast; + pub use tokenstream; } pub mod abi; diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index e73550d0719..094de6868a5 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -32,6 +32,7 @@ use ext::build::AstBuilder; use ext::expand::ExpansionConfig; use ext::hygiene::{Mark, SyntaxContext}; use fold::Folder; +use feature_gate::Features; use util::move_map::MoveMap; use fold; use parse::{token, ParseSess}; @@ -63,6 +64,7 @@ struct TestCtxt<'a> { reexport_test_harness_main: Option<Symbol>, is_libtest: bool, ctxt: SyntaxContext, + features: &'a Features, // top-level re-export submodule, filled out after folding is finished toplevel_reexport: Option<Ident>, @@ -74,7 +76,8 @@ pub fn modify_for_testing(sess: &ParseSess, resolver: &mut Resolver, should_test: bool, krate: ast::Crate, - span_diagnostic: &errors::Handler) -> ast::Crate { + span_diagnostic: &errors::Handler, + features: &Features) -> ast::Crate { // 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 @@ -84,7 +87,8 @@ pub fn modify_for_testing(sess: &ParseSess, "reexport_test_harness_main"); if should_test { - generate_test_harness(sess, resolver, reexport_test_harness_main, krate, span_diagnostic) + generate_test_harness(sess, resolver, reexport_test_harness_main, + krate, span_diagnostic, features) } else { krate } @@ -265,16 +269,20 @@ fn generate_test_harness(sess: &ParseSess, resolver: &mut Resolver, reexport_test_harness_main: Option<Symbol>, krate: ast::Crate, - sd: &errors::Handler) -> ast::Crate { + sd: &errors::Handler, + features: &Features) -> ast::Crate { // Remove the entry points let mut cleaner = EntryPointCleaner { depth: 0 }; let krate = cleaner.fold_crate(krate); let mark = Mark::fresh(Mark::root()); + let mut econfig = ExpansionConfig::default("test".to_string()); + econfig.features = Some(features); + let cx = TestCtxt { span_diagnostic: sd, - ext_cx: ExtCtxt::new(sess, ExpansionConfig::default("test".to_string()), resolver), + ext_cx: ExtCtxt::new(sess, econfig, resolver), path: Vec::new(), testfns: Vec::new(), reexport_test_harness_main, @@ -282,6 +290,7 @@ fn generate_test_harness(sess: &ParseSess, is_libtest: attr::find_crate_name(&krate.attrs).map(|s| s == "test").unwrap_or(false), toplevel_reexport: None, ctxt: SyntaxContext::empty().apply_mark(mark), + features, }; mark.set_expn_info(ExpnInfo { @@ -318,71 +327,105 @@ enum HasTestSignature { fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool { let has_test_attr = attr::contains_name(&i.attrs, "test"); - fn has_test_signature(i: &ast::Item) -> HasTestSignature { + fn has_test_signature(cx: &TestCtxt, i: &ast::Item) -> HasTestSignature { match i.node { - ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => { - let no_output = match decl.output { - ast::FunctionRetTy::Default(..) => true, - ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true, - _ => false - }; - if decl.inputs.is_empty() - && no_output - && !generics.is_parameterized() { - Yes - } else { - No + ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => { + // If the termination trait is active, the compiler will check that the output + // type implements the `Termination` trait as `libtest` enforces that. + let output_matches = if cx.features.termination_trait { + true + } else { + let no_output = match decl.output { + ast::FunctionRetTy::Default(..) => true, + ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true, + _ => false + }; + + no_output && !generics.is_parameterized() + }; + + if decl.inputs.is_empty() && output_matches { + Yes + } else { + No + } } - } - _ => NotEvenAFunction, + _ => NotEvenAFunction, } } - if has_test_attr { + let has_test_signature = if has_test_attr { let diag = cx.span_diagnostic; - match has_test_signature(i) { - Yes => {}, - No => diag.span_err(i.span, "functions used as tests must have signature fn() -> ()"), - NotEvenAFunction => diag.span_err(i.span, - "only functions may be used as tests"), + match has_test_signature(cx, i) { + Yes => true, + No => { + if cx.features.termination_trait { + diag.span_err(i.span, "functions used as tests can not have any arguments"); + } else { + diag.span_err(i.span, "functions used as tests must have signature fn() -> ()"); + } + false + }, + NotEvenAFunction => { + diag.span_err(i.span, "only functions may be used as tests"); + false + }, } - } + } else { + false + }; - has_test_attr && has_test_signature(i) == Yes + has_test_attr && has_test_signature } fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool { let has_bench_attr = attr::contains_name(&i.attrs, "bench"); - fn has_test_signature(i: &ast::Item) -> bool { + fn has_bench_signature(cx: &TestCtxt, i: &ast::Item) -> bool { match i.node { ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => { let input_cnt = decl.inputs.len(); - let no_output = match decl.output { - ast::FunctionRetTy::Default(..) => true, - ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true, - _ => false + + // If the termination trait is active, the compiler will check that the output + // type implements the `Termination` trait as `libtest` enforces that. + let output_matches = if cx.features.termination_trait { + true + } else { + let no_output = match decl.output { + ast::FunctionRetTy::Default(..) => true, + ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true, + _ => false + }; + let tparm_cnt = generics.params.iter() + .filter(|param| param.is_type_param()) + .count(); + + no_output && tparm_cnt == 0 }; - let tparm_cnt = generics.params.iter() - .filter(|param| param.is_type_param()) - .count(); // NB: inadequate check, but we're running // well before resolve, can't get too deep. - input_cnt == 1 - && no_output && tparm_cnt == 0 + input_cnt == 1 && output_matches } _ => false } } - if has_bench_attr && !has_test_signature(i) { + let has_bench_signature = has_bench_signature(cx, i); + + if has_bench_attr && !has_bench_signature { let diag = cx.span_diagnostic; - diag.span_err(i.span, "functions used as benches must have signature \ - `fn(&mut Bencher) -> ()`"); + + if cx.features.termination_trait { + diag.span_err(i.span, "functions used as benches must have signature \ + `fn(&mut Bencher) -> impl Termination`"); + } else { + diag.span_err(i.span, "functions used as benches must have signature \ + `fn(&mut Bencher) -> ()`"); + } } - has_bench_attr && has_test_signature(i) + has_bench_attr && has_bench_signature } fn is_ignored(i: &ast::Item) -> bool { @@ -700,9 +743,52 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P<ast::Expr> { }; visible_path.extend(path); - let fn_expr = ecx.expr_path(ecx.path_global(span, visible_path)); + // If termination feature is enabled, create a wrapper that invokes the fn + // like this: + // + // fn wrapper() { + // assert_eq!(0, real_function().report()); + // } + // + // and then put a reference to `wrapper` into the test descriptor. Otherwise, + // just put a direct reference to `real_function`. + let fn_expr = { + let base_fn_expr = ecx.expr_path(ecx.path_global(span, visible_path)); + if cx.features.termination_trait { + // ::std::Termination::assert_unit_test_successful + let assert_unit_test_successful = ecx.path_global( + span, + vec![ + ecx.ident_of("std"), + ecx.ident_of("Termination"), + ecx.ident_of("assert_unit_test_successful"), + ], + ); + // || {..} + ecx.lambda( + span, + vec![], + // ::std::Termination::assert_unit_test_successful(..) + ecx.expr_call( + span, + ecx.expr_path(assert_unit_test_successful), + vec![ + // $base_fn_expr() + ecx.expr_call( + span, + base_fn_expr, + vec![], + ) + ], + ), + ) + } else { + base_fn_expr + } + }; 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]); diff --git a/src/test/run-pass/termination-trait-in-test.rs b/src/test/run-pass/termination-trait-in-test.rs new file mode 100644 index 00000000000..e67e0de5c31 --- /dev/null +++ b/src/test/run-pass/termination-trait-in-test.rs @@ -0,0 +1,28 @@ +// Copyright 2017 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 + +#![feature(termination_trait)] + +use std::num::ParseIntError; + +#[test] +fn is_a_num() -> Result<(), ParseIntError> { + let _: u32 = "22".parse()?; + Ok(()) +} + +#[test] +#[should_panic] +fn not_a_num() -> Result<(), ParseIntError> { + let _: u32 = "abc".parse()?; + Ok(()) +} |
