373 lines
15 KiB
Rust
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(())
|
|
})
|
|
}
|