mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-25 06:36:37 +00:00
494 lines
15 KiB
Rust
494 lines
15 KiB
Rust
//! Comprehensive unit tests for RuVector type
|
|
//!
|
|
//! Tests cover:
|
|
//! - Vector creation and initialization
|
|
//! - Serialization/deserialization (varlena roundtrips)
|
|
//! - Vector operations (arithmetic, normalization)
|
|
//! - Distance calculations
|
|
//! - Edge cases and error conditions
|
|
//! - Memory layout and alignment
|
|
|
|
use ruvector_postgres::types::RuVector;
|
|
|
|
#[cfg(test)]
|
|
mod ruvector_unit_tests {
|
|
use super::*;
|
|
|
|
// ========================================================================
|
|
// Construction and Initialization Tests
|
|
// ========================================================================
|
|
|
|
#[test]
|
|
fn test_from_slice_basic() {
|
|
let v = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
assert_eq!(v.dimensions(), 3);
|
|
assert_eq!(v.as_slice(), &[1.0, 2.0, 3.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_slice_empty() {
|
|
let v = RuVector::from_slice(&[]);
|
|
assert_eq!(v.dimensions(), 0);
|
|
assert_eq!(v.as_slice(), &[]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_slice_single_element() {
|
|
let v = RuVector::from_slice(&[42.0]);
|
|
assert_eq!(v.dimensions(), 1);
|
|
assert_eq!(v.as_slice(), &[42.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_zeros() {
|
|
let v = RuVector::zeros(5);
|
|
assert_eq!(v.dimensions(), 5);
|
|
assert_eq!(v.as_slice(), &[0.0, 0.0, 0.0, 0.0, 0.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_zeros_large() {
|
|
let v = RuVector::zeros(1000);
|
|
assert_eq!(v.dimensions(), 1000);
|
|
assert!(v.as_slice().iter().all(|&x| x == 0.0));
|
|
}
|
|
|
|
// ========================================================================
|
|
// Varlena Serialization Tests (Round-trip)
|
|
// ========================================================================
|
|
|
|
#[test]
|
|
fn test_varlena_roundtrip_basic() {
|
|
unsafe {
|
|
let v1 = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let varlena = v1.to_varlena();
|
|
let v2 = RuVector::from_varlena(varlena);
|
|
assert_eq!(v1, v2);
|
|
assert_eq!(v2.as_slice(), &[1.0, 2.0, 3.0]);
|
|
pgrx::pg_sys::pfree(varlena as *mut std::ffi::c_void);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_varlena_roundtrip_empty() {
|
|
unsafe {
|
|
let v1 = RuVector::from_slice(&[]);
|
|
let varlena = v1.to_varlena();
|
|
let v2 = RuVector::from_varlena(varlena);
|
|
assert_eq!(v1, v2);
|
|
pgrx::pg_sys::pfree(varlena as *mut std::ffi::c_void);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_varlena_roundtrip_large() {
|
|
unsafe {
|
|
let data: Vec<f32> = (0..1024).map(|i| i as f32 * 0.1).collect();
|
|
let v1 = RuVector::from_slice(&data);
|
|
let varlena = v1.to_varlena();
|
|
let v2 = RuVector::from_varlena(varlena);
|
|
assert_eq!(v1, v2);
|
|
assert_eq!(v2.dimensions(), 1024);
|
|
pgrx::pg_sys::pfree(varlena as *mut std::ffi::c_void);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_varlena_roundtrip_negative_values() {
|
|
unsafe {
|
|
let v1 = RuVector::from_slice(&[-1.5, 2.3, -4.7, 0.0, -0.001]);
|
|
let varlena = v1.to_varlena();
|
|
let v2 = RuVector::from_varlena(varlena);
|
|
assert_eq!(v1, v2);
|
|
pgrx::pg_sys::pfree(varlena as *mut std::ffi::c_void);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_varlena_roundtrip_special_values() {
|
|
unsafe {
|
|
// Test very small and large values (but not NaN/Inf which are rejected)
|
|
let v1 = RuVector::from_slice(&[
|
|
1.0e-10, 1.0e10, -1.0e-10, -1.0e10,
|
|
0.0, -0.0, // positive and negative zero
|
|
std::f32::consts::PI,
|
|
std::f32::consts::E,
|
|
]);
|
|
let varlena = v1.to_varlena();
|
|
let v2 = RuVector::from_varlena(varlena);
|
|
|
|
// Check dimensions match
|
|
assert_eq!(v1.dimensions(), v2.dimensions());
|
|
|
|
// Check values are approximately equal
|
|
for (a, b) in v1.as_slice().iter().zip(v2.as_slice().iter()) {
|
|
assert!((a - b).abs() < 1e-10 || (a.abs() < 1e-10 && b.abs() < 1e-10));
|
|
}
|
|
|
|
pgrx::pg_sys::pfree(varlena as *mut std::ffi::c_void);
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// Vector Operations Tests
|
|
// ========================================================================
|
|
|
|
#[test]
|
|
fn test_norm_basic() {
|
|
let v = RuVector::from_slice(&[3.0, 4.0]);
|
|
assert!((v.norm() - 5.0).abs() < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_norm_zero_vector() {
|
|
let v = RuVector::zeros(10);
|
|
assert_eq!(v.norm(), 0.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_norm_unit_vectors() {
|
|
let v1 = RuVector::from_slice(&[1.0, 0.0, 0.0]);
|
|
let v2 = RuVector::from_slice(&[0.0, 1.0, 0.0]);
|
|
let v3 = RuVector::from_slice(&[0.0, 0.0, 1.0]);
|
|
|
|
assert!((v1.norm() - 1.0).abs() < 1e-6);
|
|
assert!((v2.norm() - 1.0).abs() < 1e-6);
|
|
assert!((v3.norm() - 1.0).abs() < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_normalize_basic() {
|
|
let v = RuVector::from_slice(&[3.0, 4.0]);
|
|
let n = v.normalize();
|
|
assert!((n.norm() - 1.0).abs() < 1e-6);
|
|
assert!((n.as_slice()[0] - 0.6).abs() < 1e-6);
|
|
assert!((n.as_slice()[1] - 0.8).abs() < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_normalize_zero_vector() {
|
|
let v = RuVector::zeros(3);
|
|
let n = v.normalize();
|
|
assert_eq!(n.as_slice(), &[0.0, 0.0, 0.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_normalize_already_normalized() {
|
|
let v = RuVector::from_slice(&[1.0, 0.0, 0.0]);
|
|
let n = v.normalize();
|
|
assert_eq!(n.as_slice(), &[1.0, 0.0, 0.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_basic() {
|
|
let a = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let b = RuVector::from_slice(&[4.0, 5.0, 6.0]);
|
|
let c = a.add(&b);
|
|
assert_eq!(c.as_slice(), &[5.0, 7.0, 9.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_zero() {
|
|
let a = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let b = RuVector::zeros(3);
|
|
let c = a.add(&b);
|
|
assert_eq!(c.as_slice(), a.as_slice());
|
|
}
|
|
|
|
#[test]
|
|
fn test_sub_basic() {
|
|
let a = RuVector::from_slice(&[5.0, 7.0, 9.0]);
|
|
let b = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let c = a.sub(&b);
|
|
assert_eq!(c.as_slice(), &[4.0, 5.0, 6.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sub_self() {
|
|
let a = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let c = a.sub(&a);
|
|
assert_eq!(c.as_slice(), &[0.0, 0.0, 0.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mul_scalar_basic() {
|
|
let v = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let scaled = v.mul_scalar(2.0);
|
|
assert_eq!(scaled.as_slice(), &[2.0, 4.0, 6.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mul_scalar_zero() {
|
|
let v = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let scaled = v.mul_scalar(0.0);
|
|
assert_eq!(scaled.as_slice(), &[0.0, 0.0, 0.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mul_scalar_negative() {
|
|
let v = RuVector::from_slice(&[1.0, -2.0, 3.0]);
|
|
let scaled = v.mul_scalar(-1.0);
|
|
assert_eq!(scaled.as_slice(), &[-1.0, 2.0, -3.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dot_product_basic() {
|
|
let a = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let b = RuVector::from_slice(&[4.0, 5.0, 6.0]);
|
|
assert_eq!(a.dot(&b), 32.0); // 1*4 + 2*5 + 3*6 = 32
|
|
}
|
|
|
|
#[test]
|
|
fn test_dot_product_orthogonal() {
|
|
let a = RuVector::from_slice(&[1.0, 0.0, 0.0]);
|
|
let b = RuVector::from_slice(&[0.0, 1.0, 0.0]);
|
|
assert_eq!(a.dot(&b), 0.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dot_product_zero_vector() {
|
|
let a = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let b = RuVector::zeros(3);
|
|
assert_eq!(a.dot(&b), 0.0);
|
|
}
|
|
|
|
// ========================================================================
|
|
// String Parsing Tests
|
|
// ========================================================================
|
|
|
|
#[test]
|
|
fn test_parse_basic() {
|
|
let v: RuVector = "[1.0, 2.0, 3.0]".parse().unwrap();
|
|
assert_eq!(v.as_slice(), &[1.0, 2.0, 3.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_no_spaces() {
|
|
let v: RuVector = "[1,2,3]".parse().unwrap();
|
|
assert_eq!(v.as_slice(), &[1.0, 2.0, 3.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_extra_spaces() {
|
|
let v: RuVector = "[ 1.0 , 2.0 , 3.0 ]".parse().unwrap();
|
|
assert_eq!(v.as_slice(), &[1.0, 2.0, 3.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_negative() {
|
|
let v: RuVector = "[-1.5, 2.3, -4.7]".parse().unwrap();
|
|
assert_eq!(v.as_slice(), &[-1.5, 2.3, -4.7]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_scientific_notation() {
|
|
let v: RuVector = "[1e-3, 2.5e2, -3.14e-1]".parse().unwrap();
|
|
assert_eq!(v.dimensions(), 3);
|
|
assert!((v.as_slice()[0] - 0.001).abs() < 1e-10);
|
|
assert!((v.as_slice()[1] - 250.0).abs() < 1e-6);
|
|
assert!((v.as_slice()[2] - (-0.314)).abs() < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_empty() {
|
|
let v: RuVector = "[]".parse().unwrap();
|
|
assert_eq!(v.dimensions(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_invalid_format() {
|
|
assert!("not a vector".parse::<RuVector>().is_err());
|
|
assert!("1,2,3".parse::<RuVector>().is_err()); // Missing brackets
|
|
assert!("[1,2,3".parse::<RuVector>().is_err()); // Missing closing bracket
|
|
assert!("1,2,3]".parse::<RuVector>().is_err()); // Missing opening bracket
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_invalid_numbers() {
|
|
assert!("[1.0, abc, 3.0]".parse::<RuVector>().is_err());
|
|
assert!("[1.0, , 3.0]".parse::<RuVector>().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_nan_rejected() {
|
|
assert!("[1.0, nan, 3.0]".parse::<RuVector>().is_err());
|
|
assert!("[NaN, 2.0]".parse::<RuVector>().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_infinity_rejected() {
|
|
assert!("[1.0, inf, 3.0]".parse::<RuVector>().is_err());
|
|
assert!("[1.0, infinity, 3.0]".parse::<RuVector>().is_err());
|
|
assert!("[-inf, 2.0]".parse::<RuVector>().is_err());
|
|
}
|
|
|
|
// ========================================================================
|
|
// Display/Format Tests
|
|
// ========================================================================
|
|
|
|
#[test]
|
|
fn test_display_basic() {
|
|
let v = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
assert_eq!(v.to_string(), "[1,2,3]");
|
|
}
|
|
|
|
#[test]
|
|
fn test_display_decimals() {
|
|
let v = RuVector::from_slice(&[1.5, 2.3, 3.7]);
|
|
assert_eq!(v.to_string(), "[1.5,2.3,3.7]");
|
|
}
|
|
|
|
#[test]
|
|
fn test_display_negative() {
|
|
let v = RuVector::from_slice(&[-1.0, 2.0, -3.0]);
|
|
assert_eq!(v.to_string(), "[-1,2,-3]");
|
|
}
|
|
|
|
#[test]
|
|
fn test_display_empty() {
|
|
let v = RuVector::from_slice(&[]);
|
|
assert_eq!(v.to_string(), "[]");
|
|
}
|
|
|
|
#[test]
|
|
fn test_display_parse_roundtrip() {
|
|
let original = RuVector::from_slice(&[1.5, -2.3, 4.7, 0.0]);
|
|
let s = original.to_string();
|
|
let parsed: RuVector = s.parse().unwrap();
|
|
assert_eq!(original, parsed);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Memory and Metadata Tests
|
|
// ========================================================================
|
|
|
|
#[test]
|
|
fn test_data_memory_size() {
|
|
let v = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
// Header (4 bytes: 2 dims + 2 padding) + 3 * 4 bytes = 16 bytes
|
|
assert_eq!(v.data_memory_size(), 16);
|
|
}
|
|
|
|
#[test]
|
|
fn test_data_memory_size_empty() {
|
|
let v = RuVector::from_slice(&[]);
|
|
// Header only: 4 bytes
|
|
assert_eq!(v.data_memory_size(), 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_data_memory_size_large() {
|
|
let v = RuVector::zeros(1000);
|
|
// Header (4 bytes) + 1000 * 4 bytes = 4004 bytes
|
|
assert_eq!(v.data_memory_size(), 4004);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dimensions_accessor() {
|
|
let v = RuVector::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0]);
|
|
assert_eq!(v.dimensions(), 5);
|
|
}
|
|
|
|
#[test]
|
|
fn test_into_vec() {
|
|
let v = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let vec = v.into_vec();
|
|
assert_eq!(vec, vec![1.0, 2.0, 3.0]);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Equality Tests
|
|
// ========================================================================
|
|
|
|
#[test]
|
|
fn test_equality_same_vectors() {
|
|
let a = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let b = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
assert_eq!(a, b);
|
|
}
|
|
|
|
#[test]
|
|
fn test_equality_different_values() {
|
|
let a = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let b = RuVector::from_slice(&[1.0, 2.0, 4.0]);
|
|
assert_ne!(a, b);
|
|
}
|
|
|
|
#[test]
|
|
fn test_equality_different_dimensions() {
|
|
let a = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let b = RuVector::from_slice(&[1.0, 2.0]);
|
|
assert_ne!(a, b);
|
|
}
|
|
|
|
#[test]
|
|
fn test_equality_empty_vectors() {
|
|
let a = RuVector::from_slice(&[]);
|
|
let b = RuVector::from_slice(&[]);
|
|
assert_eq!(a, b);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Clone Tests
|
|
// ========================================================================
|
|
|
|
#[test]
|
|
fn test_clone_basic() {
|
|
let v1 = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let v2 = v1.clone();
|
|
assert_eq!(v1, v2);
|
|
assert_eq!(v2.as_slice(), &[1.0, 2.0, 3.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_clone_independence() {
|
|
let v1 = RuVector::from_slice(&[1.0, 2.0, 3.0]);
|
|
let mut v2 = v1.clone();
|
|
|
|
// Modify v2
|
|
v2.as_mut_slice()[0] = 99.0;
|
|
|
|
// v1 should be unchanged
|
|
assert_eq!(v1.as_slice(), &[1.0, 2.0, 3.0]);
|
|
assert_eq!(v2.as_slice(), &[99.0, 2.0, 3.0]);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Edge Cases and Boundary Tests
|
|
// ========================================================================
|
|
|
|
#[test]
|
|
fn test_large_dimension_vector() {
|
|
let size = 10000;
|
|
let data: Vec<f32> = (0..size).map(|i| i as f32).collect();
|
|
let v = RuVector::from_slice(&data);
|
|
assert_eq!(v.dimensions(), size);
|
|
assert_eq!(v.as_slice().len(), size);
|
|
}
|
|
|
|
#[test]
|
|
fn test_various_dimension_sizes() {
|
|
// Test power-of-2 and non-power-of-2 sizes for SIMD edge cases
|
|
for size in [1, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128, 255, 256, 1023, 1024] {
|
|
let v = RuVector::zeros(size);
|
|
assert_eq!(v.dimensions(), size);
|
|
assert_eq!(v.as_slice().len(), size);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_all_same_values() {
|
|
let v = RuVector::from_slice(&[5.0, 5.0, 5.0, 5.0, 5.0]);
|
|
assert!(v.as_slice().iter().all(|&x| x == 5.0));
|
|
}
|
|
|
|
#[test]
|
|
fn test_alternating_signs() {
|
|
let data: Vec<f32> = (0..100).map(|i| if i % 2 == 0 { 1.0 } else { -1.0 }).collect();
|
|
let v = RuVector::from_slice(&data);
|
|
for (i, &val) in v.as_slice().iter().enumerate() {
|
|
let expected = if i % 2 == 0 { 1.0 } else { -1.0 };
|
|
assert_eq!(val, expected);
|
|
}
|
|
}
|
|
}
|