Three faults in one path. A merge rebuilt every module built from the repository, so one change in a repository holding twenty-six of them meant twenty-six builds. A merge into a repository a module only *packages* source from rebuilt nothing — two modules are built from the control plane's own repository and neither had ever been rebuilt when it moved — because the manifest the mesh keeps carries no build section, so a build now says which repositories it read and the mesh keeps that beside what it stood on. And a module handed over by hand could record a repository with no directory inside it, which is a module nothing can ever rebuild (novox/hq 04-ISSUES/131, /132). A change inside no module's own directory is a change to what they share, and everything built from that repository is rebuilt: rebuilding too much is the safe direction, because the fault this whole path exists for is a mesh that believes it is current and is not.
172 lines
5.2 KiB
Go
172 lines
5.2 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A build result was answered to whoever asked and kept nowhere, so "when did this last build",
|
|
// "why did it fail" and "which machine built what is running" had no answer at all.
|
|
|
|
func aBuild(id, module, failed string) Build {
|
|
b := Build{
|
|
ID: id, Repository: "https://forge.invalid/" + strings.TrimSuffix(module, "?") + ".git",
|
|
Module: module, On: "a-build-machine", Failed: failed,
|
|
}
|
|
if failed == "" {
|
|
b.Commit = "c0ffee" + id
|
|
b.Made = []Artifact{{Name: "config", Kind: "archive", Reference: "…/blobs/sha256:…"}}
|
|
}
|
|
return b
|
|
}
|
|
|
|
func TestAFailedBuildIsARowLikeAnyOther(t *testing.T) {
|
|
// One that leaves no trace is indistinguishable from a build nobody asked for, and the
|
|
// difference is the whole of whether somebody should be looking at something.
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
if err := inv.RecordBuild(ctx, aBuild("1", "", "cannot clone: no such repository")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := inv.Builds(ctx, "", 10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 1 {
|
|
t.Fatalf("got %d builds", len(got))
|
|
}
|
|
if got[0].Worked() {
|
|
t.Fatal("a failure was recorded as a success")
|
|
}
|
|
if !strings.Contains(got[0].Failed, "no such repository") {
|
|
t.Fatalf("the builder's own words were not kept: %q", got[0].Failed)
|
|
}
|
|
// And it kept what was asked for, which is the only thing a person can go and look at when
|
|
// the build never learned what it was building.
|
|
if got[0].Module != "" || got[0].Repository == "" {
|
|
t.Fatalf("got %+v", got[0])
|
|
}
|
|
}
|
|
|
|
func TestOneResultRecordedTwiceIsOneBuild(t *testing.T) {
|
|
// A result can arrive twice: as the answer to whoever asked, and on the exchange when nobody
|
|
// was. Two rows would show one build as two, and which is real is not answerable afterwards.
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
for i := 0; i < 2; i++ {
|
|
if err := inv.RecordBuild(ctx, aBuild("same", "shell", "")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
got, err := inv.Builds(ctx, "", 10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 1 {
|
|
t.Fatalf("one build was recorded %d times", len(got))
|
|
}
|
|
}
|
|
|
|
func TestBuildsComeBackNewestFirstAndCanBeAskedPerModule(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
for _, b := range []Build{
|
|
aBuild("1", "shell", ""),
|
|
aBuild("2", "meshboard", ""),
|
|
aBuild("3", "shell", "the tests failed"),
|
|
} {
|
|
if err := inv.RecordBuild(ctx, b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
all, err := inv.Builds(ctx, "", 10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(all) != 3 || all[0].ID != "3" {
|
|
t.Fatalf("newest is not first: %v", ids(all))
|
|
}
|
|
|
|
// Per module, because "what has happened to this" is asked when deciding whether to trust it.
|
|
shell, err := inv.Builds(ctx, "shell", 10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(shell) != 2 {
|
|
t.Fatalf("shell has %d builds: %v", len(shell), ids(shell))
|
|
}
|
|
for _, b := range shell {
|
|
if b.Module != "shell" {
|
|
t.Fatalf("asked for shell and got %q", b.Module)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWhatWasPublishedIsKeptWithTheBuild(t *testing.T) {
|
|
// So a digest can be traced back to the build that made it, without keeping the manifest a
|
|
// second time in a place that can disagree with the first.
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
if err := inv.RecordBuild(ctx, aBuild("1", "shell", "")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _ := inv.Builds(ctx, "", 10)
|
|
if len(got[0].Made) != 1 || got[0].Made[0].Kind != "archive" {
|
|
t.Fatalf("what was published was not kept: %+v", got[0].Made)
|
|
}
|
|
}
|
|
|
|
func TestAskingForMoreThanThereIsIsNotAnError(t *testing.T) {
|
|
inv := fresh(t)
|
|
got, err := inv.Builds(context.Background(), "", 100)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 0 {
|
|
t.Fatalf("got %d", len(got))
|
|
}
|
|
}
|
|
|
|
func ids(builds []Build) []string {
|
|
var out []string
|
|
for _, b := range builds {
|
|
out = append(out, b.ID)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// What a build read besides its module's own repository comes back for the newest build of each
|
|
// module, and only for builds that worked. Nothing recorded is absent rather than empty, which is how
|
|
// a build made before the mesh kept this is told from one that read nothing (novox/hq 04-ISSUES/131).
|
|
func TestWhatABuildReadComesBackForTheNewestBuildOfEachModule(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
older := aBuild("older", "builder", "")
|
|
older.Read = []ReadRepository{{Repository: "novox/mesh-controller", Ref: "release"}}
|
|
newer := aBuild("newer", "builder", "")
|
|
newer.Read = []ReadRepository{{Repository: "novox/mesh-controller", Ref: "main"}}
|
|
plain := aBuild("plain", "gitea", "")
|
|
failed := aBuild("failed", "route-proxy", "cannot clone")
|
|
failed.Read = []ReadRepository{{Repository: "novox/mesh-controller", Ref: "main"}}
|
|
for _, b := range []Build{older, newer, plain, failed} {
|
|
if err := inv.RecordBuild(ctx, b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
read, err := inv.ReadRepositories(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(read["builder"]) != 1 || read["builder"][0].Ref != "main" {
|
|
t.Fatalf("the newest build's reading is %+v", read["builder"])
|
|
}
|
|
if _, has := read["gitea"]; has {
|
|
t.Fatalf("a build that read nothing but its own repository reads as %+v", read["gitea"])
|
|
}
|
|
if _, has := read["route-proxy"]; has {
|
|
t.Fatal("a failed build's reading was kept as what that module reads")
|
|
}
|
|
}
|