Artifact 2e4edc7f444a72599ff26bf6bdc2760ccafb0a57e4be56d395608e0e803e383b:
- File
internal/api/repos_test.go
— part of check-in
[4a479e6ce2]
at
2026-08-18 01:15:43
on branch trunk
— api: make the repo set a concurrent registry; serve rejects positionals
Deps.Repos was a bare map, read by every op on the request path and written only at startup. Adding repositories at runtime — the point of the registry ticket — makes that a data race with no symptom until it corrupts one. api.Repos owns the lock; a *Repo is published complete or not at all, and Delete hands back the entry so the caller can close the index and stop the pipeline.
serve now refuses leftover arguments. Go's flag package stops at the first non-flag and returns the rest, so one unquoted shell variable silently dropped twelve --repo flags and quarry served the remaining eleven without a word. (user: dmestas, size: 2182) [annotate] [blame] [check-ins using]
package api import ( "sync" "testing" ) // The registry replaced a bare map because repositories can now be // added and removed while ops read it on the request path. A race here // has no symptom until it corrupts one, so this is worth pinning under // -race rather than reasoning about. func TestReposConcurrentReadsAndWrites(t *testing.T) { rs := NewRepos(map[string]*Repo{"seed": {Code: "s"}}) var wg sync.WaitGroup for i := 0; i < 8; i++ { wg.Add(2) go func(n int) { defer wg.Done() for j := 0; j < 200; j++ { rs.Get("seed") rs.Names() rs.Len() } }(i) go func(n int) { defer wg.Done() for j := 0; j < 200; j++ { name := string(rune('a' + n)) rs.Put(name, &Repo{Code: name}) rs.Delete(name) } }(i) } wg.Wait() if _, ok := rs.Get("seed"); !ok { t.Error("the seeded repo did not survive concurrent churn") } } // Names is sorted because callers render it: map order would reshuffle // the repository list on every page load. func TestReposNamesAreSorted(t *testing.T) { rs := NewRepos(map[string]*Repo{"zeta": {}, "alpha": {}, "mid": {}}) got := rs.Names() want := []string{"alpha", "mid", "zeta"} for i := range want { if got[i] != want[i] { t.Fatalf("Names() = %v, want %v", got, want) } } } // Delete returns what it removed so the caller can close an index and // stop a pipeline — both outlive the map entry otherwise. func TestReposDeleteReturnsTheEntry(t *testing.T) { rs := NewRepos(map[string]*Repo{"a": {Code: "codeA"}}) got, ok := rs.Delete("a") if !ok || got == nil || got.Code != "codeA" { t.Fatalf("Delete returned (%v, %v), want the entry", got, ok) } if _, ok := rs.Get("a"); ok { t.Error("entry still registered after Delete") } if _, ok := rs.Delete("a"); ok { t.Error("second Delete reported success") } } // A nil registry answers empty rather than panicking, so a Deps built // without one (docs generation, some tests) still works. func TestNilReposIsUsable(t *testing.T) { var rs *Repos if _, ok := rs.Get("x"); ok { t.Error("nil registry returned a repo") } if rs.Len() != 0 || rs.Names() != nil { t.Error("nil registry did not report empty") } }