Asked -seats, it reads every seat definition the heads carry, resolves the parts it includes and answers the list with what it left out and why, so the running checker hands the manifest validator and the composer a list it does not read itself (ADR 0636 §4, ADR 0644 §1). Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
117 lines
5.2 KiB
Go
117 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"mesh-delivery-checker/rules"
|
|
)
|
|
|
|
// 1921 (B0, D2): the checking program answers, on its last line, the verdict the rules give the facts in its input, and
|
|
// refuses facts of a version it does not read, an input it is not given, and a question it is not asked, naming each.
|
|
// 1921 (B0, D3): asked -seats, it answers the seat list of the heads laid for it: a definition a head adds, resolved with
|
|
// the parts it includes; what it left out, and why; and an empty list for heads with no definition.
|
|
func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) {
|
|
failing, _ := json.Marshal(rules.Tested{Level: delivery.LevelUnit, Tests: 3, Failed: []rules.Failure{{Package: "x/a", Test: "TestA", Cause: "a_test.go:9: boom"}}})
|
|
facts := rules.Facts{V: rules.FactsVersion, Group: "feat/x", Heads: "0123456789ab", Members: []rules.Member{{Repository: "novox/x", Number: 3, Head: "h"}},
|
|
Steps: []rules.Step{
|
|
{Repository: "novox/x", Number: 3, Module: "mesh-build-go", Node: "laptop", Level: &delivery.LevelAnswer{Level: delivery.LevelUnit, Tests: []json.RawMessage{failing}}},
|
|
{Repository: "novox/x", Number: 3, Failed: &delivery.FailedFirst{Machine: "laptop", Words: "novox/x#3 has files in conflict: go.mod"}},
|
|
}}
|
|
dir := t.TempDir()
|
|
write := func(name string, v any) string {
|
|
data, _ := json.Marshal(v)
|
|
p := filepath.Join(dir, name)
|
|
if err := os.WriteFile(p, data, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return p
|
|
}
|
|
v1 := write("v1.json", facts)
|
|
later := facts
|
|
later.V = rules.FactsVersion + 1
|
|
v2 := write("v2.json", later)
|
|
heads := filepath.Join(dir, "heads")
|
|
for p, body := range map[string]string{
|
|
"novox/mesh-contracts/contracts/seats/node-build-x/seat.json": `{"format":1,"seat":"node-build-x","scope":"node","description":"kept","includes":["build"],"verbs":[{"name":"own"}]}`,
|
|
"novox/mesh-contracts/contracts/parts/build.json": `{"part":"build","verbs":[{"name":"current"}],"events":[{"name":"ended"}]}`,
|
|
"novox/mesh-contracts/contracts/seats/broken/seat.json": `{"format":1,"seat":"broken","scope":"nowhere"}`,
|
|
} {
|
|
if err := os.MkdirAll(filepath.Dir(filepath.Join(heads, p)), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(heads, p), []byte(body), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
none := filepath.Join(dir, "no-heads")
|
|
if err := os.MkdirAll(filepath.Join(none, "novox/x"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for name, c := range map[string]struct {
|
|
args []string
|
|
input, heads string
|
|
exit int
|
|
said string
|
|
}{
|
|
"the seats": {args: []string{"-seats"}, heads: heads},
|
|
"no seats": {args: []string{"-seats"}, heads: none},
|
|
"no heads laid": {args: []string{"-seats"}, exit: 2, said: delivery.RunHeadsEnv + " names no directory"},
|
|
"the facts": {args: []string{"-verdict"}, input: v1},
|
|
"a later version": {args: []string{"-verdict"}, input: v2, exit: 2, said: "facts of version 2, this checking program reads version 1"},
|
|
"no input": {args: []string{"-verdict"}, exit: 2, said: delivery.RunInputEnv + " names no file"},
|
|
"nothing asked": {input: v1, exit: 2, said: "nothing asked"},
|
|
"an unknown asking": {args: []string{"-judge"}, input: v1, exit: 2, said: "-judge"},
|
|
} {
|
|
var out, errs bytes.Buffer
|
|
exit := run(c.args, func(k string) string {
|
|
switch k {
|
|
case delivery.RunInputEnv:
|
|
return c.input
|
|
case delivery.RunHeadsEnv:
|
|
return c.heads
|
|
}
|
|
return ""
|
|
}, &out, &errs)
|
|
if exit != c.exit || !strings.Contains(errs.String(), c.said) {
|
|
t.Errorf("%s: exit %d, said %q; want %d and %q", name, exit, errs.String(), c.exit, c.said)
|
|
continue
|
|
}
|
|
if c.exit != 0 {
|
|
continue
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(out.String()), "\n")
|
|
if c.heads != "" {
|
|
var sa rules.SeatsAnswer
|
|
if err := json.Unmarshal([]byte(lines[len(lines)-1]), &sa); err != nil || sa.V != rules.SeatsVersion || sa.Seats == nil {
|
|
t.Fatalf("%s: the last line %q: %v", name, lines[len(lines)-1], err)
|
|
}
|
|
if c.heads == none {
|
|
if len(sa.Seats) != 0 || len(sa.Left) != 0 {
|
|
t.Errorf("%s: %+v, want an empty list", name, sa)
|
|
}
|
|
continue
|
|
}
|
|
want := `{"description":"kept","events":[{"name":"ended"}],"format":1,"scope":"node","seat":"node-build-x","verbs":[{"name":"current"},{"name":"own"}]}`
|
|
if len(sa.Seats) != 1 || string(sa.Seats[0]) != want || len(sa.Left) != 1 || !strings.Contains(sa.Left[0].Why, `not "broken" and "nowhere"`) {
|
|
t.Errorf("%s: seats %s, left %+v; want %s and broken left out", name, sa.Seats, sa.Left, want)
|
|
}
|
|
continue
|
|
}
|
|
var a rules.Answer
|
|
if err := json.Unmarshal([]byte(lines[len(lines)-1]), &a); err != nil || a.V != rules.AnswerVersion {
|
|
t.Fatalf("%s: the last line %q: %v", name, lines[len(lines)-1], err)
|
|
}
|
|
if want := rules.Judge(facts); !reflect.DeepEqual(a.Verdict, want) || a.Verdict.State != delivery.VerdictFail || len(a.Verdict.Failures) != 2 ||
|
|
a.Verdict.Failures[1].Words != "novox/x#3 has files in conflict: go.mod" || a.Verdict.Machines[0] != "laptop" || a.Verdict.Levels[delivery.LevelUnit].Run != 3 {
|
|
t.Errorf("%s: the verdict %+v, want %+v", name, a.Verdict, want)
|
|
}
|
|
}
|
|
}
|