use sandlock_core::{Policy, Sandbox, Checkpoint};
#[tokio::test]
async fn test_checkpoint_save_load() {
let policy = Policy::builder()
.fs_read("/usr").fs_read("/lib").fs_read_if_exists("/lib64").fs_read("/bin").fs_read("/etc")
.fs_read("/proc")
.build().unwrap();
let mut sb = Sandbox::new(&policy).unwrap();
sb.spawn(&["sleep", "60"]).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let cp = sb.checkpoint().await.unwrap();
assert!(!cp.process_state.memory_maps.is_empty(), "Should capture memory maps");
assert!(!cp.process_state.regs.is_empty(), "Should capture registers");
assert!(!cp.fd_table.is_empty(), "Should capture file descriptors");
let tmp = std::env::temp_dir().join(format!("sandlock-cp-test-{}", std::process::id()));
cp.save(&tmp).unwrap();
assert!(tmp.join("meta.json").exists());
assert!(tmp.join("policy.dat").exists());
assert!(tmp.join("process/info.json").exists());
assert!(tmp.join("process/fds.json").exists());
assert!(tmp.join("process/memory_map.json").exists());
assert!(tmp.join("process/threads/0.bin").exists());
let loaded = Checkpoint::load(&tmp).unwrap();
assert_eq!(loaded.process_state.regs.len(), cp.process_state.regs.len());
assert_eq!(loaded.process_state.memory_data.len(), cp.process_state.memory_data.len());
assert_eq!(loaded.fd_table.len(), cp.fd_table.len());
assert_eq!(loaded.process_state.pid, cp.process_state.pid);
assert!(!loaded.process_state.exe.is_empty());
sb.kill().unwrap();
let _ = sb.wait().await;
let _ = std::fs::remove_dir_all(&tmp);
}
#[tokio::test]
async fn test_checkpoint_memory_maps() {
let policy = Policy::builder()
.fs_read("/usr").fs_read("/lib").fs_read_if_exists("/lib64").fs_read("/bin").fs_read("/etc")
.fs_read("/proc")
.build().unwrap();
let mut sb = Sandbox::new(&policy).unwrap();
sb.spawn(&["sleep", "60"]).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let cp = sb.checkpoint().await.unwrap();
let has_stack = cp.process_state.memory_maps.iter().any(|m| {
m.path.as_ref().map_or(false, |p| p.contains("[stack]"))
});
assert!(has_stack, "Should capture stack mapping");
assert!(!cp.process_state.memory_data.is_empty(), "Should capture writable memory");
sb.kill().unwrap();
let _ = sb.wait().await;
}
#[tokio::test]
async fn test_checkpoint_app_state_roundtrip() {
let policy = Policy::builder()
.fs_read("/usr").fs_read("/lib").fs_read_if_exists("/lib64").fs_read("/bin").fs_read("/etc")
.fs_read("/proc")
.build().unwrap();
let mut sb = Sandbox::new(&policy).unwrap();
sb.spawn(&["sleep", "60"]).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let mut cp = sb.checkpoint().await.unwrap();
let state = b"hello from save_fn \x00\xff".to_vec();
cp.app_state = Some(state.clone());
let tmp = std::env::temp_dir().join(format!("sandlock-cp-appstate-{}", std::process::id()));
cp.save(&tmp).unwrap();
assert!(tmp.join("app_state.bin").exists());
let loaded = Checkpoint::load(&tmp).unwrap();
assert_eq!(loaded.app_state, Some(state));
sb.kill().unwrap();
let _ = sb.wait().await;
let _ = std::fs::remove_dir_all(&tmp);
}
#[tokio::test]
async fn test_checkpoint_no_app_state_file() {
let policy = Policy::builder()
.fs_read("/usr").fs_read("/lib").fs_read_if_exists("/lib64").fs_read("/bin").fs_read("/etc")
.fs_read("/proc")
.build().unwrap();
let mut sb = Sandbox::new(&policy).unwrap();
sb.spawn(&["sleep", "60"]).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let cp = sb.checkpoint().await.unwrap();
assert!(cp.app_state.is_none());
let tmp = std::env::temp_dir().join(format!("sandlock-cp-noapp-{}", std::process::id()));
cp.save(&tmp).unwrap();
assert!(!tmp.join("app_state.bin").exists());
let loaded = Checkpoint::load(&tmp).unwrap();
assert!(loaded.app_state.is_none());
sb.kill().unwrap();
let _ = sb.wait().await;
let _ = std::fs::remove_dir_all(&tmp);
}
#[tokio::test]
async fn test_checkpoint_process_info() {
let policy = Policy::builder()
.fs_read("/usr").fs_read("/lib").fs_read_if_exists("/lib64").fs_read("/bin").fs_read("/etc")
.fs_read("/proc")
.build().unwrap();
let mut sb = Sandbox::new(&policy).unwrap();
sb.spawn(&["sleep", "60"]).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let expected_pid = sb.pid().unwrap();
let cp = sb.checkpoint().await.unwrap();
assert_eq!(cp.process_state.pid, expected_pid);
assert!(cp.process_state.exe.contains("sleep"), "exe should contain 'sleep', got: {}", cp.process_state.exe);
assert!(!cp.process_state.cwd.is_empty(), "cwd should not be empty");
sb.kill().unwrap();
let _ = sb.wait().await;
}
#[tokio::test]
async fn test_checkpoint_load_nonexistent() {
let result = Checkpoint::load(std::path::Path::new("/tmp/sandlock-nonexistent-checkpoint"));
assert!(result.is_err());
}
#[tokio::test]
async fn test_checkpoint_not_running() {
let policy = Policy::builder().build().unwrap();
let sb = Sandbox::new(&policy).unwrap();
let result = sb.checkpoint().await;
assert!(result.is_err(), "Checkpoint on non-running sandbox should error");
}