Recognise a trunk build by repository identity and an empty ref in the indexer and the view (issue 1281)
mesh/repo-check head 4b8e71370cc2 merged into trunk fa4be27c1e40 — pass: its merge-check.sh passed
mesh/merge-gate head 4b8e71370cc2 merged into trunk fa4be27c1e40 — pass: group fix/1281-trunk-builds-by-repository composed: every machine composes with th…
mesh/delivery delivered
mesh/delivery-group group fix/1281-trunk-builds-by-repository delivered: every member is delivered
mesh/repo-check head 4b8e71370cc2 merged into trunk fa4be27c1e40 — pass: its merge-check.sh passed
mesh/merge-gate head 4b8e71370cc2 merged into trunk fa4be27c1e40 — pass: group fix/1281-trunk-builds-by-repository composed: every machine composes with th…
mesh/delivery delivered
mesh/delivery-group group fix/1281-trunk-builds-by-repository delivered: every member is delivered
Build records name their repository by clone URL and a plan-asked build has no ref, so no module fingerprint was ever written or compared; both now use the SDK's tests.
This commit is contained in:
@@ -3,7 +3,7 @@ module mesh-delivery-indexer
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019195003-4580d80a32d9
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019203925-84a764372337
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019195003-4580d80a32d9 h1:XL1dbtSleGAixg6FX4gEnSG/AJxRG/tWw6/7U1RuzHc=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019203925-84a764372337 h1:cZyFnvDiBpnNx8ifSUG9WA61OHsn7PC2ml2s4eD7AkA=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019195003-4580d80a32d9/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019203925-84a764372337/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -57,7 +57,11 @@ func (b *Builds) Delete(key string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dirKey(repository, dir string) string { return repository + "\x00" + strings.Trim(dir, "/") }
|
// dirKey names a directory of a repository whichever way the repository is spelled: a build record names it by its clone
|
||||||
|
// URL, a forge message by "owner/name" (issue 1281).
|
||||||
|
func dirKey(repository, dir string) string {
|
||||||
|
return strings.ToLower(delivery.RepositoryName(repository)) + "\x00" + strings.Trim(dir, "/")
|
||||||
|
}
|
||||||
|
|
||||||
// Module is the module a build record of the repository names the directory as its path, if one does.
|
// Module is the module a build record of the repository names the directory as its path, if one does.
|
||||||
func (b *Builds) Module(repository, dir string) (string, bool) {
|
func (b *Builds) Module(repository, dir string) (string, bool) {
|
||||||
@@ -85,8 +89,8 @@ func (b *Builds) ModuleOf(repository, dir string) string {
|
|||||||
return path.Base(strings.Trim(dir, "/"))
|
return path.Base(strings.Trim(dir, "/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Known is a module the builds know of in a repository: its directory there and its newest successful trunk build (ref
|
// Known is a module the builds know of in a repository: its directory there and its newest successful trunk build
|
||||||
// main, state built, with source inputs) if any. The newest key of a module is any state and ref, so it is not what
|
// (delivery.BuildRecord.IsTrunkBuild) if any. The newest key of a module is any state and ref, so it is not what
|
||||||
// finds this (mesh-sdk go/delivery/buildrecord.go NewestKey); the records are.
|
// finds this (mesh-sdk go/delivery/buildrecord.go NewestKey); the records are.
|
||||||
type Known struct {
|
type Known struct {
|
||||||
Module string
|
Module string
|
||||||
@@ -115,7 +119,7 @@ func (b *Builds) Modules(repository string) []Known {
|
|||||||
} else if newer(r, m.any) {
|
} else if newer(r, m.any) {
|
||||||
m.any = r
|
m.any = r
|
||||||
}
|
}
|
||||||
if r.Ref == "main" && r.State == "built" && r.SourceInputs != nil && (m.trunk == nil || newer(r, *m.trunk)) {
|
if r.IsTrunkBuild() && (m.trunk == nil || newer(r, *m.trunk)) {
|
||||||
c := r
|
c := r
|
||||||
m.trunk = &c
|
m.trunk = &c
|
||||||
}
|
}
|
||||||
@@ -127,7 +131,7 @@ func (b *Builds) Modules(repository string) []Known {
|
|||||||
if m.trunk != nil {
|
if m.trunk != nil {
|
||||||
k.Repository, k.Path = m.trunk.Repository, strings.Trim(m.trunk.Path, "/")
|
k.Repository, k.Path = m.trunk.Repository, strings.Trim(m.trunk.Path, "/")
|
||||||
}
|
}
|
||||||
if k.Repository == repository {
|
if delivery.SameRepository(k.Repository, repository) {
|
||||||
out = append(out, k)
|
out = append(out, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package index
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The build records below are the live ones, as `mesh-controller.command builds` showed them on 2026-10-19 at about
|
||||||
|
// 21:20 local (the last 100 builds): id, state, module, repository (the clone URL, as the record names it), commit (the
|
||||||
|
// eight characters the listing shows), the time (the listing's local minute, as UTC), the source fingerprint, and the
|
||||||
|
// ref only where the listing shows "at main"; a build a delivery plan asked after a merge shows none, so the record
|
||||||
|
// carries none. No reading command shows a whole record, so two fields come from elsewhere: the path is the module's
|
||||||
|
// directory in the catalogue (a clone of mesh-catalog and mesh-media-catalog, origin/main at the same time), and the
|
||||||
|
// source-inputs are mailu's live inputs (build-1792391134620131370, captured in mesh-sdk go/delivery/fingerprint_test.go
|
||||||
|
// TestFingerprintLiveBuilds) with the module's name put in; the indexer reads only their bases, contexts and toolchains.
|
||||||
|
const (
|
||||||
|
liveTree = "cfc6fa8ff5fc1e220132e51ade653b9d3f387b3e"
|
||||||
|
liveInputsTail = `"Tree":"` + liveTree + `","Bases":["sha256:25f3cfeaceca14921366af4d1240b56457ef46273bdb508c7b0e8f469f6fd228"],"Contexts":null,` +
|
||||||
|
`"Toolchains":{"code":"typescript sha256:4b81682df5665c1f461d3f720a3a0fd7593f4aa71e4fc272dbfdc4bbd235b489 20125bd590041690"},"Unpinned":""}`
|
||||||
|
catalogURL = "http://novox.internal:20000/novox/mesh-catalog.git"
|
||||||
|
mediaCatalogURL = "http://novox.internal:20000/novox/mesh-media-catalog.git"
|
||||||
|
)
|
||||||
|
|
||||||
|
func liveRecord(id, module, repository, ref, commit, path, at, source string) [2]any {
|
||||||
|
raw := `{"id":"` + id + `","state":"built","repository":"` + repository + `",`
|
||||||
|
if ref != "" {
|
||||||
|
raw += `"ref":"` + ref + `",`
|
||||||
|
}
|
||||||
|
raw += `"module":"` + module + `","commit":"` + commit + `","path":"` + path + `","at":"` + at + `","source-fingerprint":"` + source +
|
||||||
|
`","source-inputs":{"Module":"` + module + `",` + liveInputsTail + `}`
|
||||||
|
return [2]any{delivery.BuildKey(module, id), []byte(raw)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func liveBuilds() [][2]any {
|
||||||
|
return [][2]any{
|
||||||
|
// a plan-asked build after a merge: no ref
|
||||||
|
liveRecord("build-1792417485811374131", "zsh", catalogURL, "", "e8c34487", "modules/zsh", "2026-10-19T13:57:00Z", "src1:7e600dde59f3d7f019af00741583304288883e7d3ed955f4c61da26474f3be63"),
|
||||||
|
liveRecord("build-1792435539606203626", "mesh-delivery", catalogURL, "", "9efdd87a", "modules/mesh-delivery", "2026-10-19T18:45:00Z", "src1:f79e2eedc2689cf6b3591ebfdb81ec65899131122fb2fe3dd9e384091d8a2a6d"),
|
||||||
|
// a build at main
|
||||||
|
liveRecord("build-1792417463192745880", "mailu", catalogURL, "main", "e8c34487", "modules/mailu", "2026-10-19T13:50:00Z", "src1:98d35b17d3a3295e3a0efed2b8c4316c9a9b32ec155f8343824654661b7383a2"),
|
||||||
|
// a module of another repository
|
||||||
|
liveRecord("build-1792417481951691634", "sonarr", mediaCatalogURL, "main", "197abb06", "modules/sonarr", "2026-10-19T13:57:00Z", "src1:0fff753b8c66aa8a8ac1f38d210cea9512a78a8592dd2475bfe6ade77ff21b61"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func liveIndexer(t *testing.T) (*Indexer, *memStore) {
|
||||||
|
t.Helper()
|
||||||
|
ix, mods := modulesIndexer(t, liveBuilds()...)
|
||||||
|
return ix, mods
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeWritesEveryModuleTheLiveRecordsNameInTheRepository(t *testing.T) { // 1281/AC3
|
||||||
|
ix, mods := liveIndexer(t)
|
||||||
|
msg := mergedMsg("novox/mesh-catalog", map[string]string{
|
||||||
|
"modules/zsh": liveTree, "modules/mesh-delivery": "newtree", "modules/mailu": "newtree", "modules/sonarr": "newtree",
|
||||||
|
})
|
||||||
|
if err := ix.Apply(context.Background(), msg); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, name := range []string{"zsh", "mesh-delivery", "mailu"} {
|
||||||
|
m, ok := moduleOf(t, mods, name)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: no module key written", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (m.FP == "") == (m.None == "") || m.Repository != "novox/mesh-catalog" || m.Commit != merge {
|
||||||
|
t.Errorf("%s: module = %+v, want an fp or a none, repository novox/mesh-catalog, commit %s", name, m, merge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the tree of zsh at the merge is the tree its inputs record, so its fingerprint is the one those inputs give
|
||||||
|
var in delivery.SourceInputs
|
||||||
|
in.Module, in.Tree = "zsh", liveTree
|
||||||
|
in.Bases = []string{"sha256:25f3cfeaceca14921366af4d1240b56457ef46273bdb508c7b0e8f469f6fd228"}
|
||||||
|
in.Toolchains = map[string]string{"code": "typescript sha256:4b81682df5665c1f461d3f720a3a0fd7593f4aa71e4fc272dbfdc4bbd235b489 20125bd590041690"}
|
||||||
|
want, _ := delivery.SourceFingerprint(in)
|
||||||
|
if m, _ := moduleOf(t, mods, "zsh"); m.FP != want {
|
||||||
|
t.Errorf("zsh fp = %q, want %q", m.FP, want)
|
||||||
|
}
|
||||||
|
if _, ok := mods.vals["sonarr"]; ok {
|
||||||
|
t.Error("the key of a module of mesh-media-catalog was written at a merge of mesh-catalog")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChangedDirectoryMapsToTheModuleItsLiveRecordNamesByPath(t *testing.T) { // 1281/AC4
|
||||||
|
ix, _ := liveIndexer(t)
|
||||||
|
for dir, want := range map[string]string{"modules/zsh": "zsh", "modules/mesh-delivery": "mesh-delivery", "modules/mailu/": "mailu"} {
|
||||||
|
if got, ok := ix.Builds.Module("novox/mesh-catalog", dir); !ok || got != want {
|
||||||
|
t.Errorf("Module(novox/mesh-catalog, %q) = %q, %v; want %q", dir, got, ok, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got, ok := ix.Builds.Module("novox/mesh-catalog", "modules/sonarr"); ok {
|
||||||
|
t.Errorf("Module found %q for a directory of mesh-media-catalog under mesh-catalog", got)
|
||||||
|
}
|
||||||
|
if got := ix.Builds.ModuleOf("novox/mesh-media-catalog", "modules/sonarr"); got != "sonarr" {
|
||||||
|
t.Errorf("ModuleOf(mesh-media-catalog, modules/sonarr) = %q, want sonarr", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ module mesh-delivery-view
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019124204-9ade199740e3
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019203925-84a764372337
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019124204-9ade199740e3 h1:Mu2QiGbL/sZFU5ya4lGUyQOB5OKXPy/KB40XfXTf2EU=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019203925-84a764372337 h1:cZyFnvDiBpnNx8ifSUG9WA61OHsn7PC2ml2s4eD7AkA=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019124204-9ade199740e3/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019203925-84a764372337/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ func modEntry(t *testing.T, name string, m delivery.Module) Entry {
|
|||||||
func trunkBuild(module, id, ref, state, fp, at string) []Entry {
|
func trunkBuild(module, id, ref, state, fp, at string) []Entry {
|
||||||
rec := `{"id":"` + id + `","state":"` + state + `","repository":"novox/mesh-delivery","ref":"` + ref + `","module":"` + module + `","commit":"7a74cee83f88e6585a5511c3cc53bdf0f5303d20","path":"modules/` + module + `","at":"` + at + `"`
|
rec := `{"id":"` + id + `","state":"` + state + `","repository":"novox/mesh-delivery","ref":"` + ref + `","module":"` + module + `","commit":"7a74cee83f88e6585a5511c3cc53bdf0f5303d20","path":"modules/` + module + `","at":"` + at + `"`
|
||||||
if fp != "" {
|
if fp != "" {
|
||||||
rec += `,"source-fingerprint":"` + fp + `"`
|
// the controller writes the inputs whenever it writes the fingerprint (mesh-sdk testdata/build-record-build-agent.json)
|
||||||
|
rec += `,"source-fingerprint":"` + fp + `","source-inputs":{"Module":"` + module + `","Tree":"t","Bases":null,"Contexts":null,"Toolchains":null,"Unpinned":""}`
|
||||||
}
|
}
|
||||||
rec += `}`
|
rec += `}`
|
||||||
return []Entry{
|
return []Entry{
|
||||||
@@ -188,3 +189,23 @@ func TestUnreadableIndexerValuesAreNamed(t *testing.T) { // never dropped silent
|
|||||||
t.Errorf("snapshot = %+v", s)
|
t.Errorf("snapshot = %+v", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPlanAskedBuildWithNoRefIsATrunkBuild(t *testing.T) { // 1281/AC5
|
||||||
|
// The build record of module mesh-delivery from mesh-catalog, build-1792435539606203626, as `mesh-controller.command
|
||||||
|
// builds` showed it on 2026-10-19 at about 21:20 local: built on ace at 20:45 local, the repository the clone URL, the
|
||||||
|
// source fingerprint below, and no ref (a delivery plan asked it after a merge; the listing shows no "at main").
|
||||||
|
// No reading command shows the whole record: its path is the module's directory in the catalogue, and its
|
||||||
|
// source-inputs are mailu's live ones (mesh-sdk go/delivery/fingerprint_test.go) with the module's name put in.
|
||||||
|
const live = "src1:f79e2eedc2689cf6b3591ebfdb81ec65899131122fb2fe3dd9e384091d8a2a6d"
|
||||||
|
const rec = `{"id":"build-1792435539606203626","state":"built","repository":"http://novox.internal:20000/novox/mesh-catalog.git",` +
|
||||||
|
`"module":"mesh-delivery","commit":"9efdd87a","path":"modules/mesh-delivery","at":"2026-10-19T18:45:00Z","source-fingerprint":"` + live + `",` +
|
||||||
|
`"source-inputs":{"Module":"mesh-delivery","Tree":"cfc6fa8ff5fc1e220132e51ade653b9d3f387b3e","Bases":["sha256:25f3cfeaceca14921366af4d1240b56457ef46273bdb508c7b0e8f469f6fd228"],"Contexts":null,"Toolchains":null,"Unpinned":""}}`
|
||||||
|
m := New()
|
||||||
|
fill(m,
|
||||||
|
Entry{Bucket: BuildsBucket, Key: "mesh-delivery.build.build-1792435539606203626", Value: []byte(rec), At: t0},
|
||||||
|
Entry{Bucket: BuildsBucket, Key: "mesh-delivery.newest", Value: []byte("build-1792435539606203626"), At: t0},
|
||||||
|
modEntry(t, "mesh-delivery", delivery.Module{FP: live, Repository: "novox/mesh-catalog", Commit: hA}))
|
||||||
|
if r := fpRow(t, m.Snapshot(), "mesh-delivery"); r.State != FPSame || r.Build != live {
|
||||||
|
t.Errorf("mesh-delivery = %+v, want same as the no-ref build's fingerprint", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -454,12 +454,12 @@ func (m *Model) groupRows() []GroupRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fingerprintRows compare each module the indexer fingerprinted with the source fingerprint of the module's newest
|
// fingerprintRows compare each module the indexer fingerprinted with the source fingerprint of the module's newest
|
||||||
// successful trunk build (ref main, state built) that recorded one. The newest key is any state and ref, so it is not
|
// successful trunk build (delivery.BuildRecord.IsTrunkBuild: built, on main or with no ref) that recorded one. The newest key is any state and ref, so it is not
|
||||||
// used: the records are.
|
// used: the records are.
|
||||||
func (m *Model) fingerprintRows() []FingerprintRow {
|
func (m *Model) fingerprintRows() []FingerprintRow {
|
||||||
newest := map[string]buildEntry{}
|
newest := map[string]buildEntry{}
|
||||||
for _, b := range m.builds {
|
for _, b := range m.builds {
|
||||||
if b.rec.Module == "" || b.rec.Ref != "main" || b.rec.State != "built" || b.rec.SourceFingerprint == "" {
|
if b.rec.Module == "" || !b.rec.IsTrunkBuild() || b.rec.SourceFingerprint == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if old, ok := newest[b.rec.Module]; !ok || b.rec.At.After(old.rec.At) || (b.rec.At.Equal(old.rec.At) && b.rec.ID > old.rec.ID) {
|
if old, ok := newest[b.rec.Module]; !ok || b.rec.At.After(old.rec.At) || (b.rec.At.Equal(old.rec.At) && b.rec.ID > old.rec.ID) {
|
||||||
|
|||||||
Reference in New Issue
Block a user