|
1 | 1 | use crate::{
|
2 |
| - btreemap::test::{b, btree_test}, |
| 2 | + btreemap::{ |
| 3 | + test::{b, btree_test, make_memory}, |
| 4 | + BTreeMap, |
| 5 | + }, |
3 | 6 | storable::Blob,
|
| 7 | + Memory, |
4 | 8 | };
|
5 | 9 | use proptest::collection::btree_set as pset;
|
6 | 10 | use proptest::collection::vec as pvec;
|
7 | 11 | use proptest::prelude::*;
|
8 |
| -use std::collections::BTreeSet; |
| 12 | +use std::collections::{BTreeMap as StdBTreeMap, BTreeSet}; |
9 | 13 | use test_strategy::proptest;
|
10 | 14 |
|
| 15 | +#[derive(Debug, Clone)] |
| 16 | +enum Operation { |
| 17 | + Insert { key: Vec<u8>, value: Vec<u8> }, |
| 18 | + Iter { from: usize, len: usize }, |
| 19 | + Get(usize), |
| 20 | + Remove(usize), |
| 21 | +} |
| 22 | + |
| 23 | +// A custom strategy that gives unequal weights to the different operations. |
| 24 | +// Note that `Insert` has a higher weight than `Remove` so that, on average, BTreeMaps |
| 25 | +// are growing in size the more operations are executed. |
| 26 | +fn operation_strategy() -> impl Strategy<Value = Operation> { |
| 27 | + prop_oneof![ |
| 28 | + 3 => (any::<Vec<u8>>(), any::<Vec<u8>>()) |
| 29 | + .prop_map(|(key, value)| Operation::Insert { key, value }), |
| 30 | + 1 => (any::<usize>(), any::<usize>()) |
| 31 | + .prop_map(|(from, len)| Operation::Iter { from, len }), |
| 32 | + 2 => (any::<usize>()).prop_map(Operation::Get), |
| 33 | + 1 => (any::<usize>()).prop_map(Operation::Remove), |
| 34 | + ] |
| 35 | +} |
| 36 | + |
11 | 37 | fn arb_blob() -> impl Strategy<Value = Blob<10>> {
|
12 | 38 | pvec(0..u8::MAX, 0..10).prop_map(|v| Blob::<10>::try_from(v.as_slice()).unwrap())
|
13 | 39 | }
|
14 | 40 |
|
| 41 | +// Runs a comprehensive test for the major stable BTreeMap operations. |
| 42 | +// Results are validated against a standard BTreeMap. |
| 43 | +#[proptest(cases = 10)] |
| 44 | +fn comprehensive(#[strategy(pvec(operation_strategy(), 100..5_000))] ops: Vec<Operation>) { |
| 45 | + let mem = make_memory(); |
| 46 | + let mut btree = BTreeMap::new(mem); |
| 47 | + let mut std_btree = StdBTreeMap::new(); |
| 48 | + |
| 49 | + // Execute all the operations, validating that the stable btreemap behaves similarly to a std |
| 50 | + // btreemap. |
| 51 | + for op in ops.into_iter() { |
| 52 | + execute_operation(&mut std_btree, &mut btree, op); |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +// A comprehensive fuzz test that runs until it's explicitly terminated. To run: |
| 57 | +// |
| 58 | +// ``` |
| 59 | +// cargo t comprehensive_fuzz -- --ignored --nocapture 2> comprehensive_fuzz.log |
| 60 | +// ``` |
| 61 | +// |
| 62 | +// comprehensive_fuzz.log contains all the operations to help triage a failure. |
| 63 | +#[test] |
| 64 | +#[ignore] |
| 65 | +fn comprehensive_fuzz() { |
| 66 | + use proptest::strategy::ValueTree; |
| 67 | + use proptest::test_runner::TestRunner; |
| 68 | + let mut runner = TestRunner::default(); |
| 69 | + |
| 70 | + let mem = make_memory(); |
| 71 | + let mut btree = BTreeMap::new(mem); |
| 72 | + let mut std_btree = StdBTreeMap::new(); |
| 73 | + |
| 74 | + let mut i = 0; |
| 75 | + |
| 76 | + loop { |
| 77 | + let operation = operation_strategy() |
| 78 | + .new_tree(&mut runner) |
| 79 | + .unwrap() |
| 80 | + .current(); |
| 81 | + execute_operation(&mut std_btree, &mut btree, operation); |
| 82 | + i += 1; |
| 83 | + if i % 1000 == 0 { |
| 84 | + println!("=== Step {i} ==="); |
| 85 | + println!("=== BTree Size: {}", btree.len()); |
| 86 | + } |
| 87 | + } |
| 88 | +} |
| 89 | + |
15 | 90 | #[proptest(cases = 10)]
|
16 | 91 | fn insert(#[strategy(pset(arb_blob(), 1000..10_000))] keys: BTreeSet<Blob<10>>) {
|
17 | 92 | btree_test(|mut btree| {
|
@@ -61,3 +136,65 @@ fn map_upper_bound_iter(#[strategy(pvec(0u64..u64::MAX -1 , 10..100))] keys: Vec
|
61 | 136 | Ok(())
|
62 | 137 | });
|
63 | 138 | }
|
| 139 | + |
| 140 | +// Given an operation, executes it on the given stable btreemap and standard btreemap, verifying |
| 141 | +// that the result of the operation is equal in both btrees. |
| 142 | +fn execute_operation<M: Memory>( |
| 143 | + std_btree: &mut StdBTreeMap<Vec<u8>, Vec<u8>>, |
| 144 | + btree: &mut BTreeMap<Vec<u8>, Vec<u8>, M>, |
| 145 | + op: Operation, |
| 146 | +) { |
| 147 | + match op { |
| 148 | + Operation::Insert { key, value } => { |
| 149 | + let std_res = std_btree.insert(key.clone(), value.clone()); |
| 150 | + |
| 151 | + eprintln!("Insert({}, {})", hex::encode(&key), hex::encode(&value)); |
| 152 | + let res = btree.insert(key, value); |
| 153 | + assert_eq!(std_res, res); |
| 154 | + } |
| 155 | + Operation::Iter { from, len } => { |
| 156 | + assert_eq!(std_btree.len(), btree.len() as usize); |
| 157 | + if std_btree.is_empty() { |
| 158 | + return; |
| 159 | + } |
| 160 | + |
| 161 | + let from = from % std_btree.len(); |
| 162 | + let len = len % std_btree.len(); |
| 163 | + |
| 164 | + eprintln!("Iterate({}, {})", from, len); |
| 165 | + let std_iter = std_btree.iter().skip(from).take(len); |
| 166 | + let stable_iter = btree.iter().skip(from).take(len); |
| 167 | + for ((k1, v1), (k2, v2)) in std_iter.zip(stable_iter) { |
| 168 | + assert_eq!(k1, &k2); |
| 169 | + assert_eq!(v1, &v2); |
| 170 | + } |
| 171 | + } |
| 172 | + Operation::Get(idx) => { |
| 173 | + assert_eq!(std_btree.len(), btree.len() as usize); |
| 174 | + if std_btree.is_empty() { |
| 175 | + return; |
| 176 | + } |
| 177 | + let idx = idx % std_btree.len(); |
| 178 | + |
| 179 | + if let Some((k, v)) = btree.iter().skip(idx).take(1).next() { |
| 180 | + eprintln!("Get({})", hex::encode(&k)); |
| 181 | + assert_eq!(std_btree.get(&k), Some(&v)); |
| 182 | + assert_eq!(btree.get(&k), Some(v)); |
| 183 | + } |
| 184 | + } |
| 185 | + Operation::Remove(idx) => { |
| 186 | + assert_eq!(std_btree.len(), btree.len() as usize); |
| 187 | + if std_btree.is_empty() { |
| 188 | + return; |
| 189 | + } |
| 190 | + |
| 191 | + let idx = idx % std_btree.len(); |
| 192 | + |
| 193 | + if let Some((k, v)) = btree.iter().skip(idx).take(1).next() { |
| 194 | + eprintln!("Remove({})", hex::encode(&k)); |
| 195 | + assert_eq!(std_btree.remove(&k), Some(v.clone())); |
| 196 | + assert_eq!(btree.remove(&k), Some(v)); |
| 197 | + } |
| 198 | + } |
| 199 | + }; |
| 200 | +} |
0 commit comments