157 lines
5.2 KiB
Go
157 lines
5.2 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
|
"git.novox.be/novox/mesh-sdk/go/composer"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
|
"git.novox.be/novox/mesh-sdk/go/testbus"
|
|
)
|
|
|
|
// sdkPin is the mesh-sdk version this module's go.mod requires: the composer the test builds is that version's.
|
|
func sdkPin(t *testing.T) string {
|
|
t.Helper()
|
|
raw, err := os.ReadFile("../../go.mod")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, l := range strings.Split(string(raw), "\n") {
|
|
if f := strings.Fields(l); len(f) >= 2 && f[0] == "git.novox.be/novox/mesh-sdk/go" {
|
|
return f[1]
|
|
}
|
|
}
|
|
t.Fatal("go.mod requires no mesh-sdk")
|
|
return ""
|
|
}
|
|
|
|
// goEnvOf is one value of this machine's go env.
|
|
func goEnvOf(t *testing.T, name string) string {
|
|
t.Helper()
|
|
out, err := exec.Command("go", "env", name).Output()
|
|
if err != nil {
|
|
t.Fatalf("go env %s: %v", name, err)
|
|
}
|
|
return strings.TrimSpace(string(out))
|
|
}
|
|
|
|
// 1622/AC1, AC3 against the real bus: a compose ask put on the Go build seat's check stream is pulled by the module
|
|
// started whole on that bus, which fetches the commit (here a tree of a module requiring mesh-sdk at this module's pin,
|
|
// as a reader repository is), builds mesh-sdk's composer there with nothing fetched, fills the request's manifests from
|
|
// the indexer's state and runs the composer; the step key <node>.<ask id> passes with the composer's verdict. A second
|
|
// ask of the same commit runs the kept composer: fetched and built once.
|
|
func TestAComposeAskIsBuiltOnceAndAnsweredOnTheBus(t *testing.T) {
|
|
nc, err := nats.Connect(testbus.URL(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nc.Close()
|
|
js, err := jetstream.New(nc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, stop := context.WithCancel(context.Background())
|
|
defer stop()
|
|
if err := buildstep.DeclareAskWorkers(ctx, js, delivery.AskGo, 2*time.Minute); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = js.DeleteStream(context.Background(), "SEAT_NODE_BUILD_GO") })
|
|
|
|
states := newStates()
|
|
m := moduleWith(states, "go1.26.0")
|
|
m.Work, m.Cache, m.Inside = t.TempDir(), t.TempDir(), true
|
|
m.Run = StepRunner(t.TempDir())
|
|
m.GoModCache = goEnvOf(t, "GOMODCACHE") // the module cache this module's own build filled, read and not fetched
|
|
run, gocache := m.Run, goEnvOf(t, "GOCACHE") // this machine's build cache, so the composer's packages compile once
|
|
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
|
return run(ctx, append(env, "GOCACHE="+gocache), name, args...)
|
|
}
|
|
read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`)
|
|
m.ReadManifest = read
|
|
|
|
gosum, err := os.ReadFile("../../go.sum")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fetches := 0
|
|
steps := fakeSteps(new([]string))
|
|
for i := range steps {
|
|
if steps[i].Name == "build_go_fetch" {
|
|
steps[i].Run = func(ctx context.Context, args map[string]any) (any, error) {
|
|
fetches++
|
|
ws := buildstep.NewID()
|
|
tree := filepath.Join(m.Work, ws, "tree")
|
|
if err := os.MkdirAll(tree, 0o700); err != nil {
|
|
return nil, err
|
|
}
|
|
mod := "module reader\n\ngo 1.26.0\n\nrequire git.novox.be/novox/mesh-sdk/go " + sdkPin(t) + "\n"
|
|
if err := os.WriteFile(filepath.Join(tree, "go.mod"), []byte(mod), 0o600); err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]any{"workspace": ws}, os.WriteFile(filepath.Join(tree, "go.sum"), gosum, 0o600)
|
|
}
|
|
}
|
|
}
|
|
p := m.Puller(steps, &buildstep.JetStreamAsks{JS: js, Language: delivery.AskGo})
|
|
p.PullWait = 200 * time.Millisecond
|
|
go p.Pull(ctx)
|
|
|
|
ask := func() delivery.Step {
|
|
a := composeAskOf(t, digests[0])
|
|
a.Compose.Fetch["repository"], a.Compose.Dir = "novox/mesh-controller", delivery.RootPath
|
|
raw, err := delivery.EncodeStepAsk(a)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
subject, _ := delivery.AskSubject(delivery.AskGo, delivery.AskCheck)
|
|
if _, err := js.Publish(ctx, subject, raw); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
key, _ := delivery.AskStepKey("ace", a.ID)
|
|
for deadline := time.Now().Add(90 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
|
states.mu.Lock()
|
|
v := states.steps[key]
|
|
states.mu.Unlock()
|
|
if len(v) > 0 && v[len(v)-1].State != delivery.StepRunning {
|
|
return v[len(v)-1]
|
|
}
|
|
}
|
|
t.Fatalf("the compose ask %s never ended", a.ID)
|
|
return delivery.Step{}
|
|
}
|
|
for n, wantBuilt := range []bool{true, false} {
|
|
step := ask()
|
|
if step.State != delivery.StepPassed {
|
|
t.Fatalf("ask %d: %s: %s", n+1, step.State, step.Reason)
|
|
}
|
|
var ans delivery.ComposeAnswer
|
|
var v composer.Verdict
|
|
if err := json.Unmarshal(step.Result, &ans); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(ans.Verdict, &v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ans.Built != wantBuilt || !strings.HasPrefix(ans.Key, "composer-") || len(v.Cases) != 1 || !v.Cases[0].Manifests["demo"].Accepted ||
|
|
!strings.Contains(v.SDK, sdkPin(t)) {
|
|
t.Fatalf("ask %d: answer built %v key %s, verdict %+v; want built %v and demo accepted by mesh-sdk %s", n+1, ans.Built, ans.Key, v, wantBuilt, sdkPin(t))
|
|
}
|
|
}
|
|
if fetches != 1 {
|
|
t.Errorf("two asks of one commit fetched %d times, want once", fetches)
|
|
}
|
|
}
|