Skip to content

Commit 195c42c

Browse files
authored
Auto merge of #37749 - keeperofdakeys:should-panic, r=alexcrichton
Improvements to the #[should_panic] feature Add more error checking for the `#[should_panic]` attribute, and print the expected panic string when it does not match. Fixes #29000 Eg: ```running 3 tests test test2 ... ok test test1 ... FAILED : Panic did not include expected string 'foo' test test3 ... FAILED failures: ---- test1 stdout ---- thread 'test1' panicked at 'bar', test.rs:7 note: Run with `RUST_BACKTRACE=1` for a backtrace. ---- test3 stdout ---- thread 'test3' panicked at 'bar', test.rs:18 ```
2 parents 2a6d02e + fb5ccf8 commit 195c42c

File tree

5 files changed

+151
-20
lines changed

5 files changed

+151
-20
lines changed

src/libsyntax/test.rs

+37-7
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ impl<'a> fold::Folder for TestHarnessGenerator<'a> {
132132
path: self.cx.path.clone(),
133133
bench: is_bench_fn(&self.cx, &i),
134134
ignore: is_ignored(&i),
135-
should_panic: should_panic(&i)
135+
should_panic: should_panic(&i, &self.cx)
136136
};
137137
self.cx.testfns.push(test);
138138
self.tests.push(i.ident);
@@ -395,14 +395,44 @@ fn is_ignored(i: &ast::Item) -> bool {
395395
i.attrs.iter().any(|attr| attr.check_name("ignore"))
396396
}
397397

