lpsolve 1.0.1

High-level lpsolve wrapper
Documentation
// MIRI-compatible tests for verifying memory safety
// Run with: cargo +nightly miri test --test miri_tests
//
// Note: These tests focus on the Rust wrapper's memory safety.
// They cannot test the actual FFI calls to lpsolve since MIRI
// doesn't support foreign function calls.

#![cfg(miri)]

use lpsolve::*;
use std::ffi::CString;

// Mock implementations for MIRI testing
// These allow us to test our wrapper's memory handling
// without actually calling into C code

#[test]
fn test_problem_drop_safety() {
    // Test that Problem properly cleans up on drop
    {
        let _lp = Problem::new(2, 3);
        // Problem should be dropped here without issues
    }

    // Test dropping cloned problems
    {
        let lp1 = Problem::new(2, 3);
        if let Some(lp1) = lp1 {
            let _lp2 = lp1.clone();
            // Both should be dropped safely
        }
    }
}

#[test]
fn test_slice_bounds_checking() {
    // These tests verify our assertions catch incorrect slice sizes
    // They should panic in debug mode, preventing unsafe access

    // Note: We can't actually create a Problem in MIRI since it calls FFI,
    // but we can test the safety of our slice handling patterns

    let values = vec![1.0, 2.0, 3.0];
    let indices = vec![1, 2, 3];

    // Test that we correctly handle slice length mismatches
    assert_eq!(values.len(), indices.len());

    // Test safe iteration patterns
    for (v, i) in values.iter().zip(indices.iter()) {
        // This pattern is safe even if lengths differ
        let _ = (v, i);
    }
}

#[test]
fn test_cstring_safety() {
    // Test that CString handling is memory-safe

    let name = CString::new("test_name").unwrap();
    let name_ptr = name.as_ptr();

    // Verify the string is null-terminated
    unsafe {
        let len = libc::strlen(name_ptr);
        assert_eq!(len, 9); // "test_name" is 9 chars
    }

    // Test that we handle empty strings safely
    let empty = CString::new("").unwrap();
    assert_eq!(empty.as_bytes().len(), 0);

    // Test that invalid strings are caught
    let result = CString::new("test\0null");
    assert!(result.is_err());
}

#[test]
fn test_write_callback_pointer_handling() {
    // Test the safety of our callback pointer handling pattern
    // This simulates what write_modeldata does

    use std::io::Write;

    let mut buffer = Vec::new();
    let writer: &mut dyn Write = &mut buffer;

    // Test our double-pointer pattern
    let writer_ptr = writer as *mut dyn Write;
    let writer_ptr_ptr = &writer_ptr as *const *mut dyn Write;

    // Simulate dereferencing in callback
    unsafe {
        // Cast to void pointer and back (as done in FFI)
        let void_ptr = writer_ptr_ptr as *mut libc::c_void;
        let recovered = void_ptr as *mut *mut dyn Write;
        let recovered_writer = &mut **recovered;

        // Write some data
        recovered_writer.write_all(b"test").unwrap();
    }

    assert_eq!(buffer, b"test");
}

#[test]
fn test_enum_conversions() {
    // Test that our enum conversions are safe

    // Test Verbosity enum
    let verb = Verbosity::Normal as libc::c_int;
    assert_eq!(verb, 4);

    // Test ConstraintType enum
    let ct_le = ConstraintType::Le as libc::c_int;
    let ct_eq = ConstraintType::Eq as libc::c_int;
    let ct_ge = ConstraintType::Ge as libc::c_int;
    assert_eq!(ct_le, 1);
    assert_eq!(ct_eq, 3);
    assert_eq!(ct_ge, 2);

    // Test VarType enum
    let binary = VarType::Binary as libc::c_uchar;
    let float = VarType::Float as libc::c_uchar;
    assert_eq!(binary, 1);
    assert_eq!(float, 0);
}

#[test]
fn test_option_handling() {
    // Test our Option-based error handling patterns

    fn simulate_bounds_check(col: libc::c_int, max_cols: libc::c_int) -> Option<bool> {
        if col > max_cols + 1 {
            None
        } else {
            Some(true)
        }
    }

    assert_eq!(simulate_bounds_check(1, 5), Some(true));
    assert_eq!(simulate_bounds_check(10, 5), None);
}

