Fail a candidate's build when its fetch answers files in conflict or no merge, so the trunk's build is not stored under the candidate's fingerprint (review D4)
This commit is contained in:
@@ -109,7 +109,11 @@ func (f *fakeBuildModules) serve(t *testing.T, nc *nats.Conn, module string) {
|
||||
step.Ended = time.Now().UTC()
|
||||
switch {
|
||||
case strings.HasSuffix(tool, "_fetch"):
|
||||
step.State, step.Result = delivery.StepPassed, json.RawMessage(`{"workspace":"w`+id+`","commit":"`+commit+`"}`)
|
||||
merge := ""
|
||||
if args["base"] != nil { // a fetch with a base answers the merge commit (mesh-build-go FetchResult)
|
||||
merge = `,"merge":"` + strings.Repeat("9", 40) + `"`
|
||||
}
|
||||
step.State, step.Result = delivery.StepPassed, json.RawMessage(`{"workspace":"w`+id+`","commit":"`+commit+`"`+merge+`}`)
|
||||
case f.failBuild.Load() && node == "n1":
|
||||
step.State, step.Reason = delivery.StepFailed, fmt.Sprintf("go build of %v: boom", args["artifact"])
|
||||
default:
|
||||
|
||||
@@ -2,6 +2,7 @@ package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
@@ -94,3 +95,20 @@ func TestAFingerprintTheTrunkCarriesIsBuiltFromTheTrunksCommit(t *testing.T) { /
|
||||
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")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,12 +524,26 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m del
|
||||
return
|
||||
}
|
||||
var fetched struct {
|
||||
Workspace string `json:"workspace"`
|
||||
Workspace string `json:"workspace"`
|
||||
Merge string `json:"merge"`
|
||||
Conflicts []string `json:"conflicts"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &fetched); err != nil || fetched.Workspace == "" {
|
||||
l.err = fmt.Errorf("the fetch on %s answered no workspace: %s", l.node, raw)
|
||||
return
|
||||
}
|
||||
// A candidate is built from its head merged onto the trunk. A head that does not merge ends the fetch passed with the tree at
|
||||
// the trunk, and building that would store the trunk's build under the candidate's fingerprint.
|
||||
if _, candidate := fetchArgs["base"]; candidate {
|
||||
if len(fetched.Conflicts) > 0 {
|
||||
l.err = fmt.Errorf("the head does not merge into the trunk on %s: files in conflict: %s", l.node, strings.Join(fetched.Conflicts, ", "))
|
||||
return
|
||||
}
|
||||
if fetched.Merge == "" {
|
||||
l.err = fmt.Errorf("the fetch with a base on %s answered no merge: %s", l.node, raw)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, t := range l.tasks {
|
||||
raw, err := b.step(ctx, l.module, l.node, "build", map[string]any{"workspace": fetched.Workspace, "module": m.Path, "artifact": t.Artifact})
|
||||
if err != nil {
|
||||
|
||||
@@ -24,8 +24,11 @@ type buildModules struct {
|
||||
digests map[string]string // `<node>/<artifact>` -> digest; the default is `digest`
|
||||
fails map[string]string // `<node>/<tool>` -> the reason the step fails with
|
||||
// hold, when set before the builds start, blocks every call until it is closed; entered is sent to as a call arrives.
|
||||
hold chan struct{}
|
||||
entered chan struct{}
|
||||
// conflicts, when set, is a file a fetch with a base reports in conflict; noMerge makes it answer neither a merge nor a conflict.
|
||||
conflicts string
|
||||
noMerge bool
|
||||
hold chan struct{}
|
||||
entered chan struct{}
|
||||
}
|
||||
|
||||
func newBuildModules() *buildModules {
|
||||
@@ -52,6 +55,13 @@ func (b *buildModules) CallOn(node, key string, body any) (json.RawMessage, erro
|
||||
step.State, step.Reason = delivery.StepFailed, reason
|
||||
} else if strings.HasSuffix(tool, "_fetch") {
|
||||
step.Result = json.RawMessage(`{"workspace":"w-` + node + `"}`)
|
||||
if args["base"] != nil { // a fetch with a base answers the merge, or the files in conflict (mesh-build-go FetchResult)
|
||||
if b.conflicts != "" {
|
||||
step.Result = json.RawMessage(`{"workspace":"w-` + node + `","base":"` + fmt.Sprint(args["base"]) + `","conflicts":["` + b.conflicts + `"]}`)
|
||||
} else if !b.noMerge {
|
||||
step.Result = json.RawMessage(`{"workspace":"w-` + node + `","base":"` + fmt.Sprint(args["base"]) + `","merge":"` + strings.Repeat("9", 40) + `"}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
d := digest
|
||||
if v, ok := b.digests[node+"/"+fmt.Sprint(args["artifact"])]; ok {
|
||||
|
||||
Reference in New Issue
Block a user