os/kernel/tests/error_handling_tests.rs

373 lines
15 KiB
Rust

//! Comprehensive Error Handling Tests for NOS Syscall Modules
//!
//! This module provides comprehensive test cases that verify error handling consistency
//! across all syscall modules in accordance with the NOS Error Handling Specification.
//!
//! Tests cover:
//! - Proper error propagation across all syscall modules
//! - POSIX error code conversion
//! - Error context preservation
//! - Edge case error conditions
//! - Unified error handling norms compliance
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use crate::tests::common::{TestUtils, TestFixture, IntegrationTestResult};
use crate::tests::common::{integration_test_assert, integration_test_assert_eq};
use crate::syscalls::common::{SyscallError, syscall_error_to_errno};
use crate::syscalls;
use crate::reliability::errno::*;
/// Test POSIX error code conversion for all SyscallError variants
#[test]
fn test_posix_error_code_conversion() -> IntegrationTestResult {
let fixture = TestFixture::new("posix_error_conversion")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
// Test all SyscallError variants map to correct POSIX errno values
integration_test_assert_eq!(syscall_error_to_errno(SyscallError::InvalidSyscall), ENOSYS);
integration_test_assert_eq!(syscall_error_to_errno(SyscallError::PermissionDenied), EPERM);
integration_test_assert_eq!(syscall_error_to_errno(SyscallError::InvalidArgument), EINVAL);
integration_test_assert_eq!(syscall_error_to_errno(SyscallError::NotFound), ENOENT);
integration_test_assert_eq!(syscall_error_to_errno(SyscallError::OutOfMemory), ENOMEM);
integration_test_assert_eq!(syscall_error_to_errno(SyscallError::Interrupted), EINTR);
integration_test_assert_eq!(syscall_error_to_errno(SyscallError::IoError), EIO);
integration_test_assert_eq!(syscall_error_to_errno(SyscallError::WouldBlock), EAGAIN);
integration_test_assert_eq!(syscall_error_to_errno(SyscallError::NotSupported), EOPNOTSUPP);
Ok(())
})
}
/// Test error propagation in syscall dispatch
#[test]
fn test_syscall_dispatch_error_propagation() -> IntegrationTestResult {
let fixture = TestFixture::new("syscall_dispatch_error_propagation")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
let args = [0u64; 6];
// Test invalid syscall number returns -1
let result = syscalls::dispatch(0xFFFF, &args);
integration_test_assert_eq!(result, -1, "Invalid syscall should return -1");
// Test that all syscall ranges properly handle invalid calls
// Process syscalls (0x1000-0x1FFF) - most return ENOTSUP
let result = syscalls::dispatch(0x1000, &args); // getpid - not implemented
integration_test_assert_eq!(result, -ENOSYS as isize, "Unimplemented process syscall should return ENOTSUP");
// File I/O syscalls (0x2000-0x2FFF)
let result = syscalls::dispatch(0x2000, &args); // open - not implemented
integration_test_assert_eq!(result, -ENOSYS as isize, "Unimplemented file syscall should return ENOTSUP");
// Network syscalls (0x4000-0x4FFF)
let result = syscalls::dispatch(0x4000, &args); // socket - not implemented
integration_test_assert_eq!(result, -ENOSYS as isize, "Unimplemented network syscall should return ENOTSUP");
Ok(())
})
}
/// Test error handling in memory management syscalls
#[test]
fn test_memory_syscall_error_handling() -> IntegrationTestResult {
let fixture = TestFixture::new("memory_syscall_errors")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
// Test mmap with invalid arguments
let result = syscalls::dispatch(0x3000, &[0, 0, 0, 0, 0, 0]); // mmap with zero length
integration_test_assert!(result < 0, "mmap with zero length should fail");
// Test mmap with invalid protection flags
let result = syscalls::dispatch(0x3000, &[0, 4096, 0xFF, 0, 0, 0]); // invalid protection
integration_test_assert!(result < 0, "mmap with invalid protection should fail");
// Test mmap with both MAP_SHARED and MAP_PRIVATE
let result = syscalls::dispatch(0x3000, &[0, 4096, 1, 3, 0, 0]); // both SHARED and PRIVATE
integration_test_assert!(result < 0, "mmap with both SHARED and PRIVATE should fail");
Ok(())
})
}
/// Test error context preservation across syscall layers
#[test]
fn test_error_context_preservation() -> IntegrationTestResult {
let fixture = TestFixture::new("error_context_preservation")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
// Test that errors from lower layers are properly propagated with context
let args = [0u64; 6];
// Test invalid syscall - should preserve InvalidSyscall error
let result = syscalls::dispatch(0xFFFF, &args);
integration_test_assert_eq!(result, -1, "Invalid syscall should return -1 (InvalidSyscall -> errno conversion)");
// Test unimplemented syscall - should preserve NotSupported error
let result = syscalls::dispatch(0x4000, &args); // socket
integration_test_assert_eq!(result, -(EOPNOTSUPP as isize), "NotSupported should map to EOPNOTSUPP");
Ok(())
})
}
/// Test edge case error conditions
#[test]
fn test_edge_case_error_conditions() -> IntegrationTestResult {
let fixture = TestFixture::new("edge_case_errors")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
// Test boundary conditions for syscall numbers
let args = [0u64; 6];
// Test syscall number 0 (boundary)
let result = syscalls::dispatch(0, &args);
integration_test_assert_eq!(result, -1, "Syscall 0 should be invalid");
// Test maximum valid syscall in each range
let result = syscalls::dispatch(0x1FFF, &args); // Last process syscall
integration_test_assert!(result < 0, "Unimplemented syscall should return error");
let result = syscalls::dispatch(0x2FFF, &args); // Last file syscall
integration_test_assert!(result < 0, "Unimplemented syscall should return error");
let result = syscalls::dispatch(0x3FFF, &args); // Last memory syscall
integration_test_assert!(result < 0, "Unimplemented syscall should return error");
// Test with maximum u64 values in arguments
let max_args = [u64::MAX; 6];
let result = syscalls::dispatch(0x3000, &max_args); // mmap with max values
// Should not panic, even with extreme values
integration_test_assert!(true, "Syscall with max u64 args should not panic");
Ok(())
})
}
/// Test unified error handling norms compliance
#[test]
fn test_unified_error_handling_norms() -> IntegrationTestResult {
let fixture = TestFixture::new("unified_error_norms")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
// Test that all errors follow the unified norms:
// 1. Negative return values for errors
// 2. POSIX-compatible errno values
// 3. Consistent error propagation
let args = [0u64; 6];
// Test various unimplemented syscalls return negative errno values
let test_syscalls = [
(0x1000, "getpid"),
(0x1001, "fork"),
(0x2000, "open"),
(0x2001, "close"),
(0x4000, "socket"),
(0x4001, "bind"),
(0x5000, "kill"),
(0x6000, "nanosleep"),
];
for (syscall_num, name) in &test_syscalls {
let result = syscalls::dispatch(*syscall_num, &args);
integration_test_assert!(result <= 0, alloc::format!("{} syscall should return error (<= 0)", name));
if result < 0 {
// Verify it's a valid negative errno value
let errno = (-result) as i32;
integration_test_assert!(errno > 0 && errno <= 133, alloc::format!("{} should return valid errno", name));
}
}
Ok(())
})
}
/// Test error handling consistency across syscall modules
#[test]
fn test_syscall_module_error_consistency() -> IntegrationTestResult {
let fixture = TestFixture::new("syscall_module_consistency")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
let args = [0u64; 6];
// Test that each syscall module range handles errors consistently
// Process module (0x1000-0x1FFF)
for syscall_num in (0x1000..=0x1005).step_by(1) {
let result = syscalls::dispatch(syscall_num, &args);
integration_test_assert!(result <= 0, alloc::format!("Process syscall {} should handle errors consistently", syscall_num));
}
// File I/O module (0x2000-0x2FFF)
for syscall_num in (0x2000..=0x2005).step_by(1) {
let result = syscalls::dispatch(syscall_num, &args);
integration_test_assert!(result <= 0, alloc::format!("File syscall {} should handle errors consistently", syscall_num));
}
// Memory module (0x3000-0x3FFF) - some are implemented
for syscall_num in (0x3000..=0x3005).step_by(1) {
let result = syscalls::dispatch(syscall_num, &args);
// Memory syscalls may succeed or fail, but should not panic
integration_test_assert!(true, alloc::format!("Memory syscall {} should not panic", syscall_num));
}
// Network module (0x4000-0x4FFF)
for syscall_num in (0x4000..=0x4005).step_by(1) {
let result = syscalls::dispatch(syscall_num, &args);
integration_test_assert!(result <= 0, alloc::format!("Network syscall {} should handle errors consistently", syscall_num));
}
Ok(())
})
}
/// Test error recovery and state consistency
#[test]
fn test_error_recovery_consistency() -> IntegrationTestResult {
let fixture = TestFixture::new("error_recovery")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
let args = [0u64; 6];
// Test that error conditions don't leave the system in an inconsistent state
// by making multiple calls and verifying consistent behavior
// Make several invalid calls
for _ in 0..10 {
let result = syscalls::dispatch(0xFFFF, &args);
integration_test_assert_eq!(result, -1, "Invalid syscall should consistently return -1");
}
// Make several calls to unimplemented syscalls
for _ in 0..10 {
let result = syscalls::dispatch(0x4000, &args);
integration_test_assert_eq!(result, -(EOPNOTSUPP as isize), "Unimplemented syscall should consistently return EOPNOTSUPP");
}
// Verify that valid calls still work after errors
let result = syscalls::dispatch(0x3000, &args); // mmap
// mmap may succeed or fail, but should not panic
integration_test_assert!(true, "Valid syscall should work after error conditions");
Ok(())
})
}
/// Test error message consistency and informativeness
#[test]
fn test_error_message_consistency() -> IntegrationTestResult {
let fixture = TestFixture::new("error_message_consistency")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
// Test that error codes are consistent and meaningful
// Since we can't directly access error messages in the kernel,
// we test that errno values are in valid ranges and consistent
let test_cases = [
(SyscallError::InvalidSyscall, ENOSYS),
(SyscallError::PermissionDenied, EPERM),
(SyscallError::InvalidArgument, EINVAL),
(SyscallError::NotFound, ENOENT),
(SyscallError::OutOfMemory, ENOMEM),
(SyscallError::Interrupted, EINTR),
(SyscallError::IoError, EIO),
(SyscallError::WouldBlock, EAGAIN),
(SyscallError::NotSupported, EOPNOTSUPP),
];
for (error, expected_errno) in &test_cases {
let actual_errno = syscall_error_to_errno(*error);
integration_test_assert_eq!(actual_errno, *expected_errno,
alloc::format!("{:?} should map to errno {}", error, expected_errno));
}
Ok(())
})
}
/// Test concurrent error handling (basic)
#[test]
fn test_concurrent_error_handling() -> IntegrationTestResult {
let fixture = TestFixture::new("concurrent_error_handling")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
// Test that error handling works correctly under concurrent conditions
// Since we don't have threading in this test environment, we simulate
// by making rapid successive calls
let args = [0u64; 6];
// Rapid succession of error conditions
for i in 0..100 {
let result = syscalls::dispatch(0xFFFF, &args);
integration_test_assert_eq!(result, -1, alloc::format!("Concurrent error call {} should work", i));
}
// Mix of valid and invalid calls
for i in 0..50 {
// Invalid call
let result = syscalls::dispatch(0xFFFF, &args);
integration_test_assert_eq!(result, -1, alloc::format!("Mixed invalid call {} should work", i));
// Valid call (mmap)
let result = syscalls::dispatch(0x3000, &args);
// Should not panic
integration_test_assert!(true, alloc::format!("Mixed valid call {} should not panic", i));
}
Ok(())
})
}
/// Test error handling performance (no significant overhead)
#[test]
fn test_error_handling_performance() -> IntegrationTestResult {
let fixture = TestFixture::new("error_handling_performance")
.with_setup(TestUtils::setup)
.with_cleanup(TestUtils::cleanup);
fixture.run_test(|| {
let args = [0u64; 6];
let iterations = 1000;
// Measure time for error conditions
let start_time = crate::time::get_ticks();
for _ in 0..iterations {
let _result = syscalls::dispatch(0xFFFF, &args);
}
let end_time = crate::time::get_ticks();
let duration = end_time - start_time;
// Error handling should be fast (< 1000 ticks for 1000 calls)
integration_test_assert!(duration < 1000,
alloc::format!("Error handling took too long: {} ticks for {} calls", duration, iterations));
Ok(())
})
}