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
|
//! Advanced integration tests for complex fork patterns
//!
//! This module tests advanced usage patterns and combinations of functions.
//! These tests verify:
//! - Classic double-fork daemon pattern
//! - Session creation and management (setsid)
//! - Directory changes in child processes (chdir)
//! - Process isolation between parent and child
//! - Process group queries (getpgrp)
//!
//! These tests combine multiple fork operations to test real-world
//! daemon creation patterns and process management scenarios.
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::panic)]
#![allow(clippy::match_wild_err_arm)]
#![allow(clippy::similar_names)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::indexing_slicing)]
mod common;
use std::{env, fs, process::exit, thread, time::Duration};
use fork::{Fork, chdir, fork, getpgrp, getpid, setsid, waitpid};
use common::{get_test_dir, get_unique_test_dir, setup_test_dir, wait_for_file};
#[test]
fn test_double_fork_daemon_pattern() {
let test_dir = setup_test_dir(get_unique_test_dir("int_double_fork"));
let daemon_pid_file = test_dir.join("daemon.pid");
// First fork
match fork().expect("First fork failed") {
Fork::Parent(_child) => {
// Original parent waits for daemon to create PID file
// Use longer timeout for CI environments
assert!(
wait_for_file(&daemon_pid_file, 3000),
"Daemon PID file should exist"
);
// Tests the classic double-fork daemon pattern
// Expected behavior:
// 1. First fork creates a child process
// 2. Child calls setsid() to create new session (becomes session leader)
// 3. Child forks again (grandchild)
// 4. First child exits (leaving grandchild orphaned)
// 5. Grandchild is not session leader (prevents controlling terminal acquisition)
// 6. Grandchild writes its PID to file
// 7. This is the standard daemon creation pattern
let pid_str = fs::read_to_string(&daemon_pid_file).expect("Failed to read PID file");
let daemon_pid: i32 = pid_str.trim().parse().expect("Failed to parse daemon PID");
assert!(daemon_pid > 0, "Daemon PID should be positive");
// Cleanup
fs::remove_file(&daemon_pid_file).ok();
}
Fork::Child => {
// First child - create new session
setsid().expect("setsid failed");
// Second fork to ensure we're not session leader
match fork().expect("Second fork failed") {
Fork::Parent(_) => {
// First child exits
exit(0);
}
Fork::Child => {
// This is the daemon process
let pid = getpid();
let pgid = getpgrp();
// Write PID to file
fs::write(&daemon_pid_file, format!("{}", pid))
.expect("Failed to write daemon PID");
// Daemon should be in its own process group
assert!(pgid > 0);
exit(0);
}
}
}
}
}
#[test]
fn test_setsid_creates_new_session() {
let test_dir = setup_test_dir(get_unique_test_dir("int_setsid"));
let session_file = test_dir.join("session.info");
match fork().expect("Fork failed") {
Fork::Parent(_child) => {
thread::sleep(Duration::from_millis(50));
let content = fs::read_to_string(&session_file).expect("Failed to read session file");
let parts: Vec<&str> = content.trim().split(',').collect();
let sid: i32 = parts[0].parse().expect("Failed to parse SID");
let pid: i32 = parts[1].parse().expect("Failed to parse PID");
let pgid: i32 = parts[2].parse().expect("Failed to parse PGID");
// After setsid, PID should equal PGID (session leader)
assert_eq!(pid, pgid, "Process should be session leader");
assert_eq!(sid, pid, "SID should equal PID for session leader");
// Tests session creation and management with setsid()
// Expected behavior:
// 1. Child process calls setsid()
// 2. setsid() creates new session and returns SID
// 3. Child becomes session leader (PID == PGID == SID)
// 4. This is Step 2 of the daemon pattern
// 5. Child writes SID, PID, PGID to file for verification
// Cleanup
fs::remove_file(&session_file).ok();
}
Fork::Child => {
// Create new session
let sid = setsid().expect("setsid failed");
let pid = unsafe { libc::getpid() };
let pgid = getpgrp();
fs::write(&session_file, format!("{},{},{}", sid, pid, pgid))
.expect("Failed to write session info");
exit(0);
}
}
}
#[test]
fn test_chdir_changes_directory() {
let test_dir = setup_test_dir(get_test_dir("int_chdir"));
let dir_file = test_dir.join("directory.info");
match fork().expect("Fork failed") {
Fork::Parent(_child) => {
thread::sleep(Duration::from_millis(50));
// Tests directory change in child process
// Expected behavior:
// 1. Child process calls chdir()
// 2. chdir() changes current directory to root (/)
// 3. Child verifies current directory is /
// 4. Child writes directory path to file
// 5. Parent verifies child changed directory correctly
let content = fs::read_to_string(&dir_file).expect("Failed to read dir file");
assert_eq!(content.trim(), "/", "Directory should be root");
// Cleanup
fs::remove_file(&dir_file).ok();
}
Fork::Child => {
// Change to root
chdir().expect("chdir failed");
let current = env::current_dir().expect("Failed to get current dir");
fs::write(&dir_file, current.to_str().unwrap())
.expect("Failed to write directory info");
exit(0);
}
}
}
#[test]
// Tests process isolation between parent and child
// Expected behavior:
// 1. Parent writes data to file before fork
// 2. Child can see parent's file (same filesystem)
// 3. Child writes its own file
// 4. Parent can see child's file after fork completes
// 5. Both processes can access shared filesystem but have separate memory
fn test_process_isolation() {
let test_dir = setup_test_dir(get_test_dir("int_isolation"));
let parent_file = test_dir.join("parent.txt");
let child_file = test_dir.join("child.txt");
// Parent writes before fork
fs::write(&parent_file, "parent data").expect("Failed to write parent file");
match fork().expect("Fork failed") {
Fork::Parent(_child) => {
thread::sleep(Duration::from_millis(50));
// Parent file should still exist
assert!(parent_file.exists(), "Parent file should exist");
// Child should have created its own file
assert!(child_file.exists(), "Child file should exist");
let child_content = fs::read_to_string(&child_file).expect("Failed to read child file");
assert_eq!(child_content.trim(), "child data");
// Cleanup
fs::remove_file(&parent_file).ok();
fs::remove_file(&child_file).ok();
}
Fork::Child => {
// Child can see parent's file
assert!(parent_file.exists(), "Child should see parent file");
// Child writes its own file
fs::write(&child_file, "child data").expect("Failed to write child file");
exit(0);
// Tests process group queries with getpgrp()
// Expected behavior:
// 1. Both parent and child can call getpgrp()
// 2. Both return valid positive PGID values
// 3. Initially parent and child share same process group
// 4. Used to verify process group membership
// 5. Critical for session and job control
}
}
}
#[test]
fn test_getpgrp_returns_process_group() {
match fork().expect("Fork failed") {
Fork::Parent(_child) => {
let parent_pgid = getpgrp();
assert!(parent_pgid > 0, "Parent PGID should be positive");
thread::sleep(Duration::from_millis(50));
}
Fork::Child => {
let child_pgid = getpgrp();
assert!(child_pgid > 0, "Child PGID should be positive");
exit(0);
}
}
}
#[test]
fn test_chdir_error_handling() {
// Test that chdir returns proper io::Error
match fork().expect("Fork failed") {
Fork::Parent(child) => {
waitpid(child).expect("waitpid failed");
}
Fork::Child => {
// chdir() to root should succeed
let result = chdir();
assert!(result.is_ok(), "chdir to root should succeed");
// Verify we're actually in root
let cwd = env::current_dir().expect("Failed to get current dir");
assert_eq!(cwd.to_str().unwrap(), "/", "Should be in root directory");
exit(0);
}
}
}
#[test]
fn test_chdir_returns_io_error() {
// Test that chdir returns a proper io::Error type
match fork().expect("Fork failed") {
Fork::Parent(child) => {
waitpid(child).expect("waitpid failed");
}
Fork::Child => {
// Call chdir and verify return type
let result: std::io::Result<()> = chdir();
// Should succeed
assert!(result.is_ok());
// If it were to fail, we could access error details
if let Err(e) = result {
let _errno = e.raw_os_error();
let _msg = format!("{}", e);
}
exit(0);
}
}
}
|