diff --git a/modules/mesh-delivery-checker/cmd/mesh-checking-program/main.go b/modules/mesh-delivery-checker/cmd/mesh-checking-program/main.go new file mode 100644 index 00000000..82e45fc6 --- /dev/null +++ b/modules/mesh-delivery-checker/cmd/mesh-checking-program/main.go @@ -0,0 +1,59 @@ +// Command mesh-checking-program is the checker's checking program (novox/hq ADR 0644, ADR 0656; design 47's bootstrap +// delivery): built from a check's heads and run in the build seat's sandbox through the seat node-build-go's ask run, +// with no network and no bus account, it reads the check's facts from the file MESH_RUN_INPUT names and answers the +// verdict the judging rules give them, as one JSON line on its standard output (rules.Answer). +// +// mesh-checking-program -verdict +// +// It refuses facts of a version it does not read, naming it, with exit 2. +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "os" + + "git.novox.be/novox/mesh-sdk/go/delivery" + "mesh-delivery-checker/rules" +) + +func main() { + os.Exit(run(os.Args[1:], os.Getenv, os.Stdout, os.Stderr)) +} + +// run is the command over its arguments, environment and outputs, answering its exit. +func run(args []string, getenv func(string) string, stdout, stderr io.Writer) int { + fs := flag.NewFlagSet("mesh-checking-program", flag.ContinueOnError) + fs.SetOutput(stderr) + verdict := fs.Bool("verdict", false, "answer the verdict of the facts in the input") + if err := fs.Parse(args); err != nil { + return 2 + } + refuse := func(format string, a ...any) int { + fmt.Fprintf(stderr, "mesh-checking-program: "+format+"\n", a...) + return 2 + } + if !*verdict { + return refuse("nothing asked: give -verdict") + } + file := getenv(delivery.RunInputEnv) + if file == "" { + return refuse("no input: %s names no file", delivery.RunInputEnv) + } + data, err := os.ReadFile(file) + if err != nil { + return refuse("the input cannot be read: %v", err) + } + facts, err := rules.DecodeFacts(data) + if err != nil { + return refuse("%v", err) + } + line, err := json.Marshal(rules.Answer{V: rules.AnswerVersion, Verdict: rules.Judge(facts)}) + if err != nil { + return refuse("the verdict cannot be written: %v", err) + } + fmt.Fprintf(stdout, "%s\n", line) + return 0 +} diff --git a/modules/mesh-delivery-checker/cmd/mesh-checking-program/main_test.go b/modules/mesh-delivery-checker/cmd/mesh-checking-program/main_test.go new file mode 100644 index 00000000..b45af7c6 --- /dev/null +++ b/modules/mesh-delivery-checker/cmd/mesh-checking-program/main_test.go @@ -0,0 +1,74 @@ +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. +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) + for name, c := range map[string]struct { + args []string + input string + exit int + said string + }{ + "the facts": {[]string{"-verdict"}, v1, 0, ""}, + "a later version": {[]string{"-verdict"}, v2, 2, "facts of version 2, this checking program reads version 1"}, + "no input": {[]string{"-verdict"}, "", 2, delivery.RunInputEnv + " names no file"}, + "nothing asked": {nil, v1, 2, "nothing asked"}, + "an unknown asking": {[]string{"-judge"}, v1, 2, "-judge"}, + } { + var out, errs bytes.Buffer + exit := run(c.args, func(k string) string { + if k == delivery.RunInputEnv { + return c.input + } + 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") + 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) + } + } +} diff --git a/modules/mesh-delivery-checker/rules/facts.go b/modules/mesh-delivery-checker/rules/facts.go index d903706e..b3f33e28 100644 --- a/modules/mesh-delivery-checker/rules/facts.go +++ b/modules/mesh-delivery-checker/rules/facts.go @@ -207,3 +207,12 @@ func Slowest(tally Tally) []string { func failAt(machine, words string) *delivery.Verdict { return &delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: machine, Words: words}} } + +// AnswerVersion is the version of Answer this program writes. +const AnswerVersion = 1 + +// Answer is what the checking program answers on the last line of its standard output: its version and the verdict. +type Answer struct { + V int `json:"v"` + Verdict delivery.Verdict `json:"verdict"` +}