184 lines
8.2 KiB
Go
184 lines
8.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
sdkstep "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"
|
|
)
|
|
|
|
// nodeEngineComposer is the composer command of the node-engine's repository, whose Go module is at its root (ADR 0593 §7):
|
|
// a group that changes the node-engine's composition is composed by this program built at the group's head.
|
|
const nodeEngineComposer = "github.com/novox/mesh-host/cmd/mesh-compose"
|
|
|
|
const composeCommit = "4444444444444444444444444444444444444444"
|
|
|
|
// indexerHolds is the delivery indexer's manifests state holding each body under its digest, as the indexer writes it.
|
|
func indexerHolds(t *testing.T, bodies ...string) (func(string) (json.RawMessage, error), []string) {
|
|
t.Helper()
|
|
kept, digests := map[string]json.RawMessage{}, []string{}
|
|
for _, b := range bodies {
|
|
d := delivery.ManifestDigest([]byte(b))
|
|
key, _ := delivery.ManifestsKey(d)
|
|
raw, err := delivery.EncodeManifest(delivery.Manifest{Module: "demo", JSON: b})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
kept[key], digests = raw, append(digests, d)
|
|
}
|
|
return func(key string) (json.RawMessage, error) { return kept[key], nil }, digests
|
|
}
|
|
|
|
func composeAskOf(t *testing.T, digest string) delivery.StepAsk {
|
|
t.Helper()
|
|
req, _ := json.Marshal(composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "change",
|
|
Manifests: map[string]composer.Manifest{"demo": {Digest: digest}}}}})
|
|
return delivery.StepAsk{ID: sdkstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "mesh-delivery-assigner",
|
|
At: time.Now(), Compose: &delivery.ComposeJob{Fetch: map[string]any{"repository": "novox/mesh-sdk", "commit": composeCommit},
|
|
Dir: "go", Package: composer.Package, Request: req}}
|
|
}
|
|
|
|
// composeModule is a module with work and cache directories of its own, running its commands as they are (Inside: a
|
|
// unit test has no bubblewrap to shut them off in).
|
|
func composeModule(t *testing.T) *Module {
|
|
t.Helper()
|
|
m := moduleWith(newStates(), "go1.26.0")
|
|
m.Work, m.Cache, m.Inside, m.Run = t.TempDir(), t.TempDir(), true, StepRunner(t.TempDir())
|
|
return m
|
|
}
|
|
|
|
// 1622/AC3: a composer kept under the ask's key is run again, nothing fetched or built: the answer says it was not
|
|
// built, names the key, and carries the composer's verdict whole; the request it read had its manifests filled from the
|
|
// indexer's state.
|
|
func TestAKeptComposerIsRunWithTheManifestsFilled(t *testing.T) {
|
|
m := composeModule(t)
|
|
read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`)
|
|
m.ReadManifest = read
|
|
a := composeAskOf(t, digests[0])
|
|
key := delivery.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, delivery.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()})
|
|
exe := m.composerPath(key)
|
|
if err := os.MkdirAll(filepath.Dir(exe), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// A composer that answers which manifests its request held, filled or not, as a verdict.
|
|
script := "#!/bin/sh\n" +
|
|
`if grep -q '"digest"' "$2"; then echo '{"v":1,"sdk":"stand-in","cases":[{"name":"unfilled","manifests":{}}]}'; ` +
|
|
`else echo '{"v":1,"sdk":"stand-in","cases":[{"name":"change","manifests":{"demo":{"accepted":true}}}]}'; fi` + "\n"
|
|
if err := os.WriteFile(exe, []byte(script), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fetched := 0
|
|
fetch := func(context.Context, map[string]any) (any, error) { fetched++; return nil, nil }
|
|
out, err := m.compose(context.Background(), fetch, a)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ans := out.(delivery.ComposeAnswer)
|
|
var v composer.Verdict
|
|
if err := json.Unmarshal(ans.Verdict, &v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ans.Built || ans.Key != key || fetched != 0 || len(v.Cases) != 1 || v.Cases[0].Name != "change" || !v.Cases[0].Manifests["demo"].Accepted {
|
|
t.Fatalf("answer = %+v (fetched %d), verdict %+v", ans, fetched, v)
|
|
}
|
|
if entries, _ := os.ReadDir(m.Work); len(entries) != 0 {
|
|
t.Errorf("the run's workspace was left: %v", entries)
|
|
}
|
|
}
|
|
|
|
// 1622: a manifest the indexer does not keep fails the ask, naming its digest; a fetched commit with no go.mod where
|
|
// the ask builds fails it naming the directory, and its workspace is released.
|
|
func TestAComposeAskFailsLoudly(t *testing.T) {
|
|
m := composeModule(t)
|
|
m.ReadManifest, _ = indexerHolds(t)
|
|
a := composeAskOf(t, "feedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed")
|
|
key := delivery.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, delivery.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()})
|
|
os.MkdirAll(filepath.Dir(m.composerPath(key)), 0o700)
|
|
os.WriteFile(m.composerPath(key), []byte("#!/bin/sh\nexit 0\n"), 0o700)
|
|
if _, err := m.compose(context.Background(), nil, a); err == nil || !strings.Contains(err.Error(), "feedfeed") {
|
|
t.Errorf("an unkept manifest: %v", err)
|
|
}
|
|
m2 := composeModule(t)
|
|
fetch := func(context.Context, map[string]any) (any, error) {
|
|
if err := os.MkdirAll(filepath.Join(m2.Work, "ws0000000000a", "tree", "go"), 0o700); err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]any{"workspace": "ws0000000000a"}, nil
|
|
}
|
|
if _, err := m2.compose(context.Background(), fetch, composeAskOf(t, "x")); err == nil || !strings.Contains(err.Error(), "no go.mod in go") {
|
|
t.Errorf("a commit with no go.mod: %v", err)
|
|
}
|
|
if entries, _ := os.ReadDir(m2.Work); len(entries) != 0 {
|
|
t.Errorf("the fetched workspace was left: %v", entries)
|
|
}
|
|
}
|
|
|
|
// 1622, ADR 0615: the module asks the delivery indexer's read tool for the manifests a compose ask names by digest, and
|
|
// reads none of the indexer's state.
|
|
func TestTheModuleReadsTheIndexersManifests(t *testing.T) {
|
|
if m := readManifest(t); !slices.Contains(m.Invokes, ManifestsTool) || slices.Contains(m.Reads, delivery.IndexerModule+".manifests") {
|
|
t.Errorf("invokes = %v, reads = %v: it asks %s", m.Invokes, m.Reads, ManifestsTool)
|
|
}
|
|
}
|
|
|
|
// ADR 0593 §7 (C9): a compose ask naming the node-engine's repository builds its composer command in the Go module at the
|
|
// repository's root, under a key of that repository, commit and package, and runs the program over the request with its
|
|
// manifests filled; the module's answer is the program's verdict whole.
|
|
func TestAComposeAskBuildsTheNodeEnginesComposerAtItsRoot(t *testing.T) {
|
|
m := composeModule(t)
|
|
read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`)
|
|
m.ReadManifest = read
|
|
a := composeAskOf(t, digests[0])
|
|
a.Compose.Fetch["repository"], a.Compose.Dir, a.Compose.Package = "novox/mesh-host", delivery.RootPath, nodeEngineComposer
|
|
tree := ""
|
|
var built []string
|
|
real := m.Run
|
|
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
|
if name != "go" {
|
|
return real(ctx, env, name, args...)
|
|
}
|
|
built = args
|
|
for i, arg := range args {
|
|
if arg == "-o" {
|
|
script := "#!/bin/sh\necho '{\"v\":1,\"sdk\":\"node-engine\",\"cases\":[{\"name\":\"change\",\"manifests\":{\"demo\":{\"accepted\":true}}}]}'\n"
|
|
if err := os.WriteFile(args[i+1], []byte(script), 0o700); err != nil {
|
|
return forgekey.Ran{Status: 1, Stderr: err.Error()}
|
|
}
|
|
}
|
|
}
|
|
return forgekey.Ran{}
|
|
}
|
|
fetch := func(context.Context, map[string]any) (any, error) {
|
|
tree = filepath.Join(m.Work, "ws0000000000b", "tree")
|
|
if err := os.MkdirAll(tree, 0o700); err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]any{"workspace": "ws0000000000b"}, os.WriteFile(filepath.Join(tree, "go.mod"), []byte("module github.com/novox/mesh-host\n"), 0o600)
|
|
}
|
|
out, err := m.compose(context.Background(), fetch, a)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ans := out.(delivery.ComposeAnswer)
|
|
key := delivery.ComposerKey("novox/mesh-host", composeCommit, delivery.RootPath, nodeEngineComposer, delivery.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()})
|
|
var v composer.Verdict
|
|
if err := json.Unmarshal(ans.Verdict, &v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(built) < 3 || built[0] != "-C" || built[1] != tree || built[len(built)-1] != nodeEngineComposer {
|
|
t.Errorf("go ran as %v, want it to build %s in the tree's root %s", built, nodeEngineComposer, tree)
|
|
}
|
|
if !ans.Built || ans.Key != key || v.SDK != "node-engine" || !v.Cases[0].Manifests["demo"].Accepted {
|
|
t.Errorf("answer %+v, verdict %+v, want built under %s and demo accepted", ans, v, key)
|
|
}
|
|
}
|