use bashkit::Bash;
use std::sync::{Arc, Mutex};
#[tokio::test]
async fn coproc_basic_sets_array_and_pid() {
let mut bash = Bash::new();
let result = bash
.exec(
r#"
coproc { echo hello; }
echo "read_fd=${COPROC[0]}"
echo "write_fd=${COPROC[1]}"
echo "pid=$COPROC_PID"
"#,
)
.await
.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("read_fd=63"));
assert!(result.stdout.contains("write_fd=62"));
assert!(result.stdout.contains("pid="));
let pid_line = result
.stdout
.lines()
.find(|l| l.starts_with("pid="))
.unwrap();
let pid: i64 = pid_line.trim_start_matches("pid=").parse().unwrap();
assert!(pid > 0);
}
#[tokio::test]
async fn coproc_read_u_fd() {
let mut bash = Bash::new();
let result = bash
.exec(
r#"
coproc { echo line1; echo line2; echo line3; }
read -u ${COPROC[0]} first
read -u ${COPROC[0]} second
echo "$first"
echo "$second"
"#,
)
.await
.unwrap();
assert_eq!(result.exit_code, 0);
let lines: Vec<&str> = result.stdout.trim().lines().collect();
assert_eq!(lines, vec!["line1", "line2"]);
}
#[tokio::test]
async fn coproc_read_with_fd_variable() {
let mut bash = Bash::new();
let result = bash
.exec(
r#"
coproc { echo redirected; }
read -r -u ${COPROC[0]} line
echo "$line"
"#,
)
.await
.unwrap();
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout.trim(), "redirected");
}
#[tokio::test]
async fn coproc_named() {
let mut bash = Bash::new();
let result = bash
.exec(
r#"
coproc MYPROC { echo named_output; }
echo "read_fd=${MYPROC[0]}"
echo "pid=$MYPROC_PID"
read -u ${MYPROC[0]} line
echo "$line"
"#,
)
.await
.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("read_fd=63"));
assert!(result.stdout.contains("pid="));
assert!(result.stdout.contains("named_output"));
}
#[tokio::test]
async fn coproc_multiple_named() {
let mut bash = Bash::new();
let result = bash
.exec(
r#"
coproc A { echo from_a; }
coproc B { echo from_b; }
read -u ${A[0]} a_line
read -u ${B[0]} b_line
echo "$a_line"
echo "$b_line"
"#,
)
.await
.unwrap();
assert_eq!(result.exit_code, 0);
let lines: Vec<&str> = result.stdout.trim().lines().collect();
assert_eq!(lines, vec!["from_a", "from_b"]);
}
#[tokio::test]
async fn coproc_simple_command() {
let mut bash = Bash::new();
let result = bash
.exec(
r#"
coproc echo simple_output
read -u ${COPROC[0]} line
echo "$line"
"#,
)
.await
.unwrap();
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout.trim(), "simple_output");
}
#[tokio::test]
async fn coproc_eof() {
let mut bash = Bash::new();
let result = bash
.exec(
r#"
coproc { echo only_line; }
read -u ${COPROC[0]} first
read -u ${COPROC[0]} second
echo "first=$first"
echo "second=$second"
echo "exit=$?"
"#,
)
.await
.unwrap();
assert!(result.stdout.contains("first=only_line"));
}
#[tokio::test]
async fn coproc_multiline_output() {
let mut bash = Bash::new();
let result = bash
.exec(
r#"
coproc {
echo alpha
echo beta
echo gamma
}
read -u ${COPROC[0]} a
read -u ${COPROC[0]} b
read -u ${COPROC[0]} c
echo "$a $b $c"
"#,
)
.await
.unwrap();
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout.trim(), "alpha beta gamma");
}
#[tokio::test]
async fn coproc_sets_bang_variable() {
let mut bash = Bash::new();
let result = bash
.exec(
r#"
coproc { echo test; }
echo "$!"
"#,
)
.await
.unwrap();
assert_eq!(result.exit_code, 0);
let pid = result.stdout.trim();
assert!(!pid.is_empty());
assert!(pid.parse::<i64>().is_ok());
}
#[tokio::test]
async fn coproc_stdout_not_streamed() {
let streamed_stdout = Arc::new(Mutex::new(Vec::new()));
let cb_stdout = streamed_stdout.clone();
let mut bash = Bash::new();
let result = bash
.exec_streaming(
r#"
coproc { echo hidden_value; }
read -u ${COPROC[0]} line
echo "visible:$line"
"#,
Box::new(move |stdout, _stderr| {
if !stdout.is_empty() {
cb_stdout.lock().unwrap().push(stdout.to_string());
}
}),
)
.await
.unwrap();
assert_eq!(result.stdout.trim(), "visible:hidden_value");
assert_eq!(
streamed_stdout.lock().unwrap().as_slice(),
["visible:hidden_value\n"],
"coproc body output must not be visible to streaming consumers",
);
}