package builder import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "slices" "strconv" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/catalogue" ) // One build per change (novox/hq ADR 0350): the merge check builds what the merge would move into the scratch // namespace under its reuse key, and the walk of the trunk commit takes that build by digest instead of building. // aScratch is the scratch namespace in memory: check builds by module and key, and what the walk took. type aScratch struct { builds map[string]CheckBuild took []string failTake bool } func newScratch() *aScratch { return &aScratch{builds: map[string]CheckBuild{}} } func (s *aScratch) Keep(_ context.Context, _ PullRef, _ string, _ time.Time, builds []CheckBuild) error { for _, b := range builds { s.builds[b.Module+" "+b.Key] = b } return nil } func (s *aScratch) Find(_ context.Context, module, key string) (CheckBuild, bool, error) { b, ok := s.builds[module+" "+key] return b, ok, nil } func (s *aScratch) OfPulls(_ context.Context, pulls []PullRef) ([]CheckBuild, error) { var out []CheckBuild for _, b := range s.builds { if slices.Contains(pulls, b.Pull) { out = append(out, b) } } return out, nil } func (s *aScratch) Take(_ context.Context, module string, made catalogue.Built) (catalogue.Built, error) { if s.failTake { return catalogue.Built{}, errors.New("the store refused the mount") } s.took = append(s.took, made.Reference) made.Reference = strings.Replace(made.Reference, ScratchPrefix, "", 1) return made, nil } func (s *aScratch) Forget(context.Context, PullRef) (int, error) { return 0, nil } func (s *aScratch) Expire(context.Context, time.Duration, time.Time) (int, error) { return 0, nil } func (s *aScratch) Discard(context.Context, []CheckBuild) error { return nil } const reusedModule = `{"module":"meshboard","version":"1", "build":{"artifacts":[ {"name":"server","kind":"image","from":"Dockerfile"}, {"name":"look","kind":"archive","from":"files"}]}, "resources":[ {"id":"svc","type":"container","name":"meshboard","artifact":"server"}, {"id":"theme","type":"archive","path":"/opt/meshboard","artifact":"look"}]}` // aModuleAt is a repository holding the module under mod/, whose directory git names as tree, on its trunk. func aModuleAt(t *testing.T, tree string) (*recorded, Runner, string) { t.Helper() return aModuleWith(t, tree, map[string]string{"mod/" + ManifestName: reusedModule, "mod/Dockerfile": "FROM scratch", "mod/files/theme.conf": "dark"}) } // aModuleWith is aModuleAt with the repository's files given. func aModuleWith(t *testing.T, tree string, contents map[string]string) (*recorded, Runner, string) { t.Helper() r := &recorded{tree: tree, contents: contents} run := func(ctx context.Context, dir, name string, args ...string) (string, error) { if name == "git" && len(args) > 0 && args[0] == "symbolic-ref" { r.ran = append(r.ran, name+" "+strings.Join(args, " ")) return "origin/main\n", nil } return r.run(ctx, dir, name, args...) } return r, run, t.TempDir() } func dockerBuilt(r *recorded) bool { for _, line := range r.ran { if strings.HasPrefix(line, "docker build") { return true } } return false } var aPull = PullRef{Owner: "novox", Repo: "meshboard", Number: 7} // aCheckBuilt is the check of aPull building the module from a tree, into the scratch namespace. func aCheckBuilt(t *testing.T, scratch *aScratch, tree string) CheckBuilt { t.Helper() r, run, workspace := aModuleAt(t, tree) return aCheckBuiltBy(t, scratch, r, run, workspace) } // aCheckBuiltBy is aCheckBuilt of the repository given. func aCheckBuiltBy(t *testing.T, scratch *aScratch, r *recorded, run Runner, workspace string) CheckBuilt { t.Helper() spec := CheckSpec{ID: "build-1", Owner: aPull.Owner, Repo: aPull.Repo, Number: aPull.Number, Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod", Ref: "main"}}} built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: scratch}, workspace, "", "", CheckVerdict{Head: "abcdefabcdef", Trunk: "123456123456"}, GitCredential{}, nil, logging(nil)) if len(built) != 1 || built[0].NotBuilt != "" || built[0].Key == "" { t.Fatalf("the check did not build the module for its merge: %+v", built) } if !dockerBuilt(r) { t.Fatal("the check built no image") } for repository := range r.images { if !strings.HasPrefix(repository, ScratchPrefix) { t.Fatalf("the check published %s outside the scratch namespace", repository) } } for repository := range r.archives { if !strings.HasPrefix(repository, ScratchPrefix) { t.Fatalf("the check published %s outside the scratch namespace", repository) } } return built[0] } // named is the walk's leave naming what a check's verdict says it built, as the controller reads it. func named(c CheckBuilt) []NamedBuild { n := NamedBuild{Pull: aPull, Check: "build-1", Module: c.Module, Key: c.Key, Digests: map[string]string{}} for _, m := range c.Made { d := m.Digest if d == "" { d = DigestOf(m.Reference) } n.Digests[m.Name] = d } return []NamedBuild{n} } func withVersion(t *testing.T, v string) { t.Helper() was := Version Version = v t.Cleanup(func() { Version = was }) } // A merge whose reuse key equals its check's builds nothing again: the walk copies the check's build by digest // and registers that digest. func TestAWalkWhoseKeyIsItsChecksBuildsNothingAndRegistersTheChecksDigest(t *testing.T) { withVersion(t, "builder-1") scratch := newScratch() checked := aCheckBuilt(t, scratch, "tree-one") r, run, workspace := aModuleAt(t, "tree-one") got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)}, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } if dockerBuilt(r) || len(r.images) > 0 || len(r.archives) > 0 { t.Fatalf("the walk built again: ran %v, published %v %v", r.ran, r.images, r.archives) } if got.Reused == nil || got.Why != "" { t.Fatalf("the walk did not say it reused the check's build: reused %+v, why %q", got.Reused, got.Why) } if got.ReuseKey != checked.Key { t.Fatalf("the walk's key %s is not the check's %s", got.ReuseKey, checked.Key) } if len(scratch.took) != 2 { t.Fatalf("the walk copied %v, not the check's two artifacts", scratch.took) } for _, made := range checked.Made { registered := strings.Replace(made.Reference, ScratchPrefix, "", 1) found := false for _, b := range got.Built { found = found || (b.Reference == registered && b.Digest == made.Digest) } if !found { t.Fatalf("the walk registers %+v, not the check's %s copied", got.Built, made.Reference) } } if image, _ := got.Manifest.Resources[0]["image"].(string); image != "registry.invalid/meshboard/server@sha256:"+strings.Repeat("a", 64) { t.Fatalf("the manifest pins %q, not the check's digest in the module's namespace", image) } words := got.Reused.Words() if !strings.HasPrefix(words, "reused from the check of novox/meshboard#7 at head abcdefabcdef (merged into trunk 123456123456)") || !strings.Contains(words, "sha256:") { t.Fatalf("the walk says %q", words) } } // A record no verdict names is refused and built instead, and the walk says so: the store takes writes from anything // on its network, so a record planted under another merge's key is never registered. func TestARecordNoVerdictNamesIsRefusedAndBuilt(t *testing.T) { withVersion(t, "builder-1") scratch := newScratch() checked := aCheckBuilt(t, scratch, "tree-one") for name, leave := range map[string][]NamedBuild{ "no verdict": nil, "another digest": func() []NamedBuild { n := named(checked) n[0].Digests["server"] = "sha256:" + strings.Repeat("e", 64) return n }(), "another check": func() []NamedBuild { n := named(checked); n[0].Check = "build-9"; return n }(), } { t.Run(name, func(t *testing.T) { scratch.took = nil r, run, workspace := aModuleAt(t, "tree-one") got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: leave}, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } if got.Reused != nil || len(scratch.took) > 0 || !dockerBuilt(r) { t.Fatalf("a record no verdict names was taken: %+v, copied %v", got.Reused, scratch.took) } if !strings.Contains(got.Why, "no check verdict of the walk's pull requests names it") { t.Fatalf("the walk says %q", got.Why) } }) } } // A differing key builds, and says which part differs. func TestAWalkWhoseKeyDiffersBuildsAndSaysWhy(t *testing.T) { withVersion(t, "builder-1") scratch := newScratch() checked := aCheckBuilt(t, scratch, "tree-one") r, run, workspace := aModuleAt(t, "tree-two") got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)}, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } if !dockerBuilt(r) || got.Reused != nil { t.Fatalf("a walk whose source differs did not build: reused %+v", got.Reused) } if !strings.Contains(got.Why, "the key differs") || !strings.Contains(got.Why, partTree) { t.Fatalf("the walk says %q, not which part of the key differs", got.Why) } // And a builder of another version builds too, saying so. withVersion(t, "builder-2") r, run, workspace = aModuleAt(t, "tree-one") got, err = BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)}, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } if !dockerBuilt(r) || !strings.Contains(got.Why, partBuilder) { t.Fatalf("a newer builder took the older one's build, or did not say why not: %q", got.Why) } // No check of the walk's pull requests built it: said. r, run, workspace = aModuleAt(t, "tree-one") got, _ = BuildReusing(t.Context(), run, r, Reuse{Scratch: newScratch(), Pulls: []PullRef{aPull}}, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil) if !dockerBuilt(r) || !strings.Contains(got.Why, "no check of novox/meshboard#7 built it") { t.Fatalf("the walk says %q", got.Why) } } // A copy that fails is said and falls back to a build; it never fails the walk. func TestAFailedCopyOfAChecksBuildFallsBackToBuilding(t *testing.T) { withVersion(t, "builder-1") scratch := newScratch() checked := aCheckBuilt(t, scratch, "tree-one") scratch.failTake = true r, run, workspace := aModuleAt(t, "tree-one") got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)}, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatalf("a failed copy failed the walk: %v", err) } if !dockerBuilt(r) || got.Reused != nil || len(r.images) == 0 { t.Fatal("a failed copy did not fall back to a build") } if !strings.Contains(got.Why, "copying meshboard's") || !strings.Contains(got.Why, "refused the mount") { t.Fatalf("the walk says %q", got.Why) } for repository := range r.images { if strings.HasPrefix(repository, ScratchPrefix) { t.Fatalf("the walk's own build went into the scratch namespace: %s", repository) } } } // Only a walk takes a check's build: a plain build — a check's, a dry run's, `build --ref`'s — builds, and a commit // off the trunk never takes one. func TestOnlyAWalkOfATrunkCommitTakesAChecksBuild(t *testing.T) { withVersion(t, "builder-1") scratch := newScratch() checked := aCheckBuilt(t, scratch, "tree-one") r, run, workspace := aModuleAt(t, "tree-one") if _, err := Build(t.Context(), run, r, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil); err != nil { t.Fatal(err) } if !dockerBuilt(r) || len(scratch.took) > 0 { t.Fatal("a build not asked to reuse took a check's build") } r, _, workspace = aModuleAt(t, "tree-one") // no trunk said: off it got, err := BuildReusing(t.Context(), r.run, r, Reuse{Scratch: scratch, Named: named(checked)}, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } if !dockerBuilt(r) || got.Reused != nil || !strings.Contains(got.Why, "not on its trunk") { t.Fatalf("a commit off the trunk took a check's build: %q", got.Why) } } // A check that did not pass builds nothing for its merge, and one asked to build nothing builds nothing. func TestACheckThatFailedBuildsNothingForItsMerge(t *testing.T) { if skipsTheBuilds(CheckVerdict{Gate: &Layer{Verdict: "pass"}, Repo: &Layer{Verdict: "fail"}}) == "" { t.Fatal("a check whose repository layer failed builds for its merge") } if skipsTheBuilds(CheckVerdict{Gate: &Layer{Verdict: "pass"}, Repo: &Layer{Verdict: "warning"}}) != "" { t.Fatal("a check that passed with a warning builds nothing for its merge") } r, run, workspace := aModuleAt(t, "tree-one") if built := buildForTheMerge(t.Context(), run, CheckSpec{ID: "build-1"}, CheckBuilding{Publish: r, Scratch: newScratch()}, workspace, "", "", CheckVerdict{}, GitCredential{}, nil, logging(nil)); built != nil || len(r.ran) > 0 { t.Fatalf("a check moving no module built %v, ran %v", built, r.ran) } } // A module whose source does not pin its build has no key and is not built by the check; what stands after it // is not built either. func TestACheckBuildsNoModuleWithoutAKeyNorWhatStandsAfterIt(t *testing.T) { withVersion(t, "builder-1") r, run, workspace := aModuleAt(t, "tree-one") r.contents["mod/"+ManifestName] = `{"module":"meshboard","version":"1","build":{"artifacts":[ {"name":"mirror","kind":"upstream","from":"docker.io/library/alpine:3"}]}}` spec := CheckSpec{ID: "build-1", Owner: "novox", Repo: "meshboard", Number: 7, Builds: []CheckBuildSpec{ {Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod", Tier: 0}, {Module: "after", Repository: "https://forge.invalid/after.git", Tier: 1}}} built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: newScratch()}, workspace, "", "", CheckVerdict{}, GitCredential{}, nil, logging(nil)) if len(built) != 2 || !strings.Contains(built[0].NotBuilt, "named by tag") || !strings.Contains(built[1].NotBuilt, "stands after meshboard") { t.Fatalf("built %+v", built) } if len(r.images) > 0 { t.Fatalf("a module without a key was published: %v", r.images) } } // What the key names: every input a fresh build could differ by. func TestTheReuseKeyNamesEveryInputOfTheBuild(t *testing.T) { src := newSourceInputs("m") src.tree = "t1" src.bases = []string{"store:5000/b/x@sha256:" + strings.Repeat("b", 64)} src.contexts["server"] = "ctx1" src.toolchains["tools"] = "go sha256:c 1234" manifest := catalogue.Manifest{Module: "m"} base := keyOf(src, manifest, "v1") if base.Key == "" { t.Fatalf("no key: %s", base.Why) } for name, change := range map[string]func(*sourceInputs) string{ partTree: func(s *sourceInputs) string { s.tree = "t2"; return "v1" }, partBases: func(s *sourceInputs) string { s.bases = []string{"other@sha256:" + strings.Repeat("c", 64)} return "v1" }, partContext: func(s *sourceInputs) string { s.contexts["server"] = "ctx2"; return "v1" }, partTool: func(s *sourceInputs) string { s.toolchains["tools"] = "go sha256:d 1234"; return "v1" }, partBuilder: func(*sourceInputs) string { return "v2" }, } { s := src.copy() version := change(s) if k := keyOf(s, manifest, version); k.Key == base.Key { t.Errorf("a change of %s left the key the same", name) } } // A base copied to another address is the same base: the key names it by digest. moved := src.copy() moved.bases = []string{"elsewhere:5000/scratch/b/x@sha256:" + strings.Repeat("b", 64)} if keyOf(moved, manifest, "v1").Key != base.Key { t.Error("the same base at another address changed the key") } if k := keyOf(src, manifest, ""); k.Key != "" { t.Error("a builder whose version cannot be said keyed its build") } unpinned := src.copy() unpinned.notPinned("it resolves packages from the mesh's registry at build time") if k := keyOf(unpinned, manifest, "v1"); k.Key != "" || !strings.Contains(k.Why, "registry") { t.Errorf("a build its source does not pin was keyed: %+v", k) } } // aTaggingRegistry is the in-memory registry with tags and their listing, as the store's scratch namespace needs. type aTaggingRegistry struct { *aRegistryOfRepositories tags map[string]map[string]string // repository → tag → manifest digest } func (m *aTaggingRegistry) serve() http.Handler { under := m.handler() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if repository, ok := strings.CutSuffix(strings.TrimPrefix(r.URL.Path, "/v2/"), "/tags/list"); ok { m.mu.Lock() var tags []string for tag := range m.tags[repository] { tags = append(tags, tag) } m.mu.Unlock() if tags == nil { w.WriteHeader(http.StatusNotFound) return } _ = json.NewEncoder(w).Encode(map[string]any{"name": repository, "tags": tags}) return } repository, kind, rest := splitPath(r.URL.Path) if kind == "blobs" && r.Method == http.MethodHead { m.mu.Lock() body, linked := m.blobs[rest], m.links[repository][rest] m.mu.Unlock() if !linked { w.WriteHeader(http.StatusNotFound) return } w.Header().Set("Content-Length", strconv.Itoa(len(body))) w.WriteHeader(http.StatusOK) return } if kind != "manifests" { under.ServeHTTP(w, r) return } if !strings.HasPrefix(rest, "sha256:") { m.mu.Lock() switch r.Method { case http.MethodPut: body, _ := readAll(r) if m.tags[repository] == nil { m.tags[repository] = map[string]string{} } m.tags[repository][rest] = digestOf(body) m.manifests[repository+"@"+digestOf(body)] = body m.mu.Unlock() w.WriteHeader(http.StatusCreated) return default: digest, ok := m.tags[repository][rest] body, held := m.manifests[repository+"@"+digest] m.mu.Unlock() if !ok || !held { w.WriteHeader(http.StatusNotFound) return } w.Header().Set("Docker-Content-Digest", digest) w.Header().Set("Content-Type", mediaTypeOf(body)) if r.Method == http.MethodGet { _, _ = w.Write(body) return } w.WriteHeader(http.StatusOK) return } } if r.Method == http.MethodDelete { m.mu.Lock() for _, tags := range m.tags { for tag, digest := range tags { if digest == rest { delete(tags, tag) } } } m.mu.Unlock() } under.ServeHTTP(w, r) }) } // A check's builds go after seven days, and when its pull request closes or is merged; the walk's copy in the // module's own namespace stays. func TestCheckBuildsExpireAfterSevenDaysAndGoWhenTheirPullRequestCloses(t *testing.T) { m := &aTaggingRegistry{aRegistryOfRepositories: newRegistryOfRepositories(), tags: map[string]map[string]string{}} server := httptest.NewServer(m.serve()) defer server.Close() address := strings.TrimPrefix(server.URL, "http://") registry := Registry{Address: address} ctx := t.Context() now := time.Date(2026, 10, 13, 20, 0, 0, 0, time.UTC) image := func(repository, body string) string { digest := digestOf([]byte(body)) m.manifests[repository+"@"+digest] = []byte(body) return address + "/" + repository + "@" + digest } check := func(pull PullRef, id string, at time.Time, key string) string { reference := image("scratch/meshboard/server", `{"mediaType":"`+mediaManifestOCI+`","layers":[],"n":"`+id+`"}`) build := CheckBuild{Key: key, Module: "meshboard", Made: []catalogue.Built{{Name: "server", Kind: "image", Reference: reference}}, Pull: pull, Check: id, At: at} if err := registry.Keep(ctx, pull, id, at, []CheckBuild{build}); err != nil { t.Fatal(err) } return reference } held := func(reference string) bool { _, digest, _ := strings.Cut(reference, "@") _, ok := m.manifests["scratch/meshboard/server@"+digest] return ok } other := PullRef{Owner: "novox", Repo: "meshboard", Number: 8} old := check(aPull, "build-1", now.Add(-8*24*time.Hour), "r1:"+strings.Repeat("1", 64)) fresh := check(aPull, "build-2", now.Add(-time.Hour), "r1:"+strings.Repeat("2", 64)) others := check(other, "build-3", now.Add(-time.Hour), "r1:"+strings.Repeat("3", 64)) walked := image("meshboard/server", `{"mediaType":"`+mediaManifestOCI+`","layers":[],"n":"walk"}`) if _, found, err := registry.Find(ctx, "meshboard", "r1:"+strings.Repeat("2", 64)); err != nil || !found { t.Fatalf("a check build kept is not found by its key: %v %v", found, err) } n, err := registry.Expire(ctx, CheckBuildsKept, now) if err != nil || n != 1 { t.Fatalf("expired %d checks (%v), want the one eight days old", n, err) } if held(old) || !held(fresh) || !held(others) { t.Fatalf("after the week's sweep: old %v, fresh %v, another's %v", held(old), held(fresh), held(others)) } if _, found, _ := registry.Find(ctx, "meshboard", "r1:"+strings.Repeat("1", 64)); found { t.Fatal("the expired check build's record is still found") } n, err = registry.Forget(ctx, aPull) if err != nil || n != 1 { t.Fatalf("forgot %d checks of %s (%v)", n, aPull, err) } if held(fresh) || !held(others) { t.Fatalf("after %s closed: its build held %v, another pull request's %v", aPull, held(fresh), held(others)) } if _, ok := m.manifests["meshboard/server@"+strings.SplitN(walked, "@", 2)[1]]; !ok { t.Fatal("letting go of check builds took the walk's copy in the module's namespace") } if left, _ := registry.OfPulls(ctx, []PullRef{aPull}); len(left) != 0 { t.Fatalf("%s still has check builds: %+v", aPull, left) } } // The walk copies a check build by digest: the digest registered is the digest the check built, and the copy is // in the module's own namespace. func TestAChecksBuildIsCopiedByDigestIntoTheModulesNamespace(t *testing.T) { m := &aTaggingRegistry{aRegistryOfRepositories: newRegistryOfRepositories(), tags: map[string]map[string]string{}} server := httptest.NewServer(m.serve()) defer server.Close() address := strings.TrimPrefix(server.URL, "http://") registry := Registry{Address: address} layer := []byte("layer") m.blobs[digestOf(layer)] = layer m.link("scratch/meshboard/server", digestOf(layer)) manifest := []byte(`{"mediaType":"` + mediaManifestOCI + `","layers":[{"digest":"` + digestOf(layer) + `","size":5}]}`) m.manifests["scratch/meshboard/server@"+digestOf(manifest)] = manifest archive := []byte("archive") m.blobs[digestOf(archive)] = archive m.link("scratch/meshboard/look", digestOf(archive)) got, err := registry.Take(t.Context(), "meshboard", catalogue.Built{Name: "server", Kind: "image", Reference: address + "/scratch/meshboard/server@" + digestOf(manifest)}) if err != nil { t.Fatal(err) } if got.Reference != address+"/meshboard/server@"+digestOf(manifest) { t.Fatalf("copied as %s", got.Reference) } if m.uploads != 0 || m.mounts != 1 { t.Fatalf("the image's layer was moved (%d uploads, %d mounts), not mounted", m.uploads, m.mounts) } got, err = registry.Take(t.Context(), "meshboard", catalogue.Built{Name: "look", Kind: "archive", Digest: digestOf(archive), Reference: "http://" + address + "/v2/scratch/meshboard/look/blobs/" + digestOf(archive)}) if err != nil { t.Fatal(err) } if got.Digest != digestOf(archive) || got.Reference != "http://"+address+"/v2/meshboard/look/blobs/"+digestOf(archive) || !m.links["meshboard/look"][digestOf(archive)] { t.Fatalf("the archive was copied as %+v", got) } } // Two checks that built one digest in one repository share it: letting go of one check's builds keeps it while the // other still names it, and the last to go takes it. func TestABuildTwoChecksShareGoesWithTheLastOfThem(t *testing.T) { m := &aTaggingRegistry{aRegistryOfRepositories: newRegistryOfRepositories(), tags: map[string]map[string]string{}} server := httptest.NewServer(m.serve()) defer server.Close() address := strings.TrimPrefix(server.URL, "http://") registry := Registry{Address: address} ctx := t.Context() now := time.Date(2026, 10, 13, 20, 0, 0, 0, time.UTC) body := `{"mediaType":"` + mediaManifestOCI + `","layers":[]}` digest := digestOf([]byte(body)) m.manifests["scratch/meshboard/server@"+digest] = []byte(body) reference := address + "/scratch/meshboard/server@" + digest other := PullRef{Owner: "novox", Repo: "meshboard", Number: 8} for i, pull := range []PullRef{aPull, other} { id := "build-" + strconv.Itoa(i+1) b := CheckBuild{Key: "r1:" + strings.Repeat(strconv.Itoa(i+1), 64), Module: "meshboard", Pull: pull, Check: id, At: now, Made: []catalogue.Built{{Name: "server", Kind: "image", Reference: reference}}} if err := registry.Keep(ctx, pull, id, now, []CheckBuild{b}); err != nil { t.Fatal(err) } } held := func() bool { _, ok := m.manifests["scratch/meshboard/server@"+digest]; return ok } if n, err := registry.Forget(ctx, aPull); err != nil || n != 1 { t.Fatalf("forgot %d (%v)", n, err) } if !held() { t.Fatal("letting go of one check's builds took a build another check still names") } if n, err := registry.Forget(ctx, other); err != nil || n != 1 { t.Fatalf("forgot %d (%v)", n, err) } if held() { t.Fatal("the build stayed after the last check naming it went") } }