fix(test): capture inherited stdout and stderr for subprocesses in test output (#14395)

This commit is contained in:
David Sherret 2022-04-26 14:46:49 -04:00 committed by GitHub
parent f07f246ae8
commit a1b4aa2ae6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 84 additions and 33 deletions

View File

@ -760,9 +760,8 @@ impl test::TestReporter for LspTestReporter {
.and_then(|v| v.last().map(|td| td.into()))
});
let value = match output {
test::TestOutput::PrintStdout(value)
| test::TestOutput::PrintStderr(value) => value.replace('\n', "\r\n"),
test::TestOutput::Stdout(bytes) | test::TestOutput::Stderr(bytes) => {
test::TestOutput::String(value) => value.replace('\n', "\r\n"),
test::TestOutput::Bytes(bytes) => {
String::from_utf8_lossy(bytes).replace('\n', "\r\n")
}
};

View File

@ -79,12 +79,8 @@ pub fn create_stdout_stderr_pipes(
let (stdout_reader, stdout_writer) = os_pipe::pipe().unwrap();
let (stderr_reader, stderr_writer) = os_pipe::pipe().unwrap();
start_output_redirect_thread(stdout_reader, sender.clone(), |bytes| {
TestOutput::Stdout(bytes)
});
start_output_redirect_thread(stderr_reader, sender, |bytes| {
TestOutput::Stderr(bytes)
});
start_output_redirect_thread(stdout_reader, sender.clone());
start_output_redirect_thread(stderr_reader, sender);
(stdout_writer, stderr_writer)
}
@ -92,7 +88,6 @@ pub fn create_stdout_stderr_pipes(
fn start_output_redirect_thread(
mut pipe_reader: os_pipe::PipeReader,
sender: UnboundedSender<TestEvent>,
map_test_output: impl Fn(Vec<u8>) -> TestOutput + Send + 'static,
) {
tokio::task::spawn_blocking(move || loop {
let mut buffer = [0; 512];
@ -101,7 +96,9 @@ fn start_output_redirect_thread(
Ok(size) => size,
};
if sender
.send(TestEvent::Output(map_test_output(buffer[0..size].to_vec())))
.send(TestEvent::Output(TestOutput::Bytes(
buffer[0..size].to_vec(),
)))
.is_err()
{
break;
@ -170,14 +167,10 @@ fn op_dispatch_test_event(
pub fn op_print(
state: &mut OpState,
msg: String,
is_err: bool,
_is_err: bool,
) -> Result<(), AnyError> {
let sender = state.borrow::<UnboundedSender<TestEvent>>().clone();
let msg = if is_err {
TestOutput::PrintStderr(msg)
} else {
TestOutput::PrintStdout(msg)
};
let msg = TestOutput::String(msg);
sender.send(TestEvent::Output(msg)).ok();
Ok(())
}

View File

@ -302,6 +302,12 @@ itest!(no_prompt_with_denied_perms {
output: "test/no_prompt_with_denied_perms.out",
});
itest!(captured_subprocess_output {
args: "test --allow-run --allow-read --unstable test/captured_subprocess_output.ts",
exit_code: 0,
output: "test/captured_subprocess_output.out",
});
#[test]
fn recursive_permissions_pledge() {
let output = util::deno_cmd()

View File

@ -0,0 +1,17 @@
[WILDCARD]
running 1 test from [WILDCARD]/captured_subprocess_output.ts
output ...
------- output -------
1
2
3
4
5
6
7
8
----- output end -----
ok ([WILDCARD]s)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]s)
[WILDCARD]

View File

@ -0,0 +1,23 @@
Deno.test("output", async () => {
const p = Deno.run({
cmd: [Deno.execPath(), "eval", "console.log(1); console.error(2);"],
});
await p.status();
await p.close();
Deno.spawnSync(Deno.execPath(), {
args: ["eval", "console.log(3); console.error(4);"],
stdout: "inherit",
stderr: "inherit",
});
await Deno.spawn(Deno.execPath(), {
args: ["eval", "console.log(5); console.error(6);"],
stdout: "inherit",
stderr: "inherit",
});
const c = await Deno.spawnChild(Deno.execPath(), {
args: ["eval", "console.log(7); console.error(8);"],
stdout: "inherit",
stderr: "inherit",
});
await c.status;
});

View File

@ -83,10 +83,8 @@ pub struct TestDescription {
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TestOutput {
PrintStdout(String),
PrintStderr(String),
Stdout(Vec<u8>),
Stderr(Vec<u8>),
String(String),
Bytes(Vec<u8>),
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
@ -351,18 +349,14 @@ impl TestReporter for PrettyTestReporter {
println!("{}", colors::gray("------- output -------"));
}
match output {
TestOutput::PrintStdout(line) => {
TestOutput::String(line) => {
// output everything to stdout in order to prevent
// stdout and stderr racing
print!("{}", line)
}
TestOutput::PrintStderr(line) => {
eprint!("{}", line)
}
TestOutput::Stdout(bytes) => {
TestOutput::Bytes(bytes) => {
std::io::stdout().write_all(bytes).unwrap();
}
TestOutput::Stderr(bytes) => {
std::io::stderr().write_all(bytes).unwrap();
}
}
}

View File

@ -186,8 +186,20 @@ fn op_run(state: &mut OpState, run_args: RunArgs) -> Result<RunInfo, AnyError> {
// TODO: make this work with other resources, eg. sockets
c.stdin(run_args.stdin.as_stdio(state)?);
c.stdout(run_args.stdout.as_stdio(state)?);
c.stderr(run_args.stderr.as_stdio(state)?);
c.stdout(
match run_args.stdout {
StdioOrRid::Stdio(Stdio::Inherit) => StdioOrRid::Rid(1),
value => value,
}
.as_stdio(state)?,
);
c.stderr(
match run_args.stderr {
StdioOrRid::Stdio(Stdio::Inherit) => StdioOrRid::Rid(2),
value => value,
}
.as_stdio(state)?,
);
// We want to kill child when it's closed
c.kill_on_drop(true);

View File

@ -4,6 +4,7 @@ use super::io::ChildStderrResource;
use super::io::ChildStdinResource;
use super::io::ChildStdoutResource;
use super::process::Stdio;
use super::process::StdioOrRid;
use crate::permissions::Permissions;
use deno_core::error::AnyError;
use deno_core::op;
@ -147,8 +148,14 @@ fn create_command(
}
command.stdin(args.stdio.stdin.as_stdio());
command.stdout(args.stdio.stdout.as_stdio());
command.stderr(args.stdio.stderr.as_stdio());
command.stdout(match args.stdio.stdout {
Stdio::Inherit => StdioOrRid::Rid(1).as_stdio(state)?,
value => value.as_stdio(),
});
command.stderr(match args.stdio.stderr {
Stdio::Inherit => StdioOrRid::Rid(2).as_stdio(state)?,
value => value.as_stdio(),
});
Ok(command)
}