After the build ended the module pushes the packed bundle or archive 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 a 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.
121 lines
4.4 KiB
Go
121 lines
4.4 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 makes m push to 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
|
|
addr string
|
|
}
|
|
|
|
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()
|
|
s.addr = registry
|
|
if s.fail != nil {
|
|
return s.fail
|
|
}
|
|
sum := sha256.Sum256(body)
|
|
if "sha256:"+hex.EncodeToString(sum[:]) != digest {
|
|
t.Errorf("pushed %s with bytes of another digest", digest)
|
|
}
|
|
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, res BuildResult) []byte {
|
|
t.Helper()
|
|
raw, ok := pushed.Load(res.Digest)
|
|
if !ok {
|
|
t.Fatalf("nothing was pushed under %s", res.Digest)
|
|
}
|
|
return raw.([]byte)
|
|
}
|
|
|
|
// 1587-S2 (ADR 0498 §1): a done build answers a reference of the stated form whose tail is the build's digest, and the
|
|
// store holds the archive there; a bundle and an archive alike.
|
|
func TestADoneBuildAnswersTheReferenceOfTheArchiveItPushed(t *testing.T) {
|
|
m, states, _ := buildModule(t, manifestWith(goBundle), "the program")
|
|
store := withStore(t, m)
|
|
res, st := runBuild(t, m, states, "tools")
|
|
if st.State != delivery.StepPassed {
|
|
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
|
|
}
|
|
if want := "artifact-store://demo/tools/blobs/" + res.Digest; res.Reference != want {
|
|
t.Errorf("reference %q, want %q", res.Reference, want)
|
|
}
|
|
if store.addr != "store.test:5000" || store.blobs["demo/tools@"+res.Digest] == nil {
|
|
t.Errorf("the store at %q holds %d blobs, not demo/tools@%s", store.addr, len(store.blobs), res.Digest)
|
|
}
|
|
a, states2, _ := archiveModule(t, "")
|
|
withStore(t, a)
|
|
arch, st := runBuild(t, a, states2, "files")
|
|
if st.State != delivery.StepPassed || arch.Reference != "artifact-store://demo/files/blobs/"+arch.Digest {
|
|
t.Errorf("the archive artifact: %s %q, reference %q", st.State, st.Reason, arch.Reference)
|
|
}
|
|
}
|
|
|
|
// 1587-S3 (ADR 0498 §2): a push the store refuses fails the step `not stored: <artifact>: <the store's words>`, and so
|
|
// does a store whose address the manifest did not place.
|
|
func TestARefusedPushFailsTheStepNotStored(t *testing.T) {
|
|
m, states, _ := buildModule(t, manifestWith(goBundle), "the program")
|
|
store := withStore(t, m)
|
|
store.fail = errors.New("store.test:5000 refused the blob: 500 Internal Server Error disk full")
|
|
if _, st := runBuild(t, m, states, "tools"); st.State != delivery.StepFailed || !strings.HasPrefix(st.Reason, delivery.NotStored+"tools: ") || !strings.Contains(st.Reason, "disk full") {
|
|
t.Errorf("the step ended %s: %q", st.State, st.Reason)
|
|
}
|
|
m.RegistryFile = filepath.Join(t.TempDir(), "absent")
|
|
if _, st := runBuild(t, m, states, "tools"); st.State != delivery.StepFailed || !strings.HasPrefix(st.Reason, delivery.NotStored+"tools: ") {
|
|
t.Errorf("no address: the step ended %s: %q", st.State, st.Reason)
|
|
}
|
|
}
|
|
|
|
// 1587-S4 (the lead's decision L9): the packed file is not left on the node once its push answered, a candidate's
|
|
// included: the workspace holds no archive, pushed or refused.
|
|
func TestThePackedFileLeavesTheNodeOnceItsPushAnswered(t *testing.T) {
|
|
for _, fail := range []error{nil, errors.New("refused")} {
|
|
m, states, _ := buildModule(t, manifestWith(goBundle), "the program")
|
|
withStore(t, m).fail = fail
|
|
runBuild(t, m, states, "tools")
|
|
var archives []string
|
|
filepath.WalkDir(m.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: archives left on the node: %v", fail, archives)
|
|
}
|
|
}
|
|
}
|