141 lines
4.0 KiB
Go
141 lines
4.0 KiB
Go
package build
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
func TestParseCapacityReadsNodeEqualsCountPairs(t *testing.T) {
|
|
c, err := ParseCapacity(" shanks=3, ace=2 ")
|
|
if err != nil || c["shanks"] != 3 || c["ace"] != 2 || len(c) != 2 {
|
|
t.Errorf("capacity = %v, %v", c, err)
|
|
}
|
|
if c, err := ParseCapacity(""); err != nil || len(c) != 0 {
|
|
t.Errorf("empty: %v, %v", c, err)
|
|
}
|
|
for _, bad := range []string{"shanks", "shanks=0", "shanks=x", "=2", "a=1,a=2", "shanks=-1"} {
|
|
if _, err := ParseCapacity(bad); err == nil {
|
|
t.Errorf("%q accepted", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
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(50 * 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(50 * 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")
|
|
}
|
|
}
|
|
|
|
func TestStepsOfSeveralBuildsNeverExceedANodesCapacity(t *testing.T) { // round 3, through the builder
|
|
r := newRig(t, catalogue.Artifact{Name: "tool", Kind: "bundle", Language: "go"})
|
|
r.b.Places = &Places{Capacity: map[string]int{"ace": 2}} // novox has none: one at a time
|
|
var now, most atomic.Int32
|
|
var mu sync.Mutex
|
|
perNode := map[string]int{}
|
|
r.bm.steps.during = func(key string) func() {
|
|
node, _, _ := strings.Cut(key, ".")
|
|
mu.Lock()
|
|
perNode[node]++
|
|
if int32(perNode[node]) > most.Load() && node == "ace" {
|
|
most.Store(int32(perNode[node]))
|
|
}
|
|
if node == "novox" && perNode[node] > 1 {
|
|
t.Errorf("%d steps at once on novox, which has capacity 1", perNode[node])
|
|
}
|
|
mu.Unlock()
|
|
now.Add(1)
|
|
time.Sleep(15 * time.Millisecond)
|
|
return func() { mu.Lock(); perNode[node]--; mu.Unlock(); now.Add(-1) }
|
|
}
|
|
for _, m := range []string{"m1", "m2", "m3", "m4"} {
|
|
r.b.Module(context.Background(), m, moduleValue(t, "modules/"+m))
|
|
}
|
|
r.b.Wait()
|
|
if most.Load() > 2 {
|
|
t.Errorf("%d steps at once on ace, which has capacity 2", most.Load())
|
|
}
|
|
if most.Load() < 2 {
|
|
t.Errorf("at most %d steps at once on ace: the capacity was not used", most.Load())
|
|
}
|
|
}
|
|
|
|
func TestTwoNodeAgreementStillTakesTwoDifferentNodesWhenCapacitiesAreSet(t *testing.T) { // round 3, ADR 0478 clause 4
|
|
r := newRig(t, catalogue.Artifact{Name: "tool", Kind: "bundle", Language: "go"})
|
|
r.b.Places = &Places{Capacity: map[string]int{"ace": 2, "novox": 3}}
|
|
v := r.consider(t, "modules/m")
|
|
if v.State != "done" || !strings.Contains(v.Step, "ace and novox") {
|
|
t.Errorf("build = %+v, want done on ace and novox", v)
|
|
}
|
|
}
|