use bashkit::{Bash, ExecutionLimits};
use std::path::Path;
use std::time::Duration;
#[tokio::test]
async fn bash_source_set_in_script() {
let mut bash = Bash::new();
let fs = bash.fs();
fs.write_file(
Path::new("/test.sh"),
b"#!/bin/bash\necho \"source=${BASH_SOURCE[0]}\"",
)
.await
.unwrap();
fs.chmod(Path::new("/test.sh"), 0o755).await.unwrap();
let result = bash.exec("/test.sh").await.unwrap();
assert_eq!(result.stdout.trim(), "source=/test.sh");
}
#[tokio::test]
async fn bash_source_set_in_sourced_file() {
let mut bash = Bash::new();
let fs = bash.fs();
fs.write_file(Path::new("/lib.sh"), b"echo \"source=${BASH_SOURCE[0]}\"")
.await
.unwrap();
let result = bash.exec("source /lib.sh").await.unwrap();
assert_eq!(result.stdout.trim(), "source=/lib.sh");
}
#[tokio::test]
async fn bash_source_guard_direct_execution() {
let mut bash = Bash::new();
let fs = bash.fs();
fs.write_file(
Path::new("/guard.sh"),
b"#!/bin/bash\nif [[ \"${BASH_SOURCE[0]}\" == \"$0\" ]]; then echo direct; else echo sourced; fi",
)
.await
.unwrap();
fs.chmod(Path::new("/guard.sh"), 0o755).await.unwrap();
let result = bash.exec("/guard.sh").await.unwrap();
assert_eq!(result.stdout.trim(), "direct");
}
#[tokio::test]
async fn bash_source_set_via_path_lookup() {
let mut bash = Bash::new();
let fs = bash.fs();
fs.mkdir(Path::new("/scripts"), false).await.unwrap();
fs.write_file(
Path::new("/scripts/test.sh"),
b"#!/bin/bash\necho \"source=${BASH_SOURCE[0]}\"",
)
.await
.unwrap();
fs.chmod(Path::new("/scripts/test.sh"), 0o755)
.await
.unwrap();
let result = bash
.exec("export PATH=\"/scripts:${PATH}\"\ntest.sh")
.await
.unwrap();
assert_eq!(result.stdout.trim(), "source=/scripts/test.sh");
}
#[tokio::test]
async fn bash_source_guard_via_path_lookup() {
let mut bash = Bash::new();
let fs = bash.fs();
fs.mkdir(Path::new("/bin2"), false).await.unwrap();
fs.write_file(
Path::new("/bin2/guard.sh"),
b"#!/bin/bash\nif [[ \"${BASH_SOURCE[0]}\" == \"$0\" ]]; then echo direct; else echo sourced; fi",
)
.await
.unwrap();
fs.chmod(Path::new("/bin2/guard.sh"), 0o755).await.unwrap();
let result = bash
.exec("export PATH=\"/bin2:${PATH}\"\nguard.sh")
.await
.unwrap();
assert_eq!(result.stdout.trim(), "direct");
}
#[tokio::test]
async fn bash_source_guard_sourced() {
let mut bash = Bash::new();
let fs = bash.fs();
fs.write_file(
Path::new("/guard.sh"),
b"if [[ \"${BASH_SOURCE[0]}\" == \"$0\" ]]; then echo direct; else echo sourced; fi",
)
.await
.unwrap();
let result = bash.exec("source /guard.sh").await.unwrap();
assert_eq!(result.stdout.trim(), "sourced");
}
#[tokio::test]
async fn bash_source_cleared_after_timed_out_child_bash_script() {
let mut bash = Bash::builder()
.limits(ExecutionLimits::new().timeout(Duration::from_millis(50)))
.build();
let fs = bash.fs();
fs.write_file(Path::new("/hang.sh"), b"#!/bin/bash\nsleep 1")
.await
.unwrap();
fs.chmod(Path::new("/hang.sh"), 0o755).await.unwrap();
let timeout_result = bash.exec("bash /hang.sh").await;
assert!(
timeout_result.is_err(),
"child script should hit execution timeout"
);
let result = bash
.exec("echo \"source=${BASH_SOURCE[0]}\"")
.await
.unwrap();
assert_eq!(result.stdout.trim(), "source=");
}
#[tokio::test(start_paused = true)]
async fn bash_source_cleared_after_timeout_in_sourced_function() {
let limits = bashkit::ExecutionLimits::new().timeout(std::time::Duration::from_millis(1));
let mut bash = Bash::builder().limits(limits).build();
let fs = bash.fs();
fs.write_file(Path::new("/leak.sh"), b"leakme() { sleep 10; }\nleakme\n")
.await
.unwrap();
let timed_out = bash.exec("source /leak.sh").await;
assert!(matches!(
timed_out,
Err(bashkit::Error::ResourceLimit(
bashkit::LimitExceeded::Timeout(_)
))
));
let result = bash
.exec(r#"echo "len=${#BASH_SOURCE[@]} first=${BASH_SOURCE[0]}""#)
.await
.unwrap();
assert_eq!(result.stdout.trim(), "len=0 first=");
}