hashmap-mem 0.2.2

Fast, low-overhead in-memory hashmap implementation optimized for performance using fxhash
Documentation
/*
 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/piot/hashmap-mem
 * Licensed under the MIT License. See LICENSE in the project root for license information.
 */

use std::alloc::{Layout, alloc};

use hashmap_mem::{
    MapHeader, get_or_reserve_entry, init, layout, lookup, overwrite, remove, reserve_entry,
};

#[test]
fn test_basic_insert_lookup() {
    // Test inserting and looking up a simple key-value pair
    let key_size = 4;
    let value_size = 8;
    let (_, map_init) = layout(key_size, 4, value_size, 8, 16);

    // Allocate memory for the map
    let layout = Layout::from_size_align(map_init.total_size as usize, 8).unwrap();
    let map_base = unsafe { alloc(layout) };
    assert!(!map_base.is_null());

    unsafe {
        // Initialize map
        init(map_base, &map_init);

        // Create a test key and value
        let key: u32 = 0x12345678;
        let key_ptr = (&raw const key).cast::<u8>();

        // Insert a value
        let value_ptr = get_or_reserve_entry(map_base, key_ptr);
        assert!(!value_ptr.is_null());
        *value_ptr.cast::<u64>() = 0xABCDEF0123456789;

        // Verify the element was inserted
        let header = &*(map_base as *const MapHeader);
        assert_eq!(header.element_count, 1);

        // Look up the value
        let found_ptr = lookup(map_base, key_ptr);
        assert!(!found_ptr.is_null());
        assert_eq!(*(found_ptr as *const u64), 0xABCDEF0123456789);
    }
}

#[test]
fn test_remove() {
    // Test removing entries from the map
    let key_size = 2;
    let value_size = 4;
    let (_, map_init) = layout(key_size, 2, value_size, 4, 8);

    // Allocate memory for the map
    let layout = Layout::from_size_align(map_init.total_size as usize, 8).unwrap();
    let map_base = unsafe { alloc(layout) };
    assert!(!map_base.is_null());

    unsafe {
        // Initialize map
        init(map_base, &map_init);

        let key1: u16 = 100;
        let key2: u16 = 200;
        let key1_ptr = (&raw const key1).cast::<u8>();
        let key2_ptr = (&raw const key2).cast::<u8>();

        let value1_ptr = get_or_reserve_entry(map_base, key1_ptr);
        *value1_ptr.cast::<u32>() = 1000;

        let value2_ptr = get_or_reserve_entry(map_base, key2_ptr);
        *value2_ptr.cast::<u32>() = 2000;

        // Verify it has two elements
        let header = &*(map_base as *const MapHeader);
        assert_eq!(header.element_count, 2);

        // Remove one key
        let removed = remove(map_base, key1_ptr);
        assert!(removed);

        // Verify count decreased
        let header = &*(map_base as *const MapHeader);
        assert_eq!(header.element_count, 1);

        // Verify first key no longer exists
        let found_ptr = lookup(map_base, key1_ptr);
        assert!(found_ptr.is_null());

        // Verify second key still exists
        let found_ptr = lookup(map_base, key2_ptr);
        assert!(!found_ptr.is_null());
        assert_eq!(*(found_ptr as *const u32), 2000);
    }
}

#[test]
fn test_overwrite() {
    let key_size = 4;
    let value_size = 4;
    let (_, source_init) = layout(key_size, 4, value_size, 4, 8);
    let (_, target_init) = layout(key_size, 4, value_size, 4, 16);

    let source_layout = Layout::from_size_align(source_init.total_size as usize, 8).unwrap();
    let source_base = unsafe { alloc(source_layout) };
    assert!(!source_base.is_null());

    let target_layout = Layout::from_size_align(target_init.total_size as usize, 8).unwrap();
    let target_base = unsafe { alloc(target_layout) };
    assert!(!target_base.is_null());

    unsafe {
        init(source_base, &source_init);
        init(target_base, &target_init);

        // Insert keys into source
        for i in 0..3 {
            let key = i;
            let key_ptr = (&raw const key).cast::<u8>();
            let value_ptr = get_or_reserve_entry(source_base, key_ptr);
            *value_ptr.cast::<i32>() = i * 100;
        }

        let source_header = &*(source_base as *const MapHeader);
        assert_eq!(source_header.element_count, 3);

        let success = overwrite(target_base, source_base);
        assert!(success);

        let target_header = &*(target_base as *const MapHeader);
        assert_eq!(target_header.element_count, 3);

        // Verify all keys were copied correctly
        for i in 0..3 {
            let key = i;
            let key_ptr = (&raw const key).cast::<u8>();
            let found_ptr = lookup(target_base, key_ptr);
            assert!(!found_ptr.is_null());
            assert_eq!(*(found_ptr as *const i32), i * 100);
        }
    }
}

#[test]
fn test_reserve_entry() {
    // Test that reserve_entry works for new keys and panics for existing keys
    let key_size = 4;
    let value_size = 8;
    let (_, map_init) = layout(key_size, 4, value_size, 8, 16);

    // Allocate memory for the map
    let layout = Layout::from_size_align(map_init.total_size as usize, 8).unwrap();
    let map_base = unsafe { alloc(layout) };
    assert!(!map_base.is_null());

    unsafe {
        // Initialize map
        init(map_base, &map_init);

        // Create a test key and value
        let key: u32 = 0x42424242;
        let key_ptr = (&raw const key).cast::<u8>();

        // Reserve a new entry (should succeed)
        let value_ptr = reserve_entry(map_base, key_ptr);
        assert!(!value_ptr.is_null());
        *value_ptr.cast::<u64>() = 0xDEADBEEF;

        // Verify the element was inserted
        let header = &*(map_base as *const MapHeader);
        assert_eq!(header.element_count, 1);

        // Look up the value to confirm it was set correctly
        let found_ptr = lookup(map_base, key_ptr);
        assert!(!found_ptr.is_null());
        assert_eq!(*(found_ptr as *const u64), 0xDEADBEEF);
    }
}

#[test]
#[should_panic(expected = "Key already exists in map")]
fn test_reserve_entry_panics_on_duplicate() {
    // Test that reserve_entry panics when trying to reserve an existing key
    let key_size = 4;
    let value_size = 8;
    let (_, map_init) = layout(key_size, 4, value_size, 8, 16);

    // Allocate memory for the map
    let layout = Layout::from_size_align(map_init.total_size as usize, 8).unwrap();
    let map_base = unsafe { alloc(layout) };
    assert!(!map_base.is_null());

    unsafe {
        // Initialize map
        init(map_base, &map_init);

        let key: u32 = 0x99999999;
        let key_ptr = (&raw const key).cast::<u8>();

        // Reserve first time (should succeed)
        let value_ptr = reserve_entry(map_base, key_ptr);
        assert!(!value_ptr.is_null());
        *value_ptr.cast::<u64>() = 0x1111;

        // Try to reserve the same key again (should panic)
        reserve_entry(map_base, key_ptr);
    }
}