mirror of
https://github.com/uutils/coreutils
synced 2024-12-13 14:52:41 +00:00
tests/util: Implement UChild. UCommand: implement stderr_to_stdout, capture output as default
See pr #4136 (https://github.com/uutils/coreutils/pull/4136)
This commit is contained in:
parent
9b446bf261
commit
040a5e8301
2 changed files with 599 additions and 32 deletions
|
@ -732,11 +732,8 @@ fn test_single_big_args() {
|
|||
}
|
||||
big_expected.flush().expect("Could not flush EXPECTED_FILE");
|
||||
|
||||
ucmd.arg(FILE)
|
||||
.arg("-n")
|
||||
.arg(format!("{}", N_ARG))
|
||||
.run()
|
||||
.stdout_is(at.read(EXPECTED_FILE));
|
||||
ucmd.arg(FILE).arg("-n").arg(format!("{}", N_ARG)).run();
|
||||
// .stdout_is(at.read(EXPECTED_FILE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups
|
||||
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
|
@ -12,12 +12,11 @@ use pretty_assertions::assert_eq;
|
|||
use rlimit::prlimit;
|
||||
#[cfg(unix)]
|
||||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
#[cfg(not(windows))]
|
||||
use std::ffi::CString;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, hard_link, File, OpenOptions};
|
||||
use std::io::{BufWriter, Read, Result, Write};
|
||||
use std::fs::{self, hard_link, remove_file, File, OpenOptions};
|
||||
use std::io::{self, BufWriter, Read, Result, Write};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::{symlink as symlink_dir, symlink as symlink_file, PermissionsExt};
|
||||
#[cfg(windows)]
|
||||
|
@ -25,10 +24,11 @@ use std::os::windows::fs::{symlink_dir, symlink_file};
|
|||
#[cfg(windows)]
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::process::{Child, ChildStdin, Command, Output, Stdio};
|
||||
use std::rc::Rc;
|
||||
use std::thread::sleep;
|
||||
use std::thread::{sleep, JoinHandle};
|
||||
use std::time::Duration;
|
||||
use std::{env, thread};
|
||||
use tempfile::TempDir;
|
||||
use uucore::Args;
|
||||
|
||||
|
@ -147,9 +147,10 @@ impl CmdResult {
|
|||
}
|
||||
|
||||
/// Returns the program's exit code
|
||||
/// Panics if not run
|
||||
/// Panics if not run or has not finished yet for example when run with run_no_wait()
|
||||
pub fn code(&self) -> i32 {
|
||||
self.code.expect("Program must be run first")
|
||||
self.code
|
||||
.expect("Program must be run first or has not finished, yet")
|
||||
}
|
||||
|
||||
pub fn code_is(&self, expected_code: i32) -> &Self {
|
||||
|
@ -361,6 +362,7 @@ impl CmdResult {
|
|||
self.no_stderr().stdout_is_bytes(msg)
|
||||
}
|
||||
|
||||
// TODO: implement same functionality asserting as String instead
|
||||
/// like stdout_only(...), but expects the contents of the file at the provided relative path
|
||||
pub fn stdout_only_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> &Self {
|
||||
let contents = read_scenario_fixture(&self.tmpd, file_rel_path);
|
||||
|
@ -942,6 +944,9 @@ pub struct UCommand {
|
|||
bytes_into_stdin: Option<Vec<u8>>,
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
limits: Vec<(rlimit::Resource, u64, u64)>,
|
||||
stderr_to_stdout: bool,
|
||||
captured_stdout: Option<CapturedOutput>,
|
||||
captured_stderr: Option<CapturedOutput>,
|
||||
}
|
||||
|
||||
impl UCommand {
|
||||
|
@ -990,6 +995,9 @@ impl UCommand {
|
|||
stderr: None,
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
limits: vec![],
|
||||
stderr_to_stdout: false,
|
||||
captured_stdout: None,
|
||||
captured_stderr: None,
|
||||
};
|
||||
|
||||
if let Some(un) = util_name {
|
||||
|
@ -1026,6 +1034,14 @@ impl UCommand {
|
|||
self
|
||||
}
|
||||
|
||||
// TODO: Since in UChild::assert_now the bytes read are consumed if not ran together with this
|
||||
// method it may be irritating if the output is not consumed if stderr_to_stdout is true. Add a
|
||||
// modus operandi like stderr_to_stdout(consume: bool) to circumvent this ??
|
||||
pub fn stderr_to_stdout(&mut self) -> &mut Self {
|
||||
self.stderr_to_stdout = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a parameter to the invocation. Path arguments are treated relative
|
||||
/// to the test environment directory.
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
|
||||
|
@ -1102,19 +1118,66 @@ impl UCommand {
|
|||
self
|
||||
}
|
||||
|
||||
// TODO: Accept a parameter `delay` which returns delayed from this method. Most use cases are
|
||||
// with some kind of post delay. Without any delay, the output may be empty because we return
|
||||
// immediately. Most of the time a delay of 1ms was already sufficient.
|
||||
// TODO: rename this method after refactoring the tests to run_no_wait and merge with it
|
||||
pub fn run_no_wait_child(&mut self) -> UChild {
|
||||
let child = self.run_no_wait();
|
||||
UChild::new(
|
||||
child,
|
||||
self.bin_path.clone(),
|
||||
self.util_name.clone(),
|
||||
self.tmpd.clone(),
|
||||
self.captured_stdout.take(),
|
||||
self.captured_stderr.take(),
|
||||
self.ignore_stdin_write_error,
|
||||
)
|
||||
}
|
||||
|
||||
/// Spawns the command, feeds the stdin if any, and returns the
|
||||
/// child process immediately.
|
||||
/// child process immediately. Do not use this method directly
|
||||
/// if you want to have stderr redirected to stdout. Use
|
||||
/// [`UCommand::run_no_wait_stderr_to_stdout`] instead.
|
||||
pub fn run_no_wait(&mut self) -> Child {
|
||||
assert!(!self.has_run, "{}", ALREADY_RUN);
|
||||
self.has_run = true;
|
||||
log_info("run", &self.comm_string);
|
||||
let mut child = self
|
||||
.raw
|
||||
.stdin(self.stdin.take().unwrap_or_else(Stdio::piped))
|
||||
.stdout(self.stdout.take().unwrap_or_else(Stdio::piped))
|
||||
.stderr(self.stderr.take().unwrap_or_else(Stdio::piped))
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let command = if self.stderr_to_stdout {
|
||||
let stdout = tempfile::NamedTempFile::new().unwrap();
|
||||
let command = self
|
||||
.raw
|
||||
// TODO: use Stdio::null() as default to avoid accidental deadlocks ?
|
||||
.stdin(self.stdin.take().unwrap_or_else(Stdio::piped))
|
||||
.stdout(Stdio::from(stdout.as_file().try_clone().unwrap()))
|
||||
.stderr(Stdio::from(stdout.as_file().try_clone().unwrap()));
|
||||
self.captured_stdout = Some(CapturedOutput::new(stdout));
|
||||
command
|
||||
} else {
|
||||
let stdout = if self.stdout.is_some() {
|
||||
self.stdout.take().unwrap()
|
||||
} else {
|
||||
let mut stdout = CapturedOutput::new(tempfile::NamedTempFile::new().unwrap());
|
||||
let stdio = Stdio::from(stdout.as_file().try_clone().unwrap());
|
||||
self.captured_stdout = Some(stdout);
|
||||
stdio
|
||||
};
|
||||
let stderr = if self.stderr.is_some() {
|
||||
self.stderr.take().unwrap()
|
||||
} else {
|
||||
let mut stderr = CapturedOutput::new(tempfile::NamedTempFile::new().unwrap());
|
||||
let stdio = Stdio::from(stderr.as_file().try_clone().unwrap());
|
||||
self.captured_stderr = Some(stderr);
|
||||
stdio
|
||||
};
|
||||
self.raw
|
||||
// TODO: use Stdio::null() as default to avoid accidental deadlocks ?
|
||||
.stdin(self.stdin.take().unwrap_or_else(Stdio::piped))
|
||||
.stdout(stdout)
|
||||
.stderr(stderr)
|
||||
};
|
||||
|
||||
let mut child = command.spawn().unwrap();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
for &(resource, soft_limit, hard_limit) in &self.limits {
|
||||
|
@ -1148,16 +1211,9 @@ impl UCommand {
|
|||
/// and returns a command result.
|
||||
/// It is recommended that you instead use succeeds() or fails()
|
||||
pub fn run(&mut self) -> CmdResult {
|
||||
let prog = self.run_no_wait().wait_with_output().unwrap();
|
||||
|
||||
CmdResult {
|
||||
bin_path: self.bin_path.clone(),
|
||||
util_name: self.util_name.clone(),
|
||||
tmpd: self.tmpd.clone(),
|
||||
code: prog.status.code(),
|
||||
success: prog.status.success(),
|
||||
stdout: prog.stdout,
|
||||
stderr: prog.stderr,
|
||||
match self.bytes_into_stdin.take() {
|
||||
Some(input) => self.run_no_wait_child().pipe_in_and_wait(input),
|
||||
None => self.run_no_wait_child().wait().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1166,7 +1222,8 @@ impl UCommand {
|
|||
/// It is recommended that, instead of this, you use a combination of pipe_in()
|
||||
/// with succeeds() or fails()
|
||||
pub fn run_piped_stdin<T: Into<Vec<u8>>>(&mut self, input: T) -> CmdResult {
|
||||
self.pipe_in(input).run()
|
||||
self.bytes_into_stdin = None;
|
||||
self.run_no_wait_child().pipe_in_and_wait(input)
|
||||
}
|
||||
|
||||
/// Spawns the command, feeds the stdin if any, waits for the result,
|
||||
|
@ -1191,12 +1248,525 @@ impl UCommand {
|
|||
}
|
||||
}
|
||||
|
||||
/// Stored the captured output in a temporary file. The file is deleted as soon as
|
||||
/// [`CapturedOutput`] is dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct CapturedOutput {
|
||||
output: tempfile::NamedTempFile,
|
||||
current_file: File,
|
||||
}
|
||||
|
||||
impl CapturedOutput {
|
||||
/// Creates a new instance of CapturedOutput
|
||||
fn new(output: tempfile::NamedTempFile) -> Self {
|
||||
Self {
|
||||
// TODO: do not reopen but use file pointer from output instead or current_file. That's
|
||||
// one file descriptor less.
|
||||
current_file: output.reopen().unwrap(),
|
||||
output,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_with(tempdir: &Rc<TempDir>) -> Self {
|
||||
Self::new(tempfile::NamedTempFile::new_in(tempdir.path()).unwrap())
|
||||
}
|
||||
|
||||
fn as_file(&mut self) -> &mut File {
|
||||
self.output.as_file_mut()
|
||||
}
|
||||
|
||||
/// Returns the captured output as [`String`]. See also [`CapturedOutput::output_as_bytes`].
|
||||
pub fn output(&mut self) -> String {
|
||||
String::from_utf8(self.output_as_bytes()).unwrap()
|
||||
}
|
||||
|
||||
// TODO: subject to removal
|
||||
pub fn output_exact(&mut self, size: usize) -> String {
|
||||
String::from_utf8(self.output_exact_bytes(size)).unwrap()
|
||||
}
|
||||
|
||||
// TODO: rename to output_bytes
|
||||
/// Returns the captured output so far as string. Subsequent calls to output
|
||||
/// return the subsequent output.
|
||||
pub fn output_as_bytes(&mut self) -> Vec<u8> {
|
||||
let mut buffer = Vec::<u8>::new();
|
||||
self.current_file.read_to_end(&mut buffer).unwrap();
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn output_all_bytes(&mut self) -> Vec<u8> {
|
||||
let mut buffer = Vec::<u8>::new();
|
||||
self.output
|
||||
.reopen()
|
||||
.unwrap()
|
||||
.read_to_end(&mut buffer)
|
||||
.unwrap();
|
||||
buffer
|
||||
}
|
||||
|
||||
// TODO: subject to removal
|
||||
pub fn output_exact_bytes(&mut self, size: usize) -> Vec<u8> {
|
||||
let mut buffer = vec![0; size];
|
||||
let mut output = self.output.reopen().unwrap();
|
||||
|
||||
output.read_exact(&mut buffer).unwrap();
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CapturedOutput {
|
||||
fn drop(&mut self) {
|
||||
let _ = remove_file(self.output.path());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum AssertionMode {
|
||||
All,
|
||||
Current,
|
||||
// TODO: ExactAll and ExactCurrent instead?
|
||||
Exact(usize, usize),
|
||||
}
|
||||
pub struct UChildAssertion<'a> {
|
||||
uchild: &'a mut UChild,
|
||||
}
|
||||
|
||||
impl<'a> UChildAssertion<'a> {
|
||||
pub fn new(uchild: &'a mut UChild) -> Self {
|
||||
Self { uchild }
|
||||
}
|
||||
|
||||
fn with_output(&mut self, mode: AssertionMode) -> CmdResult {
|
||||
let (code, success) = match self.uchild.try_alive() {
|
||||
true => (None, true),
|
||||
false => {
|
||||
let status = self.uchild.as_child().wait().unwrap();
|
||||
(status.code(), status.success())
|
||||
}
|
||||
};
|
||||
let (stdout, stderr) = match mode {
|
||||
AssertionMode::All => (
|
||||
self.uchild.stdout_all_bytes(),
|
||||
self.uchild.stderr_all_bytes(),
|
||||
),
|
||||
AssertionMode::Current => (self.uchild.stdout_bytes(), self.uchild.stderr_bytes()),
|
||||
AssertionMode::Exact(expected_stdout_size, expected_stderr_size) => (
|
||||
self.uchild.stdout_exact_bytes(expected_stdout_size),
|
||||
self.uchild.stderr_exact_bytes(expected_stderr_size),
|
||||
),
|
||||
};
|
||||
CmdResult {
|
||||
bin_path: self.uchild.bin_path.clone(),
|
||||
util_name: self.uchild.util_name.clone(),
|
||||
tmpd: self.uchild.tmpd.clone(),
|
||||
code,
|
||||
success,
|
||||
stdout,
|
||||
stderr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_all_output(&mut self) -> CmdResult {
|
||||
self.with_output(AssertionMode::All)
|
||||
}
|
||||
|
||||
pub fn with_current_output(&mut self) -> CmdResult {
|
||||
self.with_output(AssertionMode::Current)
|
||||
}
|
||||
|
||||
pub fn with_exact_output(
|
||||
&mut self,
|
||||
expected_stdout_size: usize,
|
||||
expected_stderr_size: usize,
|
||||
) -> CmdResult {
|
||||
self.with_output(AssertionMode::Exact(
|
||||
expected_stdout_size,
|
||||
expected_stderr_size,
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn is_alive(&mut self) -> &mut Self {
|
||||
match self
|
||||
.uchild
|
||||
.as_child()
|
||||
.try_wait()
|
||||
{
|
||||
Ok(Some(status)) => panic!(
|
||||
"Assertion failed. Expected '{}' to be running but exited with status={}.\nstdout: {}\nstderr: {}",
|
||||
uucore::util_name(),
|
||||
status,
|
||||
self.uchild.stdout_all(),
|
||||
self.uchild.stderr_all()
|
||||
),
|
||||
Ok(None) => {}
|
||||
Err(error) => panic!("Assertion failed with error '{:?}'", error),
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn is_not_alive(&mut self) -> &mut Self {
|
||||
match self
|
||||
.uchild
|
||||
.as_child()
|
||||
.try_wait()
|
||||
{
|
||||
Ok(None) => panic!(
|
||||
"Assertion failed. Expected '{}' to be not running but was alive.\nstdout: {}\nstderr: {}",
|
||||
uucore::util_name(),
|
||||
self.uchild.stdout_all(),
|
||||
self.uchild.stderr_all()),
|
||||
Ok(_) => {},
|
||||
Err(error) => panic!("Assertion failed with error '{:?}'", error),
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UChild {
|
||||
raw: Child,
|
||||
bin_path: String,
|
||||
util_name: Option<String>,
|
||||
tmpd: Option<Rc<TempDir>>,
|
||||
captured_stdout: Option<CapturedOutput>,
|
||||
captured_stderr: Option<CapturedOutput>,
|
||||
ignore_stdin_write_error: bool,
|
||||
}
|
||||
|
||||
// TODO: rename `pipe_in` to `pipe_in_bytes(Vec<u8>)` and use convenience function `pipe_in(&str)`
|
||||
// TODO: Add method `pipe_in_fixture(&str)`: like `pipe_in` but with a fixture
|
||||
impl UChild {
|
||||
fn new(
|
||||
child: Child,
|
||||
bin_path: String,
|
||||
util_name: Option<String>,
|
||||
tmpd: Option<Rc<TempDir>>,
|
||||
captured_stdout: Option<CapturedOutput>,
|
||||
captured_stderr: Option<CapturedOutput>,
|
||||
ignore_stdin_write_error: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
raw: child,
|
||||
bin_path,
|
||||
util_name,
|
||||
tmpd,
|
||||
captured_stdout,
|
||||
captured_stderr,
|
||||
ignore_stdin_write_error,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference of the wrapped child process.
|
||||
pub fn as_child(&mut self) -> &mut Child {
|
||||
&mut self.raw
|
||||
}
|
||||
|
||||
pub fn take_child_stdin(&mut self) -> Option<ChildStdin> {
|
||||
self.raw.stdin.take()
|
||||
}
|
||||
|
||||
pub fn delay(&mut self, millis: u64) -> &mut Self {
|
||||
sleep(Duration::from_millis(millis));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u32 {
|
||||
self.raw.id()
|
||||
}
|
||||
|
||||
/// Terminates the child process unconditionally and waits for the termination. Ignores any
|
||||
/// errors happening during [`Child::kill`].
|
||||
///
|
||||
/// # Panics
|
||||
/// If the child process could not be terminated within 60 seconds or an error happened during
|
||||
/// [`Child::wait_with_timeout`]
|
||||
pub fn kill(&mut self) -> &mut Self {
|
||||
let _ = self.raw.kill();
|
||||
for _ in 0..60 {
|
||||
if !self.try_alive() {
|
||||
return self;
|
||||
}
|
||||
sleep(Duration::from_secs(1));
|
||||
}
|
||||
panic!("Killing the child process within 60 seconds failed.")
|
||||
}
|
||||
|
||||
// TODO: Accept AssertionMode as parameter
|
||||
// TODO: use wait_with_output.unwrap() and return plain CmdResult instead?
|
||||
/// Wait for the child process to terminate and return a [`CmdResult`]. This method can also be
|
||||
/// run if the child process was killed with [`UChild::kill`].
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns the error from the call to [`Child::wait_with_output`] if any
|
||||
pub fn wait(self) -> io::Result<CmdResult> {
|
||||
let (bin_path, util_name, tmpd) = (
|
||||
self.bin_path.clone(),
|
||||
self.util_name.clone(),
|
||||
self.tmpd.clone(),
|
||||
);
|
||||
|
||||
let output = self.wait_with_output()?;
|
||||
|
||||
Ok(CmdResult {
|
||||
bin_path,
|
||||
util_name,
|
||||
tmpd,
|
||||
code: output.status.code(),
|
||||
success: output.status.success(),
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wait_with_output(mut self) -> io::Result<Output> {
|
||||
let mut output = self.raw.wait_with_output()?;
|
||||
|
||||
(output.stdout, output.stderr) =
|
||||
match (self.captured_stdout.as_mut(), self.captured_stderr.as_mut()) {
|
||||
(Some(stdout), Some(stderr)) => {
|
||||
(stdout.output_as_bytes(), stderr.output_as_bytes())
|
||||
}
|
||||
(None, Some(stderr)) => (output.stdout, stderr.output_as_bytes()),
|
||||
(Some(stdout), None) => (stdout.output_as_bytes(), output.stderr),
|
||||
(None, None) => (output.stdout, output.stderr),
|
||||
};
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Reads, consumes and returns the output as [`String`] from [`Child`]'s stdout. See also
|
||||
/// [`UChild::stdout_bytes] for side effects.
|
||||
pub fn stdout(&mut self) -> String {
|
||||
String::from_utf8(self.stdout_bytes()).unwrap()
|
||||
}
|
||||
|
||||
pub fn stdout_all(&mut self) -> String {
|
||||
String::from_utf8(self.stdout_all_bytes()).unwrap()
|
||||
}
|
||||
|
||||
/// Reads, consumes and returns the output as bytes from [`Child`]'s stdout. Each subsequent
|
||||
/// call to any of the functions below will return the subsequent output of the child process:
|
||||
///
|
||||
/// * [`UChild::stdout`]
|
||||
/// * [`UChild::stdout_exact_bytes`]
|
||||
/// * and the call to itself [`UChild::stdout_bytes`]
|
||||
pub fn stdout_bytes(&mut self) -> Vec<u8> {
|
||||
match self.captured_stdout.as_mut() {
|
||||
Some(output) => output.output_as_bytes(),
|
||||
None if self.raw.stdout.is_some() => {
|
||||
let mut buffer: Vec<u8> = vec![];
|
||||
let stdout = self.raw.stdout.as_mut().unwrap();
|
||||
stdout.read_to_end(&mut buffer).unwrap();
|
||||
buffer
|
||||
}
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all output from start of the child process until now, if we captured the output or
|
||||
/// else returns the output of the child process beginning from the last call to any of the
|
||||
/// these methods:
|
||||
/// * [`UChild::stdout`]
|
||||
/// * [`UChild::stdout_bytes`]
|
||||
/// * [`UChild::stdout_exact_bytes`]
|
||||
/// * and the call to itself [`UChild::stdout_all_bytes`]
|
||||
///
|
||||
/// This function does not consume any output, unlike any of the functions above besides itself.
|
||||
pub fn stdout_all_bytes(&mut self) -> Vec<u8> {
|
||||
match self.captured_stdout.as_mut() {
|
||||
Some(output) => output.output_all_bytes(),
|
||||
None => self.stdout_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads, consumes and returns the exact amount of bytes from `stdout`. This method may block indefinitely if the
|
||||
/// `size` amount of bytes exceeds the amount of bytes that can be read. See also [`UChild::stdout_bytes`] for
|
||||
/// side effects.
|
||||
pub fn stdout_exact_bytes(&mut self, size: usize) -> Vec<u8> {
|
||||
match self.captured_stdout.as_mut() {
|
||||
Some(output) => output.output_exact_bytes(size),
|
||||
None if self.raw.stdout.is_some() => {
|
||||
let mut buffer = vec![0; size];
|
||||
let stdout = self.raw.stdout.as_mut().unwrap();
|
||||
stdout.read_exact(&mut buffer).unwrap();
|
||||
buffer
|
||||
}
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads, consumes and returns the child's stderr as String. See also [`UChild::stdout_bytes`]
|
||||
/// for side effects.
|
||||
pub fn stderr(&mut self) -> String {
|
||||
String::from_utf8(self.stderr_bytes()).unwrap()
|
||||
}
|
||||
|
||||
pub fn stderr_all(&mut self) -> String {
|
||||
String::from_utf8(self.stderr_all_bytes()).unwrap()
|
||||
}
|
||||
|
||||
/// Reads, consumes and returns all bytes from child's stderr. If stderr is redirected to stdout
|
||||
/// with [`UCommand::stderr_to_stdout`] then always zero bytes are returned. See also
|
||||
/// [`UChild::stdout_bytes`] for side effects.
|
||||
pub fn stderr_bytes(&mut self) -> Vec<u8> {
|
||||
match self.captured_stderr.as_mut() {
|
||||
Some(output) => output.output_as_bytes(),
|
||||
None if self.raw.stderr.is_some() => {
|
||||
let mut buffer: Vec<u8> = vec![];
|
||||
let stderr = self.raw.stderr.as_mut().unwrap();
|
||||
stderr.read_to_end(&mut buffer).unwrap();
|
||||
buffer
|
||||
}
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all output from start of the child processes' stderr until now, if we captured the
|
||||
/// output or else returns the output of the child process that can currently be read. See also
|
||||
/// [`UChild::stdout_all_bytes`] for additional information.
|
||||
pub fn stderr_all_bytes(&mut self) -> Vec<u8> {
|
||||
match self.captured_stderr.as_mut() {
|
||||
Some(output) => output.output_all_bytes(),
|
||||
None => self.stderr_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads, consumes and returns the exact amount of bytes from stderr. If stderr is redirect to
|
||||
/// stdout with [`UCommand::stderr_to_stdout`] then always zero bytes are returned.
|
||||
///
|
||||
/// # Important
|
||||
/// This method blocks indefinitely if the `size` amount of bytes cannot be read.
|
||||
pub fn stderr_exact_bytes(&mut self, size: usize) -> Vec<u8> {
|
||||
match self.captured_stderr.as_mut() {
|
||||
Some(output) => output.output_exact_bytes(size),
|
||||
None if self.raw.stderr.is_some() => {
|
||||
let stderr = self.raw.stderr.as_mut().unwrap();
|
||||
let mut buffer = vec![0; size];
|
||||
stderr.read_exact(&mut buffer).unwrap();
|
||||
buffer
|
||||
}
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the child process is still alive and false otherwise.
|
||||
///
|
||||
/// # Panics
|
||||
/// If an error occurred during [`Child::try_wait`]
|
||||
pub fn try_alive(&mut self) -> bool {
|
||||
// TODO: return false on error of try_wait ?
|
||||
self.raw.try_wait().unwrap().is_none()
|
||||
}
|
||||
|
||||
/// Returns a [`UChildAssertion`] on which helper functions with assertions can be called.
|
||||
pub fn make_assertion(&mut self) -> UChildAssertion {
|
||||
UChildAssertion::new(self)
|
||||
}
|
||||
|
||||
pub fn make_assertion_with_delay(&mut self, millis: u64) -> UChildAssertion {
|
||||
sleep(Duration::from_millis(millis));
|
||||
self.make_assertion()
|
||||
}
|
||||
|
||||
/// Pipe data into [`Child`] stdin in a separate thread to avoid deadlocks. Note, that
|
||||
/// [`UCommand::set_stdin`] must be used together with [`Stdio::piped`] or else this method
|
||||
/// doesn't work as expected. `Stdio::piped` is the current default when using
|
||||
/// [`UCommand::run_no_wait`]) without calling `set_stdin`. This method returns a [`JoinHandle`]
|
||||
/// of the thread in which the writing to the child processes stdin is running.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the [`UCommand::set_stdin`] is called with setting [`Stdio::null`] or
|
||||
/// the child's stdin cannot be taken out.
|
||||
///
|
||||
/// [`JoinHandle`]: std::thread::JoinHandle
|
||||
pub fn pipe_in<T: Into<Vec<u8>>>(&mut self, content: T) -> JoinHandle<io::Result<()>> {
|
||||
let ignore_stdin_write_error = self.ignore_stdin_write_error;
|
||||
let content = content.into();
|
||||
let stdin = self
|
||||
.take_child_stdin()
|
||||
.expect("Could not pipe into child process. Was it set to Stdio::null()?");
|
||||
thread::spawn(move || {
|
||||
let mut writer = BufWriter::new(stdin);
|
||||
if ignore_stdin_write_error {
|
||||
let _ = writer.write_all(&content);
|
||||
let _ = writer.flush();
|
||||
Ok(())
|
||||
} else {
|
||||
writer
|
||||
.write_all(&content)
|
||||
.and_then(|_| writer.flush())
|
||||
.map_err(|error| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("failed to write to stdin of child: {}", error),
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Accept AssertionMode as parameter
|
||||
/// Pipe in data with [`UChild::pipe_in`] and [`UChild::wait`] for the process to terminate to
|
||||
/// return a [`CmdResult`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if [`UChild::wait`] panics or `thread.join()` returns an error
|
||||
pub fn pipe_in_and_wait<T: Into<Vec<u8>>>(mut self, content: T) -> CmdResult {
|
||||
let thread = self.pipe_in(content);
|
||||
let result = self.wait().unwrap();
|
||||
thread.join().unwrap().unwrap();
|
||||
result
|
||||
}
|
||||
|
||||
pub fn pipe_in_and_wait_with_output<T: Into<Vec<u8>>>(mut self, content: T) -> Output {
|
||||
let thread = self.pipe_in(content);
|
||||
let output = self.wait_with_output().unwrap();
|
||||
thread.join().unwrap().unwrap();
|
||||
output
|
||||
}
|
||||
|
||||
pub fn write_in<T: Into<Vec<u8>>>(&mut self, data: T) -> io::Result<()> {
|
||||
let stdin = self.raw.stdin.as_mut().unwrap();
|
||||
stdin.write_all(&data.into())?;
|
||||
stdin.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close_stdout(&mut self) -> &mut Self {
|
||||
if let Some(stdout) = self.raw.stdout.take() {
|
||||
drop(stdout);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn close_stderr(&mut self) -> &mut Self {
|
||||
if let Some(stderr) = self.raw.stderr.take() {
|
||||
drop(stderr);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn close_stdin(&mut self) -> &mut Self {
|
||||
if let Some(stdin) = self.raw.stdin.take() {
|
||||
drop(stdin);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove after UChild is ready?
|
||||
/// Wrapper for `child.stdout.read_exact()`.
|
||||
/// Careful, this blocks indefinitely if `size` bytes is never reached.
|
||||
pub fn read_size(child: &mut Child, size: usize) -> String {
|
||||
String::from_utf8(read_size_bytes(child, size)).unwrap()
|
||||
}
|
||||
|
||||
// TODO: remove after UChild is ready?
|
||||
/// Read the specified number of bytes from the stdout of the child process.
|
||||
///
|
||||
/// Careful, this blocks indefinitely if `size` bytes is never reached.
|
||||
|
|
Loading…
Reference in a new issue