Skip to content

Commit 1eab1b1

Browse files
committed
support unit tests with return values that implement Terminaton
Extend `Termination` trait with a method to determine what happens with a unit test. This commit incorporates work by Bastian Köcher <git@kchr.de>.
1 parent b1f8e6f commit 1eab1b1

File tree

5 files changed

+166
-43
lines changed

5 files changed

+166
-43
lines changed

src/librustc_driver/driver.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,8 @@ pub fn phase_2_configure_and_expand_inner<'a, F>(sess: &'a Session,
816816
&mut resolver,
817817
sess.opts.test,
818818
krate,
819-
sess.diagnostic())
819+
sess.diagnostic(),
820+
&sess.features.borrow())
820821
});
821822

822823
// If we're actually rustdoc then there's no need to actually compile

src/libstd/termination.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ pub trait Termination {
3737
/// Is called to get the representation of the value as status code.
3838
/// This status code is returned to the operating system.
3939
fn report(self) -> i32;
40+
41+
/// Invoked when unit tests terminate. Should panic if the unit
42+
/// test is considered a failure. By default, invokes `report()`
43+
/// and checks for a `0` result.
44+
fn assert_unit_test_successful(self) where Self: Sized {
45+
assert_eq!(self.report(), 0);
46+
}
4047
}
4148

4249
#[unstable(feature = "termination_trait", issue = "43301")]

src/libsyntax/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ pub mod syntax {
105105
pub use ext;
106106
pub use parse;
107107
pub use ast;
108+
pub use tokenstream;
108109
}
109110

110111
pub mod abi;

src/libsyntax/test.rs

Lines changed: 128 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use ext::build::AstBuilder;
3232
use ext::expand::ExpansionConfig;
3333
use ext::hygiene::{Mark, SyntaxContext};
3434
use fold::Folder;
35+
use feature_gate::Features;
3536
use util::move_map::MoveMap;
3637
use fold;
3738
use parse::{token, ParseSess};
@@ -63,6 +64,7 @@ struct TestCtxt<'a> {
6364
reexport_test_harness_main: Option<Symbol>,
6465
is_libtest: bool,
6566
ctxt: SyntaxContext,
67+
features: &'a Features,
6668

