#[cfg(test)]
mod tests {
use nucleus::container::{Container, ContainerConfig, TrustLevel};
use nucleus::error::NucleusError;
use nucleus::isolation::NamespaceConfig;
use nucleus::resources::ResourceLimits;
use nucleus::security::GVisorRuntime;
use std::path::PathBuf;
use tempfile::TempDir;
#[test]
#[ignore] fn test_container_lifecycle_echo() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let context_path = temp_dir.path().to_path_buf();
std::fs::write(context_path.join("test.txt"), "Hello from nucleus")
.expect("Failed to write test file");
let limits = ResourceLimits::unlimited()
.with_memory("128M")
.expect("Failed to set memory")
.with_cpu_cores(1.0)
.expect("Failed to set CPU");
let config = ContainerConfig::try_new(
Some("test-echo".to_string()),
vec!["/bin/echo".to_string(), "hello".to_string()],
)
.unwrap()
.with_context(context_path)
.with_limits(limits)
.with_namespaces(NamespaceConfig::minimal())
.with_trust_level(TrustLevel::Trusted);
let container = Container::new(config);
let result = container.run();
assert!(result.is_ok(), "Container should execute successfully");
let exit_code = result.unwrap();
assert_eq!(exit_code, 0, "Container should exit with code 0");
}
#[test]
fn test_container_config_builder() {
let config = ContainerConfig::try_new(
Some("test".to_string()),
vec![
"/bin/sh".to_string(),
"-c".to_string(),
"exit 0".to_string(),
],
)
.unwrap()
.with_context(PathBuf::from("/tmp/test"))
.with_namespaces(NamespaceConfig::minimal())
.with_gvisor(false);
assert_eq!(config.name, "test");
assert_eq!(config.command.len(), 3);
assert!(config.context_dir.is_some());
assert!(!config.use_gvisor);
}
#[test]
fn test_resource_limits_configuration() {
let limits = ResourceLimits::unlimited()
.with_memory("512M")
.expect("Failed to set memory")
.with_cpu_cores(2.0)
.expect("Failed to set CPU")
.with_pids(100)
.expect("Failed to set PIDs");
assert_eq!(limits.memory_bytes, Some(512 * 1024 * 1024));
assert_eq!(limits.cpu_quota_us, Some(200_000)); assert_eq!(limits.pids_max, Some(100));
}
#[test]
fn test_namespace_config_variations() {
let all = NamespaceConfig::all();
assert!(all.pid && all.mnt && all.net && all.uts && all.ipc && all.cgroup);
assert!(!all.time);
let minimal = NamespaceConfig::minimal();
assert!(minimal.pid && minimal.mnt && minimal.net && minimal.cgroup);
assert!(minimal.uts && minimal.ipc && !minimal.time);
}
#[test]
#[ignore] fn test_container_with_device_nodes() {
let config = ContainerConfig::try_new(
Some("test-dev".to_string()),
vec![
"/bin/sh".to_string(),
"-c".to_string(),
"test -c /dev/null && test -c /dev/zero && test -c /dev/random".to_string(),
],
)
.unwrap()
.with_namespaces(NamespaceConfig::minimal())
.with_trust_level(TrustLevel::Trusted);
let container = Container::new(config);
let result = container.run();
assert!(result.is_ok(), "Container should execute successfully");
let exit_code = result.unwrap();
assert_eq!(exit_code, 0, "Device nodes should be accessible");
}
#[test]
#[ignore] fn test_container_with_hostname() {
let mut namespaces = NamespaceConfig::minimal();
namespaces.uts = true;
let config = ContainerConfig::try_new(
Some("test-hostname".to_string()),
vec![
"/bin/sh".to_string(),
"-c".to_string(),
"test $(hostname) = 'custom-hostname'".to_string(),
],
)
.unwrap()
.with_namespaces(namespaces)
.with_hostname(Some("custom-hostname".to_string()))
.with_trust_level(TrustLevel::Trusted);
let container = Container::new(config);
let result = container.run();
assert!(result.is_ok(), "Container should execute successfully");
let exit_code = result.unwrap();
assert_eq!(exit_code, 0, "Hostname should be set correctly");
}
#[test]
#[ignore] fn test_container_full_isolation() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let context_path = temp_dir.path().to_path_buf();
std::fs::write(
context_path.join("test.sh"),
"#!/bin/sh\necho 'Testing full isolation'\nexit 0\n",
)
.expect("Failed to write test script");
let limits = ResourceLimits::unlimited()
.with_memory("256M")
.expect("Failed to set memory")
.with_cpu_cores(1.0)
.expect("Failed to set CPU")
.with_pids(50)
.expect("Failed to set PIDs");
let mut namespaces = NamespaceConfig::all();
namespaces.uts = true;
let config = ContainerConfig::try_new(
Some("test-full".to_string()),
vec!["/bin/sh".to_string(), "/context/test.sh".to_string()],
)
.unwrap()
.with_context(context_path)
.with_limits(limits)
.with_namespaces(namespaces)
.with_hostname(Some("nucleus-test".to_string()))
.with_trust_level(TrustLevel::Trusted);
let container = Container::new(config);
let result = container.run();
assert!(result.is_ok(), "Container should execute successfully");
let exit_code = result.unwrap();
assert_eq!(exit_code, 0, "Container should complete successfully");
}
#[test]
fn test_container_with_custom_hostname() {
let config =
ContainerConfig::try_new(Some("test".to_string()), vec!["/bin/sh".to_string()])
.unwrap()
.with_hostname(Some("custom-host".to_string()));
assert_eq!(config.hostname, Some("custom-host".to_string()));
}
#[test]
fn test_container_default_hostname() {
let config = ContainerConfig::try_new(
Some("my-container".to_string()),
vec!["/bin/sh".to_string()],
)
.unwrap();
assert_eq!(config.hostname, Some("my-container".to_string()));
}
#[test]
fn test_container_rootless_config() {
let config =
ContainerConfig::try_new(Some("test".to_string()), vec!["/bin/sh".to_string()])
.unwrap()
.with_rootless();
assert!(
config.namespaces.user,
"User namespace should be enabled in rootless mode"
);
assert!(
config.user_ns_config.is_some(),
"User namespace config should be set in rootless mode"
);
}
#[test]
fn test_gvisor_unavailable_returns_error() {
use nucleus::security::GVisorRuntime;
if GVisorRuntime::is_available() {
return;
}
let result = GVisorRuntime::new();
assert!(
result.is_err(),
"GVisorRuntime::new() should fail when runsc is not available"
);
}
#[test]
fn test_resource_stats_parsing() {
use nucleus::resources::ResourceStats;
let temp = tempfile::TempDir::new().unwrap();
let p = temp.path();
std::fs::write(p.join("memory.current"), "1048576\n").unwrap();
std::fs::write(p.join("memory.max"), "536870912\n").unwrap();
std::fs::write(p.join("cpu.stat"), "usage_usec 1000000\nother 0\n").unwrap();
std::fs::write(p.join("pids.current"), "5\n").unwrap();
let stats = ResourceStats::from_cgroup(p.to_str().unwrap()).unwrap();
assert_eq!(stats.memory_usage, 1_048_576);
assert_eq!(stats.memory_limit, 536_870_912);
assert_eq!(stats.cpu_usage_ns, 1_000_000_000); assert_eq!(stats.pid_count, 5);
}
#[test]
#[ignore] fn test_seccomp_blocks_clone_newuser() {
let config = ContainerConfig::try_new(
Some("test-seccomp-clone".to_string()),
vec![
"/bin/sh".to_string(),
"-c".to_string(),
"if ! command -v unshare >/dev/null 2>&1; then exit 0; fi; \
if unshare -U true 2>/dev/null; then exit 1; else exit 0; fi"
.to_string(),
],
)
.unwrap()
.with_namespaces(NamespaceConfig::minimal())
.with_trust_level(TrustLevel::Trusted);
let container = Container::new(config);
let result = container.run();
assert!(result.is_ok(), "Container should execute successfully");
let exit_code = result.unwrap();
assert_eq!(
exit_code, 0,
"Seccomp should deny CLONE_NEWUSER (exit 1 means filter is broken)"
);
}
#[test]
fn test_trust_level_default_is_untrusted() {
let config = ContainerConfig::try_new(None, vec!["/bin/sh".to_string()]).unwrap();
assert_eq!(config.trust_level, TrustLevel::Untrusted);
}
#[test]
fn test_untrusted_workload_rejects_host_network() {
use nucleus::network::NetworkMode;
let config = ContainerConfig::try_new(
Some("test-untrusted-host".to_string()),
vec!["/bin/sh".to_string()],
)
.unwrap()
.with_trust_level(TrustLevel::Untrusted)
.with_network(NetworkMode::Host)
.with_allow_host_network(true)
.with_namespaces(NamespaceConfig::minimal());
let container = Container::new(config);
let result = container.run();
assert!(result.is_err(), "Untrusted + host network should fail");
let err = result.unwrap_err();
assert!(
matches!(err, NucleusError::ConfigError(_)),
"Should be ConfigError, got: {:?}",
err
);
}
#[test]
fn test_untrusted_workload_requires_gvisor_or_degraded() {
if GVisorRuntime::is_available() {
return;
}
let config = ContainerConfig::try_new(
Some("test-untrusted-no-gvisor".to_string()),
vec!["/bin/sh".to_string()],
)
.unwrap()
.with_gvisor(false)
.with_trust_level(TrustLevel::Untrusted)
.with_namespaces(NamespaceConfig::minimal());
let container = Container::new(config);
let result = container.run();
assert!(
result.is_err(),
"Untrusted without gVisor or degraded should fail"
);
let err = result.unwrap_err();
assert!(
matches!(err, NucleusError::ConfigError(_)),
"Should be ConfigError, got: {:?}",
err
);
}
}