Skip to content

Commit

Permalink
test: serialised representation fixtures
Browse files Browse the repository at this point in the history
Assert the serialised form of a bloom filter matches an expected fixture
value.

This helps avoid accidentally breaking changes to the serialised
representation of a filter (part of the public API).
  • Loading branch information
domodwyer committed Dec 19, 2024
1 parent e033136 commit d87e75d
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
Cargo.lock
tests/fixtures/*.actual.json
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ serde = ["dep:serde"]

[dev-dependencies]
criterion = "0.5"
paste = "1.0.15"
proptest = { version = "1.5.0" }
quickcheck = "1.0"
quickcheck_macros = "1.0"
Expand Down
16 changes: 16 additions & 0 deletions tests/fixtures/compressed_bitmap.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"bitmap": {
"block_map": [
15
],
"bitmap": [
18414653452446586751,
9218513282017460079,
18302035097798045183,
18442222331972544511
],
"max_key": 256
},
"key_size": "KeyBytes1",
"_key_type": null
}
82 changes: 82 additions & 0 deletions tests/serialisation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#![cfg(feature = "serde")]

use std::{fmt::Debug, fs, hash::BuildHasherDefault, ops::Range, path::PathBuf};

use bloom2::{Bloom2, BloomFilterBuilder, CompressedBitmap, FilterSize};

/// Fixed value range to insert into the bloom filter.
const VALUES: Range<usize> = Range {
start: 42,
end: 100,
};

type StableBuildHasher = BuildHasherDefault<twox_hash::XxHash64>;

/// Generate a test for a specific bitmap storage type that asserts the
/// serialised representation matches some known fixture value.
macro_rules! test_serde_fixture {
(
$name:ident, // Test name - the fixture filename is derived from it.
$bitmap:ty // The concrete bitmap type to test.
) => {
paste::paste! {
#[test]
fn [<test_serde_fixture_ $name>]() {
let mut b: Bloom2<StableBuildHasher, $bitmap, usize> =
BloomFilterBuilder::hasher(StableBuildHasher::default())
.with_bitmap::<$bitmap>()
.size(FilterSize::KeyBytes1)
.build();

for i in VALUES {
b.insert(&i);
}

assert_fixture(b, stringify!($name));
}
}
};
}

test_serde_fixture!(compressed_bitmap, CompressedBitmap);

/// Serialise `t` as JSON and assert it matches a fixture value stored in a
/// file, and that deserialising the fixture results in the same filter state.
///
/// # Panics
///
/// This fn panics if the serialised output of `t` does not match the fixture
/// value read from `tests/fixtures/$name.json`, and writes the actual result to
/// `tests/fixtures/$name.actual.json` for review.
#[track_caller]
fn assert_fixture<T>(t: T, name: &str)
where
for<'a> T: serde::Serialize + serde::Deserialize<'a> + PartialEq + Debug,
{
let mut path = PathBuf::default();
path.push("tests");
path.push("fixtures");
path.push(format!("{name}.json"));

// Serialise the filter.
let got = serde_json::to_string_pretty(&t).expect("must serialise");

// Reconstruct an instance from the serialised form.
let round_trip = serde_json::from_str(&got).expect("must deserialise from serialised form");
assert_eq!(t, round_trip, "must round-trip through serialisation");

// Read the existing fixture and ensure they match.
let want = fs::read_to_string(&path).unwrap_or_else(|_| "<no fixture found>".to_string());
if got != want {
// They do not - write the new repr for use with `diff`.
path.set_file_name(format!("{name}.actual.json"));
fs::write(&path, &got).expect("failed to create fixture output file");
}

// Assert the serialised form matches.
assert!(
got == want,
"fixture output differs, wrote actual fixture output to {}",
path.display()
);
}

0 comments on commit d87e75d

Please sign in to comment.