mesh/merge-gate head 35829d5a9a8e merged into trunk cada7e3c8374 — pass: builds mesh-controller → novox; no bus step; every machine composes with the chang…
mesh/repo-check head 35829d5a9a8e merged into trunk cada7e3c8374 — pass: its merge-check.sh passed
mesh/delivery delivered
138 lines
5.7 KiB
Go
138 lines
5.7 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// asksWithPaths records every build a merge asks for, as repository, path and ref. The merge window's goroutine and
|
|
// the test's own may ask at once, so the record is written under a lock (issue 1543).
|
|
func asksWithPaths(t *testing.T) *[][3]string {
|
|
t.Helper()
|
|
var asked [][3]string
|
|
var mu sync.Mutex
|
|
was := askABuild
|
|
askABuild = func(_ context.Context, source buildSource, path, ref string) (string, error) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
asked = append(asked, [3]string{source.Repository, path, ref})
|
|
return "b-" + path, nil
|
|
}
|
|
t.Cleanup(func() { askABuild = was })
|
|
return &asked
|
|
}
|
|
|
|
// The live case of 2026-10-07 (novox/hq issue 300), replayed: a merge of the catalogue adds one module's
|
|
// directory and touches nothing else the mesh holds. Its delivery plan said "builds new: …, sent nowhere";
|
|
// the merge said it changed nothing any module the mesh holds is built from, and built nothing, so the
|
|
// module was never registered and `assign` refused it. The merge asks for its build, at the branch merged
|
|
// into, from the repository as the mesh spells it — and opens no plan, since nothing it holds moved.
|
|
func TestAMergeBuildsTheNewModuleItAdds(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
inv := open.inventory
|
|
asked := asksWithPaths(t)
|
|
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "networkmanager", Version: "1"},
|
|
inventory.Source{Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/networkmanager", Ref: "main",
|
|
BuiltFrom: "c0", Head: "c0"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m := link.SourceMoved{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: "3da80a4b00",
|
|
Paths: []string{"modules/systemd-resolved/module.json", "modules/systemd-resolved/cmd/main.go"},
|
|
ModuleDirs: []string{"modules/systemd-resolved"}, ModuleDirsSaid: true}
|
|
if err := (following{open: open}).SourceMoved(ctx, m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := [3]string{"novox/mesh-catalog", "modules/systemd-resolved", "main"}
|
|
if len(*asked) != 1 || (*asked)[0] != want {
|
|
t.Fatalf("the merge adding modules/systemd-resolved asked for %v, not %v", *asked, want)
|
|
}
|
|
if plans, err := inv.OpenPlans(ctx); err != nil || len(plans) != 0 {
|
|
t.Fatalf("a merge moving nothing the mesh holds opened a plan: %+v %v", plans, err)
|
|
}
|
|
}
|
|
|
|
// A merge that changes a held module and adds a new one does both: the held one walks its plan, the new
|
|
// one is asked for beside it. A merge adding nothing builds nothing new.
|
|
func TestAMergeBuildsItsNewModuleBesideItsPlan(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
inv := open.inventory
|
|
asked := asksWithPaths(t)
|
|
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "app", Version: "1"},
|
|
inventory.Source{Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/app", Ref: "main",
|
|
BuiltFrom: "c0", Head: "c0"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m := link.SourceMoved{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: "c1aaaaaaaa",
|
|
Paths: []string{"modules/app/index.ts", "modules/fresh/module.json"},
|
|
ModuleDirs: []string{"modules/app", "modules/fresh"}, ModuleDirsSaid: true}
|
|
if err := (following{open: open}).SourceMoved(ctx, m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
paths := map[string]bool{}
|
|
for _, a := range *asked {
|
|
paths[a[1]] = true
|
|
}
|
|
if !paths["modules/fresh"] || !paths["modules/app"] || len(*asked) != 2 {
|
|
t.Fatalf("asked %v; want the new module and the plan's first tier", *asked)
|
|
}
|
|
plans, err := inv.OpenPlans(ctx)
|
|
if err != nil || len(plans) != 1 || plans[0].Modules["fresh"] != nil || plans[0].Modules["app"] == nil {
|
|
t.Fatalf("the plan should walk app alone: %+v %v", plans, err)
|
|
}
|
|
|
|
*asked = nil
|
|
m2 := link.SourceMoved{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: "c2aaaaaaaa",
|
|
Paths: []string{"README.md"}, ModuleDirsSaid: true}
|
|
if err := (following{open: open}).SourceMoved(ctx, m2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(*asked) != 0 {
|
|
t.Fatalf("a merge adding no module asked for %v", *asked)
|
|
}
|
|
}
|
|
|
|
// 1617/AC1 (integration): a merge adding a module to a repository the mesh builds modules from, heard while a batch of
|
|
// that repository is still assembling (its window running), asks the module's first build when it is heard, so the
|
|
// module is registered while the batch waits; cutting the batch asks it no second time (1617/AC2).
|
|
func TestAMergeAddingAModuleAsksItsBuildBeforeItsBatchIsCut(t *testing.T) {
|
|
open := windowed(t)
|
|
groupsWaiting(t, "pr/c1")
|
|
asked := asksWithPaths(t)
|
|
catalog := func(commit string, made time.Time, paths ...string) link.SourceMoved {
|
|
return link.SourceMoved{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: commit,
|
|
Head: "pr/" + commit, MergedAt: made.Format(time.RFC3339Nano), Paths: paths}
|
|
}
|
|
hear(t, open, catalog("c1", t0, "modules/app/index.ts"), t0)
|
|
if len(*asked) != 0 {
|
|
t.Fatalf("a merge adding no module asked %v", *asked)
|
|
}
|
|
hear(t, open, lone(catalog("c2", t0.Add(10*time.Second), "modules/go/packages.json", "modules/go/module.json")), t0.Add(10*time.Second))
|
|
if ws, bs := walks(t, open); len(ws) != 0 || len(bs) != 1 {
|
|
t.Fatalf("the batch should still be assembling: %d walk(s), %d batch(es)", len(ws), len(bs))
|
|
}
|
|
want := [3]string{"novox/mesh-catalog", "modules/go", "main"}
|
|
if len(*asked) != 1 || (*asked)[0] != want {
|
|
t.Fatalf("heard with its batch assembling, the merge asked %v, not %v", *asked, want)
|
|
}
|
|
cutAt(t, open, t0.Add(10*time.Second+90*time.Second))
|
|
n := 0
|
|
for _, a := range *asked {
|
|
if a[1] == "modules/go" {
|
|
n++
|
|
}
|
|
}
|
|
if n != 1 {
|
|
t.Fatalf("modules/go was asked %d times: %v", n, *asked)
|
|
}
|
|
}
|