Skip to content

Commit 9341d1f

Browse files
authored
process_wrapper: write all output from rustc if json parsing fails. (#2234)
Previously we were only reporting the first line of any error rustc may emit when it panic'd or otherwise unexpectedly stopped outputting json.
1 parent 7b101e0 commit 9341d1f

File tree

5 files changed

+193
-72
lines changed

5 files changed

+193
-72
lines changed

test/process_wrapper/fake_rustc.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
//! This binary mocks the output of rustc when run with `--error-format=json` and `--json=artifacts`.
22
33
fn main() {
4+
let should_error = std::env::args().any(|arg| arg == "error");
5+
46
eprintln!(r#"{{"rendered": "should be\nin output"}}"#);
7+
if should_error {
8+
eprintln!("ERROR!\nthis should all\nappear in output.");
9+
std::process::exit(1);
10+
}
511
eprintln!(r#"{{"emit": "metadata"}}"#);
612
std::thread::sleep(std::time::Duration::from_secs(1));
713
eprintln!(r#"{{"rendered": "should not be in output"}}"#);

test/process_wrapper/rustc_quit_on_rmeta.rs

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ mod test {
99
/// fake_rustc runs the fake_rustc binary under process_wrapper with the specified
1010
/// process wrapper arguments. No arguments are passed to fake_rustc itself.
1111
///
12-
fn fake_rustc(process_wrapper_args: &[&'static str]) -> String {
12+
fn fake_rustc(
13+
process_wrapper_args: &[&'static str],
14+
fake_rustc_args: &[&'static str],
15+
should_succeed: bool,
16+
) -> String {
1317
let r = Runfiles::create().unwrap();
1418
let fake_rustc = r.rlocation(
1519
[
@@ -45,27 +49,34 @@ mod test {
4549
.args(process_wrapper_args)
4650
.arg("--")
4751
.arg(fake_rustc)
52+
.args(fake_rustc_args)
4853
.output()
4954
.unwrap();
5055

51-
assert!(
52-
output.status.success(),
53-
"unable to run process_wrapper: {} {}",
54-
str::from_utf8(&output.stdout).unwrap(),
55-
str::from_utf8(&output.stderr).unwrap(),
56-
);
56+
if should_succeed {
57+
assert!(
58+
output.status.success(),
59+
"unable to run process_wrapper: {} {}",
60+
str::from_utf8(&output.stdout).unwrap(),
61+
str::from_utf8(&output.stderr).unwrap(),
62+
);
63+
}
5764

5865
String::from_utf8(output.stderr).unwrap()
5966
}
6067

6168
#[test]
6269
fn test_rustc_quit_on_rmeta_quits() {
63-
let out_content = fake_rustc(&[
64-
"--rustc-quit-on-rmeta",
65-
"true",
66-
"--rustc-output-format",
67-
"rendered",
68-
]);
70+
let out_content = fake_rustc(
71+
&[
72+
"--rustc-quit-on-rmeta",
73+
"true",
74+
"--rustc-output-format",
75+
"rendered",
76+
],
77+
&[],
78+
true,
79+
);
6980
assert!(
7081
!out_content.contains("should not be in output"),
7182
"output should not contain 'should not be in output' but did",
@@ -74,12 +85,16 @@ mod test {
7485

7586
#[test]
7687
fn test_rustc_quit_on_rmeta_output_json() {
77-
let json_content = fake_rustc(&[
78-
"--rustc-quit-on-rmeta",
79-
"true",
80-
"--rustc-output-format",
81-
"json",
82-
]);
88+
let json_content = fake_rustc(
89+
&[
90+
"--rustc-quit-on-rmeta",
91+
"true",
92+
"--rustc-output-format",
93+
"json",
94+
],
95+
&[],
96+
true,
97+
);
8398
assert_eq!(
8499
json_content,
85100
concat!(r#"{"rendered": "should be\nin output"}"#, "\n")
@@ -88,12 +103,30 @@ mod test {
88103

89104
#[test]
90105
fn test_rustc_quit_on_rmeta_output_rendered() {
91-
let rendered_content = fake_rustc(&[
92-
"--rustc-quit-on-rmeta",
93-
"true",
94-
"--rustc-output-format",
95-
"rendered",
96-
]);
106+
let rendered_content = fake_rustc(
107+
&[
108+
"--rustc-quit-on-rmeta",
109+
"true",
110+
"--rustc-output-format",
111+
"rendered",
112+
],
113+
&[],
114+
true,
115+
);
97116
assert_eq!(rendered_content, "should be\nin output");
98117
}
118+
119+
#[test]
120+
fn test_rustc_panic() {
121+
let rendered_content = fake_rustc(&["--rustc-output-format", "json"], &["error"], false);
122+
assert_eq!(
123+
rendered_content,
124+
r#"{"rendered": "should be\nin output"}
125+
ERROR!
126+
this should all
127+
appear in output.
128+
Error: ProcessWrapperError("failed to process stderr: error parsing rustc output as json")
129+
"#
130+
);
131+
}
99132
}

util/process_wrapper/main.rs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod output;
1818
mod rustc;
1919
mod util;
2020

21+
use std::fmt;
2122
use std::fs::{copy, OpenOptions};
2223
use std::io;
2324
use std::process::{exit, Command, ExitStatus, Stdio};
@@ -49,11 +50,19 @@ fn status_code(status: ExitStatus, was_killed: bool) -> i32 {
4950
}
5051
}
5152

52-
fn main() {
53-
let opts = match options() {
54-
Err(err) => panic!("process wrapper error: {}", err),
55-
Ok(v) => v,
56-
};
53+
#[derive(Debug)]
54+
struct ProcessWrapperError(String);
55+
56+
impl fmt::Display for ProcessWrapperError {
57+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58+
write!(f, "process wrapper error: {}", self.0)
59+
}
60+
}
61+
62+
impl std::error::Error for ProcessWrapperError {}
63+
64+
fn main() -> Result<(), ProcessWrapperError> {
65+
let opts = options().map_err(|e| ProcessWrapperError(e.to_string()))?;
5766

5867
let mut child = Command::new(opts.executable)
5968
.args(opts.child_arguments)
@@ -65,14 +74,14 @@ fn main() {
6574
.truncate(true)
6675
.write(true)
6776
.open(stdout_file)
68-
.expect("process wrapper error: unable to open stdout file")
77+
.map_err(|e| ProcessWrapperError(format!("unable to open stdout file: {}", e)))?
6978
.into()
7079
} else {
7180
Stdio::inherit()
7281
})
7382
.stderr(Stdio::piped())
7483
.spawn()
75-
.expect("process wrapper error: failed to spawn child process");
84+
.map_err(|e| ProcessWrapperError(format!("failed to spawn child process: {}", e)))?;
7685

7786
let mut stderr: Box<dyn io::Write> = if let Some(stderr_file) = opts.stderr_file {
7887
Box::new(
@@ -81,13 +90,15 @@ fn main() {
8190
.truncate(true)
8291
.write(true)
8392
.open(stderr_file)
84-
.expect("process wrapper error: unable to open stderr file"),
93+
.map_err(|e| ProcessWrapperError(format!("unable to open stderr file: {}", e)))?,
8594
)
8695
} else {
8796
Box::new(io::stderr())
8897
};
8998

90-
let mut child_stderr = child.stderr.take().unwrap();
99+
let mut child_stderr = child.stderr.take().ok_or(ProcessWrapperError(
100+
"unable to get child stderr".to_string(),
101+
))?;
91102

92103
let mut was_killed = false;
93104
let result = if let Some(format) = opts.rustc_output_format {
@@ -112,13 +123,15 @@ fn main() {
112123
result
113124
} else {
114125
// Process output normally by forwarding stderr
115-
process_output(&mut child_stderr, stderr.as_mut(), LineOutput::Message)
126+
process_output(&mut child_stderr, stderr.as_mut(), move |line| {
127+
Ok(LineOutput::Message(line))
128+
})
116129
};
117-
result.expect("process wrapper error: failed to process stderr");
130+
result.map_err(|e| ProcessWrapperError(format!("failed to process stderr: {}", e)))?;
118131

119132
let status = child
120133
.wait()
121-
.expect("process wrapper error: failed to wait for child process");
134+
.map_err(|e| ProcessWrapperError(format!("failed to wait for child process: {}", e)))?;
122135
// If the child process is rustc and is killed after metadata generation, that's also a success.
123136
let code = status_code(status, was_killed);
124137
let success = code == 0;
@@ -128,15 +141,15 @@ fn main() {
128141
.create(true)
129142
.write(true)
130143
.open(tf)
131-
.expect("process wrapper error: failed to create touch file");
144+
.map_err(|e| ProcessWrapperError(format!("failed to create touch file: {}", e)))?;
132145
}
133146
if let Some((copy_source, copy_dest)) = opts.copy_output {
134-
copy(&copy_source, &copy_dest).unwrap_or_else(|_| {
135-
panic!(
136-
"process wrapper error: failed to copy {} into {}",
137-
copy_source, copy_dest
138-
)
139-
});
147+
copy(&copy_source, &copy_dest).map_err(|e| {
148+
ProcessWrapperError(format!(
149+
"failed to copy {} into {}: {}",
150+
copy_source, copy_dest, e
151+
))
152+
})?;
140153
}
141154
}
142155

util/process_wrapper/output.rs

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use std::error;
16+
use std::fmt;
1517
use std::io::{self, prelude::*};
1618

1719
/// LineOutput tells process_output what to do when a line is processed.
@@ -27,30 +29,101 @@ pub(crate) enum LineOutput {
2729
Terminate,
2830
}
2931

32+
#[derive(Debug)]
33+
pub(crate) enum ProcessError {
34+
IO(io::Error),
35+
Process(String),
36+
}
37+
38+
impl fmt::Display for ProcessError {
39+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40+
match self {
41+
Self::IO(e) => write!(f, "{}", e),
42+
Self::Process(p) => write!(f, "{}", p),
43+
}
44+
}
45+
}
46+
47+
impl error::Error for ProcessError {}
48+
49+
impl From<io::Error> for ProcessError {
50+
fn from(err: io::Error) -> Self {
51+
Self::IO(err)
52+
}
53+
}
54+
55+
impl From<String> for ProcessError {
56+
fn from(s: String) -> Self {
57+
Self::Process(s)
58+
}
59+
}
60+
61+
pub(crate) type ProcessResult = Result<(), ProcessError>;
62+
63+
/// If this is Err we assume there were issues processing the line.
64+
/// We will print the error returned and all following lines without
65+
/// any more processing.
66+
pub(crate) type LineResult = Result<LineOutput, String>;
67+
3068
/// process_output reads lines from read_end and invokes process_line on each.
3169
/// Depending on the result of process_line, the modified message may be written
3270
/// to write_end.
3371
pub(crate) fn process_output<F>(
3472
read_end: &mut dyn Read,
3573
write_end: &mut dyn Write,
3674
mut process_line: F,
37-
) -> io::Result<()>
75+
) -> ProcessResult
3876
where
39-
F: FnMut(String) -> LineOutput,
77+
F: FnMut(String) -> LineResult,
4078
{
4179
let mut reader = io::BufReader::new(read_end);
4280
let mut writer = io::LineWriter::new(write_end);
81+
// If there was an error parsing a line failed_on contains the offending line
82+
// and the error message.
83+
let mut failed_on: Option<(String, String)> = None;
4384
loop {
4485
let mut line = String::new();
4586
let read_bytes = reader.read_line(&mut line)?;
4687
if read_bytes == 0 {
4788
break;
4889
}
49-
match process_line(line) {
50-
LineOutput::Message(to_write) => writer.write_all(to_write.as_bytes())?,
51-
LineOutput::Skip => {}
52-
LineOutput::Terminate => return Ok(()),
90+
match process_line(line.clone()) {
91+
Ok(LineOutput::Message(to_write)) => writer.write_all(to_write.as_bytes())?,
92+
Ok(LineOutput::Skip) => {}
93+
Ok(LineOutput::Terminate) => return Ok(()),
94+
Err(msg) => {
95+
failed_on = Some((line, msg));
96+
break;
97+
}
5398
};
5499
}
100+
101+
// If we encountered an error processing a line we want to flush the rest of
102+
// reader into writer and return the error.
103+
if let Some((line, msg)) = failed_on {
104+
writer.write_all(line.as_bytes())?;
105+
io::copy(&mut reader, &mut writer)?;
106+
return Err(ProcessError::Process(msg));
107+
}
55108
Ok(())
56109
}
110+
111+
#[cfg(test)]
112+
mod test {
113+
use super::*;
114+
115+
#[test]
116+
fn test_json_parsing_error() {
117+
let mut input = io::Cursor::new(b"ok text\nsome more\nerror text");
118+
let mut output: Vec<u8> = vec![];
119+
let result = process_output(&mut input, &mut output, move |line| {
120+
if line == "ok text\n" {
121+
Ok(LineOutput::Skip)
122+
} else {
123+
Err("error parsing output".to_owned())
124+
}
125+
});
126+
assert!(result.is_err());
127+
assert_eq!(&output, b"some more\nerror text");
128+
}
129+
}

0 commit comments

Comments
 (0)