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
|
//! Fork functionality integration tests
//!
//! This module tests the core `fork()` and `waitpid()` functions.
//! These tests verify:
//! - Basic fork/waitpid functionality
//! - Parent-child process communication via files
//! - Multiple child process management
//! - Environment variable inheritance across fork
//! - Command execution in child processes
//! - PID uniqueness between parent and child
//! - Proper process synchronization with waitpid
//!
//! All tests use temporary files for parent-child communication since
//! forked processes have separate memory spaces.
#![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::{Command, exit},
thread,
time::Duration,
};
use fork::{Fork, fork, waitpid};
use common::{get_test_dir, setup_test_dir};
#[test]
// Tests basic fork() functionality with waitpid()
// Expected behavior:
// 1. fork() returns Ok(Fork::Parent(pid)) in parent with child PID
// 2. fork() returns Ok(Fork::Child) in child
// 3. Child PID is positive
// 4. waitpid() successfully waits for child to exit
// 5. No zombie processes remain
fn test_fork_basic() {
match fork() {
Ok(Fork::Parent(child)) => {
assert!(child > 0, "Child PID should be positive");
// Wait for child
assert!(waitpid(child).is_ok(), "waitpid should succeed");
}
Ok(Fork::Child) => {
// Child just exits
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
#[test]
// Tests parent-child communication using files
// Expected behavior:
// 1. Parent and child have separate memory spaces
// 2. Child writes a message to a file
// 3. Parent reads the message after child completes
// 4. Message content matches what child wrote
// 5. Demonstrates file-based IPC pattern
fn test_fork_parent_child_communication() {
let test_dir = setup_test_dir(get_test_dir("fork_communication"));
let message_file = test_dir.join("message.txt");
match fork() {
Ok(Fork::Parent(child)) => {
// Wait for child to write
thread::sleep(Duration::from_millis(50));
// Read message from child
let message = fs::read_to_string(&message_file).expect("Failed to read message file");
assert_eq!(message.trim(), "hello from child");
waitpid(child).expect("Failed to wait for child");
// Cleanup
fs::remove_file(&message_file).ok();
}
Ok(Fork::Child) => {
// Write message
fs::write(&message_file, "hello from child").expect("Failed to write message");
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_fork_multiple_children() {
let mut children = Vec::new();
// Tests creating and managing multiple child processes
// Expected behavior:
// 1. Parent creates 3 child processes sequentially
// 2. Each child exits with a different exit code
// 3. Parent tracks all child PIDs
// 4. Parent successfully waits for all children
// 5. No zombie processes remain
for i in 0..3 {
match fork() {
Ok(Fork::Parent(child)) => {
children.push(child);
}
Ok(Fork::Child) => {
// Each child exits with different code
exit(i);
}
Err(_) => panic!("Fork {} failed", i),
}
}
// Parent waits for all children
assert_eq!(children.len(), 3, "Should have 3 children");
for child in children {
assert!(waitpid(child).is_ok(), "Failed to wait for child {}", child);
}
}
#[test]
fn test_fork_child_inherits_environment() {
let test_dir = setup_test_dir(get_test_dir("fork_env"));
// Tests environment variable inheritance across fork
// Expected behavior:
// 1. Parent sets an environment variable before fork
// 2. Child inherits parent's environment
// 3. Child can read the environment variable
// 4. Child writes variable value to file for verification
// 5. Demonstrates environment inheritance
let env_file = test_dir.join("env.txt");
// Set a test environment variable
let test_var = "FORK_TEST_VAR";
let test_value = "test_value_12345";
unsafe {
env::set_var(test_var, test_value);
}
match fork() {
Ok(Fork::Parent(child)) => {
thread::sleep(Duration::from_millis(50));
let content = fs::read_to_string(&env_file).expect("Failed to read env file");
assert_eq!(content.trim(), test_value);
waitpid(child).expect("Failed to wait for child");
// Cleanup
fs::remove_file(&env_file).ok();
unsafe {
env::remove_var(test_var);
}
}
Ok(Fork::Child) => {
// Child should have inherited the environment
let value = env::var(test_var).expect("Environment variable not found");
fs::write(&env_file, value).expect("Failed to write env file");
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
// Tests that child process can execute external commands
// Expected behavior:
// 1. Child process forks successfully
// 2. Child executes 'echo' command
// 3. Command output is captured
// 4. Output is written to file
// 5. Parent verifies command executed successfully
#[test]
fn test_fork_child_can_execute_commands() {
let test_dir = get_test_dir("fork");
fs::create_dir_all(&test_dir).expect("Failed to create test directory");
let output_file = test_dir.join("command_output.txt");
match fork() {
Ok(Fork::Parent(child)) => {
thread::sleep(Duration::from_millis(100));
assert!(output_file.exists(), "Output file should exist");
let content = fs::read_to_string(&output_file).expect("Failed to read output");
assert!(!content.is_empty(), "Output should not be empty");
waitpid(child).expect("Failed to wait for child");
// Cleanup
fs::remove_file(&output_file).ok();
}
Ok(Fork::Child) => {
// Execute a command and save output
let output = Command::new("echo")
.arg("child executed command")
.output()
// Tests that parent and child have unique PIDs
// Expected behavior:
// 1. Parent records its PID before fork
// 2. Child records its PID after fork
// 3. Child PID differs from parent PID
// 4. fork() returns correct child PID to parent
// 5. PIDs match between fork return value and actual child PID
.expect("Failed to execute command");
fs::write(&output_file, &output.stdout).expect("Failed to write output");
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_fork_child_has_different_pid() {
let test_dir = get_test_dir("fork");
fs::create_dir_all(&test_dir).expect("Failed to create test directory");
let pid_file = test_dir.join("pids.txt");
let parent_pid = unsafe { libc::getpid() };
match fork() {
Ok(Fork::Parent(child)) => {
thread::sleep(Duration::from_millis(50));
let content = fs::read_to_string(&pid_file).expect("Failed to read pid file");
let child_pid: i32 = content.trim().parse().expect("Failed to parse PID");
assert_ne!(
parent_pid, child_pid,
"Parent and child should have different PIDs"
);
assert_eq!(
child, child_pid,
"Child PID from fork() should match actual child PID"
);
// Tests that waitpid() properly synchronizes parent-child execution
// Expected behavior:
// 1. Parent forks and immediately checks for marker file
// 2. Marker file doesn't exist yet (child hasn't run)
// 3. Parent calls waitpid() to wait for child
// 4. Child creates marker file before exiting
// 5. After waitpid(), marker file exists (child completed)
waitpid(child).expect("Failed to wait for child");
// Cleanup
fs::remove_file(&pid_file).ok();
}
Ok(Fork::Child) => {
let child_pid = unsafe { libc::getpid() };
fs::write(&pid_file, format!("{}", child_pid)).expect("Failed to write PID");
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_waitpid_waits_for_child() {
let test_dir = get_test_dir("fork");
fs::create_dir_all(&test_dir).expect("Failed to create test directory");
let marker_file = test_dir.join("wait_marker.txt");
match fork() {
Ok(Fork::Parent(child)) => {
// Marker should not exist yet
assert!(
!marker_file.exists(),
"Marker should not exist before child runs"
);
// Wait for child
waitpid(child).expect("Failed to wait for child");
// Now marker should exist
assert!(
marker_file.exists(),
"Marker should exist after child completes"
);
// Cleanup
fs::remove_file(&marker_file).ok();
}
Ok(Fork::Child) => {
// Sleep a bit to ensure parent checks first
thread::sleep(Duration::from_millis(50));
// Create marker
fs::write(&marker_file, "done").expect("Failed to write marker");
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
|