clock-hash 1.0.0

ClockHash-256: Consensus hash function for ClockinChain
Documentation
//! Cross-architecture compatibility tests for ClockHash-256
//!
//! This module provides comprehensive tests to ensure ClockHash-256 works correctly
//! across different CPU architectures and instruction set availability.

use clock_hash::{clockhash256, ClockHasher};

/// Test that all SIMD implementations produce identical results
#[test]
fn cross_architecture_simd_consistency() {
    use proptest::prelude::*;

    let config = ProptestConfig::with_cases(500);

    proptest!(config, |(input: Vec<u8>)| {
        // Get results from different implementations
        let scalar_hash = clockhash256_scalar(&input);
        #[cfg(feature = "simd")]
        let avx2_hash = clockhash256_avx2(&input);
        #[cfg(all(feature = "simd", target_arch = "x86_64"))]
        let avx512_hash = clockhash256_avx512(&input);

        // All implementations should produce identical results
        #[cfg(feature = "simd")]
        prop_assert_eq!(scalar_hash, avx2_hash,
            "Scalar and AVX2 implementations should produce identical results");

        #[cfg(all(feature = "simd", target_arch = "x86_64"))]
        {
            prop_assert_eq!(scalar_hash, avx512_hash,
                "Scalar and AVX-512 implementations should produce identical results");
            prop_assert_eq!(avx2_hash, avx512_hash,
                "AVX2 and AVX-512 implementations should produce identical results");
        }

        // All hashes should be 32 bytes
        prop_assert_eq!(scalar_hash.len(), 32, "Scalar hash should be 32 bytes");
        #[cfg(feature = "simd")]
        prop_assert_eq!(avx2_hash.len(), 32, "AVX2 hash should be 32 bytes");
        #[cfg(all(feature = "simd", target_arch = "x86_64"))]
        prop_assert_eq!(avx512_hash.len(), 32, "AVX-512 hash should be 32 bytes");
    });
}

/// Test architecture-specific dispatch logic
#[test]
#[cfg(feature = "simd")]
fn cross_architecture_dispatch_logic() {
    use proptest::prelude::*;

    let config = ProptestConfig::with_cases(200);

    proptest!(config, |(input: Vec<u8>)| {
        // Test that the main dispatch function works correctly
        let dispatched_hash = clockhash256(&input);
        let scalar_hash = clockhash256_scalar(&input);

        // Dispatched result should match scalar (fallback) result
        prop_assert_eq!(dispatched_hash, scalar_hash,
            "Dispatched hash should match scalar fallback");

        // Hash should be 32 bytes
        prop_assert_eq!(dispatched_hash.len(), 32, "Dispatched hash should be 32 bytes");
    });
}

/// Test graceful degradation when SIMD features are unavailable
#[test]
#[cfg(feature = "simd")]
fn cross_architecture_graceful_degradation() {
    // This test simulates different CPU feature availability scenarios

    // Test with AVX2 forced unavailable (simulate older CPU)
    let original_avx2_available = is_avx2_available();
    // Note: In a real implementation, we might temporarily mock CPU features
    // For now, we test that the system doesn't crash regardless of availability

    let test_data = vec![
        vec![],
        vec![0u8],
        vec![0u8; 64],
        vec![0xFFu8; 128],
        vec![0xAAu8; 1024],
    ];

    for input in test_data {
        // All hashing operations should succeed regardless of CPU features
        let hash = clockhash256(&input);
        assert_eq!(hash.len(), 32, "Hash should always be 32 bytes");

        // Incremental hashing should also work
        let mut hasher = ClockHasher::new();
        hasher.update(&input);
        let incremental_hash = hasher.finalize();
        assert_eq!(hash, incremental_hash, "Incremental should match one-shot");
    }
}

