wasm-slim 0.1.1

WASM bundle size optimizer
Documentation
//! Integration tests for timeout behavior
//!
//! Tests that long-running operations properly enforce timeouts,
//! clean up resources, and provide user-friendly error messages.

use std::process::{Command, Stdio};
use std::thread;
use std::time::{Duration, Instant};

/// Helper to simulate a long-running operation
fn simulate_long_operation(duration_ms: u64) -> Result<(), String> {
    thread::sleep(Duration::from_millis(duration_ms));
    Ok(())
}

/// Helper to simulate an operation with timeout enforcement
fn operation_with_timeout<F>(operation: F, timeout: Duration) -> Result<(), String>
where
    F: FnOnce() -> Result<(), String> + Send + 'static,
{
    use std::sync::mpsc;

    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let result = operation();
        let _ = tx.send(result);
    });

    match rx.recv_timeout(timeout) {
        Ok(result) => result,
        Err(_) => Err("Operation timed out".to_string()),
    }
}

#[test]
fn test_operation_completes_within_timeout() {
    // Test that an operation completing within timeout succeeds
    let result =
        operation_with_timeout(|| simulate_long_operation(100), Duration::from_millis(500));

    assert!(result.is_ok(), "Operation should complete within timeout");
}

#[test]
fn test_operation_exceeds_timeout() {
    // Test that an operation exceeding timeout is detected
    let result =
        operation_with_timeout(|| simulate_long_operation(500), Duration::from_millis(100));

    assert!(result.is_err(), "Operation should timeout");
    assert_eq!(result.unwrap_err(), "Operation timed out");
}

#[test]
fn test_timeout_boundary_just_under() {
    // Test operation completing just under timeout limit
    // Use a generous timeout (500ms) with a short operation (100ms = 20% of timeout)
    // to account for thread spawning, channel communication, and CI scheduling overhead
    let timeout_ms = 500;
    let operation_ms = 100; // 20% of timeout

    let start = Instant::now();
    let result = operation_with_timeout(
        move || simulate_long_operation(operation_ms),
        Duration::from_millis(timeout_ms),
    );
    let elapsed = start.elapsed();

    assert!(
        result.is_ok(),
        "Operation just under timeout should succeed (elapsed: {:?})",
        elapsed
    );
    // Be generous with timing on CI systems - allow up to 2x the timeout for scheduling overhead
    assert!(
        elapsed < Duration::from_millis(timeout_ms * 2),
        "Should complete within reasonable time (elapsed: {:?})",
        elapsed
    );
}

#[test]
fn test_timeout_boundary_just_over() {
    // Test operation completing just over timeout limit
    let timeout_ms = 100;
    let operation_ms = 300; // 300% of timeout - increased margin for reliability

    let start = Instant::now();
    let result = operation_with_timeout(
        move || simulate_long_operation(operation_ms),
        Duration::from_millis(timeout_ms),
    );
    let elapsed = start.elapsed();

    assert!(
        result.is_err(),
        "Operation just over timeout should fail (elapsed: {:?})",
        elapsed
    );
    assert!(
        elapsed >= Duration::from_millis(timeout_ms),
        "Should wait at least timeout duration (elapsed: {:?})",
        elapsed
    );
    // Be generous with timing on CI systems - allow up to 500ms tolerance for thread scheduling
    assert!(
        elapsed < Duration::from_millis(timeout_ms + 500),
        "Should timeout promptly (elapsed: {:?}, expected: ~{}ms)",
        elapsed,
        timeout_ms
    );
}

#[test]
fn test_timeout_error_message_clarity() {
    // Test that timeout errors provide clear user-friendly messages
    let result =
        operation_with_timeout(|| simulate_long_operation(500), Duration::from_millis(100));

    assert!(result.is_err());
    let error = result.unwrap_err();

    // Error message should be descriptive
    assert!(
        error.contains("timeout") || error.contains("timed out"),
        "Error message should mention timeout: {}",
        error
    );
    assert!(!error.is_empty(), "Error message should not be empty");
}

