cranpose-ui-graphics 0.9.12

Geometry, units, paths and render data for Cranpose UI
Documentation
use std::hash::Hash;

use super::*;

fn hash_of<T: Hash>(value: &T) -> u64 {
    let mut hasher = FxHasher::default();
    value.hash(&mut hasher);
    hasher.finish()
}

#[test]
fn single_words_never_collide() {
    let mut seen = std::collections::HashSet::new();
    for value in 0u64..200_000 {
        assert!(seen.insert(hash_of(&value)), "collision at {value}");
    }
}

#[test]
fn adjacent_f32_bit_patterns_stay_distinct() {
    let mut seen = std::collections::HashSet::new();
    for step in 0u32..200_000 {
        let value = f32::from_bits(0x3f80_0000 + step);
        assert!(
            seen.insert(hash_of(&value.to_bits())),
            "collision at {step}"
        );
    }
}

#[test]
fn word_order_matters() {
    assert_ne!(hash_of(&(1u64, 2u64)), hash_of(&(2u64, 1u64)));
}

#[test]
fn byte_tail_length_matters() {
    let mut short = FxHasher::default();
    short.write(&[1]);
    let mut long = FxHasher::default();
    long.write(&[1, 0]);
    assert_ne!(short.finish(), long.finish());
}

#[test]
fn strings_of_every_length_stay_distinct() {
    let mut seen = std::collections::HashSet::new();
    for len in 0..512usize {
        let value = "x".repeat(len);
        assert!(seen.insert(hash_of(&value)), "collision at len {len}");
    }
}

#[test]
fn flipping_any_single_bit_of_a_pair_changes_the_hash() {
    let base = hash_of(&(0x1234_5678_9abc_def0u64, 0u64));
    for bit in 0..64 {
        let flipped = hash_of(&(0x1234_5678_9abc_def0u64 ^ (1u64 << bit), 0u64));
        assert_ne!(base, flipped, "bit {bit} did not change the hash");
    }
}

#[test]
fn low_bits_avalanche_enough_for_hash_map_bucketing() {
    let mut buckets = [0usize; 64];
    for value in 0u64..64_000 {
        buckets[(hash_of(&value) & 63) as usize] += 1;
    }
    let expected = 64_000 / 64;
    for (index, count) in buckets.iter().enumerate() {
        assert!(
            count * 4 > expected && *count < expected * 4,
            "bucket {index} held {count} of {expected} expected"
        );
    }
}

fn hash_bytes(bytes: &[u8]) -> u64 {
    let mut hasher = FxHasher::default();
    hasher.write(bytes);
    hasher.finish()
}

/// A recording-sized column: long enough to fold in lanes.
fn column() -> Vec<u8> {
    (0..256u32).map(|index| (index * 37 % 251) as u8).collect()
}

#[test]
fn every_single_bit_of_a_long_write_changes_the_hash() {
    let base = column();
    let hash = hash_bytes(&base);
    for bit in 0..base.len() * 8 {
        let mut flipped = base.clone();
        flipped[bit / 8] ^= 1 << (bit % 8);
        assert_ne!(
            hash_bytes(&flipped),
            hash,
            "bit {bit} did not change the hash"
        );
    }
}

#[test]
fn words_trading_places_in_a_long_write_change_the_hash() {
    let base = column();
    let hash = hash_bytes(&base);
    let swapped = |first: usize, second: usize| {
        let mut bytes = base.clone();
        for offset in 0..8 {
            bytes.swap(first * 8 + offset, second * 8 + offset);
        }
        hash_bytes(&bytes)
    };
    // Two lanes of one block, the same lane of two blocks, and two lanes of
    // two blocks.
    for (first, second) in [(0, 1), (0, 4), (1, 6)] {
        assert_ne!(swapped(first, second), hash, "words {first} and {second}");
    }
}

#[test]
fn long_writes_of_every_length_stay_distinct() {
    let base = column();
    let mut seen = std::collections::HashSet::new();
    for len in 0..=base.len() {
        assert!(
            seen.insert(hash_bytes(&base[..len])),
            "collision at len {len}"
        );
    }
}