1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
|
//! Comprehensive tests for `chdir()` function
//!
//! This module thoroughly tests the `chdir()` function to ensure:
//! - Successful directory change to root (/)
//! - Proper error handling and return types
//! - Thread safety and process isolation
//! - Integration with fork and daemon patterns
//! - Multiple successive calls (idempotent behavior)
//! - Verification of actual filesystem effects
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::panic)]
#![allow(clippy::match_wild_err_arm)]
#![allow(clippy::uninlined_format_args)]
use std::{
env, fs,
path::{Path, PathBuf},
process::exit,
thread,
time::Duration,
};
use fork::{Fork, WEXITSTATUS, WIFEXITED, chdir, fork, waitpid};
#[test]
fn test_chdir_basic_success() {
// Test that chdir successfully changes to root directory
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status), "Child should exit normally");
assert_eq!(WEXITSTATUS(status), 0, "Child should exit with code 0");
}
Fork::Child => {
// Change to root directory
match chdir() {
Ok(()) => {
let cwd = env::current_dir().expect("Failed to get current dir");
assert_eq!(cwd.to_str().unwrap(), "/", "Should be in root directory");
exit(0);
}
Err(e) => {
eprintln!("chdir failed: {}", e);
exit(1);
}
}
}
}
}
#[test]
fn test_chdir_returns_unit() {
// Test that chdir returns () on success
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
Fork::Child => {
let result: std::io::Result<()> = chdir();
assert!(result.is_ok());
// Verify it returns unit type
let _unit: () = result.unwrap();
exit(0);
}
}
}
#[test]
fn test_chdir_changes_actual_working_directory() {
// Verify chdir actually changes the working directory, not just returns Ok
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
Fork::Child => {
// Get original directory
let original = env::current_dir().expect("Failed to get original dir");
// Change to root
chdir().expect("chdir failed");
// Get new directory
let new_dir = env::current_dir().expect("Failed to get new dir");
// Verify change occurred
assert_eq!(new_dir, PathBuf::from("/"), "Should be in root");
if original.as_path() != Path::new("/") {
assert_ne!(
original, new_dir,
"Directory should change when not already at /"
);
}
exit(0);
}
}
}
#[test]
fn test_chdir_idempotent() {
// Test that calling chdir multiple times is safe (idempotent)
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
Fork::Child => {
// Call chdir multiple times
chdir().expect("First chdir failed");
chdir().expect("Second chdir failed");
chdir().expect("Third chdir failed");
// Verify still in root
let cwd = env::current_dir().expect("Failed to get current dir");
assert_eq!(cwd.to_str().unwrap(), "/");
exit(0);
}
}
}
#[test]
fn test_chdir_process_isolation() {
// Test that chdir in child doesn't affect parent
let parent_dir = env::current_dir().expect("Failed to get parent dir");
match fork().expect("Fork failed") {
Fork::Parent(child) => {
// Parent waits for child
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
// Parent directory should be unchanged
let current = env::current_dir().expect("Failed to get current dir");
assert_eq!(current, parent_dir, "Parent directory should not change");
}
Fork::Child => {
// Child changes directory
chdir().expect("chdir failed");
// Verify child is in root
let cwd = env::current_dir().expect("Failed to get current dir");
assert_eq!(cwd.to_str().unwrap(), "/");
exit(0);
}
}
}
#[test]
fn test_chdir_with_file_operations() {
// Test that chdir affects file operations (relative paths)
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
Fork::Child => {
// Change to root
chdir().expect("chdir failed");
// Confirm relative operations work from new cwd
let cwd = env::current_dir().expect("Failed to get current dir");
assert_eq!(cwd.to_str().unwrap(), "/");
fs::metadata(".").expect("Root directory metadata should be readable");
exit(0);
}
}
}
#[test]
fn test_chdir_with_absolute_path_operations() {
// Test that absolute paths still work after chdir
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
Fork::Child => {
let temp_file = std::env::temp_dir().join("fork_test_chdir");
fs::write(&temp_file, "test").expect("Failed to write test file");
// Change directory
chdir().expect("chdir failed");
// Absolute path should still work
let content =
fs::read_to_string(&temp_file).expect("Failed to read with absolute path");
assert_eq!(content, "test");
// Cleanup
fs::remove_file(&temp_file).ok();
exit(0);
}
}
}
#[test]
fn test_chdir_error_type() {
// Test that chdir returns proper io::Error type
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
Fork::Child => {
let result: std::io::Result<()> = chdir();
// Should succeed for root directory
assert!(result.is_ok());
// Type check - this is an io::Error if it were to fail
match result {
Ok(()) => exit(0),
Err(e) => {
// Verify it's a proper io::Error
let _: std::io::Error = e;
exit(1);
}
}
}
}
}
#[test]
fn test_chdir_concurrent_forks() {
// Test chdir behavior with multiple concurrent child processes
let mut children = Vec::new();
for _ in 0..3 {
match fork().expect("Fork failed") {
Fork::Parent(child) => {
children.push(child);
}
Fork::Child => {
// Each child changes to root independently
chdir().expect("chdir failed");
let cwd = env::current_dir().expect("Failed to get current dir");
assert_eq!(cwd.to_str().unwrap(), "/");
// Small delay to ensure concurrency
thread::sleep(Duration::from_millis(10));
exit(0);
}
}
}
// Parent waits for all children
for child in children {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
}
#[test]
fn test_chdir_before_and_after_setsid() {
// Test that chdir works correctly with setsid (common in daemon creation)
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
Fork::Child => {
use fork::setsid;
// Create new session first
setsid().expect("setsid failed");
// Then change directory (typical daemon pattern)
chdir().expect("chdir failed");
// Verify both worked
let cwd = env::current_dir().expect("Failed to get current dir");
assert_eq!(cwd.to_str().unwrap(), "/");
let pgid = fork::getpgrp();
assert!(pgid > 0);
exit(0);
}
}
}
#[test]
fn test_chdir_uses_c_string_literal() {
// This test verifies that the modern c"" string literal is used correctly
// by ensuring chdir works without any runtime string allocation errors
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
Fork::Child => {
// Call chdir multiple times rapidly
// If there were allocation issues, this would likely fail
for _ in 0..100 {
chdir().expect("chdir failed");
}
let cwd = env::current_dir().expect("Failed to get current dir");
assert_eq!(cwd.to_str().unwrap(), "/");
exit(0);
}
}
}
#[test]
fn test_chdir_with_env_manipulation() {
// Test that chdir works correctly even when environment is modified
match fork().expect("Fork failed") {
Fork::Parent(child) => {
let status = waitpid(child).expect("waitpid failed");
assert!(WIFEXITED(status));
assert_eq!(WEXITSTATUS(status), 0);
}
Fork::Child => {
// Modify environment
// SAFETY: This child has no other threads and exits immediately after the test.
unsafe {
env::set_var("PWD", "/some/fake/path");
}
// chdir should still work correctly
chdir().expect("chdir failed");
// Verify actual directory (not PWD env var)
let cwd = env::current_dir().expect("Failed to get current dir");
assert_eq!(cwd.to_str().unwrap(), "/");
exit(0);
}
}
}
|