#[test]
fn test_multiple_timeouts_sequential() {
    // Test that multiple timeout operations work correctly in sequence
    // Use generous timeouts and larger margins for CI systems to avoid flakiness
    // The timeout operation uses 1000ms operation with 300ms timeout (3.3x margin)
    // to account for thread scheduling overhead on busy CI systems
    let results: Vec<Result<(), String>> = vec![
        operation_with_timeout(|| simulate_long_operation(50), Duration::from_millis(500)),
        operation_with_timeout(|| simulate_long_operation(1000), Duration::from_millis(300)),
        operation_with_timeout(|| simulate_long_operation(50), Duration::from_millis(500)),
    ];

    assert!(
        results[0].is_ok(),
        "First operation should succeed (50ms with 500ms timeout)"
    );
    assert!(
        results[1].is_err(),
        "Second operation should timeout (1000ms with 300ms timeout)"
    );
    assert!(
        results[2].is_ok(),
        "Third operation should succeed (50ms with 500ms timeout)"
    );
}

#[test]
fn test_cargo_bloat_timeout_simulation() {
    // Simulate a cargo-bloat command that might hang
    // We test with a quick-failing command to avoid actual hangs in tests
    let start = Instant::now();

    let result = Command::new("sleep")
        .arg("0.1")
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn();

    match result {
        Ok(mut child) => {
            // Simulate timeout check
            thread::sleep(Duration::from_millis(50));

            // Try non-blocking check
            match child.try_wait() {
                Ok(Some(_status)) => {
                    // Process completed
                    assert!(
                        start.elapsed() < Duration::from_secs(1),
                        "Process should complete quickly"
                    );
                }
                Ok(None) => {
                    // Process still running - would timeout in real scenario
                    let _ = child.kill();
                }
                Err(_) => {
                    // Error checking status
                    let _ = child.kill();
                }
            }
        }
        Err(_) => {
            // Command not found (e.g., Windows doesn't have 'sleep')
            // This is acceptable - we're testing the framework
        }
    }
}

#[test]
fn test_timeout_cleanup_no_orphaned_resources() {
    // Test that timeout doesn't leave orphaned resources
    use std::sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    };

    let cleanup_called = Arc::new(AtomicBool::new(false));
    let cleanup_called_clone = cleanup_called.clone();

    let _result = operation_with_timeout(
        move || {
            simulate_long_operation(500)?;
            cleanup_called_clone.store(true, Ordering::SeqCst);
            Ok(())
        },
        Duration::from_millis(100),
    );

    // Wait a bit for any cleanup to occur
    thread::sleep(Duration::from_millis(150));

    // In this test, cleanup wouldn't be called since operation timed out
    // The important thing is no panic or resource leak
    // In production code, cleanup should be in a Drop impl or finally block
}

#[test]
fn test_timeout_with_zero_duration() {
    // Test edge case: zero timeout
    let result = operation_with_timeout(|| simulate_long_operation(50), Duration::from_millis(0));

    // Zero timeout should immediately timeout
    assert!(result.is_err(), "Zero timeout should immediately fail");
}

#[test]
fn test_timeout_with_very_large_duration() {
    // Test edge case: very large timeout (operation completes first)
    let result = operation_with_timeout(
        || simulate_long_operation(10),
        Duration::from_secs(3600), // 1 hour
    );

    assert!(
        result.is_ok(),
        "Operation should complete before large timeout"
    );
}

#[test]
fn test_timeout_accuracy_within_tolerance() {
    // Test that timeout fires within acceptable tolerance
    // Use 500ms tolerance for CI systems with heavy load and thread scheduling delays
    let timeout_ms = 200;
    let tolerance_ms = 500; // Generous tolerance for CI systems

    let start = Instant::now();
    let result = operation_with_timeout(
        || simulate_long_operation(1000), // Definitely will timeout
        Duration::from_millis(timeout_ms),
    );
    let elapsed = start.elapsed().as_millis();

    assert!(result.is_err(), "Should timeout");
    assert!(
        elapsed >= timeout_ms as u128,
        "Timeout should fire after timeout duration"
    );
    assert!(
        elapsed < (timeout_ms + tolerance_ms) as u128,
        "Timeout should fire within tolerance ({}ms), was {}ms",
        tolerance_ms,
        elapsed
    );
}