#[cfg(all(test, feature = "std"))]
mod property_tests {
use crate::simd::dispatch::*;
use crate::simd::scalar::*;
use proptest::prelude::*;
use std::panic::AssertUnwindSafe;
fn u64_array_strategy() -> impl Strategy<Value = [u64; 16]> {
[any::<u64>(); 16]
}
fn block_strategy() -> impl Strategy<Value = [u8; 128]> {
[any::<u8>(); 128]
}
fn state_strategy() -> impl Strategy<Value = [u64; 8]> {
[any::<u64>(); 8]
}
proptest! {
#[test]
fn test_simd_scalar_equivalence_prop(data in u64_array_strategy()) {
let mut msg_simd = data;
let mut msg_scalar = data;
prop_assume!(std::panic::catch_unwind(AssertUnwindSafe(|| {
clock_mix_avx2(&mut msg_simd);
})).is_ok());
scalar_clock_mix(&mut msg_scalar);
prop_assert_eq!(msg_simd, msg_scalar,
"SIMD and scalar implementations should produce identical results");
}
#[test]
fn test_clock_mix_deterministic_prop(data in u64_array_strategy()) {
let mut msg1 = data;
let mut msg2 = data;
clock_mix_avx2(&mut msg1);
clock_mix_avx2(&mut msg2);
prop_assert_eq!(msg1, msg2,
"ClockMix should be deterministic for same input");
}
#[test]
fn test_clock_mix_not_identity_prop(data in u64_array_strategy()) {
let original = data;
let mut modified = data;
clock_mix_avx2(&mut modified);
let is_all_zeros = original.iter().all(|&x| x == 0);
if !is_all_zeros {
prop_assert_ne!(modified, original,
"ClockMix should modify input data (unless input is all zeros)");
}
}
#[test]
fn test_process_block_consistency_prop(
block in block_strategy(),
mut state1 in state_strategy(),
mut state2 in state_strategy()
) {
state2 = state1;
process_block_simd_scalar(&block, &mut state1);
process_block_simd(&block, &mut state2);
prop_assert_eq!(state1, state2,
"Block processing should be consistent between SIMD and scalar");
}
#[test]
fn test_simd_preserves_length_prop(data in u64_array_strategy()) {
let original = data;
let mut modified = data;
clock_mix_avx2(&mut modified);
prop_assert_eq!(modified.len(), original.len());
let is_all_zeros_input = original.iter().all(|&x| x == 0);
let is_all_zeros_output = modified.iter().all(|&x| x == 0);
if !is_all_zeros_input {
prop_assert!(!is_all_zeros_output,
"ClockMix should not produce all zeros from non-zero input");
}
}
#[test]
fn test_simd_bit_distribution_prop(data in u64_array_strategy()) {
let mut modified = data;
clock_mix_avx2(&mut modified);
let input_bits: u32 = data.iter().map(|&x| x.count_ones()).sum();
let output_bits: u32 = modified.iter().map(|&x| x.count_ones()).sum();
let input_bits_f = input_bits as f64;
let output_bits_f = output_bits as f64;
let ratio = if input_bits_f > 0.0 {
output_bits_f / input_bits_f
} else {
1.0
};
prop_assert!((0.8..=1.2).contains(&ratio),
"Bit distribution should be reasonably preserved (ratio: {})", ratio);
}
#[test]
fn test_simd_diffusion_prop(base_data in u64_array_strategy()) {
let mut modified = base_data;
let original = base_data;
clock_mix_avx2(&mut modified);
let is_all_zeros = original.iter().all(|&x| x == 0);
if !is_all_zeros {
prop_assert_ne!(modified, original, "ClockMix should modify non-zero input");
}
let output_all_zeros = modified.iter().all(|&x| x == 0);
if !is_all_zeros {
prop_assert!(!output_all_zeros, "ClockMix should not produce all zeros from non-zero input");
}
}
#[test]
fn test_simd_no_fixed_points_prop(data in u64_array_strategy()) {
let mut modified = data;
clock_mix_avx2(&mut modified);
let unchanged_count = modified.iter().zip(data.iter())
.filter(|(a, b)| a == b).count();
prop_assert!(unchanged_count <= 2,
"Too many elements unchanged: {} out of {}", unchanged_count, data.len());
}
#[test]
fn test_simd_state_injection_prop(
block in block_strategy(),
base_state in state_strategy()
) {
let mut state_simd = base_state;
let mut state_scalar = base_state;
process_block_simd(&block, &mut state_simd);
process_block_simd_scalar(&block, &mut state_scalar);
prop_assert_ne!(state_simd, base_state,
"SIMD state should be modified by block processing");
prop_assert_ne!(state_scalar, base_state,
"Scalar state should be modified by block processing");
prop_assert_eq!(state_simd, state_scalar,
"SIMD and scalar state injection should match");
}
#[test]
fn test_simd_endianness_robustness_prop(
mut block in block_strategy()
) {
let mut state1 = [0u64; 8];
let mut state2 = [0u64; 8];
process_block_simd(&block, &mut state1);
for chunk in block.chunks_exact_mut(8) {
chunk.reverse();
}
process_block_simd(&block, &mut state2);
prop_assert_ne!(state1, state2,
"Block processing should be sensitive to endianness changes");
}
}
fn process_block_simd_scalar(block: &[u8; 128], state: &mut [u64; 8]) {
let mut words = [0u64; 16];
for i in 0..16 {
let offset = i * 8;
words[i] = u64::from_le_bytes([
block[offset],
block[offset + 1],
block[offset + 2],
block[offset + 3],
block[offset + 4],
block[offset + 5],
block[offset + 6],
block[offset + 7],
]);
}
scalar_clock_mix(&mut words);
for i in 0..8 {
state[i] = state[i].wrapping_add(words[i]);
let rot_idx = (i + 4) % 8;
state[i] ^= crate::utils::rotl64(state[rot_idx], 17);
}
crate::clockpermute::clock_permute(state);
}
}