Files
mesh-delivery/modules/mesh-delivery-checker/internal/check/judged_test.go
T

208 lines
8.0 KiB
Go

package check
import (
"context"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The judged_by answer of mesh-build-go's digest and test steps (ADR 0535 §5; the milestone file's interface for issue
// 1898): "installed" for a step its installed program ran, "candidate <merge commit>" for one the candidate's program ran
// under runner workspace; an installed module older than the runner names nothing (it ignores the unknown argument: the
// SDK's tool server checks no argument against the input schema).
const buildGoFile = "modules/mesh-build-go/cmd/mesh-build-go/digest.go"
// candidateBench is a bench whose fetch changes the Go build module's directory, and whose build module runs the candidate
// when asked to (a module with the runner) or ignores the runner (one without).
func candidateBench(t *testing.T, takesRunner bool) *bench {
b := newBench(t)
b.fetch.Changed = []string{"README.md", buildGoFile}
judged := func(c stepCall) string {
if !takesRunner {
return ""
}
if c.args["runner"] == RunnerWorkspace {
return "candidate " + b.fetch.Merge
}
return "installed"
}
b.steps.handlers["build_go_digest"] = func(c stepCall) (any, error) {
d := echoBeside(b, b.digest[c.args["level"].(string)])
d.JudgedBy = judged(c)
return d, nil
}
b.test = func(c stepCall) (any, error) {
r := captured[Tested](t, "go-test-slow")
r.Level, _ = c.args["level"].(string)
r.JudgedBy = judged(c)
return r, nil
}
return b
}
func TestAMergeChangingTheGoBuildModuleIsAskedWithTheCandidateRunner(t *testing.T) { // 1898/AC1
b := candidateBench(t, true)
b.judge(t, caseOf(t, pullOf(catalogueRepository, 9, head1)))
asked := append(b.steps.tool("digest"), b.steps.tool("test")...)
if len(asked) < 3 {
t.Fatalf("asked %+v, want the digest and both levels", asked)
}
for _, c := range asked {
if c.args["runner"] != RunnerWorkspace {
t.Errorf("%s %v was asked with runner %v, want %s", c.tool, c.args["level"], c.args["runner"], RunnerWorkspace)
}
}
}
func TestAMergeNotChangingTheGoBuildModuleIsAskedWithoutARunner(t *testing.T) { // 1898/AC1
for _, tc := range []struct {
name, repo, path string
}{
{"another module of the catalogue", catalogueRepository, "modules/mesh-build-node/cmd/mesh-build-node/main.go"},
{"a look-alike path in another repository", repoA, buildGoFile},
} {
t.Run(tc.name, func(t *testing.T) {
b := candidateBench(t, true)
b.fetch.Changed = []string{tc.path}
out := b.judge(t, caseOf(t, pullOf(tc.repo, 9, head1)))
for _, c := range append(b.steps.tool("digest"), b.steps.tool("test")...) {
if _, ok := c.args["runner"]; ok {
t.Errorf("%s was asked with runner %v", c.tool, c.args["runner"])
}
}
if len(out.Judged) != 0 {
t.Errorf("judged = %v, want nothing said of the program", out.Judged)
}
})
}
}
func TestAnInstalledModuleWithoutTheRunnerJudgesAsTodayAndTheLineSaysSo(t *testing.T) { // 1898/AC2
b := candidateBench(t, false)
cs := caseOf(t, pullOf(catalogueRepository, 9, head1))
out := b.judge(t, cs)
if out.Verdict.State != delivery.VerdictPass {
t.Fatalf("verdict = %+v", out.Verdict)
}
// As today: the digests key kept verdicts, and the levels are asked without the runner the module ignored.
for _, c := range b.steps.tool("test") {
if _, ok := c.args["runner"]; ok {
t.Errorf("the %v level was asked again with the runner the module does not take", c.args["level"])
}
}
if keys, _ := b.kept.Keys(context.Background()); len(keys) != 2 {
t.Errorf("kept %v, want the passing package at both levels, as today", keys)
}
p := cs.Pulls[0]
want := "judged by the installed mesh-build-go: it does not run a candidate yet"
if got := out.Judged[pullKey(p)]; got != want {
t.Fatalf("judged = %q, want %q", got, want)
}
if l := Line(out.Verdict, cs, p, out.Builds, out.Slowest, out.Judged); !strings.Contains(l, want) {
t.Errorf("line %q does not say %q", l, want)
}
}
func TestACandidateJudgedCheckTakesNoKeptVerdictAndWritesNone(t *testing.T) { // 1898/AC3
b := candidateBench(t, true)
if _, err := (Kept{Store: b.kept}).Keep(context.Background(), "unit", digA, keptValue("fixture/slow", 2)); err != nil {
t.Fatal(err)
}
out := b.judge(t, caseOf(t, pullOf(catalogueRepository, 9, head1)))
if out.Verdict.State != delivery.VerdictPass {
t.Fatalf("verdict = %+v", out.Verdict)
}
tests := b.steps.tool("test")
if len(tests) != 2 {
t.Fatalf("test calls = %+v, want both levels", tests)
}
for _, c := range tests {
if _, ok := c.args["packages"]; ok {
t.Errorf("the %v level was asked for %v, want it whole", c.args["level"], c.args["packages"])
}
}
if u := out.Verdict.Levels[delivery.LevelUnit]; u.Kept != 0 {
t.Errorf("unit = %+v, want nothing taken", u)
}
if keys, _ := b.kept.Keys(context.Background()); len(keys) != 1 || keys[0] != "unit."+digA {
t.Errorf("kept keys = %v, want only the one kept before", keys)
}
}
func TestTheLineNamesTheCandidateThatJudged(t *testing.T) { // 1898/AC6
b := candidateBench(t, true)
cs := caseOf(t, pullOf(catalogueRepository, 9, head1))
out := b.judge(t, cs)
p := cs.Pulls[0]
want := "judged by the candidate's mesh-build-go at " + b.fetch.Merge[:12]
for name, l := range map[string]string{
"line": Line(out.Verdict, cs, p, out.Builds, out.Slowest, out.Judged),
"report": Report(out.Verdict, cs, p, out.Builds, out.Slowest, out.Judged),
} {
if !strings.Contains(l, want) {
t.Errorf("the %s %q does not say %q", name, l, want)
}
}
// A long failing line keeps the program it was judged by; only the lesser parts go.
v := out.Verdict
v.State = delivery.VerdictFail
v.Failed = delivery.FailedFirst{Test: "unit: git.novox.be/novox/mesh-catalog/modules/mesh-build-go/cmd/mesh-build-go.TestADigestFoldsTheSiblings", Words: strings.Repeat("x", 60)}
if l := Line(v, cs, p, out.Builds, out.Slowest, out.Judged); !strings.Contains(l, "judged by the candidate's") {
t.Errorf("the long line %q dropped the program", l)
}
}
func TestInAGroupOnlyTheBuildModulesPullRequestNamesItsProgram(t *testing.T) { // 1898/AC6
b := candidateBench(t, true)
cs := caseOf(t, pullOf(catalogueRepository, 9, head1), pullOf(repoA, 7, head1))
out := b.judge(t, cs)
for _, p := range cs.Pulls {
l := Report(out.Verdict, cs, p, out.Builds, out.Slowest, out.Judged)
says := strings.Contains(l, "judged by")
if says != (p.Repository == catalogueRepository) {
t.Errorf("%s#%d: %q", p.Repository, p.Number, l)
}
}
}
func TestACandidateThatDoesNotCompileFailsTheCheckWithTheCompilersWords(t *testing.T) { // ADR 0535 §3, checker side
b := candidateBench(t, true)
refusal := "the candidate's mesh-build-go does not compile: modules/mesh-build-go/cmd/mesh-build-go/digest.go:12:2: undefined: x"
b.steps.handlers["build_go_digest"] = func(stepCall) (any, error) { return nil, errorString(refusal) }
b.test = func(stepCall) (any, error) { return nil, errorString(refusal) }
out := b.judge(t, caseOf(t, pullOf(catalogueRepository, 9, head1)))
if out.Verdict.State != delivery.VerdictFail || !strings.Contains(out.Verdict.Failed.Words, "undefined: x") {
t.Fatalf("verdict = %+v, want a failure naming the compile error", out.Verdict)
}
if n := len(b.steps.tool("test")); n != 1 {
t.Errorf("%d test steps, want the first one's failure to end the check", n)
}
}
type errorString string
func (e errorString) Error() string { return string(e) }
func TestTheStatusTheForgeShowsNamesTheProgramThatJudged(t *testing.T) { // 1898/AC6, the forge's line
ctx := context.Background()
words := judgedByCandidate + "fedcba987654"
j := &judgeFunc{f: func(context.Context, *Case, int) (Outcome, error) {
return Outcome{Verdict: passing(), Judged: map[string]string{repoA + "#7": words}}, nil
}}
r := newRig(t, j)
r.ch.Candidate(ctx, "feat/x", candidateOf(t, "feat/x", pullOf(repoA, 7, head1)))
r.ch.Wait()
key, _ := delivery.CheckerOwedKey(repoA, 7, head1)
raw, _, _, _ := r.owed.Get(ctx, key)
o, err := delivery.DecodeCheckerOwed(raw)
if err != nil {
t.Fatal(err)
}
if o.State != "success" || !strings.Contains(o.Text, words) {
t.Errorf("status %+v, want success saying %q", o, words)
}
}