internal/rollout/bucketing_test.go
Rama main · réplica de lectura del repositorio original.
package rollout
import (
"fmt"
"math"
"testing"
"github.com/google/uuid"
)
func makeClientIDs(n int) []string {
ids := make([]string, n)
for i := range ids {
ids[i] = uuid.NewString()
}
return ids
}
func TestBucketIsDeterministic(t *testing.T) {
clientID := uuid.NewString()
salt := uuid.NewString()
first := Bucket(clientID, salt)
for i := 0; i < 10; i++ {
if got := Bucket(clientID, salt); got != first {
t.Fatalf("Bucket is not deterministic: got %v then %v", first, got)
}
}
if first < 0 || first >= 1 {
t.Fatalf("Bucket out of [0, 1): %v", first)
}
}
func TestEmptyClientIDNeverInRollout(t *testing.T) {
for _, pct := range []int{1, 10, 50, 99, 100} {
if InBucket("", "any-salt", pct) {
t.Fatalf("empty clientID must never be in rollout (pct=%d)", pct)
}
}
if got := Bucket("", "any-salt"); got != 1.0 {
t.Fatalf("empty clientID bucket must be 1.0, got %v", got)
}
}
func TestDistributionMatchesPercentage(t *testing.T) {
const sampleSize = 100000
ids := makeClientIDs(sampleSize)
salt := uuid.NewString()
for _, pct := range []int{1, 10, 50, 99} {
t.Run(fmt.Sprintf("pct=%d", pct), func(t *testing.T) {
inCount := 0
for _, id := range ids {
if InBucket(id, salt, pct) {
inCount++
}
}
observed := float64(inCount) / float64(sampleSize) * 100
if math.Abs(observed-float64(pct)) > 1 {
t.Fatalf("observed %.2f%% in bucket, expected %d%% within 1 point", observed, pct)
}
})
}
}
// TestMonotonicStickiness pins the property progression relies on: a device served at
// percentage N must stay served at every higher percentage, for the same salt.
func TestMonotonicStickiness(t *testing.T) {
ids := makeClientIDs(1000)
salt := uuid.NewString()
for _, id := range ids {
wasIn := false
for pct := 1; pct <= 99; pct++ {
in := InBucket(id, salt, pct)
if wasIn && !in {
t.Fatalf("device %s left the rollout when percentage increased to %d", id, pct)
}
wasIn = in
}
}
}
// TestSaltIndependence checks that two rollouts with different salts draw independent
// cohorts: the overlap of two 30% rollouts should be about 9% (0.3 * 0.3), not 30%
// (which would mean the same devices are always picked first).
func TestSaltIndependence(t *testing.T) {
const sampleSize = 10000
ids := makeClientIDs(sampleSize)
saltA := uuid.NewString()
saltB := uuid.NewString()
overlap := 0
for _, id := range ids {
if InBucket(id, saltA, 30) && InBucket(id, saltB, 30) {
overlap++
}
}
observed := float64(overlap) / float64(sampleSize) * 100
if math.Abs(observed-9) > 2 {
t.Fatalf("observed %.2f%% overlap between two 30%% rollouts, expected about 9%% within 2 points", observed)
}
}