Skip to content

Commit 80b861b

Browse files
committed
Auto merge of #64873 - popzxc:prettify-test-time, r=wesleywiser
Enhance report-time option ## Short overview This PR is a follow-up to a previously closed #64714 PR. ## Changes introduced by this PR * `libtest` now retrieves the type of the test within `TestDesc` (available types are: `UnitTest`, `IntegrationTest`, `DocTest`, `Unknown`). * `--report-time` subcommand of the `libtest` now supports colored output (disabled by default). * Colorized output depends on the threshold values. Default values (proposed by @wesleywiser): - For unit-tests: 50ms warn/100ms critical, - For integration-tests: 500ms warn/1000ms critical, - For doctests: same as for integration tests, - For unknown tests: `TEST_WARN_TIMEOUT_S` warn/ `TEST_WARN_TIMEOUT_S * 2` critical (it will only applied single-threaded mode, because otherwise test will be interrupted after reaching `TEST_WARN_TIMEOUT_S`). - These values can be overrided by setting environment variables (since those thresholds are somewhat constant for every project, it's more flexible to use environment variables than command line arguments). * New optional flag `--ensure-test-time` for `libtest`. With this flag applied, exectuion time limit excesss will cause test failure. ## What have not been done There was a comment that it would be nice to have an entry in the Cargo book about it. However, changes introduced by this PR (and #64663 in which `report-time` flag was added) aren't related directly to `cargo`, it's more about `libtest` itself. I'm considering that [The Unstable Book](https://doc.rust-lang.org/unstable-book/) is more appropriate place, but not sure if I'm right (and if so, how exactly it should be described). As one possible option, this PR may be merged without denoting it in the documentation, and in the next PR adding support of this feature to the `cargo` itself, I'll add a note in the Cargo book. ## Scope of this PR Logical scope of this PR is `libtest` only. However, to get test types, I had to modify also `libsyntax_ext` and `librustdoc` for them to provide information about test type. ## Rationale Rationale for colored output was submitted in #64714 Providing the information about kind of test was also proposed in #64714, and as an additional benefit this information may be useful for the tools using `libtest` (e.g. `cargo`). Adding flag to treat time limits excess seems logical to me, so projects that do care about test execution time won't have to invent a wheel. ## Backward compatibility All the changes are completely backward compatible. ## Demo ![rustc_enhanced_time](https://user-images.githubusercontent.com/12111581/65818381-c04f6800-e219-11e9-9875-322463abe24f.gif) r? @wesleywiser
2 parents 1721c96 + 15f571b commit 80b861b

File tree

9 files changed

+718
-117
lines changed

9 files changed

