@@ -362,6 +362,116 @@ fn chunked_bitset() {
362362 ) ;
363363}
364364
365+ /// Additional helper methods for testing.
366+ impl ChunkedBitSet < usize > {
367+ /// Creates a new `ChunkedBitSet` containing all `i` for which `fill_fn(i)` is true.
368+ fn fill_with ( domain_size : usize , fill_fn : impl Fn ( usize ) -> bool ) -> Self {
369+ let mut this = ChunkedBitSet :: new_empty ( domain_size) ;
370+ for i in 0 ..domain_size {
371+ if fill_fn ( i) {
372+ this. insert ( i) ;
373+ }
374+ }
375+ this
376+ }
377+
378+ /// Asserts that for each `i` in `0..self.domain_size()`, `self.contains(i) == expected_fn(i)`.
379+ #[ track_caller]
380+ fn assert_filled_with ( & self , expected_fn : impl Fn ( usize ) -> bool ) {
381+ for i in 0 ..self . domain_size ( ) {
382+ let expected = expected_fn ( i) ;
383+ assert_eq ! ( self . contains( i) , expected, "i = {i}" ) ;
384+ }
385+ }
386+ }
387+
388+ #[ test]
389+ fn chunked_bulk_ops ( ) {
390+ struct ChunkedBulkOp {
391+ name : & ' static str ,
392+ op_fn : fn ( & mut ChunkedBitSet < usize > , & ChunkedBitSet < usize > ) -> bool ,
393+ spec_fn : fn ( fn ( usize ) -> bool , fn ( usize ) -> bool , usize ) -> bool ,
394+ }
395+ let ops = & [
396+ ChunkedBulkOp {
397+ name : "union" ,
398+ op_fn : ChunkedBitSet :: union,
399+ spec_fn : |fizz, buzz, i| fizz ( i) || buzz ( i) ,
400+ } ,
401+ ChunkedBulkOp {
402+ name : "subtract" ,
403+ op_fn : ChunkedBitSet :: subtract,
404+ spec_fn : |fizz, buzz, i| fizz ( i) && !buzz ( i) ,
405+ } ,
406+ ChunkedBulkOp {
407+ name : "intersect" ,
408+ op_fn : ChunkedBitSet :: intersect,
409+ spec_fn : |fizz, buzz, i| fizz ( i) && buzz ( i) ,
410+ } ,
411+ ] ;
412+
413+ let domain_sizes = [
414+ CHUNK_BITS / 7 , // Smaller than a full chunk.
415+ CHUNK_BITS ,
416+ ( CHUNK_BITS + CHUNK_BITS / 7 ) , // Larger than a full chunk.
417+ ] ;
418+
419+ for ChunkedBulkOp { name, op_fn, spec_fn } in ops {
420+ for domain_size in domain_sizes {
421+ // If false, use different values for LHS and RHS, to test "fizz op buzz".
422+ // If true, use identical values, to test "fizz op fizz".
423+ for identical in [ false , true ] {
424+ // If false, make a clone of LHS before doing the op.
425+ // This covers optimizations that depend on whether chunk words are shared or not.
426+ for unique in [ false , true ] {
427+ // Print the current test case, so that we can see which one failed.
428+ println ! (
429+ "Testing op={name}, domain_size={domain_size}, identical={identical}, unique={unique} ..."
430+ ) ;
431+
432+ let fizz_fn = |i| i % 3 == 0 ;
433+ let buzz_fn = if identical { fizz_fn } else { |i| i % 5 == 0 } ;
434+
435+ // Check that `fizz op buzz` gives the expected results.
436+ chunked_bulk_ops_test_inner (
437+ domain_size,
438+ unique,
439+ fizz_fn,
440+ buzz_fn,
441+ op_fn,
442+ |i| spec_fn ( fizz_fn, buzz_fn, i) ,
443+ ) ;
444+ }
445+ }
446+ }
447+ }
448+ }
449+
450+ fn chunked_bulk_ops_test_inner (
451+ domain_size : usize ,
452+ unique : bool ,
453+ fizz_fn : impl Fn ( usize ) -> bool + Copy ,
454+ buzz_fn : impl Fn ( usize ) -> bool + Copy ,
455+ op_fn : impl Fn ( & mut ChunkedBitSet < usize > , & ChunkedBitSet < usize > ) -> bool ,
456+ expected_fn : impl Fn ( usize ) -> bool + Copy ,
457+ ) {
458+ // Create two bitsets, "fizz" (LHS) and "buzz" (RHS).
459+ let mut fizz = ChunkedBitSet :: fill_with ( domain_size, fizz_fn) ;
460+ let buzz = ChunkedBitSet :: fill_with ( domain_size, buzz_fn) ;
461+
462+ // If requested, clone `fizz` so that its word Rcs are not uniquely-owned.
463+ let _cloned = ( !unique) . then ( || fizz. clone ( ) ) ;
464+
465+ // Perform the op (e.g. union/subtract/intersect), and verify that the
466+ // mutated LHS contains exactly the expected values.
467+ let changed = op_fn ( & mut fizz, & buzz) ;
468+ fizz. assert_filled_with ( expected_fn) ;
469+
470+ // Verify that the "changed" return value is correct.
471+ let should_change = ( 0 ..domain_size) . any ( |i| fizz_fn ( i) != expected_fn ( i) ) ;
472+ assert_eq ! ( changed, should_change) ;
473+ }
474+
365475fn with_elements_chunked ( elements : & [ usize ] , domain_size : usize ) -> ChunkedBitSet < usize > {
366476 let mut s = ChunkedBitSet :: new_empty ( domain_size) ;
367477 for & e in elements {
0 commit comments