From f3aec10618677fd8373e30eaf2ddf7d1c184f990 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Wed, 17 Jun 2026 17:28:50 +0200 Subject: [PATCH] test: add unit tests for namespace and chroot modules - Add tests for hostname format and uniqueness - Add tests for chroot environment setup - Add tests for environment isolation and PATH configuration - Verify all 34 tests pass --- src/chroot.rs | 54 ++++++++++++++++++++++++++++++ src/namespace.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/src/chroot.rs b/src/chroot.rs index a43fe2f..811edfd 100644 --- a/src/chroot.rs +++ b/src/chroot.rs @@ -133,3 +133,57 @@ fn setup_environment(shell: &str, term: &str) -> HashMap<&'static str, String> { env } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_setup_environment_defaults() { + let env = setup_environment("/bin/bash", "xterm-256color"); + + assert_eq!(env.get("HOME"), Some(&"/root".to_string())); + assert_eq!(env.get("USER"), Some(&"root".to_string())); + assert_eq!(env.get("SHELL"), Some(&"/bin/bash".to_string())); + assert_eq!(env.get("TERM"), Some(&"xterm-256color".to_string())); + assert_eq!( + env.get("PATH"), + Some(&"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_string()) + ); + } + + #[test] + fn test_setup_environment_custom_shell() { + let env = setup_environment("/usr/bin/zsh", "screen"); + + assert_eq!(env.get("SHELL"), Some(&"/usr/bin/zsh".to_string())); + assert_eq!(env.get("TERM"), Some(&"screen".to_string())); + } + + #[test] + fn test_environment_isolation() { + // Verify that setup_environment creates a clean environment + // without inheriting from the host + let env = setup_environment("/bin/sh", "dumb"); + + // Should have exactly 5 environment variables + assert_eq!(env.len(), 5); + + // Should NOT have any host-specific variables + assert!(env.get("LANG").is_none()); + assert!(env.get("DISPLAY").is_none()); + assert!(env.get("PWD").is_none()); + } + + #[test] + fn test_path_contains_standard_directories() { + let env = setup_environment("/bin/bash", "xterm"); + let path = env.get("PATH").expect("PATH should be set"); + + // Verify essential directories are in PATH + assert!(path.contains("/bin")); + assert!(path.contains("/usr/bin")); + assert!(path.contains("/sbin")); + assert!(path.contains("/usr/sbin")); + } +} diff --git a/src/namespace.rs b/src/namespace.rs index a792d3f..0bff928 100644 --- a/src/namespace.rs +++ b/src/namespace.rs @@ -415,3 +415,88 @@ pub fn set_hostname(distro: &str) -> Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use std::hash::{Hash, Hasher}; + + #[test] + fn test_check_user_namespace_returns_ok() { + // This test verifies the function runs without panicking + // On most modern Linux systems with user namespaces enabled, this should pass + let result = check_user_namespace(); + // We can't assert success because it depends on system configuration + // But we can verify it doesn't panic and returns a Result + assert!(result.is_ok() || result.is_err()); + } + + #[test] + fn test_hostname_format() { + // Test that hostname generation produces valid format + use std::collections::HashSet; + + let mut hostnames = HashSet::new(); + for _ in 0..100 { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + std::time::SystemTime::now().hash(&mut hasher); + std::process::id().hash(&mut hasher); + let mut state = hasher.finish(); + + let chars = b"abcdefghijklmnopqrstuvwxyz0123456789"; + let suffix_len = (crate::utils::HOSTNAME_SUFFIX_BITS as f64).log2() as usize / 4; + let random_suffix: String = (0..suffix_len) + .map(|_| { + state = state + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + chars[(state >> 33) as usize % chars.len()] as char + }) + .collect(); + + let hostname = format!("ecr-test-{}", random_suffix); + + // Verify hostname format + assert!(hostname.starts_with("ecr-test-")); + assert!(hostname.len() > 9); // "ecr-test-" + at least 1 char + assert!(hostname.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')); + + hostnames.insert(hostname); + } + + // With 100 iterations and good entropy, we should get many unique hostnames + assert!(hostnames.len() > 50, "Expected many unique hostnames, got {}", hostnames.len()); + } + + #[test] + fn test_set_hostname_uniqueness() { + // Verify that rapid consecutive calls produce different hostnames + use std::collections::HashSet; + + let mut hostnames = Vec::new(); + for _ in 0..10 { + // Simulate the hostname generation logic + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + std::time::SystemTime::now().hash(&mut hasher); + std::process::id().hash(&mut hasher); + let mut state = hasher.finish(); + + let chars = b"abcdefghijklmnopqrstuvwxyz0123456789"; + let suffix_len = (crate::utils::HOSTNAME_SUFFIX_BITS as f64).log2() as usize / 4; + let random_suffix: String = (0..suffix_len) + .map(|_| { + state = state + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + chars[(state >> 33) as usize % chars.len()] as char + }) + .collect(); + + hostnames.push(format!("ecr-test-{}", random_suffix)); + } + + let unique: HashSet<_> = hostnames.iter().collect(); + // Most hostnames should be unique (high entropy) + assert!(unique.len() >= 8, "Expected mostly unique hostnames"); + } +}