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
|
//! Error handling and edge case tests
//!
//! This module tests error scenarios and edge cases for fork library functions:
//! - `setsid()` called when already a session leader (EPERM)
//! - `close_fd()` error handling
//! - Error type verification (`io::Error`)
//!
//! These tests ensure proper error propagation and handling.
#![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)]
use std::process::exit;
use fork::{Fork, close_fd, fork, getpgrp, setsid, waitpid};
#[test]
fn test_setsid_error_when_already_session_leader() {
// Tests that setsid returns error when called by a session leader
// Expected behavior:
// 1. Child calls setsid() to become session leader (succeeds)
// 2. Child immediately calls setsid() again (should fail with EPERM)
// 3. Verifies proper error handling
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child).expect("waitpid failed");
}
Ok(Fork::Child) => {
// First setsid should succeed
let sid = setsid().expect("First setsid should succeed");
assert!(sid > 0, "SID should be positive");
// Verify we're a session leader (PID == PGID)
let pid = unsafe { libc::getpid() };
let pgid = getpgrp();
assert_eq!(pid, pgid, "Should be session leader");
// Second setsid should fail with EPERM
let result = setsid();
assert!(result.is_err(), "Second setsid should fail");
let err = result.unwrap_err();
assert_eq!(
err.raw_os_error(),
Some(libc::EPERM),
"Should return EPERM when already session leader"
);
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_fork_returns_io_error_type() {
// Tests that fork returns proper io::Error type
// Expected behavior:
// 1. fork() returns io::Result<Fork>
// 2. Error type can be inspected with raw_os_error()
// 3. Verifies type signature from v0.4.0
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child).expect("waitpid failed");
}
Ok(Fork::Child) => exit(0),
Err(e) => {
// If fork somehow fails, verify it's a proper io::Error
let _errno = e.raw_os_error();
let _error_msg = format!("{}", e);
panic!("Fork failed: {}", e);
}
}
}
#[test]
fn test_waitpid_returns_io_error_type() {
// Tests that waitpid returns proper io::Error on failure
// Expected behavior:
// 1. waitpid on invalid PID returns Err(io::Error)
// 2. Error has raw_os_error() method
// 3. Error can be formatted as string
match fork() {
Ok(Fork::Parent(_)) => {
// Try to wait on invalid PID
let result = waitpid(999_999);
assert!(result.is_err(), "Should fail on invalid PID");
let err = result.unwrap_err();
// Verify io::Error properties
assert!(err.raw_os_error().is_some(), "Should have errno");
let error_string = format!("{}", err);
assert!(!error_string.is_empty(), "Should have error message");
}
Ok(Fork::Child) => exit(0),
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_setsid_returns_io_error_type() {
// Tests that setsid returns proper io::Error on failure
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child).expect("waitpid failed");
}
Ok(Fork::Child) => {
// First call succeeds
setsid().expect("First setsid should succeed");
// Second call fails
let result = setsid();
assert!(result.is_err(), "Second setsid should fail");
let err = result.unwrap_err();
// Verify io::Error properties
assert_eq!(err.raw_os_error(), Some(libc::EPERM));
let error_string = format!("{}", err);
assert!(
error_string.contains("Operation not permitted") || error_string.contains("EPERM"),
"Error message should indicate permission error: {}",
error_string
);
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_getpgrp_returns_pid_type() {
// Tests that getpgrp returns pid_t directly (not Result)
// getpgrp() always succeeds per POSIX and cannot fail
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child).expect("waitpid failed");
}
Ok(Fork::Child) => {
// getpgrp() always succeeds and returns pid_t directly (not Result)
let pgid: libc::pid_t = getpgrp();
assert!(pgid > 0, "PGID should be positive");
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_close_fd_error_handling() {
// Tests that close_fd handles errors properly
// Note: This is tricky because closing stdin/stdout/stderr
// should normally succeed. This test just verifies the function
// returns io::Result and can be error-checked.
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child).expect("waitpid failed");
}
Ok(Fork::Child) => {
// Close fds - should succeed normally
let result = close_fd();
assert!(result.is_ok(), "close_fd should succeed normally");
// Second call might fail since fds are already closed
// but this is implementation-dependent
let result2 = close_fd();
// Either succeeds (idempotent) or fails - both are acceptable
match result2 {
Ok(()) => {
// Idempotent - fine
}
Err(e) => {
// Failed - verify it's a proper io::Error
assert!(e.raw_os_error().is_some(), "Should have errno");
}
}
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_error_kind_matching() {
// Tests that io::Error kinds can be matched for specific handling
// Expected behavior:
// 1. Errors return standard io::ErrorKind variants
// 2. Can pattern match on error kinds
// 3. Demonstrates error handling patterns
match fork() {
Ok(Fork::Parent(_)) => {
// Try to wait on invalid PID
let result = waitpid(999_999);
if let Err(e) = result {
// Can match on error kind for different handling
if e.kind() == std::io::ErrorKind::NotFound {
// ECHILD can map to NotFound on some systems
}
// Verify we can access errno
assert!(e.raw_os_error().is_some(), "Should have raw OS error code");
}
}
Ok(Fork::Child) => exit(0),
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_fork_child_pid_method() {
// Tests Fork::child_pid() method returns correct PID
match fork() {
Ok(Fork::Parent(child_pid)) => {
let fork_result = Fork::Parent(child_pid);
// Test child_pid() method
let extracted_pid = fork_result.child_pid();
assert!(extracted_pid.is_some(), "Parent should have child PID");
assert_eq!(
extracted_pid.unwrap(),
child_pid,
"child_pid() should match fork result"
);
waitpid(child_pid).expect("waitpid failed");
}
Ok(Fork::Child) => {
let fork_result = Fork::Child;
// Test child_pid() method
let extracted_pid = fork_result.child_pid();
assert!(extracted_pid.is_none(), "Child should not have child PID");
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
#[test]
fn test_fork_is_parent_is_child_methods() {
// Tests Fork::is_parent() and Fork::is_child() methods
match fork() {
Ok(result) => {
if result.is_parent() {
// In parent
assert!(!result.is_child(), "Parent should not be child");
assert!(result.child_pid().is_some(), "Parent should have child PID");
let child_pid = result.child_pid().unwrap();
waitpid(child_pid).expect("waitpid failed");
} else if result.is_child() {
// In child
assert!(!result.is_parent(), "Child should not be parent");
assert!(
result.child_pid().is_none(),
"Child should not have child PID"
);
exit(0);
}
}
Err(_) => panic!("Fork failed"),
}
}
|