Rollup merge of #153759 - Zalathar:chunk-tests, r=wesleywiser

Add a suite of ChunkedBitSet union/subtract/intersect test scenarios

While working on https://github.com/rust-lang/rust/pull/153754, I found that these code paths are under-tested.
This commit is contained in:
Jonathan Brouwer
2026-05-07 22:44:05 +02:00
committed by GitHub
+110
View File
@@ -362,6 +362,116 @@ fn chunked_bitset() {
);
}
/// Additional helper methods for testing.
impl ChunkedBitSet<usize> {
/// Creates a new `ChunkedBitSet` containing all `i` for which `fill_fn(i)` is true.
fn fill_with(domain_size: usize, fill_fn: impl Fn(usize) -> bool) -> Self {
let mut this = ChunkedBitSet::new_empty(domain_size);
for i in 0..domain_size {
if fill_fn(i) {
this.insert(i);
}
}
this
}
/// Asserts that for each `i` in `0..self.domain_size()`, `self.contains(i) == expected_fn(i)`.
#[track_caller]
fn assert_filled_with(&self, expected_fn: impl Fn(usize) -> bool) {
for i in 0..self.domain_size() {
let expected = expected_fn(i);
assert_eq!(self.contains(i), expected, "i = {i}");
}
}
}
#[test]
fn chunked_bulk_ops() {
struct ChunkedBulkOp {
name: &'static str,
op_fn: fn(&mut ChunkedBitSet<usize>, &ChunkedBitSet<usize>) -> bool,
spec_fn: fn(fn(usize) -> bool, fn(usize) -> bool, usize) -> bool,
}
let ops = &[
ChunkedBulkOp {
name: "union",
op_fn: ChunkedBitSet::union,
spec_fn: |fizz, buzz, i| fizz(i) || buzz(i),
},
ChunkedBulkOp {
name: "subtract",
op_fn: ChunkedBitSet::subtract,
spec_fn: |fizz, buzz, i| fizz(i) && !buzz(i),
},
ChunkedBulkOp {
name: "intersect",
op_fn: ChunkedBitSet::intersect,
spec_fn: |fizz, buzz, i| fizz(i) && buzz(i),
},
];
let domain_sizes = [
CHUNK_BITS / 7, // Smaller than a full chunk.
CHUNK_BITS,
(CHUNK_BITS + CHUNK_BITS / 7), // Larger than a full chunk.
];
for ChunkedBulkOp { name, op_fn, spec_fn } in ops {
for domain_size in domain_sizes {
// If false, use different values for LHS and RHS, to test "fizz op buzz".
// If true, use identical values, to test "fizz op fizz".
for identical in [false, true] {
// If false, make a clone of LHS before doing the op.
// This covers optimizations that depend on whether chunk words are shared or not.
for unique in [false, true] {
// Print the current test case, so that we can see which one failed.
println!(
"Testing op={name}, domain_size={domain_size}, identical={identical}, unique={unique} ..."
);
let fizz_fn = |i| i % 3 == 0;
let buzz_fn = if identical { fizz_fn } else { |i| i % 5 == 0 };
// Check that `fizz op buzz` gives the expected results.
chunked_bulk_ops_test_inner(
domain_size,
unique,
fizz_fn,
buzz_fn,
op_fn,
|i| spec_fn(fizz_fn, buzz_fn, i),
);
}
}
}
}
}
fn chunked_bulk_ops_test_inner(
domain_size: usize,
unique: bool,
fizz_fn: impl Fn(usize) -> bool + Copy,
buzz_fn: impl Fn(usize) -> bool + Copy,
op_fn: impl Fn(&mut ChunkedBitSet<usize>, &ChunkedBitSet<usize>) -> bool,
expected_fn: impl Fn(usize) -> bool + Copy,
) {
// Create two bitsets, "fizz" (LHS) and "buzz" (RHS).
let mut fizz = ChunkedBitSet::fill_with(domain_size, fizz_fn);
let buzz = ChunkedBitSet::fill_with(domain_size, buzz_fn);
// If requested, clone `fizz` so that its word Rcs are not uniquely-owned.
let _cloned = (!unique).then(|| fizz.clone());
// Perform the op (e.g. union/subtract/intersect), and verify that the
// mutated LHS contains exactly the expected values.
let changed = op_fn(&mut fizz, &buzz);
fizz.assert_filled_with(expected_fn);
// Verify that the "changed" return value is correct.
let should_change = (0..domain_size).any(|i| fizz_fn(i) != expected_fn(i));
assert_eq!(changed, should_change);
}
fn with_elements_chunked(elements: &[usize], domain_size: usize) -> ChunkedBitSet<usize> {
let mut s = ChunkedBitSet::new_empty(domain_size);
for &e in elements {