Files
mesh-controller/internal/builder/check_base_test.go
T

213 lines
8.9 KiB
Go

//go:build integration
package builder
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
// novox/hq issue 310: the build seat ran the merge-check.sh found in the pull request's head, so a change
// could delete or gut it and skip its own tests, and a branch cut before the script escaped them. The base
// branch's script — its parts and its toolchain with it — judges the change's tree.
// aPullRequest is a repository whose main holds base, and a branch "change" off it that holds change on
// top (a nil body deletes the file): cloned as the check clones it, at the change's head. It answers the
// clone's tree.
func aPullRequest(t *testing.T, base, change map[string]*string) string {
t.Helper()
origin := t.TempDir()
git := func(dir string, args ...string) string {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
write := func(files map[string]*string) {
for name, body := range files {
at := filepath.Join(origin, name)
if body == nil {
if err := os.Remove(at); err != nil {
t.Fatal(err)
}
continue
}
if err := os.MkdirAll(filepath.Dir(at), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(at, []byte(*body), 0o755); err != nil {
t.Fatal(err)
}
}
git(origin, "add", "-A")
git(origin, "commit", "--quiet", "--allow-empty", "-m", "x")
}
git(origin, "init", "--quiet", "-b", "main")
write(base)
git(origin, "checkout", "--quiet", "-b", "change")
write(change)
head := git(origin, "rev-parse", "HEAD")
git(origin, "checkout", "--quiet", "main")
root := t.TempDir()
git(root, "clone", "--quiet", origin, "checked")
tree := filepath.Join(root, "checked")
git(tree, "checkout", "--quiet", head)
return tree
}
func s(body string) *string { return &body }
// judged runs the check that judges the change as the build seat would — each part a plain sh here.
func judged(t *testing.T, tree, base string) (Judging, *Layer) {
t.Helper()
j, err := TheCheckThatJudges(t.Context(), tree, base)
if err != nil {
t.Fatal(err)
}
if j.Script == nil {
return j, nil
}
if err := j.Put(tree); err != nil {
t.Fatal(err)
}
var out tail
layer := ownCheck(t.Context(), CheckSpec{Toolchain: "go-image", Toolchains: map[string]string{"go": "go-image",
"typescript": "ts-image"}}, ScriptParts(j.Script), tree, &out, func() bool { return false },
func(image, script string) *exec.Cmd {
cmd := exec.CommandContext(t.Context(), "sh", script)
cmd.Dir = tree
return cmd
}, func(string, string, ...any) {})
return j, layer
}
// main's check: the suite fails unless the tree holds what main requires.
const mainsCheck = "#!/bin/sh\nset -eu\ntest -f covered || { echo 'FAIL: the suite does not pass'; exit 1; }\n"
func TestAHeadWithoutTheScriptIsJudgedByMains(t *testing.T) {
// Deleted by the change — or never there, on a branch cut before main had it: the head lacks it.
tree := aPullRequest(t, map[string]*string{CheckScript: s(mainsCheck)}, map[string]*string{CheckScript: nil})
j, layer := judged(t, tree, "main")
if layer == nil || layer.Verdict != "fail" || !strings.Contains(layer.Summary, "does not pass") {
t.Fatalf("a head without the script answered %+v — main's script was not what judged it", layer)
}
if !strings.Contains(j.Said, "the change holds no "+CheckScript) || !strings.Contains(j.Said, "main's judged it") {
t.Errorf("the verdict does not say main's judged it: %q", j.Said)
}
// And a head that holds what main's script asks passes by it.
tree = aPullRequest(t, map[string]*string{CheckScript: s(mainsCheck)},
map[string]*string{CheckScript: nil, "covered": s("x")})
if _, layer = judged(t, tree, "main"); layer == nil || layer.Verdict != "pass" {
t.Fatalf("a head main's script passes answered %+v", layer)
}
}
func TestAHeadThatGutsTheScriptIsStillJudgedByMains(t *testing.T) {
tree := aPullRequest(t, map[string]*string{CheckScript: s(mainsCheck)},
map[string]*string{CheckScript: s("#!/bin/sh\nexit 0\n")})
j, layer := judged(t, tree, "main")
if layer == nil || layer.Verdict != "fail" {
t.Fatalf("a gutted script answered %+v — the change's own copy judged it", layer)
}
if !strings.Contains(j.Said, "ALTERS ITS OWN CHECK") || !strings.Contains(j.Said, CheckScript) {
t.Errorf("the verdict does not say the change alters its check: %q", j.Said)
}
// Gutting the declared part instead, or declaring none: main's lines and main's part still judge.
two := "#!/bin/sh\n# mesh-check-toolchain: typescript\n# mesh-check-also: go replays/merge-check.sh\nexit 0\n"
tree = aPullRequest(t, map[string]*string{CheckScript: s(two), "replays/merge-check.sh": s(mainsCheck)},
map[string]*string{CheckScript: s("#!/bin/sh\n# mesh-check-toolchain: typescript\nexit 0\n"),
"replays/merge-check.sh": s("exit 0\n")})
j, layer = judged(t, tree, "main")
if layer == nil || layer.Verdict != "fail" || !strings.Contains(layer.Summary, "replays/merge-check.sh failed") {
t.Fatalf("a gutted part answered %+v", layer)
}
if parts := ScriptParts(j.Script); len(parts) != 2 || parts[0].Toolchain != "typescript" {
t.Errorf("main's toolchain and parts did not judge: %+v", parts)
}
if !strings.Contains(j.Said, CheckScript+", replays/merge-check.sh") {
t.Errorf("the verdict does not name both altered scripts: %q", j.Said)
}
}
// A change that legitimately alters the check is judged by main's, says so, and passes when main's passes:
// its own version judges the pull requests after it.
func TestAChangeToTheCheckIsJudgedByTheOneItReplaces(t *testing.T) {
tree := aPullRequest(t, map[string]*string{CheckScript: s(mainsCheck), "covered": s("x")},
map[string]*string{CheckScript: s(mainsCheck + "test -f also-covered\n")})
j, layer := judged(t, tree, "main")
if layer == nil || layer.Verdict != "pass" || !strings.Contains(j.Said, "ALTERS ITS OWN CHECK") {
t.Fatalf("a change to the check answered %+v, said %q", layer, j.Said)
}
// The same script as main's: nothing to say.
tree = aPullRequest(t, map[string]*string{CheckScript: s(mainsCheck), "covered": s("x")},
map[string]*string{"other.go": s("package x\n")})
if j, layer = judged(t, tree, "main"); layer == nil || layer.Verdict != "pass" || j.Said != "" {
t.Fatalf("an unchanged check answered %+v, said %q", layer, j.Said)
}
}
// Where main holds no script, what was so stays: the head's, or none.
func TestWhereMainHoldsNoScriptTheHeadsRuns(t *testing.T) {
tree := aPullRequest(t, map[string]*string{"README.md": s("x")},
map[string]*string{CheckScript: s("echo 'the first check'; exit 0\n")})
j, layer := judged(t, tree, "main")
if layer == nil || layer.Verdict != "pass" || j.Said != "" {
t.Fatalf("the head's first script answered %+v, said %q", layer, j.Said)
}
tree = aPullRequest(t, map[string]*string{"README.md": s("x")}, map[string]*string{"b.md": s("y")})
if j, _ = judged(t, tree, "main"); j.Script != nil {
t.Fatalf("neither holds a script, and one judged: %q", j.Script)
}
// No base asked (a check by hand without one): the head's, as before.
tree = aPullRequest(t, map[string]*string{CheckScript: s(mainsCheck)}, map[string]*string{CheckScript: s("exit 0\n")})
if j, layer = judged(t, tree, ""); layer == nil || layer.Verdict != "pass" {
t.Fatalf("with no base the head's answered %+v", layer)
}
}
// A base branch that cannot be read is an error — never a fall back to the change's own script.
func TestABaseThatCannotBeReadIsAnError(t *testing.T) {
tree := aPullRequest(t, map[string]*string{CheckScript: s(mainsCheck)}, map[string]*string{CheckScript: s("exit 0\n")})
if _, err := TheCheckThatJudges(t.Context(), tree, "no-such-branch"); err == nil ||
!strings.Contains(err.Error(), "no-such-branch") {
t.Fatalf("an unreadable base said %v", err)
}
}
// The whole check, against a real container runtime: a head without the script fails by main's.
func TestACheckOfAHeadWithoutTheScriptRunsMains(t *testing.T) {
registry := checkEnvironment(t)
tree := aPullRequest(t, map[string]*string{CheckScript: s(mainsCheck)}, map[string]*string{CheckScript: nil})
head, err := gitIn(t.Context(), tree, "rev-parse", "HEAD")
if err != nil {
t.Fatal(err)
}
origin, err := gitIn(t.Context(), tree, "remote", "get-url", "origin")
if err != nil {
t.Fatal(err)
}
v, err := Check(t.Context(), Command, CheckSpec{ID: fmt.Sprintf("check-base-%d", time.Now().UnixNano()),
Repository: strings.TrimSpace(string(origin)), Ref: strings.TrimSpace(string(head)), Owner: "novox",
Repo: "hq", Base: "main", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Repo == nil || v.Repo.Verdict != "fail" || !strings.Contains(v.Repo.Summary, "main's judged it") {
t.Fatalf("a head without the script answered %+v\n%s", v.Repo, v.Report)
}
}