fix: allow 0 capacity for table

- Added guards at the bucket level, to ensure that getting an item in an empty bucket doesn't cause an error.
- Added grow() and shrink() functions to Table, to prevent a capacity of 0 from not being able to grow.
- Updated the fuzz test to use `go_fuzz-utils`.
This commit is contained in:
2026-03-19 20:46:31 -04:00
parent bb874a2aba
commit cdb5efb4a3
7 changed files with 81 additions and 32 deletions

View File

@@ -1,12 +1,11 @@
package cuckoo_test
import (
"bytes"
"encoding/binary"
"maps"
"testing"
"github.com/stretchr/testify/assert"
go_fuzz_utils "github.com/trailofbits/go-fuzz-utils"
"git.maximhutz.com/tools/go-cuckoo"
)
@@ -20,49 +19,60 @@ func offsetHash(seed uint32) cuckoo.Hash[uint32] {
}
}
type fuzzStep struct {
drop bool
key, value uint32
}
type fuzzScenario struct {
seedA, seedB uint32
steps []fuzzStep
}
func FuzzInsertLookup(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte, seedA, seedB uint32) {
f.Fuzz(func(t *testing.T, data []byte) {
var scenario fuzzScenario
assert := assert.New(t)
if tp, err := go_fuzz_utils.NewTypeProvider(data); err != nil {
return
} else if err := tp.Fill(&scenario); err != nil {
return
}
if scenario.seedA == scenario.seedB {
return
}
actual := cuckoo.NewCustomTable[uint32, uint32](
offsetHash(seedA),
offsetHash(seedB),
offsetHash(scenario.seedA),
offsetHash(scenario.seedB),
func(a, b uint32) bool { return a == b },
)
expected := map[uint32]uint32{}
if seedA == seedB {
return
}
r := bytes.NewReader(data)
var key, value uint32
var drop bool
for binary.Read(r, binary.LittleEndian, &key) == nil &&
binary.Read(r, binary.LittleEndian, &value) == nil {
if drop {
err := actual.Drop(key)
for _, step := range scenario.steps {
if step.drop {
err := actual.Drop(step.key)
assert.NoError(err)
delete(expected, key)
delete(expected, step.key)
_, err = actual.Get(key)
_, err = actual.Get(step.key)
assert.Error(err)
} else {
err := actual.Put(key, value)
err := actual.Put(step.key, step.value)
assert.NoError(err)
expected[key] = value
expected[step.key] = step.value
found, err := actual.Get(key)
found, err := actual.Get(step.key)
assert.NoError(err)
assert.Equal(value, found)
assert.Equal(step.value, found)
}
assert.Equal(expected, maps.Collect(actual.Entries()))
drop = !drop
assert.Equal(expected, maps.Collect(actual.Entries()))
}
})
}