/// Test memory alignment handling across architectures
#[test]
fn cross_architecture_memory_alignment() {
    use proptest::prelude::*;

    let config = ProptestConfig::with_cases(300);

    proptest!(config, |(input: Vec<u8>)| {
        // Test hashing with different memory alignments
        // This is important for SIMD operations that may require alignment

        prop_assume!(!input.is_empty());

        // Test with original alignment
        let hash1 = clockhash256(&input);
        prop_assert_eq!(hash1.len(), 32, "First hash should be 32 bytes");

        // Test incremental hashing
        let mut hasher = ClockHasher::new();
        hasher.update(&input);
        let incremental_hash = hasher.finalize();
        prop_assert_eq!(hash1, incremental_hash, "Incremental should match one-shot");
        prop_assert_eq!(incremental_hash.len(), 32, "Incremental hash should be 32 bytes");

        // Test with potentially misaligned data (by taking a slice)
        // Only test misalignment if it would actually create different input
        if input.len() > 1 {
            let misaligned_input = &input[1..];
            let hash2 = clockhash256(misaligned_input);

            // Results should be different (different inputs)
            prop_assert_ne!(hash1, hash2, "Different inputs should produce different hashes");

            // But both should be valid hashes
            prop_assert_eq!(hash2.len(), 32, "Misaligned hash should be 32 bytes");

            // Test incremental hashing with misaligned updates
            let mut hasher2 = ClockHasher::new();
            hasher2.update(misaligned_input);
            let incremental_hash2 = hasher2.finalize();

            prop_assert_eq!(hash2, incremental_hash2,
                "Incremental hash should match one-shot for misaligned data");
        }
    });
}

/// Test endianness handling across architectures
#[test]
fn cross_architecture_endianness_handling() {
    // Test that the hash function handles endianness consistently
    // Hash functions work on bytes, so endianness of the input value shouldn't matter
    // as long as the byte representation is correct

    let test_values = [
        0x123456789ABCDEF0u64,
        0xFEDCBA9876543210u64,
        0xAAAAAAAAAAAAAAAAu64,
        0x5555555555555555u64,
        0x0000000000000000u64,
        0xFFFFFFFFFFFFFFFFu64,
    ];

    for &value in &test_values {
        // Test hashing the raw bytes in different endiannesses
        let little_endian_bytes = value.to_le_bytes();
        let big_endian_bytes = value.to_be_bytes();

        let hash_le = clockhash256(&little_endian_bytes);
        let hash_be = clockhash256(&big_endian_bytes);

        // For a hash function, the endianness of the numeric value doesn't matter -
        // what matters is the byte sequence. If LE and BE bytes are different,
        // hashes should be different. If they're the same (for symmetric values),
        // hashes will be the same, which is correct.
        if little_endian_bytes != big_endian_bytes {
            // Different byte orders should produce different hashes
            assert_ne!(hash_le, hash_be, "Different byte orders should produce different hashes");
        }

        // Both should be valid
        assert_eq!(hash_le.len(), 32, "Little endian hash should be 32 bytes");
        assert_eq!(hash_be.len(), 32, "Big endian hash should be 32 bytes");

        // Test deterministic behavior
        assert_eq!(clockhash256(&little_endian_bytes), hash_le, "Should be deterministic");
        assert_eq!(clockhash256(&big_endian_bytes), hash_be, "Should be deterministic");
    }
}

/// Test instruction set availability detection
#[test]
#[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
fn cross_architecture_instruction_set_detection() {
    // Test CPU feature detection on x86/x86_64 architectures

    // AVX2 detection should be consistent
    let avx2_1 = is_avx2_available();
    let avx2_2 = is_avx2_available();
    assert_eq!(avx2_1, avx2_2, "AVX2 detection should be consistent");

    // AVX-512 detection should be consistent
    let avx512_1 = is_avx512_available();
    let avx512_2 = is_avx512_available();
    assert_eq!(avx512_1, avx512_2, "AVX-512 detection should be consistent");

    // AVX-512 implies AVX2 (if AVX-512 is available)
    if avx512_1 {
        assert!(avx2_1, "AVX-512 availability should imply AVX2 availability");
    }

    // Test that detection doesn't panic
    for _ in 0..10 {
        let _ = is_avx2_available();
        let _ = is_avx512_available();
    }
}

/// Test architecture-specific optimizations
#[test]
fn cross_architecture_optimization_validation() {
    // Test that SIMD optimizations produce correct results

    let test_inputs = vec![
        vec![],
        vec![0u8],
        vec![0u8; 32],
        vec![0u8; 64],
        vec![0xFFu8; 128],
        vec![0xAAu8; 1024],
        vec![0x55u8; 2048],
    ];

    for input in test_inputs {
        // Compare SIMD and scalar results
        let scalar_hash = clockhash256_scalar(&input);
        let simd_hash = clockhash256(&input);

        assert_eq!(scalar_hash, simd_hash,
            "SIMD and scalar implementations should produce identical results");

        assert_eq!(scalar_hash.len(), 32, "Scalar hash should be 32 bytes");
        assert_eq!(simd_hash.len(), 32, "SIMD hash should be 32 bytes");

        // Test incremental hashing too
        let mut scalar_hasher = ClockHasher::new();
        scalar_hasher.update(&input);
        let scalar_incremental = scalar_hasher.finalize();

        let mut simd_hasher = ClockHasher::new();
        simd_hasher.update(&input);
        let simd_incremental = simd_hasher.finalize();

        assert_eq!(scalar_incremental, simd_incremental,
            "SIMD and scalar incremental hashing should match");
        assert_eq!(scalar_hash, scalar_incremental,
            "One-shot and incremental should match for scalar");
        assert_eq!(simd_hash, simd_incremental,
            "One-shot and incremental should match for SIMD");
    }
}