+718
-117
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# `report-time`
2+
3+
The tracking issue for this feature is: [#64888]
4+
5+
[#64888]: https://github.com/rust-lang/rust/issues/64888
6+
7+
------------------------
8+
9+
The `report-time` feature adds a possibility to report execution time of the
10+
tests generated via `libtest`.
11+
12+
This is unstable feature, so you have to provide `-Zunstable-options` to get
13+
this feature working.
14+
15+
Sample usage command:
16+
17+
```sh
18+
./test_executable -Zunstable-options --report-time
19+
```
20+
21+
Available options:
22+
23+
```sh
24+
--report-time [plain|colored]
25+
Show execution time of each test. Awailable values:
26+
plain = do not colorize the execution time (default);
27+
colored = colorize output according to the `color`
28+
parameter value;
29+
Threshold values for colorized output can be
30+
configured via
31+
`RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION`
32+
and
33+
`RUST_TEST_TIME_DOCTEST` environment variables.
34+
Expected format of environment variable is
35+
`VARIABLE=WARN_TIME,CRITICAL_TIME`.
36+
Not available for --format=terse
37+
--ensure-time
38+
Treat excess of the test execution time limit as
39+
error.
40+
Threshold values for this option can be configured via
41+
`RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION`
42+
and
43+
`RUST_TEST_TIME_DOCTEST` environment variables.
44+
Expected format of environment variable is
45+
`VARIABLE=WARN_TIME,CRITICAL_TIME`.
46+
`CRITICAL_TIME` here means the limit that should not be
47+
exceeded by test.
48+
```
49+
50+
Example of the environment variable format:
51+
52+
```sh
53+
RUST_TEST_TIME_UNIT=100,200
54+
```
55+
56+
where 100 stands for warn time, and 200 stands for critical time.
57+
58+
## Examples
59+
60+
```sh
61+
cargo test --tests -- -Zunstable-options --report-time
62+
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
63+
Running target/debug/deps/example-27fb188025bec02c
64+
65+
running 3 tests
66+
test tests::unit_test_quick ... ok <0.000s>
67+
test tests::unit_test_warn ... ok <0.055s>
68+
test tests::unit_test_critical ... ok <0.110s>
69+
70+
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
71+
72+
Running target/debug/deps/tests-cedb06f6526d15d9
73+
74+
running 3 tests
75+
test unit_test_quick ... ok <0.000s>
76+
test unit_test_warn ... ok <0.550s>
77+
test unit_test_critical ... ok <1.100s>
78+
79+
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
80+
```

src/librustdoc/test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,7 @@ impl Tester for Collector {
705705
// compiler failures are test failures
706706
should_panic: testing::ShouldPanic::No,
707707
allow_fail: config.allow_fail,
708+
test_type: testing::TestType::DocTest,
708709
},
709710
testfn: testing::DynTestFn(box move || {
710711
let res = run_test(

src/libsyntax_ext/test.rs

+44
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ pub fn expand_test_or_bench(
106106
cx.path(sp, vec![test_id, cx.ident_of("ShouldPanic", sp), cx.ident_of(name, sp)])
107107
};
108108

109+
// creates test::TestType::$name
110+
let test_type_path = |name| {
111+
cx.path(sp, vec![test_id, cx.ident_of("TestType", sp), cx.ident_of(name, sp)])
112+
};
113+
109114
// creates $name: $expr
110115
let field = |name, expr| cx.field_imm(sp, cx.ident_of(name, sp), expr);
111116

@@ -181,6 +186,17 @@ pub fn expand_test_or_bench(
181186
cx.expr_path(should_panic_path("YesWithMessage")),
182187
vec![cx.expr_str(sp, sym)]),
183188
}),
189+
// test_type: ...
190+
field("test_type", match test_type(cx) {
191+
// test::TestType::UnitTest
192+
TestType::UnitTest => cx.expr_path(test_type_path("UnitTest")),
193+
// test::TestType::IntegrationTest
194+
TestType::IntegrationTest => cx.expr_path(
195+
test_type_path("IntegrationTest")
196+
),
197+
// test::TestPath::Unknown
198+
TestType::Unknown => cx.expr_path(test_type_path("Unknown")),
199+
}),
184200
// },
185201
])),
186202
// testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
@@ -261,6 +277,34 @@ fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
261277
}
262278
}
263279

280+
enum TestType {
281+
UnitTest,
282+
IntegrationTest,
283+
Unknown,
284+
}
285+
286+
/// Attempts to determine the type of test.
287+
/// Since doctests are created without macro expanding, only possible variants here
288+
/// are `UnitTest`, `IntegrationTest` or `Unknown`.
289+
fn test_type(cx: &ExtCtxt<'_>) -> TestType {
290+
// Root path from context contains the topmost sources directory of the crate.
291+
// I.e., for `project` with sources in `src` and tests in `tests` folders
292+
// (no matter how many nested folders lie inside),
293+
// there will be two different root paths: `/project/src` and `/project/tests`.
294+
let crate_path = cx.root_path.as_path();
295+
296+
if crate_path.ends_with("src") {
297+
// `/src` folder contains unit-tests.
298+
TestType::UnitTest
299+
} else if crate_path.ends_with("tests") {
300+
// `/tests` folder contains integration tests.
301+
TestType::IntegrationTest
302+
} else {
303+
// Crate layout doesn't match expected one, test type is unknown.
304+
TestType::Unknown
305+
}
306+
}
307+
264308
fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool {
265309
let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic);
266310
let ref sd = cx.parse_sess.span_diagnostic;

src/libtest/formatters/json.rs

+9
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
9494
self.write_event("test", desc.name.as_slice(), "failed", exec_time, stdout, None)
9595
}
9696

