amberjs 1.17.0

JavaScript/TypeScript runtime built with Rust and V8
Documentation
// Test Timeout Management
// Handles test execution timeouts and cancellation

use crossbeam::channel::unbounded;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

/// Timeout configuration for tests
#[derive(Debug, Clone)]
pub struct TimeoutConfig {
    pub default_timeout: Duration,
    pub max_timeout: Duration,
    pub enable_graceful_shutdown: bool,
    pub grace_period: Duration,
}
impl Default for TimeoutConfig {
    fn default() -> Self {
        TimeoutConfig {
            default_timeout: Duration::from_secs(5),
            max_timeout: Duration::from_secs(300), // 5 minutes
            enable_graceful_shutdown: true,
            grace_period: Duration::from_millis(100),
        }
    }
}
/// Test timeout handler
pub struct TestTimeout {
    config: TimeoutConfig,
}
impl TestTimeout {
    pub fn new(config: TimeoutConfig) -> Self {
        TestTimeout { config }
    }
    /// Default configuration
    pub fn default() -> Self {
        Self::new(TimeoutConfig::default())
    }
    /// Execute a function with timeout
    pub fn run_with_timeout<F, T>(&self, timeout: Duration, func: F) -> Result<T, TimeoutError>
    where
        F: FnOnce() -> T,
        F: Send + 'static,
        T: Send + 'static,
    {
        let timeout: _ = self.validate_timeout(timeout)?;
        let (sender, receiver) = unbounded::<Result<T, TimeoutError>>();
        // Spawn a thread to execute the function
        let handle: _ = std::thread::spawn(move || {
            let result: _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(func));
            match result {
                Ok(value) => sender.send(Ok(value)).unwrap(),
                Err(_) => sender.send(Err(TimeoutError::Panicked)).unwrap(),
            }
        });
        // Wait for completion or timeout
        match receiver.recv_timeout(timeout) {
            Ok(result) => {
                // Ensure thread completes
                let _: _ = handle.join();
                result
            }
            Err(_) => {
                // Timeout occurred
                Err(TimeoutError::Exceeded(timeout))
            }
        }
    }
    /// Execute a closure with timeout asynchronously
    pub fn run_async_with_timeout<F, T>(
        &self,
        timeout: Duration,
        func: F,
    ) -> Result<T, TimeoutError>
    where
        F: FnOnce() -> T + Send + 'static,
        F: std::panic::UnwindSafe,
        T: Send + 'static,
    {
        let timeout: _ = self.validate_timeout(timeout)?;
        let (sender, receiver) = unbounded::<Result<T, TimeoutError>>();

        let handle = std::thread::spawn(move || {
            let result = std::panic::catch_unwind(func).map_err(|_| TimeoutError::Panicked);
            let _ = sender.send(result);
        });

        match receiver.recv_timeout(timeout) {
            Ok(result) => {
                let _ = handle.join();
                result
            }
            Err(_) => Err(TimeoutError::Exceeded(timeout)),
        }
    }
    /// Validate timeout is within acceptable range
    fn validate_timeout(&self, timeout: Duration) -> Result<Duration, TimeoutError> {
        if timeout == Duration::from_secs(0) {
            return Ok(self.config.default_timeout);
        }
        if timeout > self.config.max_timeout {
            return Err(TimeoutError::Invalid(
                "timeout exceeds maximum allowed".to_string(),
            ));
        }
        Ok(timeout)
    }
}
/// Timeout error types
#[derive(Debug, Clone)]
pub enum TimeoutError {
    /// Test execution exceeded timeout
    Exceeded(Duration),
    /// Test panicked
    Panicked,
    /// Invalid timeout value
    Invalid(String),
}
impl std::fmt::Display for TimeoutError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TimeoutError::Exceeded(timeout) => {
                write!(f, "Test execution exceeded timeout of {:?}", timeout)
            }
            TimeoutError::Panicked => write!(f, "Test execution panicked"),
            TimeoutError::Invalid(msg) => write!(f, "Invalid timeout: {}", msg),
        }
    }
}
impl std::error::Error for TimeoutError {}
/// Timeout context for tracking multiple tests
pub struct TimeoutContext {
    start_time: Instant,
    timeout: Duration,
    active_tests: Arc<Mutex<Vec<TestTimeoutHandle>>>,
}
struct TestTimeoutHandle {
    test_name: String,
    // start_time and timeout removed - stored but never read
    // Keeping minimal structure for potential future use
}
impl TimeoutContext {
    pub fn new(timeout: Duration) -> Self {
        TimeoutContext {
            start_time: Instant::now(),
            timeout,
            active_tests: Arc::new(Mutex::new(Vec::new())),
        }
    }
    /// Register a test start
    pub fn start_test(&self, test_name: &str) -> TestTimeoutGuard {
        let handle: _ = TestTimeoutHandle {
            test_name: test_name.to_string(),
        };
        {
            let mut active = self.active_tests.lock().unwrap();
            active.push(handle);
        }
        TestTimeoutGuard {
            test_name: test_name.to_string(),
            active_tests: Arc::clone(&self.active_tests),
        }
    }
    /// Check if total elapsed time exceeds timeout
    pub fn check_elapsed(&self) -> bool {
        self.start_time.elapsed() > self.timeout
    }
    /// Get elapsed time
    pub fn elapsed(&self) -> Duration {
        self.start_time.elapsed()
    }
}
/// RAII guard for test timeout
pub struct TestTimeoutGuard {
    test_name: String,
    active_tests: Arc<Mutex<Vec<TestTimeoutHandle>>>,
}
impl Drop for TestTimeoutGuard {
    fn drop(&mut self) {
        let mut active = self.active_tests.lock().unwrap();
        active.retain(|h| h.test_name != self.test_name);
    }
}
#[cfg(test)]
mod tests {
    use super::TestTimeout;
    use std::time::Duration;

    #[test]
    fn test_timeout_creation() {
        let timeout: _ = TestTimeout::default();
        assert!(timeout.config.default_timeout > Duration::from_secs(0));
    }
    #[test]
    fn test_run_with_timeout_success() {
        let timeout: _ = TestTimeout::default();
        let result: _ = timeout.run_with_timeout(Duration::from_secs(1), || 42);
        assert_eq!(result.unwrap(), 42);
    }
    #[test]
    fn test_run_with_timeout_failure() {
        let timeout: _ = TestTimeout::default();
        let result: _ = timeout.run_with_timeout(Duration::from_millis(10), || {
            std::thread::sleep(Duration::from_millis(100));
            42
        });
        assert!(result.is_err());
    }

    #[test]
    fn run_async_with_timeout_returns_exceeded_before_blocking_closure_finishes() {
        let timeout = TestTimeout::default();
        let start = std::time::Instant::now();

        let result = timeout.run_async_with_timeout(Duration::from_millis(50), || {
            std::thread::sleep(Duration::from_secs(2));
            42
        });

        assert!(matches!(result, Err(super::TimeoutError::Exceeded(_))));
        assert!(
            start.elapsed() < Duration::from_millis(800),
            "timeout should return before the blocking closure finishes"
        );
    }
}

// Tests for test_timeout are in tests/ directory