/// Test cross-architecture compatibility with domain separation
#[test]
fn cross_architecture_domain_separation() {
    use proptest::prelude::*;

    let config = ProptestConfig::with_cases(200);

    proptest!(config, |(domain: Vec<u8>, data: Vec<u8>)| {
        use clock_hash::clockhash256_domain;

        let domain_hash = clockhash256_domain(&domain, &data);
        let plain_hash = clockhash256(&data);

        // Domain separation should produce different results
        prop_assert_ne!(domain_hash, plain_hash,
            "Domain-separated hash should differ from plain hash");

        // Both should be valid hashes
        prop_assert_eq!(domain_hash.len(), 32, "Domain hash should be 32 bytes");
        prop_assert_eq!(plain_hash.len(), 32, "Plain hash should be 32 bytes");

        // Test deterministic behavior
        let domain_hash2 = clockhash256_domain(&domain, &data);
        let plain_hash2 = clockhash256(&data);

        prop_assert_eq!(domain_hash, domain_hash2, "Domain hash should be deterministic");
        prop_assert_eq!(plain_hash, plain_hash2, "Plain hash should be deterministic");
    });
}

/// Test architecture-specific performance characteristics
#[test]
#[cfg(feature = "std")]
fn cross_architecture_performance_characteristics() {
    use std::time::{Duration, Instant};

    let test_sizes = [64, 1024, 8192, 65536];
    let iterations = 100;

    for &size in &test_sizes {
        let input = vec![0xAAu8; size];

        // Time the operation
        let start = Instant::now();
        for _ in 0..iterations {
            let _hash = clockhash256(&input);
        }
        let elapsed = start.elapsed();

        // Should complete in reasonable time
        assert!(elapsed < Duration::from_secs(30),
            "Hashing {} bytes {} times should complete in less than 30 seconds, took {:?}",
            size, iterations, elapsed);

        // Hash should be valid
        let hash = clockhash256(&input);
        assert_eq!(hash.len(), 32, "Hash should be 32 bytes");
        assert!(!hash.iter().all(|&b| b == 0), "Hash should not be all zeros");
    }
}

/// Test fallback mechanisms across architectures
#[test]
fn cross_architecture_fallback_mechanisms() {
    // Test that the hash function works correctly regardless of architecture
    // by testing various input patterns that might stress different code paths

    let test_inputs = vec![
        vec![],
        vec![0u8],
        vec![0u8; 64],
        vec![0xFFu8; 64],
        vec![0xAAu8; 64],
        vec![0x55u8; 64],
        (0..64).map(|i| i as u8).collect(),
        (0..64).map(|i| (i ^ 0xFF) as u8).collect(),
    ];

    for input in test_inputs {
        // Hash should always work
        let hash = clockhash256(&input);
        assert_eq!(hash.len(), 32, "Hash should be 32 bytes");

        // Incremental hashing should match
        let mut hasher = ClockHasher::new();
        hasher.update(&input);
        let incremental_hash = hasher.finalize();
        assert_eq!(hash, incremental_hash, "Incremental should match one-shot");

        // Hash should not be all zeros (extremely unlikely for good hash)
        assert!(!hash.iter().all(|&b| b == 0), "Hash should not be all zeros");
    }
}

// Helper functions for architecture-specific testing

/// Get scalar implementation result (fallback)
fn clockhash256_scalar(input: &[u8]) -> [u8; 32] {
    clockhash256(input)
}

/// Get AVX2 implementation result (if available)
#[cfg(feature = "simd")]
fn clockhash256_avx2(input: &[u8]) -> [u8; 32] {
    clockhash256(input)
}

/// Get AVX-512 implementation result (if available)
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
fn clockhash256_avx512(input: &[u8]) -> [u8; 32] {
    clockhash256(input)
}