97+
TrTimedFail => self.write_event(
98+
"test",
99+
desc.name.as_slice(),
100+
"failed",
101+
exec_time,
102+
stdout,
103+
Some(r#""reason": "time limit exceeded""#),
104+
),
105+
97106
TrFailedMsg(ref m) => self.write_event(
98107
"test",
99108
desc.name.as_slice(),

src/libtest/formatters/pretty.rs

+84-50
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use super::*;
33
pub(crate) struct PrettyFormatter<T> {
44
out: OutputLocation<T>,
55
use_color: bool,
6+
time_options: Option<TestTimeOptions>,
67

78
/// Number of columns to fill when aligning names
89
max_name_len: usize,
@@ -16,12 +17,14 @@ impl<T: Write> PrettyFormatter<T> {
1617
use_color: bool,
1718
max_name_len: usize,
1819
is_multithreaded: bool,
20+
time_options: Option<TestTimeOptions>,
1921
) -> Self {
2022
PrettyFormatter {
2123
out,
2224
use_color,
2325
max_name_len,
2426
is_multithreaded,
27+
time_options
2528
}
2629
}
2730

@@ -30,20 +33,24 @@ impl<T: Write> PrettyFormatter<T> {
3033
&self.out
3134
}
3235

33-
pub fn write_ok(&mut self, exec_time: Option<&TestExecTime>) -> io::Result<()> {
34-
self.write_short_result("ok", term::color::GREEN, exec_time)
36+
pub fn write_ok(&mut self) -> io::Result<()> {
37+
self.write_short_result("ok", term::color::GREEN)
3538
}
3639

37-
pub fn write_failed(&mut self, exec_time: Option<&TestExecTime>) -> io::Result<()> {
38-
self.write_short_result("FAILED", term::color::RED, exec_time)
40+
pub fn write_failed(&mut self) -> io::Result<()> {
41+
self.write_short_result("FAILED", term::color::RED)
3942
}
4043

41-
pub fn write_ignored(&mut self, exec_time: Option<&TestExecTime>) -> io::Result<()> {
42-
self.write_short_result("ignored", term::color::YELLOW, exec_time)
44+
pub fn write_ignored(&mut self) -> io::Result<()> {
45+
self.write_short_result("ignored", term::color::YELLOW)
4346
}
4447

45-
pub fn write_allowed_fail(&mut self, exec_time: Option<&TestExecTime>) -> io::Result<()> {
46-
self.write_short_result("FAILED (allowed)", term::color::YELLOW, exec_time)
48+
pub fn write_allowed_fail(&mut self) -> io::Result<()> {
49+
self.write_short_result("FAILED (allowed)", term::color::YELLOW)
50+
}
51+
52+
pub fn write_time_failed(&mut self) -> io::Result<()> {
53+
self.write_short_result("FAILED (time limit exceeded)", term::color::RED)
4754
}
4855

4956
pub fn write_bench(&mut self) -> io::Result<()> {
@@ -54,13 +61,8 @@ impl<T: Write> PrettyFormatter<T> {
5461
&mut self,
5562
result: &str,
5663
color: term::color::Color,
57-
exec_time: Option<&TestExecTime>,
5864
) -> io::Result<()> {
59-
self.write_pretty(result, color)?;
60-
if let Some(exec_time) = exec_time {
61-
self.write_plain(format!(" {}", exec_time))?;
62-
}
63-
self.write_plain("\n")
65+
self.write_pretty(result, color)
6466
}
6567

6668
pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> {
@@ -88,12 +90,48 @@ impl<T: Write> PrettyFormatter<T> {
8890
self.out.flush()
8991
}
9092

91-
pub fn write_successes(&mut self, state: &ConsoleTestState) -> io::Result<()> {
92-
self.write_plain("\nsuccesses:\n")?;
93-
let mut successes = Vec::new();
93+
fn write_time(
94+
&mut self,
95+
desc: &TestDesc,
96+
exec_time: Option<&TestExecTime>
97+
) -> io::Result<()> {
98+
if let (Some(opts), Some(time)) = (self.time_options, exec_time) {
99+
let time_str = format!(" <{}>", time);
100+
101+
let color = if opts.colored {
102+
if opts.is_critical(desc, time) {
103+
Some(term::color::RED)
104+
} else if opts.is_warn(desc, time) {
105+
Some(term::color::YELLOW)
106+
} else {
107+
None
108+
}
109+
} else {
110+
None
111+
};
112+
113+
match color {
114+
Some(color) => self.write_pretty(&time_str, color)?,
115+
None => self.write_plain(&time_str)?
116+
}
117+
}
118+
119+
Ok(())
120+
}
121+
122+
fn write_results(
123+
&mut self,
124+
inputs: &Vec<(TestDesc, Vec<u8>)>,
125+
results_type: &str
126+
) -> io::Result<()> {
127+
let results_out_str = format!("\n{}:\n", results_type);
128+
129+
self.write_plain(&results_out_str)?;
130+
131+
let mut results = Vec::new();
94132
let mut stdouts = String::new();
95-
for &(ref f, ref stdout) in &state.not_failures {
96-
successes.push(f.name.to_string());
133+
for &(ref f, ref stdout) in inputs {
134+
results.push(f.name.to_string());
97135
if !stdout.is_empty() {
98136
stdouts.push_str(&format!("---- {} stdout ----\n", f.name));
99137
let output = String::from_utf8_lossy(stdout);
@@ -106,38 +144,24 @@ impl<T: Write> PrettyFormatter<T> {
106144
self.write_plain(&stdouts)?;
107145
}
108146

109-
self.write_plain("\nsuccesses:\n")?;
110-
successes.sort();
111-
for name in &successes {
147+
self.write_plain(&results_out_str)?;
148+
results.sort();
149+
for name in &results {
112150
self.write_plain(&format!(" {}\n", name))?;
113151
}
114152
Ok(())
115153
}
116154

155+
pub fn write_successes(&mut self, state: &ConsoleTestState) -> io::Result<()> {
156+
self.write_results(&state.not_failures, "successes")
157+
}
158+
117159
pub fn write_failures(&mut self, state: &ConsoleTestState) -> io::Result<()> {
118-
self.write_plain("\nfailures:\n")?;
119-
let mut failures = Vec::new();
120-
let mut fail_out = String::new();
121-
for &(ref f, ref stdout) in &state.failures {
122-
failures.push(f.name.to_string());
123-
if !stdout.is_empty() {
124-
fail_out.push_str(&format!("---- {} stdout ----\n", f.name));
125-
let output = String::from_utf8_lossy(stdout);
126-
fail_out.push_str(&output);
127-
fail_out.push_str("\n");
128-
}
129-
}
130-
if !fail_out.is_empty() {
131-
self.write_plain("\n")?;
132-
self.write_plain(&fail_out)?;
133-
}
160+
self.write_results(&state.failures, "failures")
161+
}
134162

135-
self.write_plain("\nfailures:\n")?;
136-
failures.sort();
137-
for name in &failures {
138-
self.write_plain(&format!(" {}\n", name))?;
139-
}
140-
Ok(())
163+
pub fn write_time_failures(&mut self, state: &ConsoleTestState) -> io::Result<()> {
164+
self.write_results(&state.time_failures, "failures (time limit exceeded)")
141165
}
142166

143167
fn write_test_name(&mut self, desc: &TestDesc) -> io::Result<()> {
@@ -179,15 +203,19 @@ impl<T: Write> OutputFormatter for PrettyFormatter<T> {
179203
}
180204

181205
match *result {
182-
TrOk => self.write_ok(exec_time),
183-
TrFailed | TrFailedMsg(_) => self.write_failed(exec_time),
184-
TrIgnored => self.write_ignored(exec_time),
185-
TrAllowedFail => self.write_allowed_fail(exec_time),
206+
TrOk => self.write_ok()?,
207+
TrFailed | TrFailedMsg(_) => self.write_failed()?,
208+
TrIgnored => self.write_ignored()?,
209+
TrAllowedFail => self.write_allowed_fail()?,
186210
TrBench(ref bs) => {
187211
self.write_bench()?;
188-
self.write_plain(&format!(": {}\n", fmt_bench_samples(bs)))
212+
self.write_plain(&format!(": {}", fmt_bench_samples(bs)))?;
189213
}
214+
TrTimedFail => self.write_time_failed()?,
190215
}
216+
217+
self.write_time(desc, exec_time)?;
218+
self.write_plain("\n")
191219
}
192220

193221
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
@@ -207,7 +235,13 @@ impl<T: Write> OutputFormatter for PrettyFormatter<T> {
207235
}
208236
let success = state.failed == 0;
209237
if !success {
210-
self.write_failures(state)?;
238+
if !state.failures.is_empty() {
239+
self.write_failures(state)?;
240+
}
241+
242+
if !state.time_failures.is_empty() {
243+
self.write_time_failures(state)?;
244+
}
211245
}
212246

213247
self.write_plain("\ntest result: ")?;

src/libtest/formatters/terse.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ impl<T: Write> OutputFormatter for TerseFormatter<T> {
180180
) -> io::Result<()> {
181181
match *result {
182182
TrOk => self.write_ok(),
183-
TrFailed | TrFailedMsg(_) => self.write_failed(),
183+
TrFailed | TrFailedMsg(_) | TrTimedFail => self.write_failed(),
184184
TrIgnored => self.write_ignored(),
185185
TrAllowedFail => self.write_allowed_fail(),
186186
TrBench(ref bs) => {

0 commit comments

Comments
 (0)