Merge pull request 'Improvements to the fuzzing code' (#1437) from krtab/garage:fuzz_crdts into main-v2

Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1437
Reviewed-by: Alex <lx@deuxfleurs.fr>
This commit is contained in:
Alex
2026-05-05 10:03:36 +00:00
3 changed files with 63 additions and 115 deletions
+2 -58
View File
@@ -1,7 +1,7 @@
#![no_main]
use garage_fuzz::check_crdt_laws;
use garage_model::s3::mpu_table::{MpuPart, MpuPartKey, MultipartUpload};
use garage_table::crdt::Crdt;
use libfuzzer_sys::fuzz_target;
/// Build a MultipartUpload from an arbitrary deleted flag and parts list, using a fixed
@@ -27,67 +27,11 @@ fn make_mpu(deleted: bool, parts: Vec<(MpuPartKey, MpuPart)>) -> MultipartUpload
mpu
}
fn crdt_state(mpu: &MultipartUpload) -> (bool, &[(MpuPartKey, MpuPart)]) {
(mpu.deleted.get(), mpu.parts.items())
}
fuzz_target!(|inputs: (
(bool, Vec<(MpuPartKey, MpuPart)>),
(bool, Vec<(MpuPartKey, MpuPart)>),
(bool, Vec<(MpuPartKey, MpuPart)>)
)| {
let ((d1, p1), (d2, p2), (d3, p3)) = inputs;
let a = make_mpu(d1, p1);
let b = make_mpu(d2, p2);
let c = make_mpu(d3, p3);
// Idempotency: merge(a, a) == a
{
let mut a2 = a.clone();
a2.merge(&a.clone());
assert_eq!(
crdt_state(&a2),
crdt_state(&a),
"merge is not idempotent: {a2:#?} != {a:#?}"
);
}
// Commutativity: crdt_state(merge(a, b)) == crdt_state(merge(b, a))
let ab = {
let mut t = a.clone();
t.merge(&b);
t
};
let ba = {
let mut t = b.clone();
t.merge(&a);
t
};
assert_eq!(
crdt_state(&ab),
crdt_state(&ba),
"merge is not commutative: {ab:#?} != {ba:#?}"
);
// Associativity: crdt_state(merge(merge(a, b), c)) == crdt_state(merge(a, merge(b, c)))
let ab_c = {
let mut t = ab.clone();
t.merge(&c);
t
};
let bc = {
let mut t = b.clone();
t.merge(&c);
t
};
let a_bc = {
let mut t = a.clone();
t.merge(&bc);
t
};
assert_eq!(
crdt_state(&ab_c),
crdt_state(&a_bc),
"merge is not associative: {ab_c:#?} != {a_bc:#?}"
);
check_crdt_laws(make_mpu(d1, p1), make_mpu(d2, p2), make_mpu(d3, p3));
});
+5 -57
View File
@@ -1,7 +1,7 @@
#![no_main]
use garage_fuzz::check_crdt_laws;
use garage_model::s3::version_table::{Version, VersionBacklink, VersionBlock, VersionBlockKey};
use garage_table::crdt::Crdt;
use libfuzzer_sys::fuzz_target;
/// Build a Version from an arbitrary deleted flag and block list, using a fixed uuid/backlink
@@ -28,67 +28,15 @@ fn make_version(deleted: bool, mut blocks: Vec<(VersionBlockKey, VersionBlock)>)
v
}
fn crdt_state(v: &Version) -> (bool, &[(VersionBlockKey, VersionBlock)]) {
(v.deleted.get(), v.blocks.items())
}
fuzz_target!(|inputs: (
(bool, Vec<(VersionBlockKey, VersionBlock)>),
(bool, Vec<(VersionBlockKey, VersionBlock)>),
(bool, Vec<(VersionBlockKey, VersionBlock)>)
)| {
let ((d1, b1), (d2, b2), (d3, b3)) = inputs;
let a = make_version(d1, b1);
let b = make_version(d2, b2);
let c = make_version(d3, b3);
// Idempotency: merge(a, a) == a
{
let mut a2 = a.clone();
a2.merge(&a.clone());
assert_eq!(
crdt_state(&a2),
crdt_state(&a),
"merge is not idempotent: {a2:#?} != {a:#?}"
);
}
// Commutativity: crdt_state(merge(a, b)) == crdt_state(merge(b, a))
let ab = {
let mut t = a.clone();
t.merge(&b);
t
};
let ba = {
let mut t = b.clone();
t.merge(&a);
t
};
assert_eq!(
crdt_state(&ab),
crdt_state(&ba),
"merge is not commutative: {ab:#?} != {ba:#?}"
);
// Associativity: crdt_state(merge(merge(a, b), c)) == crdt_state(merge(a, merge(b, c)))
let ab_c = {
let mut t = ab.clone();
t.merge(&c);
t
};
let bc = {
let mut t = b.clone();
t.merge(&c);
t
};
let a_bc = {
let mut t = a.clone();
t.merge(&bc);
t
};
assert_eq!(
crdt_state(&ab_c),
crdt_state(&a_bc),
"merge is not associative: {ab_c:#?} != {a_bc:#?}"
check_crdt_laws(
make_version(d1, b1),
make_version(d2, b2),
make_version(d3, b3),
);
});
+56
View File
@@ -0,0 +1,56 @@
use garage_table::crdt::Crdt;
use std::fmt::Debug;
pub fn check_crdt_laws<T>(a: T, b: T, c: T)
where
T: Crdt + PartialEq + Clone + Debug,
{
// Idempotency: merge(a, a) == a
{
let mut a2 = a.clone();
a2.merge(&a);
assert_eq!(a2, a, "merge is not idempotent: {a2:#?} != {a:#?}");
}
// Commutativity: merge(a, b) == merge(b, a)
let ab = {
let mut t = a.clone();
t.merge(&b);
t
};
let ba = {
let mut t = b.clone();
t.merge(&a);
t
};
assert_eq!(ab, ba, "merge is not commutative: {ab:#?} != {ba:#?}");
// LX's corrolary: merge(merge(a,b),b) = merge(a,b)
let ab_b = {
let mut t = ab.clone();
t.merge(&b);
t
};
assert_eq!(ab, ab_b);
// Associativity: merge(merge(a, b), c) == merge(a, merge(b, c))
let ab_c = {
let mut t = ab;
t.merge(&c);
t
};
let bc = {
let mut t = b;
t.merge(&c);
t
};
let a_bc = {
let mut t = a;
t.merge(&bc);
t
};
assert_eq!(
ab_c, a_bc,
"merge is not associative: {ab_c:#?} != {a_bc:#?}"
);
}