LCOV - code coverage report
Current view: top level - libs/neon-shmem/src/hash - tests.rs (source / functions) Coverage Total Hit
Test: 4be46b1c0003aa3bbac9ade362c676b419df4c20.info Lines: 94.7 % 300 284
Test Date: 2025-07-22 17:50:06 Functions: 88.5 % 26 23

            Line data    Source code
       1              : use std::collections::BTreeMap;
       2              : use std::collections::HashSet;
       3              : use std::fmt::Debug;
       4              : use std::mem::MaybeUninit;
       5              : 
       6              : use crate::hash::Entry;
       7              : use crate::hash::HashMapAccess;
       8              : use crate::hash::HashMapInit;
       9              : use crate::hash::core::FullError;
      10              : 
      11              : use rand::seq::SliceRandom;
      12              : use rand::{Rng, RngCore};
      13              : use rand_distr::Zipf;
      14              : 
      15              : const TEST_KEY_LEN: usize = 16;
      16              : 
      17              : #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
      18              : struct TestKey([u8; TEST_KEY_LEN]);
      19              : 
      20              : impl From<&TestKey> for u128 {
      21            0 :     fn from(val: &TestKey) -> u128 {
      22            0 :         u128::from_be_bytes(val.0)
      23            0 :     }
      24              : }
      25              : 
      26              : impl From<u128> for TestKey {
      27       390023 :     fn from(val: u128) -> TestKey {
      28       390023 :         TestKey(val.to_be_bytes())
      29       390023 :     }
      30              : }
      31              : 
      32              : impl<'a> From<&'a [u8]> for TestKey {
      33            0 :     fn from(bytes: &'a [u8]) -> TestKey {
      34            0 :         TestKey(bytes.try_into().unwrap())
      35            0 :     }
      36              : }
      37              : 
      38           12 : fn test_inserts<K: Into<TestKey> + Copy>(keys: &[K]) {
      39           12 :     let w = HashMapInit::<TestKey, usize>::new_resizeable_named(100000, 120000, "test_inserts")
      40           12 :         .attach_writer();
      41              : 
      42       110005 :     for (idx, k) in keys.iter().enumerate() {
      43       110005 :         let res = w.entry((*k).into());
      44       110005 :         match res {
      45            0 :             Entry::Occupied(mut e) => {
      46            0 :                 e.insert(idx);
      47            0 :             }
      48       110005 :             Entry::Vacant(e) => {
      49       110005 :                 let res = e.insert(idx);
      50       110005 :                 assert!(res.is_ok());
      51              :             }
      52              :         };
      53              :     }
      54              : 
      55       110005 :     for (idx, k) in keys.iter().enumerate() {
      56       110005 :         let x = w.get(&(*k).into());
      57       110005 :         let value = x.as_deref().copied();
      58       110005 :         assert_eq!(value, Some(idx));
      59              :     }
      60           12 : }
      61              : 
      62              : #[test]
      63            1 : fn dense() {
      64              :     // This exercises splitting a node with prefix
      65            1 :     let keys: &[u128] = &[0, 1, 2, 3, 256];
      66            1 :     test_inserts(keys);
      67              : 
      68              :     // Dense keys
      69            1 :     let mut keys: Vec<u128> = (0..10000).collect();
      70            1 :     test_inserts(&keys);
      71              : 
      72              :     // Do the same in random orders
      73           10 :     for _ in 1..10 {
      74            9 :         keys.shuffle(&mut rand::rng());
      75            9 :         test_inserts(&keys);
      76            9 :     }
      77            1 : }
      78              : 
      79              : #[test]
      80            1 : fn sparse() {
      81              :     // sparse keys
      82            1 :     let mut keys: Vec<TestKey> = Vec::new();
      83            1 :     let mut used_keys = HashSet::new();
      84        10001 :     for _ in 0..10000 {
      85              :         loop {
      86        10000 :             let key = rand::random::<u128>();
      87        10000 :             if used_keys.contains(&key) {
      88            0 :                 continue;
      89        10000 :             }
      90        10000 :             used_keys.insert(key);
      91        10000 :             keys.push(key.into());
      92        10000 :             break;
      93              :         }
      94              :     }
      95            1 :     test_inserts(&keys);
      96            1 : }
      97              : 
      98              : #[derive(Clone, Debug)]
      99              : struct TestOp(TestKey, Option<usize>);
     100              : 
     101       179350 : fn apply_op(
     102       179350 :     op: &TestOp,
     103       179350 :     map: &mut HashMapAccess<TestKey, usize>,
     104       179350 :     shadow: &mut BTreeMap<TestKey, usize>,
     105       179350 : ) {
     106              :     // apply the change to the shadow tree first
     107       179350 :     let shadow_existing = if let Some(v) = op.1 {
     108       126384 :         shadow.insert(op.0, v)
     109              :     } else {
     110        52966 :         shadow.remove(&op.0)
     111              :     };
     112              : 
     113       179350 :     let entry = map.entry(op.0);
     114       179350 :     let hash_existing = match op.1 {
     115       126384 :         Some(new) => match entry {
     116        64716 :             Entry::Occupied(mut e) => Some(e.insert(new)),
     117        61668 :             Entry::Vacant(e) => {
     118        61668 :                 _ = e.insert(new).unwrap();
     119        61668 :                 None
     120              :             }
     121              :         },
     122        52966 :         None => match entry {
     123        22708 :             Entry::Occupied(e) => Some(e.remove()),
     124        30258 :             Entry::Vacant(_) => None,
     125              :         },
     126              :     };
     127              : 
     128       179350 :     assert_eq!(shadow_existing, hash_existing);
     129       179350 : }
     130              : 
     131           15 : fn do_random_ops(
     132           15 :     num_ops: usize,
     133           15 :     size: u32,
     134           15 :     del_prob: f64,
     135           15 :     writer: &mut HashMapAccess<TestKey, usize>,
     136           15 :     shadow: &mut BTreeMap<TestKey, usize>,
     137           15 :     rng: &mut rand::rngs::ThreadRng,
     138           15 : ) {
     139        79350 :     for i in 0..num_ops {
     140        79350 :         let key: TestKey = ((rng.next_u32() % size) as u128).into();
     141        79350 :         let op = TestOp(
     142        79350 :             key,
     143        79350 :             if rng.random_bool(del_prob) {
     144        51627 :                 Some(i)
     145              :             } else {
     146        27723 :                 None
     147              :             },
     148              :         );
     149        79350 :         apply_op(&op, writer, shadow);
     150              :     }
     151           15 : }
     152              : 
     153           46 : fn do_deletes(
     154           46 :     num_ops: usize,
     155           46 :     writer: &mut HashMapAccess<TestKey, usize>,
     156           46 :     shadow: &mut BTreeMap<TestKey, usize>,
     157           46 : ) {
     158          545 :     for _ in 0..num_ops {
     159          545 :         let (k, _) = shadow.pop_first().unwrap();
     160          545 :         writer.remove(&k);
     161          545 :     }
     162           46 : }
     163              : 
     164            3 : fn do_shrink(
     165            3 :     writer: &mut HashMapAccess<TestKey, usize>,
     166            3 :     shadow: &mut BTreeMap<TestKey, usize>,
     167            3 :     from: u32,
     168            3 :     to: u32,
     169            3 : ) {
     170            3 :     assert!(writer.shrink_goal().is_none());
     171            3 :     writer.begin_shrink(to);
     172            3 :     assert_eq!(writer.shrink_goal(), Some(to as usize));
     173         2050 :     for i in to..from {
     174         2050 :         if let Some(entry) = writer.entry_at_bucket(i as usize) {
     175          225 :             shadow.remove(&entry._key);
     176          225 :             entry.remove();
     177         1825 :         }
     178              :     }
     179            3 :     let old_usage = writer.get_num_buckets_in_use();
     180            3 :     writer.finish_shrink().unwrap();
     181            3 :     assert!(writer.shrink_goal().is_none());
     182            3 :     assert_eq!(writer.get_num_buckets_in_use(), old_usage);
     183            3 : }
     184              : 
     185              : #[test]
     186            1 : fn random_ops() {
     187            1 :     let mut writer =
     188            1 :         HashMapInit::<TestKey, usize>::new_resizeable_named(100000, 120000, "test_random")
     189            1 :             .attach_writer();
     190            1 :     let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
     191              : 
     192            1 :     let distribution = Zipf::new(u128::MAX as f64, 1.1).unwrap();
     193            1 :     let mut rng = rand::rng();
     194       100001 :     for i in 0..100000 {
     195       100000 :         let key: TestKey = (rng.sample(distribution) as u128).into();
     196              : 
     197       100000 :         let op = TestOp(key, if rng.random_bool(0.75) { Some(i) } else { None });
     198              : 
     199       100000 :         apply_op(&op, &mut writer, &mut shadow);
     200              :     }
     201            1 : }
     202              : 
     203              : #[test]
     204            1 : fn test_shuffle() {
     205            1 :     let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(1000, 1200, "test_shuf")
     206            1 :         .attach_writer();
     207            1 :     let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
     208            1 :     let mut rng = rand::rng();
     209              : 
     210            1 :     do_random_ops(10000, 1000, 0.75, &mut writer, &mut shadow, &mut rng);
     211            1 :     writer.shuffle();
     212            1 :     do_random_ops(10000, 1000, 0.75, &mut writer, &mut shadow, &mut rng);
     213            1 : }
     214              : 
     215              : #[test]
     216            1 : fn test_grow() {
     217            1 :     let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(1000, 2000, "test_grow")
     218            1 :         .attach_writer();
     219            1 :     let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
     220            1 :     let mut rng = rand::rng();
     221              : 
     222            1 :     do_random_ops(10000, 1000, 0.75, &mut writer, &mut shadow, &mut rng);
     223            1 :     let old_usage = writer.get_num_buckets_in_use();
     224            1 :     writer.grow(1500).unwrap();
     225            1 :     assert_eq!(writer.get_num_buckets_in_use(), old_usage);
     226            1 :     assert_eq!(writer.get_num_buckets(), 1500);
     227            1 :     do_random_ops(10000, 1500, 0.75, &mut writer, &mut shadow, &mut rng);
     228            1 : }
     229              : 
     230              : #[test]
     231            1 : fn test_clear() {
     232            1 :     let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(1500, 2000, "test_clear")
     233            1 :         .attach_writer();
     234            1 :     let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
     235            1 :     let mut rng = rand::rng();
     236            1 :     do_random_ops(2000, 1500, 0.75, &mut writer, &mut shadow, &mut rng);
     237            1 :     writer.clear();
     238            1 :     assert_eq!(writer.get_num_buckets_in_use(), 0);
     239            1 :     assert_eq!(writer.get_num_buckets(), 1500);
     240          809 :     while let Some((key, _)) = shadow.pop_first() {
     241          808 :         assert!(writer.get(&key).is_none());
     242              :     }
     243            1 :     do_random_ops(2000, 1500, 0.75, &mut writer, &mut shadow, &mut rng);
     244          650 :     for i in 0..(1500 - writer.get_num_buckets_in_use()) {
     245          650 :         writer.insert((1500 + i as u128).into(), 0).unwrap();
     246          650 :     }
     247            1 :     assert_eq!(writer.insert(5000.into(), 0), Err(FullError {}));
     248            1 :     writer.clear();
     249            1 :     assert!(writer.insert(5000.into(), 0).is_ok());
     250            1 : }
     251              : 
     252              : #[test]
     253            1 : fn test_idx_remove() {
     254            1 :     let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(1500, 2000, "test_clear")
     255            1 :         .attach_writer();
     256            1 :     let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
     257            1 :     let mut rng = rand::rng();
     258            1 :     do_random_ops(2000, 1500, 0.25, &mut writer, &mut shadow, &mut rng);
     259          101 :     for _ in 0..100 {
     260          100 :         let idx = (rng.next_u32() % 1500) as usize;
     261          100 :         if let Some(e) = writer.entry_at_bucket(idx) {
     262           22 :             shadow.remove(&e._key);
     263           22 :             e.remove();
     264           78 :         }
     265              :     }
     266          273 :     while let Some((key, val)) = shadow.pop_first() {
     267          272 :         assert_eq!(*writer.get(&key).unwrap(), val);
     268              :     }
     269            1 : }
     270              : 
     271              : #[test]
     272            1 : fn test_idx_get() {
     273            1 :     let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(1500, 2000, "test_clear")
     274            1 :         .attach_writer();
     275            1 :     let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
     276            1 :     let mut rng = rand::rng();
     277            1 :     do_random_ops(2000, 1500, 0.25, &mut writer, &mut shadow, &mut rng);
     278          101 :     for _ in 0..100 {
     279          100 :         let idx = (rng.next_u32() % 1500) as usize;
     280          100 :         if let Some(pair) = writer.get_at_bucket(idx) {
     281              :             {
     282           21 :                 let v: *const usize = &pair.1;
     283           21 :                 assert_eq!(writer.get_bucket_for_value(v), idx);
     284              :             }
     285              :             {
     286           21 :                 let v: *const usize = &pair.1;
     287           21 :                 assert_eq!(writer.get_bucket_for_value(v), idx);
     288              :             }
     289           79 :         }
     290              :     }
     291            1 : }
     292              : 
     293              : #[test]
     294            1 : fn test_shrink() {
     295            1 :     let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(1500, 2000, "test_shrink")
     296            1 :         .attach_writer();
     297            1 :     let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
     298            1 :     let mut rng = rand::rng();
     299              : 
     300            1 :     do_random_ops(10000, 1500, 0.75, &mut writer, &mut shadow, &mut rng);
     301            1 :     do_shrink(&mut writer, &mut shadow, 1500, 1000);
     302            1 :     assert_eq!(writer.get_num_buckets(), 1000);
     303            1 :     do_deletes(500, &mut writer, &mut shadow);
     304            1 :     do_random_ops(10000, 500, 0.75, &mut writer, &mut shadow, &mut rng);
     305            1 :     assert!(writer.get_num_buckets_in_use() <= 1000);
     306            1 : }
     307              : 
     308              : #[test]
     309            1 : fn test_shrink_grow_seq() {
     310            1 :     let mut writer =
     311            1 :         HashMapInit::<TestKey, usize>::new_resizeable_named(1000, 20000, "test_grow_seq")
     312            1 :             .attach_writer();
     313            1 :     let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
     314            1 :     let mut rng = rand::rng();
     315              : 
     316            1 :     do_random_ops(500, 1000, 0.1, &mut writer, &mut shadow, &mut rng);
     317            1 :     eprintln!("Shrinking to 750");
     318            1 :     do_shrink(&mut writer, &mut shadow, 1000, 750);
     319            1 :     do_random_ops(200, 1000, 0.5, &mut writer, &mut shadow, &mut rng);
     320            1 :     eprintln!("Growing to 1500");
     321            1 :     writer.grow(1500).unwrap();
     322            1 :     do_random_ops(600, 1500, 0.1, &mut writer, &mut shadow, &mut rng);
     323            1 :     eprintln!("Shrinking to 200");
     324           46 :     while shadow.len() > 100 {
     325           45 :         do_deletes(1, &mut writer, &mut shadow);
     326           45 :     }
     327            1 :     do_shrink(&mut writer, &mut shadow, 1500, 200);
     328            1 :     do_random_ops(50, 1500, 0.25, &mut writer, &mut shadow, &mut rng);
     329            1 :     eprintln!("Growing to 10k");
     330            1 :     writer.grow(10000).unwrap();
     331            1 :     do_random_ops(10000, 5000, 0.25, &mut writer, &mut shadow, &mut rng);
     332            1 : }
     333              : 
     334              : #[test]
     335            1 : fn test_bucket_ops() {
     336            1 :     let writer = HashMapInit::<TestKey, usize>::new_resizeable_named(1000, 1200, "test_bucket_ops")
     337            1 :         .attach_writer();
     338            1 :     match writer.entry(1.into()) {
     339            0 :         Entry::Occupied(mut e) => {
     340            0 :             e.insert(2);
     341            0 :         }
     342            1 :         Entry::Vacant(e) => {
     343            1 :             _ = e.insert(2).unwrap();
     344            1 :         }
     345              :     }
     346            1 :     assert_eq!(writer.get_num_buckets_in_use(), 1);
     347            1 :     assert_eq!(writer.get_num_buckets(), 1000);
     348            1 :     assert_eq!(*writer.get(&1.into()).unwrap(), 2);
     349            1 :     let pos = match writer.entry(1.into()) {
     350            1 :         Entry::Occupied(e) => {
     351            1 :             assert_eq!(e._key, 1.into());
     352            1 :             e.bucket_pos as usize
     353              :         }
     354              :         Entry::Vacant(_) => {
     355            0 :             panic!("Insert didn't affect entry");
     356              :         }
     357              :     };
     358            1 :     assert_eq!(writer.entry_at_bucket(pos).unwrap()._key, 1.into());
     359            1 :     assert_eq!(*writer.get_at_bucket(pos).unwrap(), (1.into(), 2));
     360              :     {
     361            1 :         let ptr: *const usize = &*writer.get(&1.into()).unwrap();
     362            1 :         assert_eq!(writer.get_bucket_for_value(ptr), pos);
     363              :     }
     364            1 :     writer.remove(&1.into());
     365            1 :     assert!(writer.get(&1.into()).is_none());
     366            1 : }
     367              : 
     368              : #[test]
     369            1 : fn test_shrink_zero() {
     370            1 :     let mut writer =
     371            1 :         HashMapInit::<TestKey, usize>::new_resizeable_named(1500, 2000, "test_shrink_zero")
     372            1 :             .attach_writer();
     373            1 :     writer.begin_shrink(0);
     374         1501 :     for i in 0..1500 {
     375         1500 :         writer.entry_at_bucket(i).map(|x| x.remove());
     376              :     }
     377            1 :     writer.finish_shrink().unwrap();
     378            1 :     assert_eq!(writer.get_num_buckets_in_use(), 0);
     379            1 :     let entry = writer.entry(1.into());
     380            1 :     if let Entry::Vacant(v) = entry {
     381            1 :         assert!(v.insert(2).is_err());
     382              :     } else {
     383            0 :         panic!("Somehow got non-vacant entry in empty map.")
     384              :     }
     385            1 :     writer.grow(50).unwrap();
     386            1 :     let entry = writer.entry(1.into());
     387            1 :     if let Entry::Vacant(v) = entry {
     388            1 :         assert!(v.insert(2).is_ok());
     389              :     } else {
     390            0 :         panic!("Somehow got non-vacant entry in empty map.")
     391              :     }
     392            1 :     assert_eq!(writer.get_num_buckets_in_use(), 1);
     393            1 : }
     394              : 
     395              : #[test]
     396              : #[should_panic]
     397            1 : fn test_grow_oom() {
     398            1 :     let writer = HashMapInit::<TestKey, usize>::new_resizeable_named(1500, 2000, "test_grow_oom")
     399            1 :         .attach_writer();
     400            1 :     writer.grow(20000).unwrap();
     401            1 : }
     402              : 
     403              : #[test]
     404              : #[should_panic]
     405            1 : fn test_shrink_bigger() {
     406            1 :     let mut writer =
     407            1 :         HashMapInit::<TestKey, usize>::new_resizeable_named(1500, 2500, "test_shrink_bigger")
     408            1 :             .attach_writer();
     409            1 :     writer.begin_shrink(2000);
     410            1 : }
     411              : 
     412              : #[test]
     413              : #[should_panic]
     414            1 : fn test_shrink_early_finish() {
     415            1 :     let writer =
     416            1 :         HashMapInit::<TestKey, usize>::new_resizeable_named(1500, 2500, "test_shrink_early_finish")
     417            1 :             .attach_writer();
     418            1 :     writer.finish_shrink().unwrap();
     419            1 : }
     420              : 
     421              : #[test]
     422              : #[should_panic]
     423            1 : fn test_shrink_fixed_size() {
     424            1 :     let mut area = [MaybeUninit::uninit(); 10000];
     425            1 :     let init_struct = HashMapInit::<TestKey, usize>::with_fixed(3, &mut area);
     426            1 :     let mut writer = init_struct.attach_writer();
     427            1 :     writer.begin_shrink(1);
     428            1 : }
        

Generated by: LCOV version 2.1-beta