Skip to content

Commit b2903c7

Browse files
Merge branch 'main' into EXC-1404-memory-manager-support-freeing-a-memory
2 parents 2d10771 + 77d2a17 commit b2903c7

File tree

4 files changed

+222
-30
lines changed

4 files changed

+222
-30
lines changed

src/btreemap.rs

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ use allocator::Allocator;
3636
pub use iter::Iter;
3737
use iter::{Cursor, Index};
3838
use node::{DerivedPageSize, Entry, Node, NodeType, PageSize, Version};
39+
use std::borrow::Cow;
3940
use std::marker::PhantomData;
4041
use std::ops::{Bound, RangeBounds};
41-
use std::{borrow::Cow, fmt};
4242

4343
#[cfg(test)]
4444
mod proptests;
@@ -1187,32 +1187,6 @@ where
11871187
}
11881188
}
11891189

1190-
/// An error returned when inserting entries into the map.
1191-
#[derive(Debug, PartialEq, Eq)]
1192-
pub enum InsertError {
1193-
KeyTooLarge { given: usize, max: usize },
1194-
ValueTooLarge { given: usize, max: usize },
1195-
}
1196-
1197-
impl fmt::Display for InsertError {
1198-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1199-
match self {
1200-
Self::KeyTooLarge { given, max } => {
1201-
write!(
1202-
f,
1203-
"InsertError::KeyTooLarge Expected key to be <= {max} bytes but received key with {given} bytes."
1204-
)
1205-
}
1206-
Self::ValueTooLarge { given, max } => {
1207-
write!(
1208-
f,
1209-
"InsertError::ValueTooLarge Expected value to be <= {max} bytes but received value with {given} bytes."
1210-
)
1211-
}
1212-
}
1213-
}
1214-
}
1215-
12161190
#[cfg(test)]
12171191
mod test {
12181192
use super::*;
@@ -1223,7 +1197,7 @@ mod test {
12231197
use std::cell::RefCell;
12241198
use std::rc::Rc;
12251199

1226-
fn make_memory() -> Rc<RefCell<Vec<u8>>> {
1200+
pub(crate) fn make_memory() -> Rc<RefCell<Vec<u8>>> {
12271201
Rc::new(RefCell::new(Vec::new()))
12281202
}
12291203

src/btreemap/proptests.rs

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,92 @@
11
use crate::{
2-
btreemap::test::{b, btree_test},
2+
btreemap::{
3+
test::{b, btree_test, make_memory},
4+
BTreeMap,
5+
},
36
storable::Blob,
7+
Memory,
48
};
59
use proptest::collection::btree_set as pset;
610
use proptest::collection::vec as pvec;
711
use proptest::prelude::*;
8-
use std::collections::BTreeSet;
12+
use std::collections::{BTreeMap as StdBTreeMap, BTreeSet};
913
use test_strategy::proptest;
1014

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+
1137
fn arb_blob() -> impl Strategy<Value = Blob<10>> {
1238
pvec(0..u8::MAX, 0..10).prop_map(|v| Blob::<10>::try_from(v.as_slice()).unwrap())
1339
}
1440

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+
1590
#[proptest(cases = 10)]
1691
fn insert(#[strategy(pset(arb_blob(), 1000..10_000))] keys: BTreeSet<Blob<10>>) {
1792
btree_test(|mut btree| {
@@ -61,3 +136,65 @@ fn map_upper_bound_iter(#[strategy(pvec(0u64..u64::MAX -1 , 10..100))] keys: Vec
61136
Ok(())
62137
});
63138
}
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+
}

src/storable.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ pub enum Bound {
7070
},
7171
}
7272

73+
impl Bound {
74+
/// Returns the maximum size of the type if bounded, panics if unbounded.
75+
pub const fn max_size(&self) -> u32 {
76+
if let Bound::Bounded { max_size, .. } = self {
77+
*max_size
78+
} else {
79+
panic!("Cannot get max size of unbounded type.");
80+
}
81+
}
82+
83+
/// Returns true if the type is fixed in size, false otherwise.
84+
pub const fn is_fixed_size(&self) -> bool {
85+
if let Bound::Bounded { is_fixed_size, .. } = self {
86+
*is_fixed_size
87+
} else {
88+
false
89+
}
90+
}
91+
}
92+
7393
/// Variable-size, but limited in capacity byte array.
7494
#[derive(Eq, Copy, Clone)]
7595
pub struct Blob<const N: usize> {
@@ -450,6 +470,46 @@ where
450470
};
451471
}
452472

473+
impl<T: Storable> Storable for Option<T> {
474+
fn to_bytes(&self) -> Cow<[u8]> {
475+
match self {
476+
Some(t) => {
477+
let mut bytes = t.to_bytes().into_owned();
478+
bytes.push(1);
479+
Cow::Owned(bytes)
480+
}
481+
None => Cow::Borrowed(&[0]),
482+
}
483+
}
484+
485+
fn from_bytes(bytes: Cow<[u8]>) -> Self {
486+
match bytes.split_last() {
487+
Some((last, rest)) => match last {
488+
0 => {
489+
assert!(rest.is_empty(), "Invalid Option encoding: unexpected prefix before the None marker: {rest:?}");
490+
None
491+
}
492+
1 => Some(T::from_bytes(Cow::Borrowed(rest))),
493+
_ => panic!("Invalid Option encoding: unexpected variant marker {last}"),
494+
},
495+
None => panic!("Invalid Option encoding: expected at least one byte"),
496+
}
497+
}
498+
499+
const BOUND: Bound = {
500+
match T::BOUND {
501+
Bound::Bounded {
502+
max_size,
503+
is_fixed_size,
504+
} => Bound::Bounded {
505+
max_size: max_size + 1,
506+
is_fixed_size,
507+
},
508+
Bound::Unbounded => Bound::Unbounded,
509+
}
510+
};
511+
}
512+
453513
pub(crate) struct Bounds {
454514
pub max_size: u32,
455515
pub is_fixed_size: bool,

src/storable/tests.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,27 @@ proptest! {
3535
fn f32_roundtrip(v in any::<f32>()) {
3636
prop_assert_eq!(v, Storable::from_bytes(v.to_bytes()));
3737
}
38+
39+
#[test]
40+
fn optional_f64_roundtrip(v in proptest::option::of(any::<f64>())) {
41+
prop_assert_eq!(v, Storable::from_bytes(v.to_bytes()));
42+
}
43+
44+
#[test]
45+
fn optional_string_roundtrip(v in proptest::option::of(any::<String>())) {
46+
prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes()));
47+
}
48+
49+
#[test]
50+
fn optional_tuple_roundtrip(v in proptest::option::of((any::<u64>(), uniform20(any::<u8>())))) {
51+
prop_assert_eq!(v, Storable::from_bytes(v.to_bytes()));
52+
}
53+
54+
#[test]
55+
fn optional_tuple_variable_width_u8_roundtrip(v in proptest::option::of((any::<u64>(), pvec(any::<u8>(), 0..40)))) {
56+
let v = v.map(|(n, bytes)| (n, Blob::<48>::try_from(&bytes[..]).unwrap()));
57+
prop_assert_eq!(v, Storable::from_bytes(v.to_bytes()));
58+
}
3859
}
3960

4061
#[test]

0 commit comments

Comments
 (0)