Files
mesh-catalog/modules/mesh-build-node/cmd/mesh-build-node/push_test.go
T
jschoubben 3a25b6e1f8 mesh-build-node: push each done bundle to the artifact store from its own process and answer its reference (issue 1587, S5-S8, ADR 0498 §1 §2, lead decision L9)
After the build ended the module pushes the packed bundle with mesh-sdk's PushBlob under
<module>/<artifact> by its digest, a candidate's as a trunk build's, and the step answers
artifact-store://<module>/<artifact>/blobs/<digest> instead of the archive's path. A push
that fails fails the step not stored: <artifact>: <the store's words>; the packed bytes are
never written to the node. The manifest requires artifact-store and places its address.
2026-10-22 18:25:14 +02:00

100 lines
3.2 KiB
Go

package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// pushed are the blobs the fake artifact store took, by digest, for the tests that read an archive back.
var pushed sync.Map
// fakeStore is a store of the test's own: its address in RegistryFile, each blob kept by repository and digest; fail,
// when set, is the store's refusal.
type fakeStore struct {
mu sync.Mutex
blobs map[string][]byte // "<repository>@<digest>"
fail error
}
func withStore(t *testing.T, m *Module) *fakeStore {
t.Helper()
s := &fakeStore{blobs: map[string][]byte{}}
m.RegistryFile = filepath.Join(t.TempDir(), "registry")
if err := os.WriteFile(m.RegistryFile, []byte("store.test:5000\n"), 0o644); err != nil {
t.Fatal(err)
}
m.Push = func(ctx context.Context, registry, repository, digest string, body []byte) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.fail != nil {
return s.fail
}
if sum := sha256.Sum256(body); "sha256:"+hex.EncodeToString(sum[:]) != digest || registry != "store.test:5000" {
t.Errorf("pushed %s to %s with bytes of another digest", digest, registry)
}
s.blobs[repository+"@"+digest] = append([]byte(nil), body...)
pushed.Store(digest, s.blobs[repository+"@"+digest])
return nil
}
return s
}
// archiveOf is the archive a build pushed, by its digest.
func archiveOf(t *testing.T, r BuildResult) []byte {
t.Helper()
raw, ok := pushed.Load(r.Digest)
if !ok {
t.Fatalf("nothing was pushed under %s", r.Digest)
}
return raw.([]byte)
}
// 1587-S6 (ADR 0498 §1): a done bundle answers the reference of the archive it pushed, under the manifest's module.
func TestADoneBundleAnswersTheReferenceOfTheArchiveItPushed(t *testing.T) {
m, _, _ := builtMachine(t, ``)
store := withStore(t, m)
out, err := m.build(context.Background(), buildArgs())
if err != nil {
t.Fatal(err)
}
r := out.(BuildResult)
if want := "artifact-store://m/tools/blobs/" + r.Digest; r.Reference != want {
t.Errorf("reference %q, want %q", r.Reference, want)
}
if store.blobs["m/tools@"+r.Digest] == nil {
t.Errorf("the store holds %d blobs, not m/tools@%s", len(store.blobs), r.Digest)
}
}
// 1587-S7 and S8 (ADR 0498 §2, the lead's decision L9): a push the store refuses fails the step `not stored:
// <artifact>: <the store's words>`, and whatever the push answered, no packed file is left on the node.
func TestARefusedPushFailsNotStoredAndNoPackedFileStays(t *testing.T) {
for _, fail := range []error{nil, errors.New("store.test:5000 refused the blob: 507 Insufficient Storage")} {
m, _, work := builtMachine(t, ``)
withStore(t, m).fail = fail
_, err := m.build(context.Background(), buildArgs())
if fail != nil && (err == nil || !strings.HasPrefix(err.Error(), delivery.NotStored+"tools: ") || !strings.Contains(err.Error(), "507")) {
t.Errorf("a refused push answered %v", err)
}
var archives []string
filepath.WalkDir(work, func(p string, d os.DirEntry, err error) error {
if err == nil && strings.HasSuffix(p, ".tar.gz") {
archives = append(archives, p)
}
return nil
})
if len(archives) != 0 {
t.Errorf("push answered %v: packed files left on the node: %v", fail, archives)
}
}
}