The controller asks no build of anybody (ADR 0501 §5, ADR 0525 clause 6, ADR 0503 §3). Every module of a walk is taken from the builder's done key; a key not done yet waits, named in the plan, and never fails it. - C2b/C2c: internal/builder and cmd/mesh-controller's asks go (askABuild, onlyTheBuildersOwn, buildOneAsked, takeIn from an outcome, buildAgentsOwn, the builder's say and its fallback, the daemon hearing an outcome). The server follows the retired seats' built subjects only to let go of them, because the installer's carried user list names them. - C2d: replay, queue, cancel, clear, kill, pause, resume, delivery-check, check-here and merge-gate are refused by name. - C3: the check queues (CHECKS_READY, CHECKS_OTHER) and their workers, the checks-running and plans-building buckets and their grants, the build queue and its hold (plans_building.go, queue.go, link/holds.go, builds_nats.go, checks_nats.go). - C4: merge_gate*.go, the pull request's repo-check path, the Checked event and its layers; a pull request asks the controller nothing but to tell mesh-config what its head declares. - C5: the builds bucket is written only by key_takein. - C10 (help and refusals): the terminal's help lists only what is served. Integration tests of the plan's tiers, sends, stops and retries now run on a stand-in builder with a done key for every module (walk_keys_scaffold_integration_test.go); the tests of the asks, the queue, the merge gate and the replays are deleted.
153 lines
8.3 KiB
Go
153 lines
8.3 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// **The mesh's module graph decides what a pull request's check runs**, not the repository (novox/hq
|
|
// ADR 0237 as amended): a change is mapped onto the graph by the rule a merge is (issue 278), the gate
|
|
// runs when it touches a module — a new one included — and a repository that is the mesh's and touches
|
|
// none still has its own merge-check.sh run.
|
|
func TestThePullRequestIsMappedOntoTheModuleGraph(t *testing.T) {
|
|
const catalogue = "http://forge.internal:20000/novox/mesh-catalog.git"
|
|
const controller = "http://forge.internal:20000/novox/mesh-controller.git"
|
|
const host = "http://forge.internal:20000/novox/mesh-host.git"
|
|
photos := fromRepo("photos", "http://forge.internal:20000/novox/photos.git", "")
|
|
photos.Source.Ref = "nox-mesh"
|
|
snake := fromRepo("snake", "jschoubben/snake", "")
|
|
snake.Source.Seat = "git"
|
|
entries := []inventory.Entry{
|
|
fromRepo("gitea", catalogue, "modules/gitea"),
|
|
fromRepo("keycloak", catalogue, "modules/keycloak"),
|
|
fromRepo("nats", catalogue, "modules/nats"),
|
|
fromRepo("mesh-controller", controller, ""),
|
|
fromRepo("mesh-host", host, ""),
|
|
photos, snake,
|
|
}
|
|
pull := func(owner, repo, base string, paths []string, dirs ...string) link.PullUpdated {
|
|
return link.PullUpdated{Owner: owner, Repo: repo, Base: base, Commit: "abc", Paths: paths,
|
|
ModuleDirs: dirs, ModuleDirsSaid: true}
|
|
}
|
|
for _, c := range []struct {
|
|
what string
|
|
p link.PullUpdated
|
|
modules, new, manifest string
|
|
mesh bool
|
|
judge string
|
|
}{
|
|
{"one module's own files", pull("novox", "mesh-catalog", "main", []string{"modules/gitea/index.ts"}, "modules/gitea"),
|
|
"gitea", "", "modules/gitea/module.json", true, ""},
|
|
{"a file no module's build reads touches no module (issue 280)",
|
|
pull("novox", "mesh-catalog", "main", []string{"merge-check.sh", "README.md"}), "", "", "", true, ""},
|
|
{"files the announcer could not list: everything built from it",
|
|
link.PullUpdated{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: "abc", PathsTruncated: true,
|
|
Paths: []string{"README.md"}}, "gitea,keycloak,nats", "",
|
|
"modules/gitea/module.json,modules/keycloak/module.json,modules/nats/module.json", true, ""},
|
|
{"a new module, said by the head", pull("novox", "mesh-catalog", "main",
|
|
[]string{"modules/newmod/index.ts", "modules/newmod/module.json"}, "modules/newmod"),
|
|
"", "modules/newmod", "modules/newmod/module.json", true, ""},
|
|
{"the controller judges itself", pull("novox", "mesh-controller", "main", []string{"cmd/x.go"}),
|
|
"mesh-controller", "", "module.json", true, "self"},
|
|
{"the node-engine is judged by the controller the mesh runs, as any module is (issue 1391)",
|
|
pull("novox", "mesh-host", "main", []string{"validate/v.go"}), "mesh-host", "", "module.json", true, ""},
|
|
{"the core's owner, no module: the mesh's, its own check alone", pull("novox", "hq", "main", []string{"README.md"}),
|
|
"", "", "", true, ""},
|
|
{"a branch nothing is built from: the mesh's repository, no module", pull("novox", "photos", "master",
|
|
[]string{"server/x.js"}), "", "", "", true, ""},
|
|
{"the branch a module is built from", pull("novox", "photos", "nox-mesh", []string{"server/x.js"}),
|
|
"photos", "", "module.json", true, ""},
|
|
{"a repository on the forge's seat", pull("jschoubben", "snake", "main", []string{"index.html"}),
|
|
"snake", "", "module.json", true, ""},
|
|
{"a repository of nobody's, touching nothing", pull("someone", "dotfiles", "main", []string{"x"}),
|
|
"", "", "", false, ""},
|
|
{"a repository adding a module at its root", pull("someone", "newapp", "main", []string{"module.json", "x.js"}),
|
|
"", ".", "module.json", true, ""},
|
|
} {
|
|
s := pullScope(c.p, entries, nil, nil)
|
|
got := []string{strings.Join(s.Modules, ","), strings.Join(s.New, ","), strings.Join(s.Manifests, ",")}
|
|
want := []string{c.modules, c.new, c.manifest}
|
|
for i, what := range []string{"modules", "new", "manifests"} {
|
|
if got[i] != want[i] {
|
|
t.Errorf("%s: %s %q, wanted %q", c.what, what, got[i], want[i])
|
|
}
|
|
}
|
|
if s.Mesh != c.mesh {
|
|
t.Errorf("%s: the mesh's %v, wanted %v", c.what, s.Mesh, c.mesh)
|
|
}
|
|
if s.gated() != (c.modules != "" || c.new != "") {
|
|
t.Errorf("%s: gated %v", c.what, s.gated())
|
|
}
|
|
}
|
|
}
|
|
|
|
// A module whose build packages another repository's source is touched by a change to it.
|
|
func TestAPullRequestTouchesWhatPackagesItsRepository(t *testing.T) {
|
|
entries := []inventory.Entry{fromRepo("node-tools", "http://forge.internal:20000/novox/mesh-tools.git", "node-tools")}
|
|
read := map[string][]inventory.ReadRepository{"node-tools": {{Repository: "http://forge.internal:20000/novox/mesh-sdk.git"}}}
|
|
s := pullScope(link.PullUpdated{Owner: "novox", Repo: "mesh-sdk", Base: "main", Commit: "abc", Paths: []string{"go/x.go"}}, entries, read, nil)
|
|
if strings.Join(s.Modules, ",") != "node-tools" || len(s.Manifests) != 0 {
|
|
t.Fatalf("a change to what node-tools packages touched %v (manifests %v)", s.Modules, s.Manifests)
|
|
}
|
|
}
|
|
|
|
// **The check asks the planner, never a mapping of its own** (novox/hq ADR 0238): what a pull request
|
|
// reaches is reachOfMerge's answer — the same that plans a merge — so a file at the root touches no
|
|
// module in both, and what stands on a touched module is named as its dependents in both.
|
|
func TestAPullRequestReachesWhatAMergeOfItWouldPlan(t *testing.T) {
|
|
const catalogue = "http://forge.internal:20000/novox/mesh-catalog.git"
|
|
nats := fromRepo("nats", catalogue, "modules/nats")
|
|
nats.Source.BuiltFrom = "old"
|
|
gitea := fromRepo("gitea", catalogue, "modules/gitea")
|
|
gitea.Source.BuiltFrom = "old"
|
|
tools := fromRepo("node-tools", "http://forge.internal:20000/novox/mesh-tools.git", "node-tools")
|
|
entries := []inventory.Entry{nats, gitea, tools}
|
|
// node-tools stands on the bus's image; a code edge, so a plan takes it along.
|
|
edges := []inventory.Edge{{From: "node-tools", To: "nats", Kind: inventory.EdgeStandsOn}}
|
|
read := map[string][]inventory.ReadRepository{}
|
|
|
|
for _, c := range []struct {
|
|
what string
|
|
paths []string
|
|
moved, dependents string
|
|
width int
|
|
unread string
|
|
}{
|
|
{"a file at the root, read by no build", []string{"merge-check.sh"}, "", "", 0, "merge-check.sh"},
|
|
{"the bus's own directory", []string{"modules/nats/Dockerfile"}, "nats", "node-tools", 2, ""},
|
|
{"both, and a README", []string{"modules/gitea/index.ts", "modules/nats/x", "README.md"}, "gitea,nats", "node-tools", 3, "README.md"},
|
|
} {
|
|
p := link.PullUpdated{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: "head", Paths: c.paths}
|
|
s := pullScope(p, entries, read, edges)
|
|
m := link.SourceMoved{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: "head", Paths: c.paths}
|
|
r := reachOfMerge(m, entries, read, edges)
|
|
if got := strings.Join(s.Modules, ","); got != c.moved || got != strings.Join(r.Moved(), ",") {
|
|
t.Errorf("%s: the check reaches %q, the planner %v, wanted %q", c.what, got, r.Moved(), c.moved)
|
|
}
|
|
if got := strings.Join(s.Dependents, ","); got != c.dependents {
|
|
t.Errorf("%s: dependents %q, wanted %q", c.what, got, c.dependents)
|
|
}
|
|
if s.Width != c.width || s.Width != len(r.Plan.Modules) {
|
|
t.Errorf("%s: a merge would build %d, the planner says %d, wanted %d", c.what, s.Width, len(r.Plan.Modules), c.width)
|
|
}
|
|
if got := strings.Join(s.Unread, ","); got != c.unread {
|
|
t.Errorf("%s: unread %q, wanted %q", c.what, got, c.unread)
|
|
}
|
|
}
|
|
|
|
// A repository whose build another module's recipe packages: that module is reached through the
|
|
// planner's `read`, and what stands on it after it.
|
|
read["gitea"] = []inventory.ReadRepository{{Repository: "http://forge.internal:20000/novox/mesh-sdk.git"}}
|
|
s := pullScope(link.PullUpdated{Owner: "novox", Repo: "mesh-sdk", Base: "main", Commit: "head",
|
|
Paths: []string{"src/index.ts"}}, entries, read, edges)
|
|
if strings.Join(s.Modules, ",") != "gitea" || len(s.Manifests) != 0 {
|
|
t.Fatalf("a change to the source gitea packages reaches %v (manifests %v)", s.Modules, s.Manifests)
|
|
}
|
|
}
|
|
|
|
// A check stopped by its pull request's key moving on says no verdict (issue 974, review 2): an error said for it would name the
|
|
// live head, and the newer request is the one that judges. A check that failed for any other reason still says its error.
|