Skip to content

Commit 6d20265

Browse files
authored
Rollup merge of rust-lang#62600 - emmericp:libtest-add-show-output, r=gnzlbg
libtest: add --show-output flag to print stdout of successful tests This pull request adds a new flag `--show-output` for tests to show the output of successful tests. For most formatters this was already supported just not exposed via the CLI (apparently only used by `librustdoc`). I've also added support for this option in the JSON formatter. This kind of fixes rust-lang#54669 which wants `--format json` to work with `--nocapture`, which is... well, impossible. What this issue really calls for is `--show-output` as implemented here.
2 parents 0444b9f + 409a41d commit 6d20265

File tree

12 files changed

+101
-47
lines changed

12 files changed

+101
-47
lines changed

src/librustdoc/markdown.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,6 @@ pub fn test(mut options: Options, diag: &errors::Handler) -> i32 {
154154

155155
options.test_args.insert(0, "rustdoctest".to_string());
156156
testing::test_main(&options.test_args, collector.tests,
157-
testing::Options::new().display_output(options.display_warnings));
157+
Some(testing::Options::new().display_output(options.display_warnings)));
158158
0
159159
}

src/librustdoc/test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ pub fn run(options: Options) -> i32 {
120120
testing::test_main(
121121
&test_args,
122122
tests,
123-
testing::Options::new().display_output(display_warnings)
123+
Some(testing::Options::new().display_output(display_warnings))
124124
);
125125

126126
0

src/libtest/formatters/json.rs

+40-31
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,57 @@ impl<T: Write> JsonFormatter<T> {
99
Self { out }
1010
}
1111

12-
fn write_message(&mut self, s: &str) -> io::Result<()> {
12+
fn writeln_message(&mut self, s: &str) -> io::Result<()> {
1313
assert!(!s.contains('\n'));
1414

1515
self.out.write_all(s.as_ref())?;
1616
self.out.write_all(b"\n")
1717
}
1818

19+
fn write_message(&mut self, s: &str) -> io::Result<()> {
20+
assert!(!s.contains('\n'));
21+
22+
self.out.write_all(s.as_ref())
23+
}
24+
1925
fn write_event(
2026
&mut self,
2127
ty: &str,
2228
name: &str,
2329
evt: &str,
24-
extra: Option<String>,
30+
stdout: Option<Cow<'_, str>>,
31+
extra: Option<&str>,
2532
) -> io::Result<()> {
26-
if let Some(extras) = extra {
33+
self.write_message(&*format!(
34+
r#"{{ "type": "{}", "name": "{}", "event": "{}""#,
35+
ty, name, evt
36+
))?;
37+
if let Some(stdout) = stdout {
2738
self.write_message(&*format!(
28-
r#"{{ "type": "{}", "name": "{}", "event": "{}", {} }}"#,
29-
ty, name, evt, extras
30-
))
31-
} else {
39+
r#", "stdout": "{}""#,
40+
EscapedString(stdout)
41+
))?;
42+
}
43+
if let Some(extra) = extra {
3244
self.write_message(&*format!(
33-
r#"{{ "type": "{}", "name": "{}", "event": "{}" }}"#,
34-
ty, name, evt
35-
))
45+
r#", {}"#,
46+
extra
47+
))?;
3648
}
49+
self.writeln_message(" }")
3750
}
3851
}
3952

