Merge pull request 'The seat calls a gate that raised no machine the mesh composes an error, whatever judged it (hq issue 285)' (#106) from fix/seat-refuses-a-gate-that-raised-nothing into main
This commit was merged in pull request #106.
This commit is contained in:
@@ -298,6 +298,13 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
|
|||||||
say("check", "the facts of %s (%s): %d machine(s), the bus at %s, the store at %s", f.Taken.Format(time.RFC3339),
|
say("check", "the facts of %s (%s): %d machine(s), the bus at %s, the store at %s", f.Taken.Format(time.RFC3339),
|
||||||
short(strings.TrimPrefix(digest, "sha256:")), len(f.Machines), f.Versions.Bus, f.Versions.Store)
|
short(strings.TrimPrefix(digest, "sha256:")), len(f.Machines), f.Versions.Bus, f.Versions.Store)
|
||||||
|
|
||||||
|
// **What an earlier delivery of this same ask left is removed first** (novox/hq issue 285): an ask is
|
||||||
|
// redelivered when the holder that took it stopped mid-check — the build agent itself updated by the
|
||||||
|
// rollout it is checking — and its containers, named by the ask's id, are still there. Raising the
|
||||||
|
// store again under that name was refused, and the check said it could not run.
|
||||||
|
if n, err := RemoveContainersOf(ctx, run, spec.ID); err == nil && n > 0 {
|
||||||
|
say("check", "removed %d throwaway container(s) an earlier delivery of this check left", n)
|
||||||
|
}
|
||||||
// The throwaway store and bus, of the versions the mesh runs, removed whatever happens.
|
// The throwaway store and bus, of the versions the mesh runs, removed whatever happens.
|
||||||
defer func() {
|
defer func() {
|
||||||
removing, done := context.WithTimeout(context.Background(), time.Minute)
|
removing, done := context.WithTimeout(context.Background(), time.Minute)
|
||||||
@@ -526,14 +533,17 @@ func gateLayer(ctx context.Context, spec CheckSpec, tree, root, gate, verdictFil
|
|||||||
// The gate could not judge: not the change's fault, and never a pass.
|
// The gate could not judge: not the change's fault, and never a pass.
|
||||||
return "error", "the merge gate could not judge the change: " + lastLine(out.String())
|
return "error", "the merge gate could not judge the change: " + lastLine(out.String())
|
||||||
}
|
}
|
||||||
var said struct {
|
|
||||||
Verdict string `json:"verdict"`
|
|
||||||
Summary string `json:"summary"`
|
|
||||||
}
|
|
||||||
raw, err := os.ReadFile(verdictFile)
|
raw, err := os.ReadFile(verdictFile)
|
||||||
if err != nil || json.Unmarshal(raw, &said) != nil || said.Verdict == "" {
|
if err != nil {
|
||||||
return "error", "the merge gate said no verdict"
|
return "error", "the merge gate said no verdict"
|
||||||
}
|
}
|
||||||
|
said, ok := readGateVerdict(raw)
|
||||||
|
if !ok {
|
||||||
|
return "error", "the merge gate said no verdict"
|
||||||
|
}
|
||||||
|
if verdict, summary, raised := gateRaisedTheMesh(said); !raised {
|
||||||
|
return verdict, summary
|
||||||
|
}
|
||||||
|
|
||||||
// 3. **The replays of what the mesh runs** (to-be 45 §9, M9): mesh-lab's, from its main — reviewed code,
|
// 3. **The replays of what the mesh runs** (to-be 45 §9, M9): mesh-lab's, from its main — reviewed code,
|
||||||
// so given the container runtime the resolver replay raises containers with — against the bus of the
|
// so given the container runtime the resolver replay raises containers with — against the bus of the
|
||||||
@@ -683,6 +693,58 @@ func newProblems(change, base string) []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gateVerdict is what of the gate's verdict the seat reads: the verdict, and every machine — whether it
|
||||||
|
// composes on the mesh and whether it composed in the gate's store without the change.
|
||||||
|
type gateVerdict struct {
|
||||||
|
Verdict string `json:"verdict"`
|
||||||
|
Summary string `json:"summary"`
|
||||||
|
Machines []struct {
|
||||||
|
Described string `json:"described"`
|
||||||
|
Live bool `json:"live-composes"`
|
||||||
|
Base struct {
|
||||||
|
Composes bool `json:"composes"`
|
||||||
|
} `json:"base"`
|
||||||
|
} `json:"machines"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// readGateVerdict reads the verdict the gate wrote, from its first line that opens a JSON document: a
|
||||||
|
// judge from before the verdict was alone on its output printed what composition said ahead of it.
|
||||||
|
func readGateVerdict(raw []byte) (gateVerdict, bool) {
|
||||||
|
var v gateVerdict
|
||||||
|
text := string(raw)
|
||||||
|
if i := strings.Index(text, "\n{"); i >= 0 && !strings.HasPrefix(strings.TrimSpace(text), "{") {
|
||||||
|
text = text[i+1:]
|
||||||
|
}
|
||||||
|
if json.Unmarshal([]byte(text), &v) != nil || v.Verdict == "" {
|
||||||
|
return gateVerdict{}, false
|
||||||
|
}
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// gateRaisedTheMesh is the seat's own reading of a verdict that passes (novox/hq issue 285): **a machine
|
||||||
|
// the mesh composes that did not compose in the gate's store without the change makes the gate an error,
|
||||||
|
// never a pass** — the change was judged against a machine that is not the mesh's, broken against broken.
|
||||||
|
// The judge says so itself since issue 285, but the judge is the controller the mesh runs, and one from
|
||||||
|
// before it passed such a verdict; read here too, the rule holds whatever judged. It answers false, with
|
||||||
|
// the verdict to report, when the gate did not raise the mesh.
|
||||||
|
func gateRaisedTheMesh(v gateVerdict) (string, string, bool) {
|
||||||
|
if v.Verdict != "pass" && v.Verdict != "warning" {
|
||||||
|
return "", "", true
|
||||||
|
}
|
||||||
|
var unraised []string
|
||||||
|
for _, m := range v.Machines {
|
||||||
|
if m.Live && !m.Base.Composes {
|
||||||
|
unraised = append(unraised, m.Described)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(unraised) == 0 {
|
||||||
|
return "", "", true
|
||||||
|
}
|
||||||
|
return "error", fmt.Sprintf("the mesh as it is could not be raised, so the change cannot be judged against it: "+
|
||||||
|
"%d of %d machines compose on the mesh and not in the gate (the first: %s) — novox/hq issue 285",
|
||||||
|
len(unraised), len(v.Machines), unraised[0]), false
|
||||||
|
}
|
||||||
|
|
||||||
// gitShow is a file as a ref has it.
|
// gitShow is a file as a ref has it.
|
||||||
func gitShow(ctx context.Context, dir, ref, file string) ([]byte, error) {
|
func gitShow(ctx context.Context, dir, ref, file string) ([]byte, error) {
|
||||||
cmd := exec.CommandContext(ctx, "git", "show", ref+":"+filepath.ToSlash(file))
|
cmd := exec.CommandContext(ctx, "git", "show", ref+":"+filepath.ToSlash(file))
|
||||||
|
|||||||
@@ -429,3 +429,54 @@ func TestAFailedScriptIsSaidByWhatFailed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **Issue 285**: a judge from before the rule passed "every machine composes with the change as it did
|
||||||
|
// without (0 of 4 compose)". The seat reads the machines itself: a machine the mesh composes that did not
|
||||||
|
// compose in the gate's store makes the gate an error, whatever judged it.
|
||||||
|
func TestTheSeatCallsAGateThatRaisedNoMachineAnError(t *testing.T) {
|
||||||
|
old := "bus users without a credential: controller\n" + `{"verdict":"pass","summary":"every machine composes with the change as it did without (0 of 2 compose)",
|
||||||
|
"machines":[{"described":"the hub","live-composes":true,"base":{"composes":false}},
|
||||||
|
{"described":"a machine","live-composes":true,"base":{"composes":false}}]}`
|
||||||
|
v, ok := readGateVerdict([]byte(old))
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("a verdict after a line of composition's was not read")
|
||||||
|
}
|
||||||
|
verdict, summary, raised := gateRaisedTheMesh(v)
|
||||||
|
if raised || verdict != "error" || !strings.Contains(summary, "2 of 2 machines") || !strings.Contains(summary, "the hub") {
|
||||||
|
t.Errorf("0 of 2 composing is %q %q", verdict, summary)
|
||||||
|
}
|
||||||
|
good := `{"verdict":"pass","summary":"(2 of 2 compose)","machines":[{"described":"the hub","live-composes":true,"base":{"composes":true}},
|
||||||
|
{"described":"a laptop","live-composes":false,"base":{"composes":false}}]}`
|
||||||
|
v, _ = readGateVerdict([]byte(good))
|
||||||
|
if _, _, raised := gateRaisedTheMesh(v); !raised {
|
||||||
|
t.Error("a machine that does not compose on the mesh either was read as the gate's failure")
|
||||||
|
}
|
||||||
|
failed := `{"verdict":"fail","summary":"x","machines":[{"described":"the hub","live-composes":true,"base":{"composes":false}}]}`
|
||||||
|
v, _ = readGateVerdict([]byte(failed))
|
||||||
|
if _, _, raised := gateRaisedTheMesh(v); !raised {
|
||||||
|
t.Error("a failing verdict is the change's, and stands")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Issue 285**: an ask redelivered after its holder stopped mid-check found its own throwaway store still
|
||||||
|
// there under its name, and the check said it could not run. What an earlier delivery left goes first.
|
||||||
|
func TestARedeliveredCheckRemovesWhatItsEarlierDeliveryLeft(t *testing.T) {
|
||||||
|
registry := checkEnvironment(t)
|
||||||
|
repo, head := aCheckedRepository(t, map[string]string{CheckScript: "echo checked\n"})
|
||||||
|
id := fmt.Sprintf("check-again-%d", time.Now().UnixNano())
|
||||||
|
if out, err := exec.Command("docker", "run", "-d", "--rm", "--label", BuildLabel+"="+id, "--name", id+"-store",
|
||||||
|
"postgres:17-alpine", "sleep", "300").CombinedOutput(); err != nil {
|
||||||
|
t.Skipf("no container for the earlier delivery: %v %s", err, out)
|
||||||
|
}
|
||||||
|
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: head, Owner: "novox",
|
||||||
|
Repo: "hq", Number: 7, Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("a redelivered check could not run: %v", err)
|
||||||
|
}
|
||||||
|
if v.Repo == nil || v.Repo.Verdict != "pass" {
|
||||||
|
t.Errorf("the repository's check answered %+v", v.Repo)
|
||||||
|
}
|
||||||
|
if left := labelled(id); len(left) > 0 {
|
||||||
|
t.Errorf("the check left %d container(s) behind", len(left))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user