Files
go-cuckoo/cuckoo_fuzz_test.go
M.V. Hutz 7cc1657403
All checks were successful
CI / Check PR Title (push) Has been skipped
CI / Makefile Lint (push) Successful in 47s
CI / Go Lint (push) Successful in 51s
CI / Markdown Lint (push) Successful in 46s
CI / Unit Tests (push) Successful in 47s
CI / Fuzz Tests (push) Successful in 1m19s
CI / Mutation Tests (push) Successful in 1m36s
refactor!: shorter constructors, bucketsubtable (#22)
## Description

Currently, the name of `bucket` is a bit confusing, because it is considered a 'table' in literature (as well as the whole hash table). A `bucket` is better described as a 'subtable', which is used by the total hash table to perform cuckoo hashing.

In addition, the constructors `NewTable`, `NewTableBy`, and `NewCustomTable` were given shorter names, because the package name `cuckoo` already implies that `New*` would create a hash table with cuckoo hashing. This package has one use-case, and so it unambiguous what constructors produce.

## Changes

- `NewTable` -> `New`
- `NewTableBy` -> `NewBy`
- `NewCustomTable` -> `NewCustom`
- `bucket` -> `subtable`

### Design Decisions

- I would have renamed `Table` and `subtable` to map equivalents, but 'submap' implies that a certain subsection of the map is contained within it, which isn't quite right.
- I chose not to go with `Map` and `table`, because of the split naming convention.

## Checklist

- [x] Tests pass
- [x] Docs updated

Reviewed-on: #22
Co-authored-by: M.V. Hutz <git@maximhutz.me>
Co-committed-by: M.V. Hutz <git@maximhutz.me>
2026-04-16 03:15:39 +00:00

93 lines
2.0 KiB
Go

package cuckoo_test
import (
"fmt"
"maps"
"os"
"testing"
"github.com/stretchr/testify/assert"
go_fuzz_utils "github.com/trailofbits/go-fuzz-utils"
"git.maximhutz.com/tools/go-cuckoo"
)
func offsetHash(seed uint32) cuckoo.Hash[uint32] {
return func(x uint32) uint64 {
v := uint64(x) ^ uint64(seed)
v = (v ^ (v >> 30)) * 0xbf58476d1ce4e5b9
v = (v ^ (v >> 27)) * 0x94d049bb133111eb
return v ^ (v >> 31)
}
}
type fuzzStep struct {
drop bool
key, value uint32
}
type fuzzScenario struct {
seedA, seedB uint32
capacity, growthFactor uint8
steps []fuzzStep
}
func FuzzInsertLookup(f *testing.F) {
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
}
seedA, seedB := scenario.seedA, scenario.seedB
growthFactor := max(2, int(scenario.growthFactor))
capacity := int(scenario.capacity)
// If they are the same number, the hashes will clash, always causing an
// error.
if seedA == seedB {
t.Skip()
}
fmt.Fprintf(os.Stderr, "seedA=%d seedB=%d capacity=%d growthFactor=%d\n",
seedA, seedB, capacity, growthFactor)
actual := cuckoo.NewCustom[uint32, uint32](
offsetHash(seedA),
offsetHash(seedB),
func(a, b uint32) bool { return a == b },
cuckoo.Capacity(capacity),
cuckoo.GrowthFactor(growthFactor),
)
expected := map[uint32]uint32{}
for _, step := range scenario.steps {
if step.drop {
err := actual.Drop(step.key)
assert.NoError(err)
delete(expected, step.key)
_, ok := actual.Get(step.key)
assert.False(ok)
} else {
err := actual.Put(step.key, step.value)
assert.NoError(err)
expected[step.key] = step.value
found, ok := actual.Get(step.key)
assert.True(ok)
assert.Equal(step.value, found)
}
assert.Equal(expected, maps.Collect(actual.Entries()))
}
})
}