use bashkit::Bash;
use std::path::Path;
async fn run(script: &str) -> (Bash, bashkit::ExecResult) {
let mut bash = Bash::builder().build();
let result = bash.exec(script).await.unwrap();
(bash, result)
}
#[tokio::test]
async fn stdout_to_dev_stderr_goes_to_stderr() {
let (bash, r) = run("echo err > /dev/stderr").await;
assert_eq!(r.stdout, "");
assert_eq!(r.stderr, "err\n");
assert!(!bash.fs().exists(Path::new("/dev/stderr")).await.unwrap());
}
#[tokio::test]
async fn append_to_dev_stderr_goes_to_stderr() {
let (bash, r) = run("echo a >> /dev/stderr; echo b >>/dev/stderr").await;
assert_eq!(r.stderr, "a\nb\n");
assert!(!bash.fs().exists(Path::new("/dev/stderr")).await.unwrap());
}
#[tokio::test]
async fn stderr_to_dev_stdout_goes_to_stdout() {
let (bash, r) = run("echo err >&2 2> /dev/stdout; { echo e2 >&2; } 2>/dev/stdout").await;
assert_eq!(r.stderr, "err\n");
assert_eq!(r.stdout, "e2\n");
assert!(!bash.fs().exists(Path::new("/dev/stdout")).await.unwrap());
}
#[tokio::test]
async fn dev_fd_numbers_alias_descriptors() {
let (_, r) = run("echo one > /dev/fd/2; echo two > /dev/fd/1").await;
assert_eq!(r.stderr, "one\n");
assert_eq!(r.stdout, "two\n");
}
#[tokio::test]
async fn dev_fd_path_follows_exec_opened_descriptor() {
let (bash, r) = run("exec 3>/tmp/log; echo hi > /dev/fd/3; exec 3>&-; cat /tmp/log").await;
assert_eq!(r.stdout, "hi\n");
assert!(!bash.fs().exists(Path::new("/dev/fd/3")).await.unwrap());
}
#[tokio::test]
async fn dev_stderr_variable_target() {
let (_, r) = run("LOG=/dev/stderr; echo warn >> \"$LOG\"").await;
assert_eq!(r.stdout, "");
assert_eq!(r.stderr, "warn\n");
}
#[tokio::test]
async fn dev_stderr_respects_outer_redirect() {
let (_, r) = run("f() { echo err > /dev/stderr; }; f 2>/dev/null; echo done").await;
assert_eq!(r.stdout, "done\n");
assert_eq!(r.stderr, "");
}
#[tokio::test]
async fn both_to_dev_stderr() {
let (_, r) = run("{ echo out; echo err >&2; } &> /dev/stderr").await;
assert_eq!(r.stdout, "");
assert_eq!(r.stderr, "out\nerr\n");
}
#[tokio::test]
async fn mixed_dup_and_dev_stdout() {
let (_, r) = run("{ echo out; echo err >&2; } 2>&1 >/dev/stderr").await;
assert_eq!(r.stdout, "out\nerr\n");
assert_eq!(r.stderr, "");
}
#[tokio::test]
async fn exec_redirect_to_dev_stderr() {
let (bash, r) = run("exec 3>/dev/stderr; echo via3 >&3; exec 3>&-").await;
assert_eq!(r.stdout, "");
assert_eq!(r.stderr, "via3\n");
assert!(!bash.fs().exists(Path::new("/dev/stderr")).await.unwrap());
}
#[tokio::test]
async fn input_from_dev_stdin_keeps_stdin() {
let (_, r) = run("echo piped | cat < /dev/stdin; cat < /dev/fd/0 <<< here").await;
assert_eq!(r.stdout, "piped\nhere\n");
}
#[tokio::test]
async fn dot_dot_spelling_is_normalized() {
let (bash, r) = run("echo err > /dev/../dev/stderr").await;
assert_eq!(r.stderr, "err\n");
assert!(!bash.fs().exists(Path::new("/dev/stderr")).await.unwrap());
}
#[tokio::test]
async fn cat_dev_stdin_operand_reads_stdin() {
let (_, r) = run("echo hi | cat /dev/stdin; echo x | cat /dev/fd/0").await;
assert_eq!(r.stdout, "hi\nx\n");
assert_eq!(r.stderr, "");
}
#[tokio::test]
async fn tee_dev_stderr_operand_copies_to_stderr() {
let (bash, r) = run("echo t | tee /dev/stderr").await;
assert_eq!(r.stdout, "t\n");
assert_eq!(r.stderr, "t\n");
assert!(!bash.fs().exists(Path::new("/dev/stderr")).await.unwrap());
}
#[tokio::test]
async fn tee_dev_stderr_operand_follows_redirects() {
let (_, r) =
run("echo t | tee /dev/stderr 2>/dev/null; echo u | tee /dev/stderr 2>&1 >/dev/null").await;
assert_eq!(r.stdout, "t\nu\n");
assert_eq!(r.stderr, "");
}
#[tokio::test]
async fn text_tools_read_dev_stdin_operand() {
let (_, r) = run("echo w | grep w /dev/stdin; echo json | sed s/j/J/ /dev/stdin; echo q | head -1 /dev/stdin").await;
assert_eq!(r.stdout, "w\nJson\nq\n");
}
#[tokio::test]
async fn dev_stdin_exists_for_test_builtin() {
let (_, r) = run("[ -e /dev/stdin ] && echo yes").await;
assert_eq!(r.stdout, "yes\n");
}
#[tokio::test]
async fn process_substitution_fds_still_work() {
let (_, r) = run("diff <(echo 1) <(echo 1) && echo same; cat <(echo p)").await;
assert_eq!(r.stdout, "same\np\n");
}
#[tokio::test]
async fn literal_dev_stdin_argument_is_untouched() {
let (_, r) = run("echo /dev/stdin").await;
assert_eq!(r.stdout, "/dev/stdin\n");
}
#[tokio::test]
async fn stream_operand_checks_admission_before_redirect() {
let mut bash = Bash::builder()
.concurrent_jobs(false)
.limits(bashkit::ExecutionLimits::new().max_stderr_bytes(0))
.build();
let result = bash.exec("printf hi | tee /dev/fd/2 2>/dev/null").await;
assert!(matches!(result, Err(bashkit::Error::ResourceLimit(_))));
assert!(bash.exec(":").await.unwrap().is_success());
}
#[tokio::test]
async fn stream_collection_checks_combined_admission() {
let mut bash = Bash::builder()
.concurrent_jobs(false)
.limits(bashkit::ExecutionLimits::new().max_stdout_bytes(3))
.build();
let result = bash.exec("printf hi | tee /dev/stdout >/dev/null").await;
assert!(matches!(result, Err(bashkit::Error::ResourceLimit(_))));
}
#[tokio::test]
async fn stream_aliases_and_append_match_bash() {
let script = "printf hi | tee -a /dev/stdout /dev/fd/1 /dev/stderr /dev/fd/2";
let (_, result) = run(script).await;
assert_eq!(result.stdout, "hihihi");
assert_eq!(result.stderr, "hihi");
let oracle = std::process::Command::new("bash")
.args(["-c", script])
.output()
.unwrap();
assert_eq!(result.stdout.as_bytes(), oracle.stdout);
assert_eq!(result.stderr.as_bytes(), oracle.stderr);
assert_eq!(Some(result.exit_code), oracle.status.code());
}