6769
// top-level re-export submodule, filled out after folding is finished
6870
toplevel_reexport: Option<Ident>,
@@ -74,7 +76,8 @@ pub fn modify_for_testing(sess: &ParseSess,
7476
resolver: &mut Resolver,
7577
should_test: bool,
7678
krate: ast::Crate,
77-
span_diagnostic: &errors::Handler) -> ast::Crate {
79+
span_diagnostic: &errors::Handler,
80+
features: &Features) -> ast::Crate {
7881
// Check for #[reexport_test_harness_main = "some_name"] which
7982
// creates a `use some_name = __test::main;`. This needs to be
8083
// unconditional, so that the attribute is still marked as used in
@@ -84,7 +87,8 @@ pub fn modify_for_testing(sess: &ParseSess,
8487
"reexport_test_harness_main");
8588

8689
if should_test {
87-
generate_test_harness(sess, resolver, reexport_test_harness_main, krate, span_diagnostic)
90+
generate_test_harness(sess, resolver, reexport_test_harness_main,
91+
krate, span_diagnostic, features)
8892
} else {
8993
krate
9094
}
@@ -265,23 +269,28 @@ fn generate_test_harness(sess: &ParseSess,
265269
resolver: &mut Resolver,
266270
reexport_test_harness_main: Option<Symbol>,
267271
krate: ast::Crate,
268-
sd: &errors::Handler) -> ast::Crate {
272+
sd: &errors::Handler,
273+
features: &Features) -> ast::Crate {
269274
// Remove the entry points
270275
let mut cleaner = EntryPointCleaner { depth: 0 };
271276
let krate = cleaner.fold_crate(krate);
272277

273278
let mark = Mark::fresh(Mark::root());
274279

280+
let mut econfig = ExpansionConfig::default("test".to_string());
281+
econfig.features = Some(features);
282+
275283
let cx = TestCtxt {
276284
span_diagnostic: sd,
277-
ext_cx: ExtCtxt::new(sess, ExpansionConfig::default("test".to_string()), resolver),
285+
ext_cx: ExtCtxt::new(sess, econfig, resolver),
278286
path: Vec::new(),
279287
testfns: Vec::new(),
280288
reexport_test_harness_main,
281289
// NB: doesn't consider the value of `--crate-name` passed on the command line.
282290
is_libtest: attr::find_crate_name(&krate.attrs).map(|s| s == "test").unwrap_or(false),
283291
toplevel_reexport: None,
284292
ctxt: SyntaxContext::empty().apply_mark(mark),
293+
features,
285294
};
286295

287296
mark.set_expn_info(ExpnInfo {
@@ -318,71 +327,105 @@ enum HasTestSignature {
318327
fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
319328
let has_test_attr = attr::contains_name(&i.attrs, "test");
320329

321-
fn has_test_signature(i: &ast::Item) -> HasTestSignature {
330+
fn has_test_signature(cx: &TestCtxt, i: &ast::Item) -> HasTestSignature {
322331
match i.node {
323-
ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => {
324-
let no_output = match decl.output {
325-
ast::FunctionRetTy::Default(..) => true,
326-
ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
327-
_ => false
328-
};
329-
if decl.inputs.is_empty()
330-
&& no_output
331-
&& !generics.is_parameterized() {
332-
Yes
333-
} else {
334-
No
332+
ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => {
333+
// If the termination trait is active, the compiler will check that the output
334+
// type implements the `Termination` trait as `libtest` enforces that.
335+
let output_matches = if cx.features.termination_trait {
336+
true
337+
} else {
338+
let no_output = match decl.output {
339+
ast::FunctionRetTy::Default(..) => true,
340+
ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
341+
_ => false
342+
};
343+
344+
no_output && !generics.is_parameterized()
345+
};
346+
347+
if decl.inputs.is_empty() && output_matches {
348+
Yes
349+
} else {
350+
No
351+
}
335352
}
336-
}
337-
_ => NotEvenAFunction,
353+
_ => NotEvenAFunction,
338354
}
339355
}
340356

341-
if has_test_attr {
357+
let has_test_signature = if has_test_attr {
342358
let diag = cx.span_diagnostic;
343-
match has_test_signature(i) {
344-
Yes => {},
345-
No => diag.span_err(i.span, "functions used as tests must have signature fn() -> ()"),
346-
NotEvenAFunction => diag.span_err(i.span,
347-
"only functions may be used as tests"),
359+
match has_test_signature(cx, i) {
360+
Yes => true,
361+
No => {
362+
if cx.features.termination_trait {
363+
diag.span_err(i.span, "functions used as tests can not have any arguments");
364+
} else {
365+
diag.span_err(i.span, "functions used as tests must have signature fn() -> ()");
366+
}
367+
false
368+
},
369+
NotEvenAFunction => {
370+
diag.span_err(i.span, "only functions may be used as tests");
371+
false
372+
},
348373
}
349-
}
374+
} else {
375+
false
376+
};
350377

351-
has_test_attr && has_test_signature(i) == Yes
378+
has_test_attr && has_test_signature
352379
}
353380

354381
fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
355382
let has_bench_attr = attr::contains_name(&i.attrs, "bench");
356383

357-
fn has_test_signature(i: &ast::Item) -> bool {
384+
fn has_bench_signature(cx: &TestCtxt, i: &ast::Item) -> bool {
358385
match i.node {
359386
ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => {
360387
let input_cnt = decl.inputs.len();
361-
let no_output = match decl.output {
362-
ast::FunctionRetTy::Default(..) => true,
363-
ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
364-
_ => false
388+
389+
// If the termination trait is active, the compiler will check that the output
390+
// type implements the `Termination` trait as `libtest` enforces that.
391+
let output_matches = if cx.features.termination_trait {
392+
true
393+
} else {
394+
let no_output = match decl.output {
395+
ast::FunctionRetTy::Default(..) => true,
396+
ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
397+
_ => false
398+
};
399+
let tparm_cnt = generics.params.iter()
400+
.filter(|param| param.is_type_param())
401+
.count();
402+
403+
no_output && tparm_cnt == 0
365404
};
366-
let tparm_cnt = generics.params.iter()
367-
.filter(|param| param.is_type_param())
368-
.count();
369405

370406
// NB: inadequate check, but we're running
371407
// well before resolve, can't get too deep.
372-
input_cnt == 1
373-
&& no_output && tparm_cnt == 0
408+
input_cnt == 1 && output_matches
374409
}
375410
_ => false
376411
}
377412
}
378413

379-
if has_bench_attr && !has_test_signature(i) {
414+
let has_bench_signature = has_bench_signature(cx, i);
415+
416+
if has_bench_attr && !has_bench_signature {
380417
let diag = cx.span_diagnostic;
381-
diag.span_err(i.span, "functions used as benches must have signature \
382-
`fn(&mut Bencher) -> ()`");
418+
419+
if cx.features.termination_trait {
420+
diag.span_err(i.span, "functions used as benches must have signature \
421+
`fn(&mut Bencher) -> impl Termination`");
422+
} else {
423+
diag.span_err(i.span, "functions used as benches must have signature \
424+
`fn(&mut Bencher) -> ()`");
425+
}
383426
}
384427

385-
has_bench_attr && has_test_signature(i)
428+
has_bench_attr && has_bench_signature
386429
}
387430

388431
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> {
700743
};
701744
visible_path.extend(path);
702745

703-
let fn_expr = ecx.expr_path(ecx.path_global(span, visible_path));
746+
// If termination feature is enabled, create a wrapper that invokes the fn
747+
// like this:
748+
//
749+
// fn wrapper() {
750+
// assert_eq!(0, real_function().report());
751+
// }
752+
//
753+
// and then put a reference to `wrapper` into the test descriptor. Otherwise,
754+
// just put a direct reference to `real_function`.
755+
let fn_expr = {
756+
let base_fn_expr = ecx.expr_path(ecx.path_global(span, visible_path));
757+
if cx.features.termination_trait {
758+
// ::std::Termination::assert_unit_test_successful
759+
let assert_unit_test_successful = ecx.path_global(
760+
span,
761+
vec![
762+
ecx.ident_of("std"),
763+
ecx.ident_of("Termination"),
764+
ecx.ident_of("assert_unit_test_successful"),
765+
],
766+
);
767+
// || {..}
768+
ecx.lambda(
769+
span,
770+
vec![],
771+
// ::std::Termination::assert_unit_test_successful(..)
772+
ecx.expr_call(
773+
span,
774+
ecx.expr_path(assert_unit_test_successful),
775+
vec![
776+
// $base_fn_expr()
777+
ecx.expr_call(
778+
span,
779+
base_fn_expr,
780+
vec![],
781+
)
782+
],
783+
),
784+
)
785+
} else {
786+
base_fn_expr
787+
}
788+
};
704789

705790
let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" };
791+
706792
// self::test::$variant_name($fn_expr)
707793
let testfn_expr = ecx.expr_call(span, ecx.expr_path(test_path(variant_name)), vec![fn_expr]);
708794

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2017 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+
// compile-flags: --test
12+
13+
#![feature(termination_trait)]
14+
15+
use std::num::ParseIntError;
16+
17+
#[test]
18+
fn is_a_num() -> Result<(), ParseIntError> {
19+
let _: u32 = "22".parse()?;
20+
Ok(())
21+
}
22+
23+
#[test]
24+
#[should_panic]
25+
fn not_a_num() -> Result<(), ParseIntError> {
26+
let _: u32 = "abc".parse()?;
27+
Ok(())
28+
}

0 commit comments

Comments
 (0)