4053
impl<T: Write> OutputFormatter for JsonFormatter<T> {
4154
fn write_run_start(&mut self, test_count: usize) -> io::Result<()> {
42-
self.write_message(&*format!(
55+
self.writeln_message(&*format!(
4356
r#"{{ "type": "suite", "event": "started", "test_count": {} }}"#,
4457
test_count
4558
))
4659
}
4760

4861
fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> {
49-
self.write_message(&*format!(
62+
self.writeln_message(&*format!(
5063
r#"{{ "type": "test", "event": "started", "name": "{}" }}"#,
5164
desc.name
5265
))
@@ -57,34 +70,30 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
5770
desc: &TestDesc,
5871
result: &TestResult,
5972
stdout: &[u8],
73+
state: &ConsoleTestState,
6074
) -> io::Result<()> {
75+
let stdout = if (state.options.display_output || *result != TrOk) && stdout.len() > 0 {
76+
Some(String::from_utf8_lossy(stdout))
77+
} else {
78+
None
79+
};
6180
match *result {
62-
TrOk => self.write_event("test", desc.name.as_slice(), "ok", None),
63-
64-
TrFailed => {
65-
let extra_data = if stdout.len() > 0 {
66-
Some(format!(
67-
r#""stdout": "{}""#,
68-
EscapedString(String::from_utf8_lossy(stdout))
69-
))
70-
} else {
71-
None
72-
};
81+
TrOk => self.write_event("test", desc.name.as_slice(), "ok", stdout, None),
7382

74-
self.write_event("test", desc.name.as_slice(), "failed", extra_data)
75-
}
83+
TrFailed => self.write_event("test", desc.name.as_slice(), "failed", stdout, None),
7684

7785
TrFailedMsg(ref m) => self.write_event(
7886
"test",
7987
desc.name.as_slice(),
8088
"failed",
81-
Some(format!(r#""message": "{}""#, EscapedString(m))),
89+
stdout,
90+
Some(&*format!(r#""message": "{}""#, EscapedString(m))),
8291
),
8392

84-
TrIgnored => self.write_event("test", desc.name.as_slice(), "ignored", None),
93+
TrIgnored => self.write_event("test", desc.name.as_slice(), "ignored", stdout, None),
8594

8695
TrAllowedFail => {
87-
self.write_event("test", desc.name.as_slice(), "allowed_failure", None)
96+
self.write_event("test", desc.name.as_slice(), "allowed_failure", stdout, None)
8897
}
8998

9099
TrBench(ref bs) => {
@@ -105,20 +114,20 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
105114
desc.name, median, deviation, mbps
106115
);
107116

108-
self.write_message(&*line)
117+
self.writeln_message(&*line)
109118
}
110119
}
111120
}
112121

113122
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
114-
self.write_message(&*format!(
123+
self.writeln_message(&*format!(
115124
r#"{{ "type": "test", "event": "timeout", "name": "{}" }}"#,
116125
desc.name
117126
))
118127
}
119128

120129
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
121-
self.write_message(&*format!(
130+
self.writeln_message(&*format!(
122131
"{{ \"type\": \"suite\", \
123132
\"event\": \"{}\", \
124133
\"passed\": {}, \

src/libtest/formatters/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub(crate) trait OutputFormatter {
1717
desc: &TestDesc,
1818
result: &TestResult,
1919
stdout: &[u8],
20+
state: &ConsoleTestState,
2021
) -> io::Result<()>;
2122
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool>;
2223
}

src/libtest/formatters/pretty.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,13 @@ impl<T: Write> OutputFormatter for PrettyFormatter<T> {
162162
Ok(())
163163
}
164164

165-
fn write_result(&mut self, desc: &TestDesc, result: &TestResult, _: &[u8]) -> io::Result<()> {
165+
fn write_result(
166+
&mut self,
167+
desc: &TestDesc,
168+
result: &TestResult,
169+
_: &[u8],
170+
_: &ConsoleTestState,
171+
) -> io::Result<()> {
166172
if self.is_multithreaded {
167173
self.write_test_name(desc)?;
168174
}

src/libtest/formatters/terse.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,13 @@ impl<T: Write> OutputFormatter for TerseFormatter<T> {
170170
Ok(())
171171
}
172172

173-
fn write_result(&mut self, desc: &TestDesc, result: &TestResult, _: &[u8]) -> io::Result<()> {
173+
fn write_result(
174+
&mut self,
175+
desc: &TestDesc,
176+
result: &TestResult,
177+
_: &[u8],
178+
_: &ConsoleTestState,
179+
) -> io::Result<()> {
174180
match *result {
175181
TrOk => self.write_ok(),
176182
TrFailed | TrFailedMsg(_) => self.write_failed(),

src/libtest/lib.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ impl Options {
274274

275275
// The default console test runner. It accepts the command line
276276
// arguments and a vector of test_descs.
277-
pub fn test_main(args: &[String], tests: Vec<TestDescAndFn>, options: Options) {
277+
pub fn test_main(args: &[String], tests: Vec<TestDescAndFn>, options: Option<Options>) {
278278
let mut opts = match parse_opts(args) {
279279
Some(Ok(o)) => o,
280280
Some(Err(msg)) => {
@@ -283,8 +283,9 @@ pub fn test_main(args: &[String], tests: Vec<TestDescAndFn>, options: Options) {
283283
}
284284
None => return,
285285
};
286-
287-
opts.options = options;
286+
if let Some(options) = options {
287+
opts.options = options;
288+
}
288289
if opts.list {
289290
if let Err(e) = list_tests_console(&opts, tests) {
290291
eprintln!("error: io error when listing tests: {:?}", e);
@@ -325,7 +326,7 @@ pub fn test_main_static(tests: &[&TestDescAndFn]) {
325326
_ => panic!("non-static tests passed to test::test_main_static"),
326327
})
327328
.collect();
328-
test_main(&args, owned_tests, Options::new())
329+
test_main(&args, owned_tests, None)
329330
}
330331

331332
/// Invoked when unit tests terminate. Should panic if the unit
@@ -448,6 +449,11 @@ fn optgroups() -> getopts::Options {
448449
json = Output a json document",
449450
"pretty|terse|json",
450451
)
452+
.optflag(
453+
"",
454+
"show-output",
455+
"Show captured stdout of successful tests"
456+
)
451457
.optopt(
452458
"Z",
453459
"",
@@ -647,7 +653,7 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
647653
format,
648654
test_threads,
649655
skip: matches.opt_strs("skip"),
650-
options: Options::new(),
656+
options: Options::new().display_output(matches.opt_present("show-output")),
651657
};
652658

653659
Some(Ok(test_opts))
@@ -880,7 +886,7 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu
880886
TeTimeout(ref test) => out.write_timeout(test),
881887
TeResult(test, result, stdout) => {
882888
st.write_log_result(&test, &result)?;
883-
out.write_result(&test, &result, &*stdout)?;
889+
out.write_result(&test, &result, &*stdout, &st)?;
884890
match result {
885891
TrOk => {
886892
st.passed += 1;

src/libtest/tests.rs

+11
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,17 @@ fn parse_ignored_flag() {
180180
assert_eq!(opts.run_ignored, RunIgnored::Only);
181181
}
182182

183+
#[test]
184+
fn parse_show_output_flag() {
185+
let args = vec![
186+
"progname".to_string(),
187+
"filter".to_string(),
188+
"--show-output".to_string(),
189+
];
190+
let opts = parse_opts(&args).unwrap().unwrap();
191+
assert!(opts.options.display_output);
192+
}
193+
183194
#[test]
184195
fn parse_include_ignored_flag() {
185196
let args = vec![

src/test/run-make-fulldeps/libtest-json/Makefile

+8-4
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
# Test expected libtest's JSON output
44

5-
OUTPUT_FILE := $(TMPDIR)/libtest-json-output.json
5+
OUTPUT_FILE_DEFAULT := $(TMPDIR)/libtest-json-output-default.json
6+
OUTPUT_FILE_STDOUT_SUCCESS := $(TMPDIR)/libtest-json-output-stdout-success.json
67

78
all:
89
$(RUSTC) --test f.rs
9-
RUST_BACKTRACE=0 $(call RUN,f) -Z unstable-options --test-threads=1 --format=json > $(OUTPUT_FILE) || true
10+
RUST_BACKTRACE=0 $(call RUN,f) -Z unstable-options --test-threads=1 --format=json > $(OUTPUT_FILE_DEFAULT) || true
11+
RUST_BACKTRACE=0 $(call RUN,f) -Z unstable-options --test-threads=1 --format=json --show-output > $(OUTPUT_FILE_STDOUT_SUCCESS) || true
1012

11-
cat $(OUTPUT_FILE) | "$(PYTHON)" validate_json.py
13+
cat $(OUTPUT_FILE_DEFAULT) | "$(PYTHON)" validate_json.py
14+
cat $(OUTPUT_FILE_STDOUT_SUCCESS) | "$(PYTHON)" validate_json.py
1215

1316
# Compare to output file
14-
diff output.json $(OUTPUT_FILE)
17+
diff output-default.json $(OUTPUT_FILE_DEFAULT)
18+
diff output-stdout-success.json $(OUTPUT_FILE_STDOUT_SUCCESS)

src/test/run-make-fulldeps/libtest-json/f.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#[test]
22
fn a() {
3+
println!("print from successful test");
34
// Should pass
45
}
56

67
#[test]
78
fn b() {
8-
assert!(false)
9+
assert!(false);
910
}
1011

1112
#[test]

src/test/run-make-fulldeps/libtest-json/output.json renamed to src/test/run-make-fulldeps/libtest-json/output-default.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{ "type": "test", "event": "started", "name": "a" }
33
{ "type": "test", "name": "a", "event": "ok" }
44
{ "type": "test", "event": "started", "name": "b" }
5-
{ "type": "test", "name": "b", "event": "failed", "stdout": "thread 'main' panicked at 'assertion failed: false', f.rs:8:5\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.\n" }
5+
{ "type": "test", "name": "b", "event": "failed", "stdout": "thread 'main' panicked at 'assertion failed: false', f.rs:9:5\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.\n" }
66
{ "type": "test", "event": "started", "name": "c" }
77
{ "type": "test", "name": "c", "event": "ok" }
88
{ "type": "test", "event": "started", "name": "d" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{ "type": "suite", "event": "started", "test_count": 4 }
2+
{ "type": "test", "event": "started", "name": "a" }
3+
{ "type": "test", "name": "a", "event": "ok", "stdout": "print from successful test\n" }
4+
{ "type": "test", "event": "started", "name": "b" }
5+
{ "type": "test", "name": "b", "event": "failed", "stdout": "thread 'main' panicked at 'assertion failed: false', f.rs:9:5\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.\n" }
6+
{ "type": "test", "event": "started", "name": "c" }
7+
{ "type": "test", "name": "c", "event": "ok", "stdout": "thread 'main' panicked at 'assertion failed: false', f.rs:15:5\n" }
8+
{ "type": "test", "event": "started", "name": "d" }
9+
{ "type": "test", "name": "d", "event": "ignored" }
10+
{ "type": "suite", "event": "failed", "passed": 2, "failed": 1, "allowed_fail": 0, "ignored": 1, "measured": 0, "filtered_out": 0 }

0 commit comments

Comments
 (0)