mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-delivery-indexer/cmd/mesh-delivery-indexer.TestIndexerCountsOnlyTheAssignedBuildNodesToolchains: within 5 s of n2…
Candidate.Base was never written non-zero (review round 2): the builder's base lookup would have found no trunk for any live candidate and built no preview at all. The indexer now writes, whenever it writes a candidate's fingerprints, the gate's source revision whose trunk heads it read the previews onto; that head of the pull request's repository is the preview's trunk. No mesh-sdk change: the field and its meaning were there. The builder's test now takes the candidate the indexer's own test writes (testdata/candidate-written.json, kept equal to it), not a hand-made one.
182 lines
7.2 KiB
Go
182 lines
7.2 KiB
Go
package build
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// trunkModule writes the trunk's record of the module m at a fingerprint of its own, so that the candidate's fingerprint (fp1)
|
|
// is one the trunk does not carry.
|
|
func trunkModule(t *testing.T, r *rig) {
|
|
t.Helper()
|
|
data, err := delivery.EncodeModule(delivery.Module{Repository: "novox/mesh-catalog", Commit: "7a74cee83f88e6585a5511c3cc53bdf0f5303d20", FP: "src1:" + hex64('f'),
|
|
FP2: "src2:" + hex64('e'), Manifest: mdigest('a'), Path: "modules/m", Toolchains: agreedToolchains})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r.b.Module(context.Background(), "m", data)
|
|
r.b.Wait()
|
|
r.bm.mu.Lock()
|
|
r.bm.calls = nil
|
|
r.bm.mu.Unlock()
|
|
}
|
|
|
|
func candidateValue(t *testing.T, group string, number int, head string) []byte {
|
|
t.Helper()
|
|
pulls := []delivery.CandidatePull{{Head: delivery.Head{Repository: "novox/mesh-catalog", Number: number, Head: head}, Modules: []string{"m"},
|
|
Fps: map[string]delivery.CandidateModule{"m": {FP2: fp1, Manifest: mdigest('a')}}}}
|
|
hash, err := delivery.HeadsHash([]delivery.Head{pulls[0].Head})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, err := delivery.EncodeCandidate(delivery.Candidate{V: delivery.Version, Group: group, HeadsHash: hash, Pulls: pulls, FPSet: delivery.FingerprintSet(pulls)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return data
|
|
}
|
|
|
|
const (
|
|
// trunkHead is the trunk head the rig's source revision names: newer than the module value's commit, which stays at the
|
|
// commit its fingerprint first appeared at.
|
|
trunkHead = "3333333333333333333333333333333333333333"
|
|
headX = "1111111111111111111111111111111111111111"
|
|
headY = "2222222222222222222222222222222222222222"
|
|
)
|
|
|
|
// indexerCandidate is a candidate as the indexer writes it, with the gate's source revision it names (mesh-delivery-indexer
|
|
// internal/index testdata/candidate-written.json, kept equal to what the indexer writes by its own test).
|
|
func indexerCandidate(t *testing.T) (rev uint64, heads map[string]string, c delivery.Candidate, raw []byte) {
|
|
t.Helper()
|
|
data, err := os.ReadFile("../../../mesh-delivery-indexer/internal/index/testdata/candidate-written.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var w struct {
|
|
Source struct {
|
|
Rev uint64 `json:"rev"`
|
|
Heads map[string]string `json:"heads"`
|
|
} `json:"source"`
|
|
Candidate json.RawMessage `json:"candidate"`
|
|
}
|
|
if err := json.Unmarshal(data, &w); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c, err = delivery.DecodeCandidate(w.Candidate); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return w.Source.Rev, w.Source.Heads, c, w.Candidate
|
|
}
|
|
|
|
func TestACandidatesFingerprintIsBuiltOnceAsAKeyOfItsOwnFromTheHeadMergedOntoTheTrunk(t *testing.T) { // N7/AC1, N7/AC2 (issue 1684)
|
|
// The candidate the indexer writes is merged onto the trunk head of the source revision it names, never onto the module
|
|
// value's commit, which stays at the commit its fingerprint first appeared at (review of the indexer writing on change).
|
|
{
|
|
rev, heads, c, raw := indexerCandidate(t)
|
|
p := c.Pulls[0]
|
|
cm := p.Fps["avahi"]
|
|
ir := newRig(t, goBundle)
|
|
ir.mf.byDigest[cm.Manifest] = catalogue.Manifest{Module: "avahi", Build: &catalogue.Build{Artifacts: []catalogue.Artifact{goBundle}}}
|
|
ir.b.Trunk = func(_ context.Context, at uint64, repository string) (string, error) {
|
|
if at != rev {
|
|
return "", fmt.Errorf("source revision %d is not heard", at)
|
|
}
|
|
return heads[repository], nil
|
|
}
|
|
const valueCommit = "7a74cee83f88e6585a5511c3cc53bdf0f5303d20"
|
|
value, err := delivery.EncodeModule(delivery.Module{Repository: p.Repository, Commit: valueCommit, FP2: "src2:" + hex64('e'),
|
|
Manifest: cm.Manifest, Path: "modules/avahi", Toolchains: agreedToolchains})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ir.b.Module(context.Background(), "avahi", value)
|
|
ir.b.Wait()
|
|
ir.bm.mu.Lock()
|
|
ir.bm.calls = nil
|
|
ir.bm.mu.Unlock()
|
|
ir.b.Candidate(context.Background(), "feat%2Fx", raw)
|
|
ir.b.Wait()
|
|
fetches := ir.bm.callsOf("build_go_fetch")
|
|
if len(fetches) != 1 {
|
|
t.Fatalf("%d fetches of the indexer's candidate, want one", len(fetches))
|
|
}
|
|
f := fetches[0].body.(map[string]any)
|
|
if want := heads[p.Repository]; want == "" || f["base"] != want || f["base"] == valueCommit || f["commit"] != p.Head.Head {
|
|
t.Errorf("fetch args = %+v, want the head merged onto the trunk head %.8s of source revision %d", f, want, rev)
|
|
}
|
|
}
|
|
r := newRig(t, goBundle)
|
|
trunkModule(t, r)
|
|
r.b.Candidate(context.Background(), "feat%2Fx", candidateValue(t, "feat/x", 7, headX))
|
|
r.b.Wait()
|
|
v := valueOf(t, r.st, r.key)
|
|
if v.State != delivery.BuildDone || len(v.Artifacts) != 1 {
|
|
t.Fatalf("build = %+v, want the candidate's fingerprint built", v)
|
|
}
|
|
fetches := r.bm.callsOf("build_go_fetch")
|
|
if len(fetches) != 1 { // one node per fingerprint (ADR 0478 clause 4)
|
|
t.Fatalf("%d fetches, want one", len(fetches))
|
|
}
|
|
f := fetches[0].body.(map[string]any)
|
|
if f["commit"] != headX || f["ref"] != "refs/pull/7/head" || f["base"] != trunkHead || f["repository"] != "novox/mesh-catalog" {
|
|
t.Errorf("fetch args = %+v, want the head, its ref and the trunk head the candidate was previewed onto", f)
|
|
}
|
|
if b := r.bm.callsOf("build_go_build")[0].body.(map[string]any); b["module"] != "modules/m" {
|
|
t.Errorf("build args = %+v", b)
|
|
}
|
|
// Two heads carrying one fingerprint are one build.
|
|
r.b.Candidate(context.Background(), "feat%2Fy", candidateValue(t, "feat/y", 8, headY))
|
|
r.b.Wait()
|
|
if n := len(r.bm.callsOf("build_go_fetch")); n != 1 {
|
|
t.Errorf("%d fetches after a second head with the same fingerprint, want no more", n)
|
|
}
|
|
}
|
|
|
|
func TestACandidateWhoseModuleHasNoTrunkRecordIsNotBuilt(t *testing.T) {
|
|
r := newRig(t, goBundle)
|
|
r.b.Candidate(context.Background(), "feat%2Fx", candidateValue(t, "feat/x", 7, headX))
|
|
r.b.Wait()
|
|
if _, ok := r.st.vals[r.key]; ok {
|
|
t.Error("a build key was written for a module the builder knows no directory of")
|
|
}
|
|
}
|
|
|
|
func TestAFingerprintTheTrunkCarriesIsBuiltFromTheTrunksCommit(t *testing.T) { // the trunk's source wins
|
|
r := newRig(t, goBundle)
|
|
r.b.Candidate(context.Background(), "feat%2Fx", candidateValue(t, "feat/x", 7, headX))
|
|
r.b.Module(context.Background(), "m", moduleValue(t, "modules/m")) // the trunk now carries fp1
|
|
r.b.Wait()
|
|
for _, c := range r.bm.callsOf("build_go_fetch") {
|
|
if f := c.body.(map[string]any); f["ref"] != nil || f["base"] != nil {
|
|
t.Errorf("fetch args = %+v: a trunk fingerprint is built from the trunk's commit alone", f)
|
|
}
|
|
}
|
|
if v := valueOf(t, r.st, r.key); v.State != delivery.BuildDone {
|
|
t.Errorf("build = %+v", v)
|
|
}
|
|
}
|
|
|
|
func TestACandidateWhoseHeadDoesNotMergeIsFailedAndNoTrunkBuildIsStoredUnderIt(t *testing.T) { // review D4
|
|
for name, set := range map[string]func(*rig){
|
|
"conflict": func(r *rig) { r.bm.conflicts = "go.mod" },
|
|
"no merge": func(r *rig) { r.bm.noMerge = true },
|
|
} {
|
|
r := newRig(t, goBundle)
|
|
trunkModule(t, r)
|
|
set(r)
|
|
r.b.Candidate(context.Background(), "feat%2Fx", candidateValue(t, "feat/x", 7, headX))
|
|
r.b.Wait()
|
|
v := valueOf(t, r.st, r.key)
|
|
if v.State != delivery.BuildFailed || (name == "conflict" && !strings.Contains(v.Error, "go.mod")) || len(r.bm.callsOf("build_go_build")) != 0 {
|
|
t.Errorf("%s: build = %+v, builds %d, want failed and nothing built", name, v, len(r.bm.callsOf("build_go_build")))
|
|
}
|
|
}
|
|
}
|