feat(namepool): Add themed name pools for polecats

Polecats now get themed names from the Mad Max universe by default
(furiosa, nux, slit, etc.) instead of generic polecat-01, polecat-02.

Changes:
- Add NamepoolConfig to config/types.go for per-rig theme configuration
- Update namepool.go with three built-in themes:
  - mad-max (default): furiosa, nux, imperator, etc.
  - minerals: obsidian, quartz, ruby, etc.
  - wasteland: rust, chrome, fury, etc.
- Add gt namepool commands: themes, set, add, reset
- Update manager.go to load namepool config from rig settings

Configuration in .gastown/config.json:
```json
{
  "namepool": {
    "style": "minerals",
    "max_before_numbering": 50
  }
}
```

Issue: beads-rs0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-19 21:59:03 -08:00
parent c7e83b1619
commit 900a440ce8
6 changed files with 753 additions and 121 deletions
+19 -2
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/git"
"github.com/steveyegge/gastown/internal/rig"
)
@@ -32,8 +33,24 @@ func NewManager(r *rig.Rig, g *git.Git) *Manager {
// Use the mayor's rig directory for beads operations (rig-level beads)
mayorRigPath := filepath.Join(r.Path, "mayor", "rig")
// Initialize name pool
pool := NewNamePool(r.Path, r.Name)
// Try to load rig config for namepool settings
rigConfigPath := filepath.Join(r.Path, ".gastown", "config.json")
var pool *NamePool
rigConfig, err := config.LoadRigConfig(rigConfigPath)
if err == nil && rigConfig.Namepool != nil {
// Use configured namepool settings
pool = NewNamePoolWithConfig(
r.Path,
r.Name,
rigConfig.Namepool.Style,
rigConfig.Namepool.Names,
rigConfig.Namepool.MaxBeforeNumbering,
)
} else {
// Use defaults
pool = NewNamePool(r.Path, r.Name)
}
_ = pool.Load() // Load existing state, ignore errors for new rigs
return &Manager{
+217 -55
View File
@@ -10,15 +10,55 @@ import (
)
const (
// PoolSize is the number of reusable names in the pool.
PoolSize = 50
// DefaultPoolSize is the number of reusable names in the pool.
DefaultPoolSize = 50
// NamePrefix is the prefix for pooled polecat names.
NamePrefix = "polecat-"
// DefaultTheme is the default theme for new rigs.
DefaultTheme = "mad-max"
)
// Built-in themes with themed polecat names.
var BuiltinThemes = map[string][]string{
"mad-max": {
"furiosa", "nux", "slit", "rictus", "dementus",
"capable", "toast", "dag", "cheedo", "valkyrie",
"keeper", "morsov", "ace", "warboy", "imperator",
"organic", "coma", "splendid", "angharad", "max",
"immortan", "bullet", "toecutter", "goose", "nightrider",
"glory", "scrotus", "chumbucket", "corpus", "dinki",
"prime", "vuvalini", "rockryder", "wretched", "buzzard",
"gastown", "bullet-farmer", "citadel", "wasteland", "fury",
"road-warrior", "interceptor", "blackfinger", "wraith", "witness",
"chrome", "shiny", "mediocre", "guzzoline", "aqua-cola",
},
"minerals": {
"obsidian", "quartz", "jasper", "onyx", "opal",
"topaz", "garnet", "ruby", "amber", "jade",
"pearl", "flint", "granite", "basalt", "marble",
"shale", "slate", "pyrite", "mica", "agate",
"malachite", "turquoise", "lapis", "emerald", "sapphire",
"diamond", "amethyst", "citrine", "zircon", "peridot",
"coral", "jet", "moonstone", "sunstone", "bloodstone",
"rhodonite", "sodalite", "hematite", "magnetite", "calcite",
"fluorite", "selenite", "kyanite", "labradorite", "amazonite",
"chalcedony", "carnelian", "aventurine", "chrysoprase", "heliodor",
},
"wasteland": {
"rust", "chrome", "nitro", "guzzle", "witness",
"shiny", "fury", "thunder", "dust", "scavenger",
"radrat", "ghoul", "mutant", "raider", "vault",
"pipboy", "nuka", "brahmin", "deathclaw", "mirelurk",
"synth", "institute", "enclave", "brotherhood", "minuteman",
"railroad", "atom", "crater", "foundation", "refuge",
"settler", "wanderer", "courier", "lone", "chosen",
"tribal", "khan", "legion", "ncr", "ranger",
"overseer", "sentinel", "paladin", "scribe", "initiate",
"elder", "lancer", "knight", "squire", "proctor",
},
}
// NamePool manages a bounded pool of reusable polecat names.
// Names in the pool are polecat-01 through polecat-50.
// Names are drawn from a themed pool (mad-max by default).
// When the pool is exhausted, overflow names use rigname-N format.
type NamePool struct {
mu sync.RWMutex
@@ -26,14 +66,23 @@ type NamePool struct {
// RigName is the rig this pool belongs to.
RigName string `json:"rig_name"`
// InUse tracks which pool indices are currently in use.
// Key is the pool index (1-50), value is true if in use.
InUse map[int]bool `json:"in_use"`
// Theme is the current theme name (e.g., "mad-max", "minerals").
Theme string `json:"theme"`
// CustomNames allows overriding the built-in theme names.
CustomNames []string `json:"custom_names,omitempty"`
// InUse tracks which pool names are currently in use.
// Key is the name itself, value is true if in use.
InUse map[string]bool `json:"in_use"`
// OverflowNext is the next overflow sequence number.
// Starts at PoolSize+1 (51) and increments.
// Starts at MaxSize+1 and increments.
OverflowNext int `json:"overflow_next"`
// MaxSize is the maximum number of themed names before overflow.
MaxSize int `json:"max_size"`
// stateFile is the path to persist pool state.
stateFile string
}
@@ -42,12 +91,50 @@ type NamePool struct {
func NewNamePool(rigPath, rigName string) *NamePool {
return &NamePool{
RigName: rigName,
InUse: make(map[int]bool),
OverflowNext: PoolSize + 1,
Theme: DefaultTheme,
InUse: make(map[string]bool),
OverflowNext: DefaultPoolSize + 1,
MaxSize: DefaultPoolSize,
stateFile: filepath.Join(rigPath, ".gastown", "namepool.json"),
}
}
// NewNamePoolWithConfig creates a name pool with specific configuration.
func NewNamePoolWithConfig(rigPath, rigName, theme string, customNames []string, maxSize int) *NamePool {
if theme == "" {
theme = DefaultTheme
}
if maxSize <= 0 {
maxSize = DefaultPoolSize
}
return &NamePool{
RigName: rigName,
Theme: theme,
CustomNames: customNames,
InUse: make(map[string]bool),
OverflowNext: maxSize + 1,
MaxSize: maxSize,
stateFile: filepath.Join(rigPath, ".gastown", "namepool.json"),
}
}
// getNames returns the list of names to use for the pool.
func (p *NamePool) getNames() []string {
// Custom names take precedence
if len(p.CustomNames) > 0 {
return p.CustomNames
}
// Look up built-in theme
if names, ok := BuiltinThemes[p.Theme]; ok {
return names
}
// Fall back to default theme
return BuiltinThemes[DefaultTheme]
}
// Load loads the pool state from disk.
func (p *NamePool) Load() error {
p.mu.Lock()
@@ -57,8 +144,8 @@ func (p *NamePool) Load() error {
if err != nil {
if os.IsNotExist(err) {
// Initialize with empty state
p.InUse = make(map[int]bool)
p.OverflowNext = PoolSize + 1
p.InUse = make(map[string]bool)
p.OverflowNext = p.MaxSize + 1
return nil
}
return err
@@ -69,13 +156,24 @@ func (p *NamePool) Load() error {
return err
}
// Preserve the theme and custom names if already set
if p.Theme == "" && loaded.Theme != "" {
p.Theme = loaded.Theme
}
if len(p.CustomNames) == 0 && len(loaded.CustomNames) > 0 {
p.CustomNames = loaded.CustomNames
}
p.InUse = loaded.InUse
if p.InUse == nil {
p.InUse = make(map[int]bool)
p.InUse = make(map[string]bool)
}
p.OverflowNext = loaded.OverflowNext
if p.OverflowNext < PoolSize+1 {
p.OverflowNext = PoolSize + 1
if p.OverflowNext < p.MaxSize+1 {
p.OverflowNext = p.MaxSize + 1
}
if loaded.MaxSize > 0 {
p.MaxSize = loaded.MaxSize
}
return nil
@@ -100,17 +198,20 @@ func (p *NamePool) Save() error {
}
// Allocate returns a name from the pool.
// It prefers lower-numbered pool slots, and falls back to overflow names
// It prefers names in order from the theme list, and falls back to overflow names
// when the pool is exhausted.
func (p *NamePool) Allocate() (string, error) {
p.mu.Lock()
defer p.mu.Unlock()
// Try to find first available slot in pool (prefer low numbers)
for i := 1; i <= PoolSize; i++ {
if !p.InUse[i] {
p.InUse[i] = true
return p.formatPoolName(i), nil
names := p.getNames()
// Try to find first available name from the theme
for i := 0; i < len(names) && i < p.MaxSize; i++ {
name := names[i]
if !p.InUse[name] {
p.InUse[name] = true
return name, nil
}
}
@@ -126,17 +227,27 @@ func (p *NamePool) Release(name string) {
p.mu.Lock()
defer p.mu.Unlock()
idx := p.parsePoolIndex(name)
if idx > 0 && idx <= PoolSize {
delete(p.InUse, idx)
// Check if it's a themed name
if p.isThemedName(name) {
delete(p.InUse, name)
}
// Overflow names are not reusable, so we don't track them
}
// IsPoolName returns true if the name is a pool name (polecat-NN format).
// isThemedName checks if a name is in the theme pool.
func (p *NamePool) isThemedName(name string) bool {
names := p.getNames()
for _, n := range names {
if n == name {
return true
}
}
return false
}
// IsPoolName returns true if the name is a pool name (themed or numbered).
func (p *NamePool) IsPoolName(name string) bool {
idx := p.parsePoolIndex(name)
return idx > 0 && idx <= PoolSize
return p.isThemedName(name)
}
// ActiveCount returns the number of names currently in use from the pool.
@@ -152,8 +263,8 @@ func (p *NamePool) ActiveNames() []string {
defer p.mu.RUnlock()
var names []string
for idx := range p.InUse {
names = append(names, p.formatPoolName(idx))
for name := range p.InUse {
names = append(names, name)
}
sort.Strings(names)
return names
@@ -164,9 +275,8 @@ func (p *NamePool) MarkInUse(name string) {
p.mu.Lock()
defer p.mu.Unlock()
idx := p.parsePoolIndex(name)
if idx > 0 && idx <= PoolSize {
p.InUse[idx] = true
if p.isThemedName(name) {
p.InUse[name] = true
}
}
@@ -177,41 +287,93 @@ func (p *NamePool) Reconcile(existingPolecats []string) {
defer p.mu.Unlock()
// Clear current state
p.InUse = make(map[int]bool)
p.InUse = make(map[string]bool)
// Mark all existing polecats as in use
for _, name := range existingPolecats {
idx := p.parsePoolIndex(name)
if idx > 0 && idx <= PoolSize {
p.InUse[idx] = true
if p.isThemedName(name) {
p.InUse[name] = true
}
}
}
// formatPoolName formats a pool index as a name.
func (p *NamePool) formatPoolName(idx int) string {
return fmt.Sprintf("%s%02d", NamePrefix, idx)
}
// formatOverflowName formats an overflow sequence number as a name.
func (p *NamePool) formatOverflowName(seq int) string {
return fmt.Sprintf("%s-%d", p.RigName, seq)
}
// parsePoolIndex extracts the pool index from a pool name.
// Returns 0 if not a valid pool name.
func (p *NamePool) parsePoolIndex(name string) int {
if len(name) < len(NamePrefix)+2 {
return 0
}
if name[:len(NamePrefix)] != NamePrefix {
return 0
// GetTheme returns the current theme name.
func (p *NamePool) GetTheme() string {
p.mu.RLock()
defer p.mu.RUnlock()
return p.Theme
}
// SetTheme sets the theme and resets the pool.
// Existing in-use names are preserved if they exist in the new theme.
func (p *NamePool) SetTheme(theme string) error {
p.mu.Lock()
defer p.mu.Unlock()
if _, ok := BuiltinThemes[theme]; !ok {
return fmt.Errorf("unknown theme: %s (available: mad-max, minerals, wasteland)", theme)
}
var idx int
_, err := fmt.Sscanf(name[len(NamePrefix):], "%d", &idx)
if err != nil {
return 0
// Preserve names that exist in both themes
newNames := BuiltinThemes[theme]
newInUse := make(map[string]bool)
for name := range p.InUse {
for _, n := range newNames {
if n == name {
newInUse[name] = true
break
}
}
}
return idx
p.Theme = theme
p.InUse = newInUse
p.CustomNames = nil
return nil
}
// ListThemes returns the list of available built-in themes.
func ListThemes() []string {
themes := make([]string, 0, len(BuiltinThemes))
for theme := range BuiltinThemes {
themes = append(themes, theme)
}
sort.Strings(themes)
return themes
}
// GetThemeNames returns the names in a specific theme.
func GetThemeNames(theme string) ([]string, error) {
if names, ok := BuiltinThemes[theme]; ok {
return names, nil
}
return nil, fmt.Errorf("unknown theme: %s", theme)
}
// AddCustomName adds a custom name to the pool.
func (p *NamePool) AddCustomName(name string) {
p.mu.Lock()
defer p.mu.Unlock()
// Check if already in custom names
for _, n := range p.CustomNames {
if n == name {
return
}
}
p.CustomNames = append(p.CustomNames, name)
}
// Reset clears the pool state, releasing all names.
func (p *NamePool) Reset() {
p.mu.Lock()
defer p.mu.Unlock()
p.InUse = make(map[string]bool)
p.OverflowNext = p.MaxSize + 1
}
+181 -64
View File
@@ -15,22 +15,22 @@ func TestNamePool_Allocate(t *testing.T) {
pool := NewNamePool(tmpDir, "testrig")
// First allocation should be polecat-01
// First allocation should be first themed name (furiosa)
name, err := pool.Allocate()
if err != nil {
t.Fatalf("Allocate error: %v", err)
}
if name != "polecat-01" {
t.Errorf("expected polecat-01, got %s", name)
if name != "furiosa" {
t.Errorf("expected furiosa, got %s", name)
}
// Second allocation should be polecat-02
// Second allocation should be nux
name, err = pool.Allocate()
if err != nil {
t.Fatalf("Allocate error: %v", err)
}
if name != "polecat-02" {
t.Errorf("expected polecat-02, got %s", name)
if name != "nux" {
t.Errorf("expected nux, got %s", name)
}
}
@@ -47,21 +47,21 @@ func TestNamePool_Release(t *testing.T) {
name1, _ := pool.Allocate()
name2, _ := pool.Allocate()
if name1 != "polecat-01" || name2 != "polecat-02" {
if name1 != "furiosa" || name2 != "nux" {
t.Fatalf("unexpected allocations: %s, %s", name1, name2)
}
// Release first one
pool.Release("polecat-01")
pool.Release("furiosa")
// Next allocation should reuse polecat-01
// Next allocation should reuse furiosa
name, _ := pool.Allocate()
if name != "polecat-01" {
t.Errorf("expected polecat-01 to be reused, got %s", name)
if name != "furiosa" {
t.Errorf("expected furiosa to be reused, got %s", name)
}
}
func TestNamePool_PrefersLowNumbers(t *testing.T) {
func TestNamePool_PrefersOrder(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "namepool-test-*")
if err != nil {
t.Fatal(err)
@@ -75,20 +75,20 @@ func TestNamePool_PrefersLowNumbers(t *testing.T) {
pool.Allocate()
}
// Release 03 and 01
pool.Release("polecat-03")
pool.Release("polecat-01")
// Release slit and furiosa
pool.Release("slit")
pool.Release("furiosa")
// Next allocation should be 01 (lowest available)
// Next allocation should be furiosa (first in theme order)
name, _ := pool.Allocate()
if name != "polecat-01" {
t.Errorf("expected polecat-01 (lowest), got %s", name)
if name != "furiosa" {
t.Errorf("expected furiosa (first in order), got %s", name)
}
// Next should be 03
// Next should be slit
name, _ = pool.Allocate()
if name != "polecat-03" {
t.Errorf("expected polecat-03, got %s", name)
if name != "slit" {
t.Errorf("expected slit, got %s", name)
}
}
@@ -99,10 +99,10 @@ func TestNamePool_Overflow(t *testing.T) {
}
defer func() { _ = os.RemoveAll(tmpDir) }()
pool := NewNamePool(tmpDir, "gastown")
pool := NewNamePoolWithConfig(tmpDir, "gastown", "mad-max", nil, 5)
// Exhaust the pool
for i := 0; i < PoolSize; i++ {
// Exhaust the small pool
for i := 0; i < 5; i++ {
pool.Allocate()
}
@@ -111,15 +111,15 @@ func TestNamePool_Overflow(t *testing.T) {
if err != nil {
t.Fatalf("Allocate error: %v", err)
}
expected := "gastown-51"
expected := "gastown-6"
if name != expected {
t.Errorf("expected overflow name %s, got %s", expected, name)
}
// Next overflow
name, _ = pool.Allocate()
if name != "gastown-52" {
t.Errorf("expected gastown-52, got %s", name)
if name != "gastown-7" {
t.Errorf("expected gastown-7, got %s", name)
}
}
@@ -130,26 +130,26 @@ func TestNamePool_OverflowNotReusable(t *testing.T) {
}
defer func() { _ = os.RemoveAll(tmpDir) }()
pool := NewNamePool(tmpDir, "gastown")
pool := NewNamePoolWithConfig(tmpDir, "gastown", "mad-max", nil, 3)
// Exhaust the pool
for i := 0; i < PoolSize; i++ {
for i := 0; i < 3; i++ {
pool.Allocate()
}
// Get overflow name
overflow1, _ := pool.Allocate()
if overflow1 != "gastown-51" {
t.Fatalf("expected gastown-51, got %s", overflow1)
if overflow1 != "gastown-4" {
t.Fatalf("expected gastown-4, got %s", overflow1)
}
// Release it - should not be reused
pool.Release(overflow1)
// Next allocation should be gastown-52, not gastown-51
// Next allocation should be gastown-5, not gastown-4
name, _ := pool.Allocate()
if name != "gastown-52" {
t.Errorf("expected gastown-52 (overflow increments), got %s", name)
if name != "gastown-5" {
t.Errorf("expected gastown-5 (overflow increments), got %s", name)
}
}
@@ -163,10 +163,10 @@ func TestNamePool_SaveLoad(t *testing.T) {
pool := NewNamePool(tmpDir, "testrig")
// Allocate some names
pool.Allocate() // 01
pool.Allocate() // 02
pool.Allocate() // 03
pool.Release("polecat-02")
pool.Allocate() // furiosa
pool.Allocate() // nux
pool.Allocate() // slit
pool.Release("nux")
// Save state
if err := pool.Save(); err != nil {
@@ -179,15 +179,15 @@ func TestNamePool_SaveLoad(t *testing.T) {
t.Fatalf("Load error: %v", err)
}
// Should have 01 and 03 in use
// Should have furiosa and slit in use
if pool2.ActiveCount() != 2 {
t.Errorf("expected 2 active, got %d", pool2.ActiveCount())
}
// Next allocation should be 02 (released slot)
// Next allocation should be nux (released slot)
name, _ := pool2.Allocate()
if name != "polecat-02" {
t.Errorf("expected polecat-02, got %s", name)
if name != "nux" {
t.Errorf("expected nux, got %s", name)
}
}
@@ -201,7 +201,7 @@ func TestNamePool_Reconcile(t *testing.T) {
pool := NewNamePool(tmpDir, "testrig")
// Simulate existing polecats from filesystem
existing := []string{"polecat-03", "polecat-07", "some-other-name"}
existing := []string{"slit", "valkyrie", "some-other-name"}
pool.Reconcile(existing)
@@ -209,10 +209,10 @@ func TestNamePool_Reconcile(t *testing.T) {
t.Errorf("expected 2 active after reconcile, got %d", pool.ActiveCount())
}
// Should allocate 01 first (not 03 or 07)
// Should allocate furiosa first (not slit or valkyrie)
name, _ := pool.Allocate()
if name != "polecat-01" {
t.Errorf("expected polecat-01, got %s", name)
if name != "furiosa" {
t.Errorf("expected furiosa, got %s", name)
}
}
@@ -229,13 +229,12 @@ func TestNamePool_IsPoolName(t *testing.T) {
name string
expected bool
}{
{"polecat-01", true},
{"polecat-50", true},
{"polecat-51", false}, // > PoolSize
{"furiosa", true},
{"nux", true},
{"max", true},
{"gastown-51", false}, // overflow format
{"Nux", false}, // legacy name
{"polecat-", false}, // invalid
{"polecat-abc", false},
{"random-name", false},
{"polecat-01", false}, // old format
}
for _, tc := range tests {
@@ -255,17 +254,18 @@ func TestNamePool_ActiveNames(t *testing.T) {
pool := NewNamePool(tmpDir, "testrig")
pool.Allocate() // 01
pool.Allocate() // 02
pool.Allocate() // 03
pool.Release("polecat-02")
pool.Allocate() // furiosa
pool.Allocate() // nux
pool.Allocate() // slit
pool.Release("nux")
names := pool.ActiveNames()
if len(names) != 2 {
t.Errorf("expected 2 active names, got %d", len(names))
}
if names[0] != "polecat-01" || names[1] != "polecat-03" {
t.Errorf("expected [polecat-01, polecat-03], got %v", names)
// Names are sorted
if names[0] != "furiosa" || names[1] != "slit" {
t.Errorf("expected [furiosa, slit], got %v", names)
}
}
@@ -279,17 +279,17 @@ func TestNamePool_MarkInUse(t *testing.T) {
pool := NewNamePool(tmpDir, "testrig")
// Mark some slots as in use
pool.MarkInUse("polecat-05")
pool.MarkInUse("polecat-10")
pool.MarkInUse("dementus")
pool.MarkInUse("valkyrie")
// Allocate should skip those
name, _ := pool.Allocate()
if name != "polecat-01" {
t.Errorf("expected polecat-01, got %s", name)
if name != "furiosa" {
t.Errorf("expected furiosa, got %s", name)
}
// Mark more and verify count
if pool.ActiveCount() != 3 { // 01, 05, 10
// Verify count
if pool.ActiveCount() != 3 { // furiosa, dementus, valkyrie
t.Errorf("expected 3 active, got %d", pool.ActiveCount())
}
}
@@ -313,3 +313,120 @@ func TestNamePool_StateFilePath(t *testing.T) {
t.Errorf("state file not found at expected path: %v", err)
}
}
func TestNamePool_Themes(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "namepool-test-*")
if err != nil {
t.Fatal(err)
}
defer func() { _ = os.RemoveAll(tmpDir) }()
// Test minerals theme
pool := NewNamePoolWithConfig(tmpDir, "testrig", "minerals", nil, 50)
name, err := pool.Allocate()
if err != nil {
t.Fatalf("Allocate error: %v", err)
}
if name != "obsidian" {
t.Errorf("expected obsidian (first mineral), got %s", name)
}
// Test theme switching
if err := pool.SetTheme("wasteland"); err != nil {
t.Fatalf("SetTheme error: %v", err)
}
// obsidian should be released (not in wasteland theme)
name, _ = pool.Allocate()
if name != "rust" {
t.Errorf("expected rust (first wasteland name), got %s", name)
}
}
func TestNamePool_CustomNames(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "namepool-test-*")
if err != nil {
t.Fatal(err)
}
defer func() { _ = os.RemoveAll(tmpDir) }()
custom := []string{"alpha", "beta", "gamma", "delta"}
pool := NewNamePoolWithConfig(tmpDir, "testrig", "", custom, 4)
name, _ := pool.Allocate()
if name != "alpha" {
t.Errorf("expected alpha, got %s", name)
}
name, _ = pool.Allocate()
if name != "beta" {
t.Errorf("expected beta, got %s", name)
}
}
func TestListThemes(t *testing.T) {
themes := ListThemes()
if len(themes) != 3 {
t.Errorf("expected 3 themes, got %d", len(themes))
}
// Check that all expected themes are present
expected := map[string]bool{"mad-max": true, "minerals": true, "wasteland": true}
for _, theme := range themes {
if !expected[theme] {
t.Errorf("unexpected theme: %s", theme)
}
}
}
func TestGetThemeNames(t *testing.T) {
names, err := GetThemeNames("mad-max")
if err != nil {
t.Fatalf("GetThemeNames error: %v", err)
}
if len(names) != 50 {
t.Errorf("expected 50 mad-max names, got %d", len(names))
}
if names[0] != "furiosa" {
t.Errorf("expected first name to be furiosa, got %s", names[0])
}
// Test invalid theme
_, err = GetThemeNames("invalid-theme")
if err == nil {
t.Error("expected error for invalid theme")
}
}
func TestNamePool_Reset(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "namepool-test-*")
if err != nil {
t.Fatal(err)
}
defer func() { _ = os.RemoveAll(tmpDir) }()
pool := NewNamePool(tmpDir, "testrig")
// Allocate several names
for i := 0; i < 10; i++ {
pool.Allocate()
}
if pool.ActiveCount() != 10 {
t.Errorf("expected 10 active, got %d", pool.ActiveCount())
}
// Reset
pool.Reset()
if pool.ActiveCount() != 0 {
t.Errorf("expected 0 active after reset, got %d", pool.ActiveCount())
}
// Should allocate furiosa again
name, _ := pool.Allocate()
if name != "furiosa" {
t.Errorf("expected furiosa after reset, got %s", name)
}
}