4 Commits

Author SHA1 Message Date
3bb663dc8b ci: release workflow with 'go-releaser' 2026-03-29 21:49:59 +02:00
db2dfb466b ci: add 'check-pr-title' job to pipeline 2026-03-29 21:47:11 +02:00
b1ab3a6326 chore: remove ./.claude/settings.json (#14)
All checks were successful
CI / Go Lint (push) Successful in 29s
CI / Makefile Lint (push) Successful in 28s
CI / Markdown Lint (push) Successful in 16s
CI / Unit Tests (push) Successful in 28s
CI / Fuzz Tests (push) Successful in 59s
CI / Mutation Tests (push) Successful in 1m7s
There shouldn't be a global settings folder for Claude in the repository. Also, add final newline to `.gitignore`.

Reviewed-on: #14
Co-authored-by: M.V. Hutz <git@maximhutz.me>
Co-committed-by: M.V. Hutz <git@maximhutz.me>
2026-03-28 19:52:06 +00:00
36d76d4929 feat: wider fuzz tests, stronger options. (#9)
All checks were successful
CI / Go Lint (push) Successful in 38s
CI / Markdown Lint (push) Successful in 12s
CI / Makefile Lint (push) Successful in 30s
CI / Unit Tests (push) Successful in 28s
CI / Fuzz Tests (push) Successful in 1m1s
CI / Mutation Tests (push) Successful in 57s
- Added all `Option`-s to the fuzz tests.
  - Minimum load is always <=20%.
- Tested all options, and force a panic on all invalid options.
  - Capacity must now be non-negative.
  - Minimum load should be <=20%, but just put it as a recommendation.

Reviewed-on: #9
Co-authored-by: M.V. Hutz <git@maximhutz.me>
Co-committed-by: M.V. Hutz <git@maximhutz.me>
2026-03-25 01:26:56 +00:00
6 changed files with 81 additions and 15 deletions

View File

@@ -1,7 +0,0 @@
{
"permissions": {
"allow": [
"Bash(curl -s -w \"\\\\n---HTTP_STATUS:%{http_code}---\" https://raw.githubusercontent.com/kubernetes/kubernetes/master/.markdownlint.yaml)"
]
}
}

View File

@@ -6,6 +6,17 @@ on:
pull_request: pull_request:
jobs: jobs:
check-pr-title:
name: Check PR Title
runs-on: ubuntu-latest
steps:
- run: |
TITLE="${{ gitea.event.pull_request.title }}"
if ! echo "$TITLE" | grep -qE '^(feat|fix|docs|chore|ci|test|refactor|perf|build|style|revert)(\(.+\))?(!)?: .+'; then
echo "::error::Pull Request title must follow conventional commits"
exit 1
fi
lint-go: lint-go:
name: Go Lint name: Go Lint
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -0,0 +1,22 @@
name: Release
on:
push:
tags: ["v*"]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: goreleaser/goreleaser-action@v6
with:
args: release --clean
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}

1
.gitignore vendored
View File

@@ -24,4 +24,3 @@ go.work.sum
# env file # env file
.env .env

View File

@@ -1,7 +1,10 @@
package cuckoo_test package cuckoo_test
import ( import (
"fmt"
"maps" "maps"
"math"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -26,6 +29,8 @@ type fuzzStep struct {
type fuzzScenario struct { type fuzzScenario struct {
seedA, seedB uint32 seedA, seedB uint32
capacity, growthFactor uint8
load float64
steps []fuzzStep steps []fuzzStep
} }
@@ -40,14 +45,33 @@ func FuzzInsertLookup(f *testing.F) {
return return
} }
if scenario.seedA == scenario.seedB { seedA, seedB := scenario.seedA, scenario.seedB
return growthFactor := max(2, int(scenario.growthFactor))
capacity := int(scenario.capacity)
minimumLoad := math.Abs(math.Mod(scenario.load, 1.0))
// If they are the same number, the hashes will clash, always causing an
// error.
if seedA == seedB {
t.Skip()
} }
// If the load is too high, the hashs will not be able to allocate
// properly.
if minimumLoad > 0.20 {
t.Skip()
}
fmt.Fprintf(os.Stderr, "seedA=%d seedB=%d capacity=%d growthFactor=%d minimumLoad=%f\n",
seedA, seedB, capacity, growthFactor, minimumLoad)
actual := cuckoo.NewCustomTable[uint32, uint32]( actual := cuckoo.NewCustomTable[uint32, uint32](
offsetHash(scenario.seedA), offsetHash(seedA),
offsetHash(scenario.seedB), offsetHash(seedB),
func(a, b uint32) bool { return a == b }, func(a, b uint32) bool { return a == b },
cuckoo.Capacity(capacity),
cuckoo.GrowthFactor(growthFactor),
cuckoo.MinimumLoad(minimumLoad),
) )
expected := map[uint32]uint32{} expected := map[uint32]uint32{}

View File

@@ -1,5 +1,7 @@
package cuckoo package cuckoo
import "fmt"
// DefaultCapacity is the initial capacity of a [Table]. It is inspired from // DefaultCapacity is the initial capacity of a [Table]. It is inspired from
// Java's [HashMap] implementation, which also uses 16. // Java's [HashMap] implementation, which also uses 16.
// //
@@ -27,19 +29,34 @@ type settings struct {
type Option func(*settings) type Option func(*settings)
// Capacity modifies the starting capacity of each bucket of the [Table]. The // Capacity modifies the starting capacity of each bucket of the [Table]. The
// value must be greater than 0. // value must be non-negative.
func Capacity(value int) Option { func Capacity(value int) Option {
if value < 0 {
panic(fmt.Sprintf("go-cuckoo: Capacity must be non-negative, got %d", value))
}
return func(s *settings) { s.bucketSize = uint64(value) } return func(s *settings) { s.bucketSize = uint64(value) }
} }
// MinimumLoad modifies the [DefaultMinimumLoad] of the [Table]. The value must // MinimumLoad modifies the [DefaultMinimumLoad] of the [Table]. The value must
// be between 0.00 and 1.00. // be between 0.00 and 1.00.
//
// The higher the minimum load, the more likely that a [Table.Put] will not
// succeed. Minimum loads above 20% are not tested.
func MinimumLoad(value float64) Option { func MinimumLoad(value float64) Option {
if value < 0.00 || value > 1.00 {
panic(fmt.Sprintf("go-cuckoo: MinimumLoad must be between 0.00 and 1.00, got %f", value))
}
return func(s *settings) { s.minLoadFactor = value } return func(s *settings) { s.minLoadFactor = value }
} }
// GrowthFactor controls how much the capacity of the [Table] multiplies when // GrowthFactor controls how much the capacity of the [Table] multiplies when
// it must resize. The value must be greater than 1. // it must resize. The value must be greater than 1.
func GrowthFactor(value int) Option { func GrowthFactor(value int) Option {
if value < 2 {
panic(fmt.Sprintf("go-cuckoo: GrowthFactor must be greater than 1, got %d", value))
}
return func(s *settings) { s.growthFactor = uint64(value) } return func(s *settings) { s.growthFactor = uint64(value) }
} }