#[test]
fn test_vector_capacity() {
    // Test that we handle vector capacity correctly

    let mut vec = Vec::with_capacity(10);
    vec.push(1.0);
    vec.push(2.0);

    // Even though capacity is 10, len is 2
    assert_eq!(vec.len(), 2);
    assert!(vec.capacity() >= 10);

    // Test that we use len(), not capacity() for bounds
    let slice = &vec[..];
    assert_eq!(slice.len(), 2);
}

#[test]
fn test_max_overflow_safety() {
    // Test our use of std::cmp::max for length determination

    let values = vec![1.0, 2.0, 3.0];
    let indices = vec![1, 2];

    let len = std::cmp::max(values.len(), indices.len());
    assert_eq!(len, 3);

    // In real code, we should validate these match
    // This is why we have debug_assert!(values.len() == indices.len())
}

#[test]
fn test_integer_overflow_patterns() {
    // Test patterns that could cause integer overflow

    let row: libc::c_int = 1000;
    let col: libc::c_int = 1000;

    // Safe multiplication check
    let result = (row as usize).checked_mul(col as usize);
    assert_eq!(result, Some(1_000_000));

    // Test overflow detection
    let large_row: libc::c_int = libc::c_int::MAX / 2;
    let large_col: libc::c_int = 3;
    let overflow_result = (large_row as usize).checked_mul(large_col as usize);
    assert!(overflow_result.is_some()); // Might overflow on 32-bit systems
}

#[test]
fn test_null_pointer_patterns() {
    // Test our null pointer checking patterns

    fn simulate_cptr_macro<T>(ptr: *mut T) -> Option<*mut T> {
        if ptr.is_null() {
            None
        } else {
            Some(ptr)
        }
    }

    let null_ptr: *mut libc::c_int = std::ptr::null_mut();
    assert_eq!(simulate_cptr_macro(null_ptr), None);

    let valid_ptr = Box::into_raw(Box::new(42));
    assert_eq!(simulate_cptr_macro(valid_ptr), Some(valid_ptr));

    // Clean up
    unsafe { let _ = Box::from_raw(valid_ptr); }
}

#[test]
fn test_float_comparisons() {
    // Test floating point comparison patterns

    let a = 1.0;
    let b = 1.0 + f64::EPSILON;

    // Direct equality can be problematic
    assert_ne!(a, b);

    // Better to use epsilon comparison
    let epsilon = 1e-10;
    assert!((a - b).abs() < epsilon || a == b);
}

#[test]
fn test_assertion_patterns() {
    // Test that our assertion patterns are correct

    let num_rows = 5;
    let values = vec![0.0; (num_rows + 1) as usize];

    // This is the pattern we use
    assert!(values.len() == (num_rows + 1) as usize);

    // Also test > pattern for safety margin
    let safe_values = vec![0.0; (num_rows + 2) as usize];
    assert!(safe_values.len() > num_rows as usize);
}

#[test]
fn test_send_safety() {
    // Test that Problem can be safely sent between threads
    // Note: We can't actually create a Problem in MIRI,
    // but we can test the Send trait pattern

    fn assert_send<T: Send>() {}

    // This would compile only if Problem implements Send
    // assert_send::<Problem>();

    // Test thread safety pattern
    use std::thread;

    let data = vec![1, 2, 3];
    let handle = thread::spawn(move || {
        data.len()
    });

    let result = handle.join().unwrap();
    assert_eq!(result, 3);
}

#[test]
fn test_panic_safety_in_clone() {
    // Test that panic in clone is handled safely

    struct PanickyClone {
        data: Vec<i32>,
    }

    impl Clone for PanickyClone {
        fn clone(&self) -> Self {
            if self.data.len() > 2 {
                panic!("Simulated OOM in clone");
            }
            PanickyClone {
                data: self.data.clone(),
            }
        }
    }

    let small = PanickyClone { data: vec![1, 2] };
    let cloned = small.clone(); // Should succeed
    assert_eq!(cloned.data.len(), 2);

    let large = PanickyClone { data: vec![1, 2, 3] };
    let result = std::panic::catch_unwind(|| {
        large.clone()
    });
    assert!(result.is_err());
}

#[test]
fn test_bitflags_safety() {
    // Test that our bitflags usage is safe

    let options = MPSOptions::NORMAL | MPSOptions::FREE;
    assert!(options.contains(MPSOptions::NORMAL));
    assert!(options.contains(MPSOptions::FREE));
    assert!(!options.contains(MPSOptions::IBM));

    // Test bits() method
    let bits = options.bits();
    assert_eq!(bits, 4 | 8); // NORMAL=4, FREE=8
}