1
use std::{
2
    collections::{hash_map::RandomState, HashSet},
3
    hash::{BuildHasher, Hasher},
4
    time::{SystemTime, UNIX_EPOCH},
5
};
6

            
7
use crate::budmap::{BudMap, Entry};
8

            
9
/// This hash implementor is designed to cause collisions by being a terrible
10
/// algorithm (only xor) and restricting the hash space to 8 bits. This means
11
/// any collection using this hasher will be *guaranteed* to have collisions
12
/// after 256 entries.
13
#[derive(Clone, Copy)]
14
struct BadHasher(u8);
15

            
16
impl Hasher for BadHasher {
17
9008009
    fn finish(&self) -> u64 {
18
9008009
        u64::from(self.0)
19
9008009
    }
20

            
21
9008009
    fn write(&mut self, bytes: &[u8]) {
22
45048045
        for byte in bytes {
23
36040036
            self.0 ^= *byte;
24
36040036
        }
25
9008009
    }
26
}
27

            
28
/// A simple LCG RNG.
29
struct DebugRng(u32);
30

            
31
impl DebugRng {
32
2
    pub fn seed_from_time() -> Self {
33
2
        let time = SystemTime::now();
34
2
        Self::new(
35
2
            time.duration_since(UNIX_EPOCH)
36
2
                .expect("invalid sytem time")
37
2
                .subsec_nanos(),
38
2
        )
39
2
    }
40

            
41
2
    pub fn new(seed: u32) -> Self {
42
2
        println!("DebugRng Seed: {seed}");
43
2
        Self(seed)
44
2
    }
45

            
46
67824
    pub fn next(&mut self) -> u32 {
47
67824
        self.0 = self.0.wrapping_mul(16807).wrapping_add(1) % u32::MAX;
48
67824
        self.0
49
67824
    }
50
}
51

            
52
1
#[test]
53
1
fn ordered_map_test() {
54
1
    let mut map = BudMap::default();
55
1
    assert!(map.is_empty());
56
1
    assert_eq!(map.get(&0), None);
57
1
    assert_eq!(map.remove(&0), None);
58
1
    assert_eq!(map.get_by_index(0), None);
59
1
    assert_eq!(map.iter().next(), None);
60

            
61
1
    map.insert(1, 1);
62
1
    map.insert(4, 2);
63
1
    map.insert(2, 3);
64
1
    map.insert(3, 4);
65
1
    map.insert(5, 5);
66
1

            
67
1
    // Verify get-by-index ordering
68
1
    assert_eq!(map.get_by_index(0), Some(&1));
69
1
    assert_eq!(map.get_by_index(1), Some(&2));
70
1
    assert_eq!(map.get_by_index(2), Some(&3));
71
1
    assert_eq!(map.get_by_index(3), Some(&4));
72
1
    assert_eq!(map.get_by_index(4), Some(&5));
73

            
74
    // Verify iteration order
75
1
    let mut iter = map.iter();
76
1
    assert_eq!(iter.next(), Some((&1, &1)));
77
1
    assert_eq!(iter.next(), Some((&4, &2)));
78
1
    assert_eq!(iter.next(), Some((&2, &3)));
79
1
    assert_eq!(iter.next(), Some((&3, &4)));
80
1
    assert_eq!(iter.next(), Some((&5, &5)));
81
1
    assert!(iter.next().is_none());
82

            
83
    // Verify retrieving by key works
84
1
    assert_eq!(map.get(&1), Some(&1));
85
1
    assert_eq!(map.get(&4), Some(&2));
86
1
    assert_eq!(map.get(&2), Some(&3));
87
1
    assert_eq!(map.get(&3), Some(&4));
88
1
    assert_eq!(map.get(&5), Some(&5));
89

            
90
    // Remove an entry
91
1
    assert_eq!(map.remove(&2), Some(3));
92

            
93
    // Verify the new order matches what we expect with indexing
94
1
    assert_eq!(map.get_by_index(0), Some(&1));
95
1
    assert_eq!(map.get_by_index(1), Some(&2));
96
1
    assert_eq!(map.get_by_index(2), Some(&5));
97
1
    assert_eq!(map.get_by_index(3), Some(&4));
98
1
    assert_eq!(map.get_by_index(4), None);
99

            
100
    // Verify new order with iteration
101
1
    let mut iter = map.iter();
102
1
    assert_eq!(iter.next(), Some((&1, &1)));
103
1
    assert_eq!(iter.next(), Some((&4, &2)));
104
1
    assert_eq!(iter.next(), Some((&5, &5)));
105
1
    assert_eq!(iter.next(), Some((&3, &4)));
106
1
    assert!(iter.next().is_none());
107

            
108
    // Verify key status
109
1
    assert_eq!(map.get(&1), Some(&1));
110
1
    assert_eq!(map.get(&4), Some(&2));
111
1
    assert_eq!(map.get(&2), None);
112
1
    assert_eq!(map.get(&3), Some(&4));
113
1
    assert_eq!(map.get(&5), Some(&5));
114
1
}
115

            
116
impl BuildHasher for BadHasher {
117
    type Hasher = Self;
118

            
119
9008009
    fn build_hasher(&self) -> Self::Hasher {
120
9008009
        *self
121
9008009
    }
122
}
123

            
124
2
fn rebin<Hasher: BuildHasher>(hasher: Hasher) {
125
2
    let mut map = BudMap::with_hasher(hasher);
126
2002
    for i in 0..1000 {
127
2000
        map.insert(i, i);
128
2000
    }
129

            
130
    // Test getting by key
131
2002
    for i in 0..1000 {
132
2000
        assert_eq!(map.get(&i), Some(&i));
133
    }
134

            
135
2002
    for i in 0..1000 {
136
2000
        assert_eq!(map.get_by_index(i), Some(&i));
137
    }
138
2
}
139

            
140
1
#[test]
141
1
fn rebin_std_hasher() {
142
1
    rebin(RandomState::default());
143
1
}
144

            
145
1
#[test]
146
1
fn rebin_bad_hasher() {
147
1
    rebin(BadHasher(0));
148
1
}
149

            
150
27128
fn insert_or_remove<Hasher: BuildHasher>(
151
27128
    rng: &mut DebugRng,
152
27128
    map: &mut BudMap<u32, u32, Hasher>,
153
27128
    expected_entries: &mut Vec<(u32, u32)>,
154
27128
    insert_weight: u32,
155
27128
    removal_weight: u32,
156
27128
) {
157
27128
    let total_weight = insert_weight + removal_weight;
158
27128
    if rng.next() % total_weight < removal_weight && !expected_entries.is_empty() {
159
        // Remove an element the same way the implementation does
160
13564
        let index_to_remove = (rng.next() as usize) % expected_entries.len();
161
13564
        let mut removed_entry = expected_entries.pop().expect("map was empty");
162
13564
        if index_to_remove < expected_entries.len() {
163
13540
            std::mem::swap(&mut expected_entries[index_to_remove], &mut removed_entry);
164
13540
        }
165
13564
        println!("Removed {}: {}", removed_entry.0, removed_entry.1);
166
13564
        assert_eq!(
167
13564
            map.remove(&removed_entry.0).expect("key not found"),
168
13564
            removed_entry.1
169
13564
        );
170
    } else {
171
        // Add an element 2963 2948
172
13564
        let key = rng.next();
173
13564
        let value = rng.next();
174

            
175
13564
        let removed_value = if let Some((_, entry_value)) = expected_entries
176
13564
            .iter_mut()
177
30858515
            .find(|(entry_key, _)| entry_key == &key)
178
        {
179
            println!("Replacing {key} with {value}");
180
            let old_value = *entry_value;
181
            *entry_value = value;
182
            Some(old_value)
183
        } else {
184
13564
            println!("Inserting {key}: {value}");
185
13564
            expected_entries.push((key, value));
186
13564
            None
187
        };
188

            
189
13564
        assert_eq!(map.insert(key, value), removed_value);
190
    }
191
27128
}
192
27128
fn verify_contents<Hasher: BuildHasher>(
193
27128
    map: &BudMap<u32, u32, Hasher>,
194
27128
    expected_entries: &Vec<(u32, u32)>,
195
27128
) {
196
27128
    // Verify once by iteration and once by key lookups.
197
27128
    assert_eq!(map.len(), expected_entries.len());
198

            
199
61730594
    for (map_entry, vec_entry) in map.iter().zip(expected_entries.iter()) {
200
61730594
        assert_eq!(map_entry.0, &vec_entry.0);
201
61730594
        assert_eq!(map_entry.1, &vec_entry.1);
202
    }
203

            
204
61757722
    for (expected_key, expected_value) in expected_entries {
205
61730594
        assert_eq!(
206
61730594
            map.get(expected_key),
207
61730594
            Some(expected_value),
208
            "{expected_key} did not match expectations"
209
        );
210
    }
211

            
212
27128
    let mut bins_pointed_to = HashSet::new();
213
27128
    let mut bins_with_data = HashSet::new();
214
185456919
    for (bin_index, bin) in map.bins.iter().enumerate() {
215
185456919
        if bin.entry_index.as_opt().is_some() {
216
61730594
            assert!(bins_with_data.insert(bin_index));
217
61730594
            if let Some(collision_index) = bin.collision_index.as_opt() {
218
18871404
                assert!(bins_pointed_to.insert(collision_index));
219
42859190
            }
220
123726325
        }
221
    }
222
27128
    let bad_bins = bins_pointed_to.difference(&bins_with_data);
223
27128
    for bin in bad_bins {
224
        panic!("{bin}");
225
    }
226
27128
}
227

            
228
2
fn random_build_and_drain<Hasher: BuildHasher>(hasher: Hasher, maximum_size: usize) {
229
2
    let mut expected_entries: Vec<(u32, u32)> = Vec::new();
230
2
    let mut map = BudMap::with_capacity_and_hasher(1, hasher);
231
2
    let mut rng = DebugRng::seed_from_time();
232
2

            
233
2
    // Build the map up to 10,000 entries, using a variable amount of chance for
234
2
    // removals.
235
2
    let desired_weight = rng.next() % 8 + 10;
236
2
    let opposite_weight = rng.next() % 5 + 2;
237
13652
    while map.len() < maximum_size {
238
13650
        insert_or_remove(
239
13650
            &mut rng,
240
13650
            &mut map,
241
13650
            &mut expected_entries,
242
13650
            desired_weight,
243
13650
            opposite_weight,
244
13650
        );
245
13650

            
246
13650
        verify_contents(&map, &expected_entries);
247
13650
    }
248

            
249
    // Now do the reverse
250
13480
    while !map.is_empty() {
251
13478
        insert_or_remove(
252
13478
            &mut rng,
253
13478
            &mut map,
254
13478
            &mut expected_entries,
255
13478
            opposite_weight,
256
13478
            desired_weight,
257
13478
        );
258
13478

            
259
13478
        verify_contents(&map, &expected_entries);
260
13478
    }
261
2
}
262

            
263
1
#[test]
264
1
fn random_build_and_drain_bad_hasher() {
265
1
    // miri runs out of memory in CI when run with too large of a data set. Give
266
1
    // that this map has no unsafe code, we could just ignore the tests but it
267
1
    // seems better to just reduce the count.
268
1
    #[cfg(miri)]
269
1
    const COUNT: usize = 30;
270
1
    #[cfg(not(miri))]
271
1
    const COUNT: usize = 3_000;
272
1
    random_build_and_drain(BadHasher(0), COUNT);
273
1
}
274

            
275
1
#[test]
276
1
fn random_build_and_drain_std_hasher() {
277
1
    // miri runs out of memory in CI when run with too large of a data set. Give
278
1
    // that this map has no unsafe code, we could just ignore the tests but it
279
1
    // seems better to just reduce the count.
280
1
    #[cfg(miri)]
281
1
    const COUNT: usize = 50;
282
1
    #[cfg(not(miri))]
283
1
    const COUNT: usize = 5_000;
284
1
    random_build_and_drain(RandomState::default(), COUNT);
285
1
}
286

            
287
1
#[test]
288
1
fn entry() {
289
1
    let mut map = BudMap::default();
290
1
    assert!(matches!(map.entry(0), Entry::Vacant(_)));
291
    // Ensure that the vacant entry didn't show up somehow.
292
1
    assert_eq!(map.len(), 0);
293

            
294
1
    match map.entry(0) {
295
1
        Entry::Vacant(entry) => entry.insert("a"),
296
        Entry::Occupied(_) => unreachable!(),
297
    }
298

            
299
1
    assert_eq!(map.len(), 1);
300
1
    assert_eq!(map.get(&0), Some(&"a"));
301

            
302
1
    match map.entry(0) {
303
1
        Entry::Occupied(entry) => {
304
1
            assert_eq!(entry.key(), &0);
305
1
            assert_eq!(entry.value(), &"a");
306
1
            assert_eq!(entry.replace("b"), "a");
307
        }
308
        Entry::Vacant(_) => unreachable!(),
309
    }
310
1
    assert_eq!(map.get(&0), Some(&"b"));
311

            
312
1
    match map.entry(0) {
313
1
        Entry::Occupied(entry) => {
314
1
            assert_eq!(entry.remove(), "b");
315
        }
316
        Entry::Vacant(_) => unreachable!(),
317
    }
318
1
    assert_eq!(map.get(&0), None);
319
1
}
320

            
321
1
#[test]
322
1
fn hash_misses() {
323
1
    // Because we know bad hasher's approach to hashing, we can create some
324
1
    // specific test cases that are hard to cover otherwise.
325
1
    let mut map = BudMap::with_hasher(BadHasher(0));
326
1
    map.insert(0xFF, 1);
327
1
    map.insert(0xFF00, 2);
328
1

            
329
1
    // We've tested the simple paths in other tests, but testing that collision
330
1
    // paths return None on various APIs isn't covered anywhere else.
331
1
    assert_eq!(map.get(&0x00FF_0000), None);
332
1
    assert_eq!(map.remove(&0x00FF_0000), None);
333
1
    assert!(matches!(map.entry(0x00FF_0000), Entry::Vacant(_)));
334
1
}
335

            
336
1
#[test]
337
1
fn collision_reuse() {
338
1
    let mut map = BudMap::with_hasher(BadHasher(0));
339
1
    map.insert(0xFF, 1);
340
1
    assert_eq!(map.bins.len(), 8, "smallest map size changed");
341
1
    map.insert(0xFF00, 2);
342
1
    assert_eq!(map.bins.len(), 9, "didn't collide");
343
1
    assert_eq!(map.remove(&0xFF00), Some(2));
344
1
    map.insert(0xFF00, 3);
345
1
    assert_eq!(map.bins.len(), 9, "new bin allocated");
346
1
}