DLV-B8: the tag ask's narration is a case of the builder's whole-module test, and two instances together joins the archive table
The builder's integration tests fall from 17 to 16: the two-instance test is a row of the archive table; the bed holds the tag asks and their key-ttl bounds and the builder's process log. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -9,7 +9,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -31,6 +30,7 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
|
||||
"mesh-delivery-builder/internal/build"
|
||||
"mesh-delivery-builder/internal/versions"
|
||||
)
|
||||
|
||||
// busConf is the shape of the composed accounts file (mesh-controller internal/broker): one account with JetStream, the
|
||||
@@ -195,7 +195,10 @@ type bed struct {
|
||||
builds jetstream.KeyValue
|
||||
tools map[string]jetstream.KeyValue
|
||||
instances jetstream.KeyValue
|
||||
stop func() // stops the builder started last
|
||||
tagAsks jetstream.KeyValue
|
||||
tagBounds jetstream.KeyValue
|
||||
out *saidLog // the builder's process log, every instance's
|
||||
stop func() // stops the builder started last
|
||||
fake *fakeBuildModules
|
||||
srvURL string
|
||||
dir string
|
||||
@@ -272,6 +275,13 @@ func startBedWith(t *testing.T, closed bool) *bed {
|
||||
mk(delivery.ManifestsBucket, delivery.ManifestsHistory)
|
||||
b.builds = mk(delivery.BuilderBuildsBucket, delivery.BuilderBuildsHistory)
|
||||
b.instances = mk(instancesBucket, 1)
|
||||
// The tag asks and their bounds (design 51 *Tagged versions*): the bounds declared key-ttl, so an ended bound leaves the
|
||||
// bus's marker a watch hears.
|
||||
b.tagAsks = mk(versions.TagAsksBucket, 1)
|
||||
if b.tagBounds, err = js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: versions.TagAskBoundsBucket, History: 1, LimitMarkerTTL: time.Minute}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.out = &saidLog{}
|
||||
// The builder's module log, as the runtime raises it (ADR 0297): its stream on the log's subjects.
|
||||
logStream := buscompose.LogStreamName(delivery.BuilderModule, delivery.BuilderLog)
|
||||
if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: logStream, Subjects: []string{buscompose.LogSubject(delivery.BuilderModule, delivery.BuilderLog) + ".>"},
|
||||
@@ -389,12 +399,48 @@ func (b *bed) start(t *testing.T, node string, env map[string]string) {
|
||||
}
|
||||
runCtx, stop := context.WithCancel(b.ctx)
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- run(runCtx, func(k string) string { return e[k] }, io.Discard) }()
|
||||
go func() { done <- run(runCtx, func(k string) string { return e[k] }, b.out) }()
|
||||
var once sync.Once
|
||||
b.stop = func() { once.Do(func() { stop(); <-done }) }
|
||||
t.Cleanup(b.stop)
|
||||
}
|
||||
|
||||
// saidLog is the builder's process log as its instances write it.
|
||||
type saidLog struct {
|
||||
mu sync.Mutex
|
||||
buf strings.Builder
|
||||
}
|
||||
|
||||
func (l *saidLog) Write(p []byte) (int, error) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.buf.Write(p)
|
||||
}
|
||||
|
||||
// awaitSaid reads the process log until it holds want, within 20 s, and answers the line.
|
||||
func (b *bed) awaitSaid(t *testing.T, want string) string {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(20 * time.Second)
|
||||
for {
|
||||
b.out.mu.Lock()
|
||||
all := b.out.buf.String()
|
||||
b.out.mu.Unlock()
|
||||
for _, line := range strings.Split(all, "\n") {
|
||||
if strings.Contains(line, want) {
|
||||
return line
|
||||
}
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("the builder never said %q; it said:\n%s", want, all)
|
||||
}
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
t.Fatal(b.ctx.Err())
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// await reads the build key until pred holds of it, within 20 s.
|
||||
func (b *bed) await(t *testing.T, module string, pred func(delivery.BuilderBuild) bool) delivery.BuilderBuild {
|
||||
t.Helper()
|
||||
@@ -426,7 +472,16 @@ func isState(state string) func(delivery.BuilderBuild) bool {
|
||||
func TestBuilderMakesADoneKeyWithTheDigests(t *testing.T) { // 1607/AC7
|
||||
b := startBed(t)
|
||||
digest := b.module(t, "netcheck", "catalogue-a67f0196-netcheck.module.json", "modules/netcheck")
|
||||
b.start(t, "n1", nil)
|
||||
// DLV-B6: a tag ask kept and never numbered (no check builds its head) is narrated once its bound ends on the bus, with
|
||||
// what it waits on, and its bound is written again; no timer runs in the builder.
|
||||
key, ask, err := versions.NewTagAsk("novox/mesh-sdk", "feat/x", "abc", "a group member's check", "me@shanks", time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.tagAsks.Put(b.ctx, key, ask); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.start(t, "n1", map[string]string{"MESH_BUILDER_TAG_ASK_BOUND": "1s"})
|
||||
v := b.await(t, "netcheck", isState(delivery.BuildDone))
|
||||
if v.By != "n1.mesh-delivery-builder" || v.Manifest != digest || len(v.Artifacts) != 2 {
|
||||
t.Fatalf("build = %+v", v)
|
||||
@@ -455,38 +510,51 @@ func TestBuilderMakesADoneKeyWithTheDigests(t *testing.T) { // 1607/AC7
|
||||
t.Errorf("%s called %d times, want %d (on one node, ADR 0478 clause 4)", tool, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTwoBuilderInstancesStartingTogetherEndWithOneDoneKey: two builders on two nodes start together on one module key of an
|
||||
// archive only (no toolchain named, as the indexer writes it); the second takes over the key the first holds building
|
||||
// (ADR 0478 clause 6), and the key ends done, written once.
|
||||
func TestTwoBuilderInstancesStartingTogetherEndWithOneDoneKey(t *testing.T) { // 1607/AC7
|
||||
b := startBed(t)
|
||||
b.archive(t, "ca-trust", "modules/ca-trust", commit)
|
||||
b.start(t, "n1", nil)
|
||||
b.start(t, "n2", nil)
|
||||
v := b.await(t, "ca-trust", isState(delivery.BuildDone))
|
||||
if len(v.Artifacts) != 1 || v.Artifacts[0].Kind != "archive" || v.Artifacts[0].Digest != digestOf("files") {
|
||||
t.Fatalf("build = %+v, want the archive's digest", v)
|
||||
said := b.awaitSaid(t, "the tag ask of novox/mesh-sdk feat/x at abc (feat-x) is not answered after")
|
||||
if !strings.Contains(said, "the check's build of abc is not claimed yet") {
|
||||
t.Errorf("the narration = %q, want what the ask waits on", said)
|
||||
}
|
||||
if n := b.fake.count("mesh-build-go.build_go_build"); n < 1 { // each instance that built, on one node
|
||||
t.Errorf("build_go_build was called %d times, want one for each instance that built", n)
|
||||
if e, err := b.tagAsks.Get(b.ctx, key); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if a, _ := versions.DecodeTagAsk(e.Value()); a.Answered.IsZero() || !a.Unanswered() {
|
||||
t.Errorf("the tag ask = %+v, want kept and answered, no version yet", a)
|
||||
}
|
||||
for deadline := time.Now().Add(10 * time.Second); ; time.Sleep(50 * time.Millisecond) {
|
||||
if _, err := b.tagBounds.Get(b.ctx, key); err == nil {
|
||||
break
|
||||
} else if time.Now().After(deadline) {
|
||||
t.Fatalf("the bound is not written again after its narration: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAnArchiveOnlyModuleAndARootModuleAreBuilt: the indexer gives an archive no language, so modules.ca-trust has no
|
||||
// toolchains; the builder builds it on the build module's nodes all the same (review round 4). A module whose path is "."
|
||||
// is given to the build tools as it is (1615/AC4). One table: the two tests were merged to pay for the enrolment's.
|
||||
// is given to the build tools as it is (1615/AC4). Two builders on two nodes start together on the archive's key: the
|
||||
// second takes over the key the first holds building (ADR 0478 clause 6), and the key ends done, written once (1607/AC7).
|
||||
// One table: three tests were merged, to pay for the enrolment's and to bring mesh-delivery's count down (DLV-B8).
|
||||
func TestAnArchiveOnlyModuleAndARootModuleAreBuilt(t *testing.T) {
|
||||
for _, c := range []struct{ name, path string }{{"no toolchain", "modules/ca-trust"}, {"root", delivery.RootPath}} {
|
||||
for _, c := range []struct {
|
||||
name, path string
|
||||
two bool
|
||||
}{{"no toolchain", "modules/ca-trust", false}, {"root", delivery.RootPath, false}, {"two instances together", "modules/ca-trust", true}} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
b := startBed(t)
|
||||
b.archive(t, "ca-trust", c.path, commit)
|
||||
b.start(t, "n1", nil)
|
||||
if c.two {
|
||||
b.start(t, "n2", nil)
|
||||
}
|
||||
v := b.await(t, "ca-trust", isState(delivery.BuildDone))
|
||||
if c.name == "no toolchain" && (len(v.Toolchains) != 0 || len(v.Artifacts) != 1 || strings.HasPrefix(v.Step, "one node (")) {
|
||||
t.Errorf("build = %+v, want done naming no toolchain", v)
|
||||
}
|
||||
if c.two && (len(v.Artifacts) != 1 || v.Artifacts[0].Kind != "archive" || v.Artifacts[0].Digest != digestOf("files")) {
|
||||
t.Errorf("build = %+v, want the archive's digest", v)
|
||||
}
|
||||
if n := b.fake.count("mesh-build-go.build_go_build"); c.two && n < 1 { // each instance that built, on one node
|
||||
t.Errorf("build_go_build was called %d times, want one for each instance that built", n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user