Merge pull request 'Ask a new module's first build when its merge is heard, not when its batch is cut, so it can be assigned right after its delivery (issue 1617)' (#417) from fix/1617-register-new-modules into main
This commit was merged in pull request #417.
This commit is contained in:
@@ -232,6 +232,8 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
|
|||||||
fmt.Print(nothingReadsIt(repository, m))
|
fmt.Print(nothingReadsIt(repository, m))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// **A module the merge adds is asked for now** (novox/hq issue 1617), not when the batch is cut.
|
||||||
|
askNewModulesAtHearing(ctx, inv, m, entries, read, facts.from)
|
||||||
// **Its delivery group's word, before the plans are held** (ADR 0306 decision 2): a slow answer holds up no walk.
|
// **Its delivery group's word, before the plans are held** (ADR 0306 decision 2): a slow answer holds up no walk.
|
||||||
own := ownPath(touches)
|
own := ownPath(touches)
|
||||||
waits, holding, err := groupWaitHolding(ctx, inv, m, deliverySeatHeld(entries))
|
waits, holding, err := groupWaitHolding(ctx, inv, m, deliverySeatHeld(entries))
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/novox/mesh-controller/internal/inventory"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 1617/AC2 (unit): a new module whose first build a merge's hearing asked is not asked again when its batch is cut,
|
||||||
|
// and one whose ask failed, was never handed over or is long lost is.
|
||||||
|
func TestAModuleWhoseBuildIsComingIsNotAskedAgain(t *testing.T) {
|
||||||
|
now := time.Date(2026, 10, 21, 12, 0, 0, 0, time.UTC)
|
||||||
|
source := buildSource{Repository: "http://forge/novox/mesh-catalog.git", Seat: "git"}
|
||||||
|
asked := func(path string, mod func(*inventory.RequestOutcome)) inventory.RequestOutcome {
|
||||||
|
r := inventory.RequestOutcome{BuildRequest: inventory.BuildRequest{ID: "b-" + path, Repository: source.Repository,
|
||||||
|
Seat: "git", Path: path, For: "merge", At: now.Add(-3 * time.Minute)}}
|
||||||
|
if mod != nil {
|
||||||
|
mod(&r)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
requests := []inventory.RequestOutcome{
|
||||||
|
asked("modules/go", nil),
|
||||||
|
asked("modules/failed", func(r *inventory.RequestOutcome) { r.Heard, r.Failed = true, "no module.json" }),
|
||||||
|
asked("modules/notasked", func(r *inventory.RequestOutcome) { r.NotAsked = "the bus is silent" }),
|
||||||
|
asked("modules/lost", func(r *inventory.RequestOutcome) { r.At = now.Add(-2 * time.Hour) }),
|
||||||
|
asked("modules/byhand", func(r *inventory.RequestOutcome) { r.For = "build" }),
|
||||||
|
}
|
||||||
|
got := unaskedDirs([]string{"modules/go", "modules/failed", "modules/notasked", "modules/lost", "modules/byhand",
|
||||||
|
"modules/nodejs"}, requests, source, now)
|
||||||
|
want := []string{"modules/failed", "modules/notasked", "modules/lost", "modules/byhand", "modules/nodejs"}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("asked again %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
"github.com/novox/mesh-controller/internal/inventory"
|
"github.com/novox/mesh-controller/internal/inventory"
|
||||||
@@ -99,3 +100,38 @@ func TestAMergeBuildsItsNewModuleBesideItsPlan(t *testing.T) {
|
|||||||
t.Fatalf("a merge adding no module asked for %v", *asked)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -415,7 +415,59 @@ func askNewModules(ctx context.Context, inv *inventory.Inventory, m link.SourceM
|
|||||||
if len(added) == 0 || len(from) == 0 {
|
if len(added) == 0 || len(from) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return askBuilds(ctx, inv, m, buildSource{Repository: from[0].Source.Repository, Seat: from[0].Source.Seat}, added)
|
source := buildSource{Repository: from[0].Source.Repository, Seat: from[0].Source.Seat}
|
||||||
|
// Asked once (novox/hq issue 1617): a module is asked when its merge is heard, and again here when the batch is cut.
|
||||||
|
var requests []inventory.RequestOutcome
|
||||||
|
for _, dir := range added {
|
||||||
|
if got, err := inv.RequestsNamed(ctx, inventory.BuildRequest{Repository: source.Repository, Path: dir}.Name()); err == nil {
|
||||||
|
requests = append(requests, got...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return askBuilds(ctx, inv, m, source, unaskedDirs(added, requests, source, time.Now()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// askedInFlightFor is how long a merge's build request stands as the build coming for its module: past it, a lost one
|
||||||
|
// is asked again.
|
||||||
|
const askedInFlightFor = time.Hour
|
||||||
|
|
||||||
|
// unaskedDirs are the directories of a new module no build of a merge is on its way for: a kept request of the same
|
||||||
|
// repository and directory, asked for a merge and handed over, whose outcome has not come and is not given up on,
|
||||||
|
// is the build coming (novox/hq issue 1617).
|
||||||
|
func unaskedDirs(added []string, requests []inventory.RequestOutcome, source buildSource, now time.Time) []string {
|
||||||
|
var out []string
|
||||||
|
for _, dir := range added {
|
||||||
|
path := dir
|
||||||
|
if path == "." {
|
||||||
|
path = ""
|
||||||
|
}
|
||||||
|
coming := false
|
||||||
|
for _, r := range requests {
|
||||||
|
if r.For == "merge" && r.Repository == source.Repository && strings.Trim(r.Path, "/") == path &&
|
||||||
|
r.NotAsked == "" && r.OutcomeUnknown == "" && !r.Heard && now.Sub(r.At) < askedInFlightFor {
|
||||||
|
coming = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !coming {
|
||||||
|
out = append(out, dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// askNewModulesAtHearing asks the first build of the modules a merge adds to a repository the mesh builds modules from,
|
||||||
|
// when the merge is heard, not when its batch is cut (novox/hq issue 1617): a batch waits out its window and the walk
|
||||||
|
// before it, and a module added by a merge delivered meanwhile could not be assigned. Outside the batch and its plan.
|
||||||
|
func askNewModulesAtHearing(ctx context.Context, inv *inventory.Inventory, m link.SourceMoved, entries []inventory.Entry,
|
||||||
|
read map[string][]inventory.ReadRepository, from []inventory.Entry) {
|
||||||
|
if len(from) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, added, _ := touchedBy(from, entries, m, read)
|
||||||
|
if asked := askNewModules(ctx, inv, m, from, added); len(asked) > 0 {
|
||||||
|
fmt.Printf("%s/%s merged into %s (%.8s) adds %s: the first build is asked now, before its batch is cut\n",
|
||||||
|
m.Owner, m.Repo, m.Base, m.Commit, strings.Join(asked, ", "))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// askBuilds asks the first build of each directory of a merge's repository, as askNewModules does, from the source given.
|
// askBuilds asks the first build of each directory of a merge's repository, as askNewModules does, from the source given.
|
||||||
|
|||||||
Reference in New Issue
Block a user