package main import ( "reflect" "sort" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" ) // novox/hq issue 611: mesh-controller #293 changed one test file. Its delivery plan, made while another walk built // build-agent and route-proxy (issue 606), read them whole and named them; at the cut their recorded build sources // held none of its files, so its walk moved nothing and its record named no module. mesh-delivery could not tell // "matched what runs" from "never built" and stopped the delivery, asking the operator about a change already on // every machine. The walk now says, per module, that its build matched what runs (delivery.unchanged, ADR 0267 §5). // The shape of #293: a test file of the controller's repository. The controller, built from the root, and the two // modules packaging the repository each recorded a build source holding none of it: each is said unchanged, with // why; a module of another repository is not named. func TestAMergeSaysTheModulesWhoseBuildSourceItLeftUnchanged(t *testing.T) { const catalogueRepo = "http://forge.internal:20000/novox/mesh-catalog.git" const controllerRepo = "http://forge.internal:20000/novox/mesh-controller.git" entries := []inventory.Entry{ fromRepo("mesh-controller", controllerRepo, ""), fromRepo("build-agent", catalogueRepo, "modules/build-agent"), fromRepo("route-proxy", catalogueRepo, "modules/route-proxy"), fromRepo("gitea", catalogueRepo, "modules/gitea"), } read := map[string][]inventory.ReadRepository{ "mesh-controller": {{Own: true, Paths: []string{"go.mod", "module.json", "cmd/mesh-controller/", "internal/builder/"}}}, "build-agent": { {Repository: "novox/mesh-controller", Ref: "main", Paths: []string{"go.mod", "cmd/mesh-builder/", "internal/builder/"}}, {Own: true, Paths: []string{"modules/build-agent/Dockerfile", "modules/build-agent/module.json"}}, }, "route-proxy": { {Repository: "novox/mesh-controller", Ref: "main", Paths: []string{"go.mod", "examples/route-proxy/"}}, {Own: true, Paths: []string{"modules/route-proxy/Dockerfile", "modules/route-proxy/module.json"}}, }, } m := link.SourceMoved{Owner: "novox", Repo: "mesh-controller", Base: "main", Commit: "8f1aec0", Paths: []string{"internal/builder/replay363_test.go"}} if moved := reachOfMerge(m, entries, read, sharedRepositoryEdges).Moved(); len(moved) != 0 { t.Fatalf("the fixture moves %v: a test file is in no build source", moved) } got := unchangedOfMerge(m, entries, read) var names []string for n, why := range got { names = append(names, n) if !strings.Contains(why, "holds none of") || !strings.Contains(why, "internal/builder/replay363_test.go") || !strings.Contains(why, "the build the mesh holds is its build") { t.Errorf("%s is said unchanged as %q", n, why) } } if want := []string{"build-agent", "mesh-controller", "route-proxy"}; !reflect.DeepEqual(sortedOf(names), want) { t.Fatalf("said unchanged: %v; want %v", sortedOf(names), want) } // A module whose build source holds a changed file is moved, never said unchanged. touched := m touched.Paths = []string{"internal/builder/builder.go"} if got := unchangedOfMerge(touched, entries, read); got["mesh-controller"] != "" || got["build-agent"] != "" { t.Errorf("a module the merge moves is said unchanged: %v", got) } if got := unchangedOfMerge(touched, entries, read); got["route-proxy"] == "" { t.Errorf("the route proxy, whose build source holds none of it, is not said unchanged: %v", got) } // With no build source recorded, the module is read whole and moved: nothing says it unchanged. unsaid := map[string][]inventory.ReadRepository{"build-agent": {{Repository: "novox/mesh-controller", Ref: "main"}}} if got := unchangedOfMerge(m, entries, unsaid); len(got) != 0 { t.Errorf("modules with no recorded build source are said unchanged: %v", got) } // Files not all said: every module is moved, none said unchanged. truncated := m truncated.PathsTruncated = true if got := unchangedOfMerge(truncated, entries, read); len(got) != 0 { t.Errorf("a merge whose files were not all said says %v unchanged", got) } } // A module the walk builds anyway — another merge's, or a dependent — is not said unchanged: the modules map keeps // meaning what the walk builds, and the two never name one module. func TestAWalkSaysUnchangedOnlyWhatItDoesNotBuild(t *testing.T) { unchanged := map[string]string{"build-agent": "why a", "route-proxy": "why b"} got := unchangedLeft(unchanged, map[string]*inventory.PlanModule{"build-agent": {}}) if !reflect.DeepEqual(got, map[string]string{"route-proxy": "why b"}) { t.Fatalf("left %v", got) } if got := unchangedLeft(map[string]string{"x": "y"}, map[string]*inventory.PlanModule{"x": {}}); got != nil { t.Fatalf("left %v; want none", got) } } // Against the store, the path a real merge takes: a merge whose module recorded a build source that holds none // of its files is cut into a walk done with nothing to walk, whose record names the module unchanged and why. func TestAWalkThatMovedNothingSaysWhichModulesMatchedWhatRuns(t *testing.T) { open := windowed(t) endsItsLine(t) asksRecorded(t) ctx := t.Context() inv := open.inventory if err := inv.RecordBuild(ctx, inventory.Build{ID: "build-one", Repository: "novox/one", Ref: "main", Module: "one", Commit: "c0", On: "builder", Asked: t0.Add(-time.Hour), Sources: []inventory.BuildSource{{Paths: []string{"main.go", "go.mod"}}}}); err != nil { t.Fatal(err) } readme := repoMerge("one", "one1aaaaaaaa", t0) readme.Paths = []string{"README.md"} hear(t, open, readme, t0.Add(time.Second)) cutAt(t, open, t0.Add(2*time.Minute)) ws, _ := walks(t, open) if len(ws) != 1 { t.Fatalf("walks: %+v", ws) } w := ws[0] if w.State != inventory.PlanDone || len(w.Tiers) != 0 || len(w.Modules) != 0 { t.Fatalf("the walk reads %s, tiers %v, modules %v; want done with nothing to walk", w.State, w.Tiers, w.Modules) } if w.Delivery == nil || !strings.Contains(w.Delivery.Unchanged["one"], "holds none of the 1 changed file(s)") { t.Fatalf("the walk does not say one matched what runs: %+v", w.Delivery) } if !strings.Contains(w.Note, "unchanged") || !strings.Contains(w.Note, "one") { t.Errorf("the walk's note does not name it: %q", w.Note) } // A merge that changes the build source is walked, and says nothing unchanged. code := repoMerge("one", "one2bbbbbbbb", t0.Add(3*time.Minute)) hear(t, open, code, t0.Add(3*time.Minute+time.Second)) cutAt(t, open, t0.Add(6*time.Minute)) ws, _ = walks(t, open) if len(ws) != 2 { t.Fatalf("walks: %+v", ws) } if _, built := ws[0].Modules["one"]; !built || ws[0].Delivery == nil || len(ws[0].Delivery.Unchanged) != 0 { t.Fatalf("the walk of a change to main.go reads modules %v, delivery %+v", ws[0].Modules, ws[0].Delivery) } } func sortedOf(names []string) []string { out := append([]string{}, names...) sort.Strings(out) return out }