use sandlock_core::{Policy, Sandbox};
use std::time::{Duration, SystemTime};
#[tokio::test]
async fn test_random_seed_deterministic() {
let policy = Policy::builder()
.fs_read("/usr")
.fs_read("/lib")
.fs_read_if_exists("/lib64")
.fs_read("/bin")
.fs_read("/etc")
.fs_read("/proc")
.fs_read("/dev")
.random_seed(42)
.build()
.unwrap();
let r1 = Sandbox::run(&policy, &["sh", "-c", "od -A n -N 16 -t x1 /dev/urandom"])
.await
.unwrap();
let r2 = Sandbox::run(&policy, &["sh", "-c", "od -A n -N 16 -t x1 /dev/urandom"])
.await
.unwrap();
assert!(r1.success(), "First run failed");
assert!(r2.success(), "Second run failed");
let out1 = String::from_utf8_lossy(r1.stdout.as_deref().unwrap_or_default());
let out2 = String::from_utf8_lossy(r2.stdout.as_deref().unwrap_or_default());
assert!(
!out1.trim().is_empty(),
"Expected non-empty output from /dev/urandom read"
);
assert_eq!(
out1.trim(),
out2.trim(),
"Two runs with same seed should produce identical /dev/urandom output"
);
}
#[tokio::test]
async fn test_random_seed_different_seeds() {
let p1 = Policy::builder()
.fs_read("/usr")
.fs_read("/lib")
.fs_read_if_exists("/lib64")
.fs_read("/bin")
.fs_read("/etc")
.fs_read("/dev")
.random_seed(1)
.build()
.unwrap();
let p2 = Policy::builder()
.fs_read("/usr")
.fs_read("/lib")
.fs_read_if_exists("/lib64")
.fs_read("/bin")
.fs_read("/etc")
.fs_read("/dev")
.random_seed(999)
.build()
.unwrap();
let cmd = &["sh", "-c", "od -A n -N 16 -t x1 /dev/urandom"];
let r1 = Sandbox::run(&p1, cmd).await.unwrap();
let r2 = Sandbox::run(&p2, cmd).await.unwrap();
assert!(r1.success());
assert!(r2.success());
let out1 = String::from_utf8_lossy(r1.stdout.as_deref().unwrap_or_default());
let out2 = String::from_utf8_lossy(r2.stdout.as_deref().unwrap_or_default());
assert!(
!out1.trim().is_empty(),
"Expected non-empty output"
);
assert_ne!(
out1.trim(),
out2.trim(),
"Different seeds should produce different /dev/urandom output"
);
}
#[tokio::test]
async fn test_time_start_frozen() {
let y2k = SystemTime::UNIX_EPOCH + Duration::from_secs(961027200);
let policy = Policy::builder()
.fs_read("/usr")
.fs_read("/lib")
.fs_read_if_exists("/lib64")
.fs_read("/bin")
.fs_read("/etc")
.fs_read("/proc")
.time_start(y2k)
.build()
.unwrap();
let result = Sandbox::run(&policy, &["date", "+%Y"]).await.unwrap();
assert!(result.success(), "date command failed");
let stdout = String::from_utf8_lossy(result.stdout.as_deref().unwrap_or_default());
assert_eq!(stdout.trim(), "2000", "Expected year 2000, got: {:?}", stdout.trim());
}
#[tokio::test]
async fn test_time_start_basic_commands_work() {
let past = SystemTime::UNIX_EPOCH + Duration::from_secs(1000000000); let policy = Policy::builder()
.fs_read("/usr")
.fs_read("/lib")
.fs_read_if_exists("/lib64")
.fs_read("/bin")
.fs_read("/etc")
.time_start(past)
.build()
.unwrap();
let result = Sandbox::run(&policy, &["echo", "hello"]).await.unwrap();
assert!(result.success());
}
#[tokio::test]
async fn test_combined_determinism() {
let past = SystemTime::UNIX_EPOCH + Duration::from_secs(946684800);
let policy = Policy::builder()
.fs_read("/usr")
.fs_read("/lib")
.fs_read_if_exists("/lib64")
.fs_read("/bin")
.fs_read("/etc")
.fs_read("/proc")
.time_start(past)
.random_seed(42)
.build()
.unwrap();
let result = Sandbox::run(&policy, &["echo", "deterministic"]).await.unwrap();
assert!(result.success());
}
#[tokio::test]
async fn test_deterministic_dirs() {
let policy = Policy::builder()
.fs_read("/usr")
.fs_read("/lib")
.fs_read_if_exists("/lib64")
.fs_read("/bin")
.fs_read("/etc")
.fs_read("/proc")
.deterministic_dirs(true)
.build()
.unwrap();
let scan = "python3 - <<'PY'\nimport os\nprint('\\n'.join(e.name for e in os.scandir('/etc')))\nPY";
let r1 = Sandbox::run(&policy, &["sh", "-c", scan]).await.unwrap();
let r2 = Sandbox::run(&policy, &["sh", "-c", scan]).await.unwrap();
assert!(
r1.success(),
"First directory scan failed: {}",
String::from_utf8_lossy(r1.stderr.as_deref().unwrap_or_default())
);
assert!(
r2.success(),
"Second directory scan failed: {}",
String::from_utf8_lossy(r2.stderr.as_deref().unwrap_or_default())
);
let out1 = String::from_utf8_lossy(r1.stdout.as_deref().unwrap_or_default());
let out2 = String::from_utf8_lossy(r2.stdout.as_deref().unwrap_or_default());
assert!(
!out1.trim().is_empty(),
"Expected non-empty ls output"
);
assert_eq!(
out1, out2,
"Two directory scans should produce identical output with deterministic_dirs"
);
let lines: Vec<&str> = out1.lines()
.filter(|l| *l != "." && *l != "..")
.collect();
let mut sorted = lines.clone();
sorted.sort();
assert_eq!(lines, sorted, "getdents output should be lexicographically sorted");
}
#[tokio::test]
async fn test_hostname_virtualization() {
let policy = Policy::builder()
.fs_read("/usr")
.fs_read("/lib")
.fs_read_if_exists("/lib64")
.fs_read("/bin")
.fs_read("/etc")
.hostname("mybox")
.build()
.unwrap();
let result = Sandbox::run(&policy, &["hostname"]).await.unwrap();
assert!(result.success(), "hostname command failed");
let stdout = String::from_utf8_lossy(result.stdout.as_deref().unwrap_or_default());
assert_eq!(stdout.trim(), "mybox", "Expected hostname 'mybox', got: {:?}", stdout.trim());
let result = Sandbox::run(&policy, &["cat", "/etc/hostname"]).await.unwrap();
assert!(result.success(), "cat /etc/hostname failed");
let stdout = String::from_utf8_lossy(result.stdout.as_deref().unwrap_or_default());
assert_eq!(stdout.trim(), "mybox", "Expected /etc/hostname 'mybox', got: {:?}", stdout.trim());
}