package buildrun import ( "context" "strings" "testing" "time" ) func acquireAsync(p *Places, node string) chan func() { got := make(chan func(), 1) go func() { release, err := p.Acquire(context.Background(), node) if err != nil { close(got) return } got <- release }() return got } func TestThreeStepsOnANodeOfCapacityTwoRunTwoAtOnceAndTheThirdStartsWhenOneEnds(t *testing.T) { // round 3 p := &Places{Capacity: map[string]int{"ace": 2}} a, b := acquireAsync(p, "ace"), acquireAsync(p, "ace") ra, rb := <-a, <-b third := acquireAsync(p, "ace") select { case <-third: t.Fatal("a third step started on a node of capacity 2 with two running") case <-time.After(10 * time.Millisecond): } if p.Running()["ace"] != 2 { t.Errorf("running = %v, want 2 on ace", p.Running()) } ra() select { case r := <-third: r() case <-time.After(2 * time.Second): t.Fatal("the third step did not start when one ended") } rb() } func TestANodeWithNoSettingRunsOneStepAtATime(t *testing.T) { // round 3 p := &Places{Capacity: map[string]int{"shanks": 3}} first := <-acquireAsync(p, "g14") second := acquireAsync(p, "g14") select { case <-second: t.Fatal("two steps on a node without a setting") case <-time.After(10 * time.Millisecond): } first() r := <-second r() if p.CapacityOf("g14") != 1 || p.CapacityOf("shanks") != 3 { t.Errorf("capacities = %d, %d", p.CapacityOf("g14"), p.CapacityOf("shanks")) } } func TestAWaitingStepStopsWhenItsContextEnds(t *testing.T) { // round 3 p := &Places{} r := <-acquireAsync(p, "ace") defer r() ctx, cancel := context.WithCancel(context.Background()) done := make(chan error, 1) go func() { _, err := p.Acquire(ctx, "ace"); done <- err }() cancel() select { case err := <-done: if err == nil { t.Error("Acquire returned no error for a cancelled wait") } case <-time.After(2 * time.Second): t.Fatal("a cancelled wait did not return") } } // The operator's decision of 2026-10-21 13:40: the builder builds on shanks and ace only; a capacity of 0 keeps it off a node. func TestACapacityOfZeroKeepsThePartOffTheNodeAndANodeNotNamedKeepsOne(t *testing.T) { // issue 1661 p := &Places{Capacity: map[string]int{"shanks": 3, "ace": 2, "g14": 0, "novox": 0}} if p.CapacityOf("g14") != 0 || p.CapacityOf("novox") != 0 || p.CapacityOf("ace") != 2 || p.CapacityOf("other") != DefaultCapacity { t.Errorf("capacities = %d %d %d %d, want 0 0 2 and the default for a node not named", p.CapacityOf("g14"), p.CapacityOf("novox"), p.CapacityOf("ace"), p.CapacityOf("other")) } entries := tcs("go", "go1.27.1", "ace", "g14", "novox", "shanks", "other") for i := 0; i < 6; i++ { nodes, _, err := ChooseNodes("go", entries, p, nil, 2) if err != nil { t.Fatal(err) } for _, n := range nodes { if n == "g14" || n == "novox" { t.Fatalf("choice %d: %v includes a node of capacity 0", i, nodes) } } } // Only nodes of capacity 0 report: nothing qualifies, and the build says why. _, _, err := ChooseNodes("go", tcs("go", "go1.27.1", "g14", "novox"), p, nil, 2) if err == nil || !strings.Contains(err.Error(), "capacity") { t.Errorf("only capacity-0 nodes: error = %v, want it to name the capacity", err) } }