use bashkit::Bash;
#[tokio::test]
async fn l_proc_002_no_job_control() {
let mut bash = Bash::new();
let result = bash
.exec("sleep 0.2 & kill -STOP %1; wait %1; echo rc=$?; suspend")
.await
.unwrap();
assert!(result.stdout.contains("rc=0"), "stdout: {}", result.stdout);
assert_eq!(result.exit_code, 127);
assert!(result.stderr.contains("suspend: command not found"));
}
#[tokio::test]
async fn l_proc_003_no_process_spawning() {
let mut bash = Bash::new();
let result = bash.exec("/usr/bin/gcc -v").await.unwrap();
assert_eq!(result.exit_code, 127);
assert!(
result.stderr.contains("No such file or directory"),
"stderr: {}",
result.stderr
);
let result = bash
.exec("echo vfs > /tmp/f; /bin/sh -c 'cat /tmp/f; [ \"$(readlink /proc/1/exe)\" = /bin/bash ] && echo no-host'")
.await
.unwrap();
assert_eq!(result.stdout, "vfs\nno-host\n");
let result = bash.exec("definitely-not-a-command").await.unwrap();
assert_eq!(result.exit_code, 127);
assert!(result.stderr.contains("command not found"));
}
#[tokio::test]
async fn l_fs_002_no_permission_enforcement() {
let mut bash = Bash::new();
let result = bash
.exec("echo secret > /f && chmod 000 /f && cat /f")
.await
.unwrap();
assert_eq!(result.exit_code, 0, "stderr: {}", result.stderr);
assert_eq!(result.stdout, "secret\n");
}
#[tokio::test]
async fn l_net_001_no_raw_sockets() {
let mut bash = Bash::new();
let result = bash.exec("echo x > /dev/tcp/example.com/80").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(
result.stderr.contains("/dev/tcp"),
"stderr: {}",
result.stderr
);
}
#[cfg(feature = "http_client")]
#[tokio::test]
async fn l_net_002_default_deny_no_resolution() {
let mut bash = Bash::new();
let result = bash.exec("curl -s https://example.com").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(
result.stderr.contains("network access not configured"),
"must fail with the default-deny diagnostic, got: {}",
result.stderr
);
}
#[tokio::test]
async fn l_sig_001_signal_traps_not_delivered() {
let mut bash = Bash::new();
let result = bash
.exec(r#"trap 'echo TRAPPED' INT; kill -INT $$; echo after"#)
.await
.unwrap();
assert!(
result.stdout.contains("TRAPPED"),
"a self-sent INT must fire the trap: {}",
result.stdout
);
assert!(result.stdout.contains("after"));
let result = bash
.exec(r#"echo body; kill -TERM $$; echo never"#)
.await
.unwrap();
assert_eq!(result.exit_code, 143);
assert!(!result.stdout.contains("never"), "{}", result.stdout);
let result = bash
.exec(r#"trap 'echo EXITED' EXIT; echo body"#)
.await
.unwrap();
assert!(
result.stdout.contains("EXITED"),
"EXIT trap must fire: {}",
result.stdout
);
}
#[tokio::test]
async fn l_grep_001_noop_flags() {
let mut bash = Bash::new();
let plain = bash.exec("printf 'a\\nb\\n' | grep a").await.unwrap();
let colored = bash
.exec("printf 'a\\nb\\n' | grep --color=always --line-buffered a")
.await
.unwrap();
assert_eq!(plain.exit_code, 0);
assert_eq!(colored.exit_code, 0);
assert_eq!(plain.stdout, colored.stdout);
}
#[tokio::test]
async fn l_grep_002_no_stray_backslash_warning() {
let mut bash = Bash::new();
let r = bash.exec(r"echo a/b | grep 'a\/b'").await.unwrap();
assert_eq!(r.stdout, "a/b\n");
assert_eq!(r.stderr, "");
}
#[tokio::test]
async fn l_grep_003_recursive_name_order() {
let mut bash = Bash::new();
let r = bash
.exec("mkdir -p /t/d; echo x > /t/d/b; echo x > /t/d/a; grep -r x /t/d")
.await
.unwrap();
assert_eq!(r.stdout, "/t/d/a:x\n/t/d/b:x\n");
}
#[cfg(feature = "terminal")]
#[tokio::test]
async fn l_term_001_vi_is_a_subset() {
use bashkit::terminal::{Terminal, TerminalStatus};
let mut term = Terminal::new(Bash::builder());
term.run_until_idle().await;
term.send("vi /tmp/f\r:!touch /tmp/escaped\r");
assert_eq!(term.run_until_idle().await, TerminalStatus::Idle);
assert!(
term.screen_text().contains("E492"),
"{}",
term.screen_text()
);
term.send(":q\r");
term.run_until_idle().await;
assert!(!term.fs().exists("/tmp/escaped".as_ref()).await.unwrap());
}
#[cfg(feature = "terminal")]
#[tokio::test]
async fn l_term_002_cat_does_not_wait_for_terminal_input() {
use bashkit::terminal::Terminal;
let mut term = Terminal::new(Bash::builder());
term.run_until_idle().await;
term.send("cat; echo rc=$?\r");
term.run_until_idle().await;
assert!(
term.screen_text().ends_with("rc=0\n$"),
"{}",
term.screen_text()
);
}
#[tokio::test]
async fn l_pipe_001_stages_run_sequentially() {
let mut bash = Bash::new();
let result = bash
.exec("seq 20000 > /tmp/big; sort -n /tmp/big | head -1; echo \"${PIPESTATUS[*]}\"")
.await
.unwrap();
assert_eq!(result.stdout, "1\n0 0\n");
}
#[tokio::test]
async fn l_pipe_001_grep_tr_stream() {
let mut bash = Bash::new();
let result = bash
.exec(
"seq 20000 > /tmp/big; grep . /tmp/big | head -1; echo \"${PIPESTATUS[*]}\"\n\
while :; do echo y; done | grep y | head -2; echo \"${PIPESTATUS[*]}\"\n\
yes | tr y n | head -1; echo \"${PIPESTATUS[*]}\"\n\
yes | tr -d '\\n' | head -c 5; echo \" ${PIPESTATUS[*]}\"\n\
yes | grep -n y | head -2\n\
yes | grep -o -m3 y | cat; echo \"${PIPESTATUS[*]}\"",
)
.await
.unwrap();
assert_eq!(
result.stdout,
"1\n141 0\ny\ny\n141 141 0\nn\n141 141 0\nyyyyy 141 141 0\n1:y\n2:y\ny\ny\ny\n141 0 0\n"
);
assert!(result.stderr.is_empty(), "{}", result.stderr);
}
#[tokio::test]
async fn l_pipe_001_cat_streams() {
let mut bash = Bash::new();
let result = bash
.exec(
"seq 20000 > /tmp/big; cat /tmp/big | head -1; echo \"${PIPESTATUS[*]}\"\n\
while :; do echo y; done | cat | head -2; echo \"${PIPESTATUS[*]}\"",
)
.await
.unwrap();
assert_eq!(result.stdout, "1\n141 0\ny\ny\n141 141 0\n");
assert!(result.stderr.is_empty(), "{}", result.stderr);
}
#[tokio::test]
async fn l_pipe_001_generators_stream() {
let mut bash = Bash::new();
let result = bash
.exec("yes | head -1; echo \"${PIPESTATUS[*]}\"")
.await
.unwrap();
assert_eq!(result.stdout, "y\n141 0\n");
assert!(result.stderr.is_empty(), "{}", result.stderr);
}
#[tokio::test]
async fn l_pipe_001_buffered_filter_options_drain_pipe() {
let mut bash = Bash::new();
let result = bash
.exec(
"src() { for i in 1 2 3 3 4; do echo \"a$i\"; done; }\n\
src | grep -c a | cat\n\
src | grep -A1 a2 | cat\n\
src | tr -s 3 | tr -d '\\n' | cat; echo\n\
src | grep -q a4 | cat; echo \"${PIPESTATUS[*]}\"",
)
.await
.unwrap();
assert_eq!(result.stdout, "5\na2\na3\na1a2a3a3a4\n0 0 0\n");
}
#[tokio::test]
async fn l_proc_004_child_shell_depth() {
let mut bash = Bash::new();
let setup = "for i in 1 2 3 4 5 6 7 8; do echo \"sh /tmp/d$((i+1)).sh\" > /tmp/d$i.sh; done; echo 'echo deep' > /tmp/d9.sh";
bash.exec(setup).await.unwrap();
let r = bash.exec("sh /tmp/d2.sh").await.unwrap();
assert_eq!(r.stdout, "deep\n");
let r = bash.exec("sh /tmp/d1.sh").await.unwrap();
assert_eq!(r.stdout, "");
assert!(
r.stderr.contains("maximum nesting depth exceeded"),
"{}",
r.stderr
);
}
#[tokio::test]
async fn l_make_001_eval_and_file_unsupported() {
let mut bash = Bash::new();
for f in ["$(eval X=1)", "$(file >out,x)"] {
let script = format!("printf 'all:\\n\\t@echo %s\\n' '{f}' > Makefile; make");
let result = bash.exec(&script).await.unwrap();
assert_eq!(result.exit_code, 2, "{f}");
assert!(
result.stderr.contains("L-MAKE-001"),
"{f}: {}",
result.stderr
);
}
}
#[tokio::test]
async fn l_make_002_no_builtin_or_chained_rules() {
let mut bash = Bash::new();
let result = bash
.exec("cd /tmp && touch x.c && printf 'all: x.o\\n' > Makefile && make")
.await
.unwrap();
assert_eq!(result.exit_code, 2);
assert!(result.stderr.contains("No rule to make target 'x.o'"));
let result = bash
.exec(
"cd /tmp && touch x.a && printf '%%.b: %%.a\\n\\tcp $< $@\\n%%.c: %%.b\\n\\tcp $< $@\\nall: y.c\\n' > Makefile; mv x.a y.a; make",
)
.await
.unwrap();
assert_eq!(result.exit_code, 2, "{}", result.stdout);
assert!(result.stderr.contains("No rule to make target 'y.c'"));
let result = bash
.exec("cd /tmp && mkdir -p src && touch src/z.c && printf 'vpath %%.c src\\nall: z.c\\n\\t@echo built\\n' > Makefile && make")
.await
.unwrap();
assert_eq!(result.exit_code, 2);
assert!(!result.stdout.contains("built"));
}
#[tokio::test]
async fn l_make_003_sequential_sandbox_shell() {
let mut bash = Bash::new();
let result = bash
.exec("cd /tmp && printf 'SHELL=/bin/zsh\\nall: a b\\na:\\n\\t@echo a\\nb:\\n\\t@echo b\\n' > Makefile && make -j4")
.await
.unwrap();
assert_eq!(result.exit_code, 0, "{}", result.stderr);
assert_eq!(result.stdout, "a\nb\n");
let result = bash
.exec("cd /tmp && printf 'r:\\n\\t@echo $(MAKELEVEL)\\n\\t@$(MAKE) -s r\\n' > Makefile && make -s r")
.await
.unwrap();
assert_ne!(result.exit_code, 0);
assert_eq!(result.stdout.lines().last(), Some("3"), "{}", result.stdout);
assert!(result.stderr.contains("recursive make depth exceeds 4"));
}