398-
fn should_panic(i: &ast::Item) -> ShouldPanic {
398+
fn should_panic(i: &ast::Item, cx: &TestCtxt) -> ShouldPanic {
399399
match i.attrs.iter().find(|attr| attr.check_name("should_panic")) {
400400
Some(attr) => {
401-
let msg = attr.meta_item_list()
402-
.and_then(|list| list.iter().find(|mi| mi.check_name("expected")))
403-
.and_then(|li| li.meta_item())
404-
.and_then(|mi| mi.value_str());
405-
ShouldPanic::Yes(msg)
401+
let sd = cx.span_diagnostic;
402+
if attr.is_value_str() {
403+
sd.struct_span_warn(
404+
attr.span(),
405+
"attribute must be of the form: \
406+
`#[should_panic]` or \
407+
`#[should_panic(expected = \"error message\")]`"
408+
).note("Errors in this attribute were erroneously allowed \
409+
and will become a hard error in a future release.")
410+
.emit();
411+
return ShouldPanic::Yes(None);
412+
}
413+
match attr.meta_item_list() {
414+
// Handle #[should_panic]
415+
None => ShouldPanic::Yes(None),
416+
// Handle #[should_panic(expected = "foo")]
417+
Some(list) => {
418+
let msg = list.iter()
419+
.find(|mi| mi.check_name("expected"))
420+
.and_then(|mi| mi.meta_item())
421+
.and_then(|mi| mi.value_str());
422+
if list.len() != 1 || msg.is_none() {
423+
sd.struct_span_warn(
424+
attr.span(),
425+
"argument must be of the form: \
426+
`expected = \"error message\"`"
427+
).note("Errors in this attribute were erroneously \
428+
allowed and will become a hard error in a \
429+
future release.").emit();
430+
ShouldPanic::Yes(None)
431+
} else {
432+
ShouldPanic::Yes(msg)
433+
}
434+
},
435+
}
406436
}
407437
None => ShouldPanic::No,
408438
}

src/libtest/lib.rs

+30-13
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ const TEST_WARN_TIMEOUT_S: u64 = 60;
7575
// to be used by rustc to compile tests in libtest
7676
pub mod test {
7777
pub use {Bencher, TestName, TestResult, TestDesc, TestDescAndFn, TestOpts, TrFailed,
78-
TrIgnored, TrOk, Metric, MetricMap, StaticTestFn, StaticTestName, DynTestName,
79-
DynTestFn, run_test, test_main, test_main_static, filter_tests, parse_opts,
80-
StaticBenchFn, ShouldPanic};
78+
TrFailedMsg, TrIgnored, TrOk, Metric, MetricMap, StaticTestFn, StaticTestName,
79+
DynTestName, DynTestFn, run_test, test_main, test_main_static, filter_tests,
80+
parse_opts, StaticBenchFn, ShouldPanic};
8181
}
8282

8383
pub mod stats;
@@ -473,6 +473,7 @@ pub struct BenchSamples {
473473
pub enum TestResult {
474474
TrOk,
475475
TrFailed,
476+
TrFailedMsg(String),
476477
TrIgnored,
477478
TrMetrics(MetricMap),
478479
TrBench(BenchSamples),
@@ -611,7 +612,7 @@ impl<T: Write> ConsoleTestState<T> {
611612
pub fn write_result(&mut self, result: &TestResult) -> io::Result<()> {
612613
match *result {
613614
TrOk => self.write_ok(),
614-
TrFailed => self.write_failed(),
615+
TrFailed | TrFailedMsg(_) => self.write_failed(),
615616
TrIgnored => self.write_ignored(),
616617
TrMetrics(ref mm) => {
617618
self.write_metric()?;
@@ -638,6 +639,7 @@ impl<T: Write> ConsoleTestState<T> {
638639
match *result {
639640
TrOk => "ok".to_owned(),
640641
TrFailed => "failed".to_owned(),
642+
TrFailedMsg(ref msg) => format!("failed: {}", msg),
641643
TrIgnored => "ignored".to_owned(),
642644
TrMetrics(ref mm) => mm.fmt_metrics(),
643645
TrBench(ref bs) => fmt_bench_samples(bs),
@@ -773,6 +775,14 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu
773775
st.failed += 1;
774776
st.failures.push((test, stdout));
775777
}
778+
TrFailedMsg(msg) => {
779+
st.failed += 1;
780+
let mut stdout = stdout;
781+
stdout.extend_from_slice(
782+
format!("note: {}", msg).as_bytes()
783+
);
784+
st.failures.push((test, stdout));
785+
}
776786
}
777787
Ok(())
778788
}
@@ -1270,12 +1280,16 @@ fn calc_result(desc: &TestDesc, task_result: Result<(), Box<Any + Send>>) -> Tes
12701280
match (&desc.should_panic, task_result) {
12711281
(&ShouldPanic::No, Ok(())) |
12721282
(&ShouldPanic::Yes, Err(_)) => TrOk,
1273-
(&ShouldPanic::YesWithMessage(msg), Err(ref err))
1283+
(&ShouldPanic::YesWithMessage(msg), Err(ref err)) =>
12741284
if err.downcast_ref::<String>()
1275-
.map(|e| &**e)
1276-
.or_else(|| err.downcast_ref::<&'static str>().map(|e| *e))
1277-
.map(|e| e.contains(msg))
1278-
.unwrap_or(false) => TrOk,
1285+
.map(|e| &**e)
1286+
.or_else(|| err.downcast_ref::<&'static str>().map(|e| *e))
1287+
.map(|e| e.contains(msg))
1288+
.unwrap_or(false) {
1289+
TrOk
1290+
} else {
1291+
TrFailedMsg(format!("Panic did not include expected string '{}'", msg))
1292+
},
12791293
_ => TrFailed,
12801294
}
12811295
}
@@ -1482,8 +1496,9 @@ pub mod bench {
14821496

14831497
#[cfg(test)]
14841498
mod tests {
1485-
use test::{TrFailed, TrIgnored, TrOk, filter_tests, parse_opts, TestDesc, TestDescAndFn,
1486-
TestOpts, run_test, MetricMap, StaticTestName, DynTestName, DynTestFn, ShouldPanic};
1499+
use test::{TrFailed, TrFailedMsg, TrIgnored, TrOk, filter_tests, parse_opts, TestDesc,
1500+
TestDescAndFn, TestOpts, run_test, MetricMap, StaticTestName, DynTestName,
1501+
DynTestFn, ShouldPanic};
14871502
use std::sync::mpsc::channel;
14881503

14891504
#[test]
@@ -1565,18 +1580,20 @@ mod tests {
15651580
fn f() {
15661581
panic!("an error message");
15671582
}
1583+
let expected = "foobar";
1584+
let failed_msg = "Panic did not include expected string";
15681585
let desc = TestDescAndFn {
15691586
desc: TestDesc {
15701587
name: StaticTestName("whatever"),
15711588
ignore: false,
1572-
should_panic: ShouldPanic::YesWithMessage("foobar"),
1589+
should_panic: ShouldPanic::YesWithMessage(expected),
15731590
},
15741591
testfn: DynTestFn(Box::new(move |()| f())),
15751592
};
15761593
let (tx, rx) = channel();
15771594
run_test(&TestOpts::new(), false, desc, tx);
15781595
let (_, res, _) = rx.recv().unwrap();
1579-
assert!(res == TrFailed);
1596+
assert!(res == TrFailedMsg(format!("{} '{}'", failed_msg, expected)));
15801597
}
15811598

15821599
#[test]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2014 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+
// error-pattern:panicked at 'bar'
14+
// check-stdout
15+
#[test]
16+
#[should_panic(expected = "foo")]
17+
pub fn test_bar() {
18+
panic!("bar")
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2014 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+
// error-pattern:panicked at 'explicit panic'
14+
// check-stdout
15+
#[test]
16+
#[should_panic(expected = "foo")]
17+
pub fn test_explicit() {
18+
panic!()
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2016 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+
#[test]
14+
#[should_panic = "foo"]
15+
//~^ WARN: attribute must be of the form:
16+
fn test1() {
17+
panic!();
18+
}
19+
20+
#[test]
21+
#[should_panic(expected)]
22+
//~^ WARN: argument must be of the form:
23+
fn test2() {
24+
panic!();
25+
}
26+
27+
#[test]
28+
#[should_panic(expect)]
29+
//~^ WARN: argument must be of the form:
30+
fn test3() {
31+
panic!();
32+
}
33+
34+
#[test]
35+
#[should_panic(expected(foo, bar))]
36+
//~^ WARN: argument must be of the form:
37+
fn test4() {
38+
panic!();
39+
}
40+
41+
#[test]
42+
#[should_panic(expected = "foo", bar)]
43+
//~^ WARN: argument must be of the form:
44+
fn test5() {
45+
panic!();
46+
}

0 commit comments

Comments
 (0)