Eval external command result immediately when using do command with -c (#6645)

* make capture error works better in do command

* remove into string test because we have no way to generate Value::Error for now
This commit is contained in:
WindSoilder 2022-09-30 20:14:02 +08:00 committed by GitHub
parent 6f59167960
commit 530ff3893e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 17 deletions

View File

@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
Value,
};
#[derive(Clone)]
@ -102,9 +103,75 @@ impl Command for Do {
Err(_) => Ok(PipelineData::new(call.head)),
}
} else if capture_errors {
// collect stdout and stderr and check exit code.
// if exit code is not 0, return back ShellError.
match result {
Ok(x) => Ok(x),
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
Ok(PipelineData::ExternalStream {
stdout,
stderr,
exit_code,
span,
metadata,
}) => {
// collect all output first.
let mut stderr_ctrlc = None;
let stderr_msg = match stderr {
None => "".to_string(),
Some(stderr_stream) => {
stderr_ctrlc = stderr_stream.ctrlc.clone();
stderr_stream.into_string().map(|s| s.item)?
}
};
let mut stdout_ctrlc = None;
let stdout_msg = match stdout {
None => "".to_string(),
Some(stdout_stream) => {
stdout_ctrlc = stdout_stream.ctrlc.clone();
stdout_stream.into_string().map(|s| s.item)?
}
};
let mut exit_code_ctrlc = None;
let exit_code: Vec<Value> = match exit_code {
None => vec![],
Some(exit_code_stream) => {
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
exit_code_stream.into_iter().collect()
}
};
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
return Err(ShellError::ExternalCommand(
"External command runs to failed".to_string(),
stderr_msg,
span,
));
}
}
// construct pipeline data to our caller
Ok(PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(vec![Ok(stdout_msg.into_bytes())].into_iter()),
stdout_ctrlc,
span,
)),
stderr: Some(RawStream::new(
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
stderr_ctrlc,
span,
)),
exit_code: Some(ListStream::from_stream(
exit_code.into_iter(),
exit_code_ctrlc,
)),
span,
metadata,
})
}
Ok(other) => Ok(other),
Err(e) => Err(e),
}
} else {
result

View File

@ -5,11 +5,47 @@ fn capture_errors_works() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {$env.use} | describe
do -c {$env.use}
"#
));
assert_eq!(actual.out, "error");
assert!(actual.err.contains("column_not_found"));
}
#[test]
fn capture_errors_works_for_external() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {nu --testbin fail}
"#
));
assert!(actual.err.contains("External command runs to failed"));
assert_eq!(actual.out, "");
}
#[test]
fn capture_errors_works_for_external_with_pipeline() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {nu --testbin fail} | echo `text`
"#
));
assert!(actual.err.contains("External command runs to failed"));
assert_eq!(actual.out, "");
}
#[test]
fn capture_errors_works_for_external_with_semicolon() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {nu --testbin fail}; echo `text`
"#
));
assert!(actual.err.contains("External command runs to failed"));
assert_eq!(actual.out, "");
}
#[test]

View File

@ -172,18 +172,6 @@ fn from_nothing() {
assert_eq!(actual.out, "");
}
#[test]
fn from_error() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {$env.use} | into string
"#
));
assert_eq!(actual.out, "nu::shell::column_not_found");
}
#[test]
fn int_into_string() {
let actual = nu!(