ai-code-battle/cmd/acb-evolver/internal/selector/tournament_test.go
jedarden bd4b0d3244 Add LLM prompt builder and ensemble integration (Phase 7)
- selector: tournament selection for parent sampling from island populations
- prompt: assembles evolution prompts from parent code, replay analysis, and meta description
- llm: OpenAI-compatible client routing to ZAI proxy with fast (GLM-5-Turbo) and strong (GLM-5) tiers, plus code block extraction from model responses
- Tests for prompt assembly, code extraction, and tournament selection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:26:09 -04:00

90 lines
2.5 KiB
Go

package selector
import (
"math/rand"
"testing"
evolverdb "github.com/aicodebattle/acb/cmd/acb-evolver/internal/db"
)
func makePrograms(fitnesses ...float64) []*evolverdb.Program {
out := make([]*evolverdb.Program, len(fitnesses))
for i, f := range fitnesses {
out[i] = &evolverdb.Program{ID: int64(i + 1), Fitness: f}
}
return out
}
func TestTournamentSelect_empty(t *testing.T) {
rng := rand.New(rand.NewSource(42))
got := TournamentSelect(nil, 3, rng)
if got != nil {
t.Fatalf("expected nil for empty population, got %+v", got)
}
}
func TestTournamentSelect_singleProgram(t *testing.T) {
rng := rand.New(rand.NewSource(42))
programs := makePrograms(5.0)
got := TournamentSelect(programs, 3, rng)
if got != programs[0] {
t.Fatalf("expected sole program to be returned")
}
}
func TestTournamentSelect_kLargerThanPopulation(t *testing.T) {
rng := rand.New(rand.NewSource(42))
programs := makePrograms(1.0, 3.0, 2.0)
// k=10 > len=3, so the global best (fitness=3.0) must be returned.
got := TournamentSelect(programs, 10, rng)
if got.Fitness != 3.0 {
t.Fatalf("expected global best (fitness=3.0), got %.1f", got.Fitness)
}
}
func TestTournamentSelect_selectsBestAmongSampled(t *testing.T) {
// Use a fixed seed so the test is deterministic.
rng := rand.New(rand.NewSource(1))
programs := makePrograms(1.0, 5.0, 2.0, 4.0, 3.0)
// Run many tournaments; the highest-fitness program (5.0) should win
// significantly more often than any other.
wins := make(map[float64]int)
const rounds = 200
for i := 0; i < rounds; i++ {
p := TournamentSelect(programs, 3, rng)
wins[p.Fitness]++
}
if wins[5.0] == 0 {
t.Fatalf("best program (fitness=5.0) never won in %d rounds", rounds)
}
// It should win the most.
for f, w := range wins {
if f != 5.0 && w >= wins[5.0] {
t.Errorf("program with fitness=%.1f won %d times, >= best program %d times", f, w, wins[5.0])
}
}
}
func TestSelectParents_count(t *testing.T) {
rng := rand.New(rand.NewSource(42))
programs := makePrograms(1.0, 2.0, 3.0, 4.0, 5.0)
parents := SelectParents(programs, 4, 2, rng)
if len(parents) != 4 {
t.Fatalf("expected 4 parents, got %d", len(parents))
}
for i, p := range parents {
if p == nil {
t.Errorf("parent[%d] is nil", i)
}
}
}
func TestSelectParents_nEqualsOne(t *testing.T) {
rng := rand.New(rand.NewSource(99))
programs := makePrograms(1.0, 2.0, 3.0)
parents := SelectParents(programs, 1, 2, rng)
if len(parents) != 1 {
t.Fatalf("expected 1 parent, got %d", len(parents))
}
}