Checker: off mesh-sdk go/delivery onto the seats' types and the clients (verdict, kept, preview, owed, candidates, modules, manifests, packages, source, turns, haves), its holders and buckets its own constants checked against its manifest (D-11), the check ask's fetch typed with the group package and the check answer's fetch read as the Delivery SDK's FetchAnswer, a merge with no program told by merge (B2g; ADR 0634 §1, §2, §6; issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -10,13 +10,14 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
)
|
||||
|
||||
// reads counts the values this call read from the bus: the runtime launches the tools for one call, so the count is the call's.
|
||||
@@ -68,7 +69,7 @@ func (r runsOf) CommitOf(module string) string {
|
||||
if err != nil || len(raw) == 0 {
|
||||
return ""
|
||||
}
|
||||
m, err := delivery.DecodeModule(raw)
|
||||
m, err := meshcatalogclient.DecodeModule(raw)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -292,11 +293,11 @@ func main() {
|
||||
tries := stdio.State(check.TriesState)
|
||||
source := stdio.State("mesh-delivery-gate.source")
|
||||
trunks := func() (map[string]string, error) {
|
||||
e, err := source.Get(delivery.SourceMainKey)
|
||||
e, err := source.Get(meshdeliveryclient.SourceMainKey)
|
||||
if err != nil || e == nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := delivery.DecodeSource(e.Value)
|
||||
s, err := meshdeliveryclient.DecodeSource(e.Value)
|
||||
return s.Heads, err
|
||||
}
|
||||
r.Trunks = trunks
|
||||
@@ -330,7 +331,7 @@ func main() {
|
||||
tools := append(append(append(Tools(r), TryTools(trying)...), SeatTryTools(trying)...), RecoveryTools(r, written)...)
|
||||
// checker_first_views and checker_first_view_again: where the checker's first views of the indexer's and the assigner's
|
||||
// states stand, and asking one again by hand (ADR 0615, DLV-M2).
|
||||
tools = append(tools, views.StatusTools(delivery.CheckerModule, "checker_first_views", "checker_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, time.Now)...)
|
||||
tools = append(tools, views.StatusTools(check.CheckerModule, "checker_first_views", "checker_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, time.Now)...)
|
||||
if err := stdio.Serve("", logged(tools)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[mesh-delivery-checker-tools] %v\n", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
@@ -110,7 +110,7 @@ func follow(ctx context.Context, w jetstream.KeyWatcher, each func(jetstream.Key
|
||||
type manifestReader struct{ kv jetstream.KeyValue }
|
||||
|
||||
func (m manifestReader) Manifest(ctx context.Context, digest string) (catalogue.Manifest, error) {
|
||||
key, err := delivery.ManifestsKey(digest)
|
||||
key, err := meshcatalogclient.ManifestsKey(digest)
|
||||
if err != nil {
|
||||
return catalogue.Manifest{}, err
|
||||
}
|
||||
@@ -118,11 +118,11 @@ func (m manifestReader) Manifest(ctx context.Context, digest string) (catalogue.
|
||||
if err != nil {
|
||||
return catalogue.Manifest{}, fmt.Errorf("manifests.%s: %w", key, err)
|
||||
}
|
||||
kept, err := delivery.DecodeManifest(e.Value())
|
||||
kept, err := meshcatalogclient.DecodeManifest(e.Value())
|
||||
if err != nil {
|
||||
return catalogue.Manifest{}, err
|
||||
}
|
||||
return parseManifest(kept.JSON)
|
||||
return parseManifest(kept.Manifest)
|
||||
}
|
||||
|
||||
// parseManifest decodes a manifest the indexer kept, as every reader does (novox/hq ADR 0578 §3): judged by nothing here,
|
||||
|
||||
@@ -5,19 +5,24 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
nodeengineseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
nodeengineseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
|
||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The checker's composition of a check on the bus (novox/hq ADR 0593 §7, §9; check.EngineCompositions): each placed
|
||||
@@ -65,18 +70,18 @@ func (p placedNodes) Placed() ([]string, error) {
|
||||
defer cancel()
|
||||
kv, err := p.kv(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the state %s cannot be read: %w", delivery.PlacementBucket, err)
|
||||
return nil, fmt.Errorf("the state %s cannot be read: %w", buildrun.PlacementBucket, err)
|
||||
}
|
||||
lister, err := kv.ListKeys(ctx)
|
||||
if errors.Is(err, jetstream.ErrNoKeysFound) {
|
||||
return nil, nil // a placement that places nothing
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the state %s cannot be listed: %w", delivery.PlacementBucket, err)
|
||||
return nil, fmt.Errorf("the state %s cannot be listed: %w", buildrun.PlacementBucket, err)
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for key := range lister.Keys() {
|
||||
if node, _, _, err := delivery.ParsePlacementKey(key); err == nil {
|
||||
if node, _, _, err := meshmoduleassignerclient.ParseKey(key); err == nil {
|
||||
seen[node] = true
|
||||
}
|
||||
}
|
||||
@@ -89,20 +94,20 @@ func (p placedNodes) Placed() ([]string, error) {
|
||||
}
|
||||
|
||||
// seatSandbox runs a compose job on the Go build seat's check stream, as the checker's own ask, and answers the
|
||||
// composer's verdict from the step's result (delivery.ComposeAnswer).
|
||||
// composer's verdict from the step's result (buildstep.ComposeAnswer).
|
||||
type seatSandbox struct{ asker buildrun.Asker }
|
||||
|
||||
func (s seatSandbox) Compose(ctx context.Context, job delivery.ComposeJob) (composer.Verdict, error) {
|
||||
ask := delivery.StepAsk{ID: buildstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: delivery.CheckerModule,
|
||||
func (s seatSandbox) Compose(ctx context.Context, job buildstep.ComposeJob) (composer.Verdict, error) {
|
||||
ask := buildstep.StepAsk{ID: buildstep.NewID(), Kind: buildstep.AskCheck, Language: buildstep.AskGo, Asker: check.CheckerModule,
|
||||
At: time.Now().UTC(), Compose: &job}
|
||||
step, err := s.asker.Ask(ctx, ask)
|
||||
if err != nil {
|
||||
return composer.Verdict{}, err
|
||||
}
|
||||
if step.State != delivery.StepPassed {
|
||||
if step.State != buildstep.StepPassed {
|
||||
return composer.Verdict{}, fmt.Errorf("the compose ask %s ended %s: %s", ask.ID, step.State, step.Reason)
|
||||
}
|
||||
var a delivery.ComposeAnswer
|
||||
var a buildstep.ComposeAnswer
|
||||
if err := json.Unmarshal(step.Result, &a); err != nil {
|
||||
return composer.Verdict{}, fmt.Errorf("the compose ask %s answered no compose answer: %w", ask.ID, err)
|
||||
}
|
||||
@@ -114,17 +119,17 @@ func (s seatSandbox) Compose(ctx context.Context, job delivery.ComposeJob) (comp
|
||||
}
|
||||
|
||||
// manifestVerdicts are the indexer's refusals of the trunk's manifests, by module, from its modules' values
|
||||
// (delivery.Module.VerdictOf, ADR 0578 §3): what the sandbox's composer holds a refused module by.
|
||||
// (meshcatalog.ModulesByModuleValue.VerdictOf, ADR 0578 §3): what the sandbox's composer holds a refused module by.
|
||||
type manifestVerdicts struct {
|
||||
mu sync.Mutex
|
||||
by map[string]delivery.ManifestVerdict
|
||||
by map[string]meshcatalog.VerdictsByDigestValue
|
||||
}
|
||||
|
||||
func (m *manifestVerdicts) put(module string, v *delivery.ManifestVerdict) {
|
||||
func (m *manifestVerdicts) put(module string, v *meshcatalog.VerdictsByDigestValue) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.by == nil {
|
||||
m.by = map[string]delivery.ManifestVerdict{}
|
||||
m.by = map[string]meshcatalog.VerdictsByDigestValue{}
|
||||
}
|
||||
if v == nil {
|
||||
delete(m.by, module)
|
||||
@@ -132,13 +137,13 @@ func (m *manifestVerdicts) put(module string, v *delivery.ManifestVerdict) {
|
||||
}
|
||||
// The sandbox's composer may be the node-engine of an older delivery, which does not know the verdict waits: it is handed a
|
||||
// wait as the refusal it is held as.
|
||||
m.by[module] = v.AsRefused()
|
||||
m.by[module] = meshcatalogclient.VerdictAsRefused(*v)
|
||||
}
|
||||
|
||||
func (m *manifestVerdicts) all() map[string]delivery.ManifestVerdict {
|
||||
func (m *manifestVerdicts) all() map[string]meshcatalog.VerdictsByDigestValue {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
out := make(map[string]delivery.ManifestVerdict, len(m.by))
|
||||
out := make(map[string]meshcatalog.VerdictsByDigestValue, len(m.by))
|
||||
for k, v := range m.by {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
@@ -22,18 +22,30 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
|
||||
gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
"git.novox.be/novox/mesh-sdk/go/views/viewstest"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
)
|
||||
|
||||
// busConf is the shape of the composed accounts file (mesh-controller internal/broker): one account with JetStream, the
|
||||
@@ -98,7 +110,7 @@ type fakeBuildModules struct {
|
||||
// names are the nodes' names the last check ask carried, for the names check (ADR 0567 §12).
|
||||
names []string
|
||||
// packages are, by repository, the package versions the last Go check ask of it carried (StepAsk.Packages).
|
||||
packages map[string]delivery.PackageVersions
|
||||
packages map[string]buildstep.PackageVersions
|
||||
}
|
||||
|
||||
func (f *fakeBuildModules) count(tool string) int {
|
||||
@@ -158,7 +170,7 @@ func (f *fakeBuildModules) result(t *testing.T, tool string, args map[string]any
|
||||
// does since R4g (ADR 0525; issue 1887 A5): each ask's job runs with step bodies answering the captured results, and a
|
||||
// check takes the kept verdicts of the checker's kept bucket by their digests.
|
||||
func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.KeyValue) {
|
||||
kv, err := f.js.CreateKeyValue(f.ctx, jetstream.KeyValueConfig{Bucket: delivery.StepsBucket(module), History: 1})
|
||||
kv, err := f.js.CreateKeyValue(f.ctx, jetstream.KeyValueConfig{Bucket: buildrun.StepsBucket(module), History: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -178,7 +190,7 @@ func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.Key
|
||||
}
|
||||
steps := gobuild.AskSteps{Fetch: call("fetch"), Test: call("test"),
|
||||
Kept: func(ctx context.Context, level, digest string) (int, bool, error) {
|
||||
key, err := delivery.KeptKey(level, digest)
|
||||
key, err := meshdeliverycheckerclient.KeptKey(level, digest)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
@@ -189,8 +201,8 @@ func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.Key
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
v, err := delivery.DecodeKept(e.Value())
|
||||
return v.Tests, err == nil, err
|
||||
v, err := meshdeliverycheckerclient.DecodeKept(e.Value())
|
||||
return int(v.Tests), err == nil, err
|
||||
}}
|
||||
if module == "mesh-build-go" {
|
||||
steps.Digest = call("digest")
|
||||
@@ -201,13 +213,13 @@ func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.Key
|
||||
}
|
||||
p := &gobuild.Puller{Source: &gobuild.JetStreamAsks{JS: f.js, Language: language}, Language: language, Node: "n1", Put: put,
|
||||
Capacity: func() int { return 2 }, PullWait: 200 * time.Millisecond,
|
||||
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
|
||||
Run: func(ctx context.Context, a buildstep.StepAsk) (any, error) {
|
||||
if a.Check != nil {
|
||||
f.mu.Lock()
|
||||
f.names = a.Check.NodeNames
|
||||
if repository, _ := a.Check.Fetch["repository"].(string); a.Language == delivery.AskGo {
|
||||
if repository := a.Check.Fetch.Repository; a.Language == buildstep.AskGo {
|
||||
if f.packages == nil {
|
||||
f.packages = map[string]delivery.PackageVersions{}
|
||||
f.packages = map[string]buildstep.PackageVersions{}
|
||||
}
|
||||
f.packages[repository] = a.Packages
|
||||
}
|
||||
@@ -311,13 +323,13 @@ func (b *bed) said(t *testing.T, words string) {
|
||||
// the seat verb's caller-named and plain subjects); the others never answer. It answers what it was asked.
|
||||
func (b *bed) engine(t *testing.T, nodes ...string) func() []ne.ComposeCandidateInput {
|
||||
t.Helper()
|
||||
kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: delivery.PlacementBucket, History: 1})
|
||||
kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: buildrun.PlacementBucket, History: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, n := range nodes {
|
||||
pk, _ := delivery.PlacementNodeKey(n)
|
||||
raw, _ := json.Marshal(delivery.Placement{Node: n})
|
||||
pk, _ := meshmoduleassignerclient.NodeKey(n)
|
||||
raw, _ := meshmoduleassignerclient.EncodeNode(meshmoduleassigner.PlacementByNodeNodeValue{Node: n})
|
||||
if _, err := kv.Put(b.ctx, pk, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -412,7 +424,7 @@ func startBed(t *testing.T) *bed {
|
||||
if _, err := js.CreateOrUpdateConsumer(ctx, stream, jetstream.ConsumerConfig{Durable: "n1_mesh-delivery-checker", AckPolicy: jetstream.AckExplicitPolicy}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: delivery.ToolchainsBucket(module), History: 1})
|
||||
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: buildrun.ToolchainsBucket(module), History: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -424,11 +436,11 @@ func startBed(t *testing.T) *bed {
|
||||
}
|
||||
// The checker's streams as its manifest declares them (ADR 0619, ADR 0620): checks, which the indexer and the forge
|
||||
// bridge hear, and asks, with verdict and tag-wanted at the subjects the mesh-delivery-checker definition names.
|
||||
if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "CHECKS", Subjects: []string{meshdeliverychecker.HolderSubjectPreviewWritten(delivery.CheckerModule)}}); err != nil {
|
||||
if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "CHECKS", Subjects: []string{meshdeliverychecker.HolderSubjectPreviewWritten(check.CheckerModule)}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "ASKS", Subjects: []string{meshdeliverychecker.HolderSubjectVerdict(delivery.CheckerModule),
|
||||
meshdeliverychecker.HolderSubjectTagWanted(delivery.CheckerModule)}}); err != nil {
|
||||
if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "ASKS", Subjects: []string{meshdeliverychecker.HolderSubjectVerdict(check.CheckerModule),
|
||||
meshdeliverychecker.HolderSubjectTagWanted(check.CheckerModule)}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The shared events stream with the seats' and modules' events (the package registry seats' published among them), and the checker's durable consumer of it.
|
||||
@@ -438,11 +450,11 @@ func startBed(t *testing.T) *bed {
|
||||
if _, err := js.CreateOrUpdateConsumer(ctx, process.EventsStream, jetstream.ConsumerConfig{Durable: "n1_mesh-delivery-checker", AckPolicy: jetstream.AckExplicitPolicy}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for name, history := range map[string]int{delivery.CheckerVerdictsBucket: delivery.CheckerVerdictsHistory, delivery.CheckerKeptBucket: delivery.CheckerKeptHistory,
|
||||
delivery.CheckerOwedBucket: delivery.CheckerOwedHistory, delivery.PreviewsBucket: delivery.PreviewsHistory, delivery.CandidatesBucket: delivery.CandidatesHistory,
|
||||
delivery.ModulesBucket: delivery.ModulesHistory, delivery.ManifestsBucket: delivery.ManifestsHistory, delivery.SourceBucket: delivery.SourceHistory,
|
||||
delivery.PresenceBucket: delivery.PresenceHistory, delivery.HaveBucket: delivery.HaveHistory, check.TriesBucket: 1,
|
||||
delivery.TurnsBucket: delivery.TurnsHistory, delivery.BuilderBuildsBucket: 1} {
|
||||
for name, history := range map[string]int{check.CheckerVerdictsBucket: 5, check.CheckerKeptBucket: 1,
|
||||
check.CheckerOwedBucket: 5, check.PreviewsBucket: 5, check.CandidatesBucket: 10,
|
||||
check.ModulesBucket: 20, check.ManifestsBucket: 1, check.SourceBucket: 64,
|
||||
check.PresenceBucket: 20, check.HaveBucket: 20, check.TriesBucket: 1,
|
||||
check.TurnsBucket: 5, builds.BuilderBuildsBucket: 1} {
|
||||
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: name, History: uint8(history)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -451,17 +463,17 @@ func startBed(t *testing.T) *bed {
|
||||
}
|
||||
// The indexer and the assigner as the owners of the states the checker follows: their read tools and views families
|
||||
// (ADR 0615, DLV-M2).
|
||||
for _, st := range []string{candidatesState, modulesState, manifestsState, delivery.PackagesState, delivery.IndexerPredefinedState, seatDefinitionsState} {
|
||||
viewstest.Serve(t, ctx, js, ctl, delivery.IndexerModule, st, delivery.IndexerModule+".indexer_read_"+strings.ReplaceAll(st, "-", "_"), "n1_"+delivery.CheckerModule)
|
||||
for _, st := range []string{candidatesState, modulesState, manifestsState, meshcatalog.StatePackages, check.IndexerPredefinedState, seatDefinitionsState} {
|
||||
viewstest.Serve(t, ctx, js, ctl, check.IndexerModule, st, check.IndexerModule+".indexer_read_"+strings.ReplaceAll(st, "-", "_"), "n1_"+check.CheckerModule)
|
||||
}
|
||||
viewstest.Serve(t, ctx, js, ctl, delivery.AssignerModule, delivery.PlacementState, delivery.AssignerModule+".assigner_read_placement", "n1_"+delivery.CheckerModule)
|
||||
viewstest.Serve(t, ctx, js, ctl, buildrun.AssignerModule, meshmoduleassigner.StatePlacement, buildrun.AssignerModule+".assigner_read_placement", "n1_"+check.CheckerModule)
|
||||
// 1929/AC1: the indexer's value of the repository's module, with no TypeScript manifest, so the Go build module alone
|
||||
// judges the repository (an unknown repository is judged by both, issue 1887 A12).
|
||||
known, err := delivery.EncodeModule(delivery.Module{Repository: repo, Commit: trunk, Path: "modules/m", None2: "the fingerprint is computed with its chain"})
|
||||
known, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: repo, Commit: trunk, Path: "modules/m", None2: "the fingerprint is computed with its chain"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.buckets[delivery.ModulesBucket].Put(ctx, "m", known); err != nil {
|
||||
if _, err := b.buckets[check.ModulesBucket].Put(ctx, "m", known); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.setOnline(t, "n1", true)
|
||||
@@ -471,8 +483,8 @@ func startBed(t *testing.T) *bed {
|
||||
}
|
||||
b.serveForge(t, ctl)
|
||||
b.fake = &fakeBuildModules{js: js, ctx: ctx, calls: map[string]int{}}
|
||||
b.fake.serve(t, "mesh-build-go", b.buckets[delivery.CheckerKeptBucket])
|
||||
b.fake.serve(t, "mesh-build-node", b.buckets[delivery.CheckerKeptBucket])
|
||||
b.fake.serve(t, "mesh-build-go", b.buckets[check.CheckerKeptBucket])
|
||||
b.fake.serve(t, "mesh-build-node", b.buckets[check.CheckerKeptBucket])
|
||||
b.report(t, "mesh-build-go", "n1", "go", "go1.27.1", "1")
|
||||
b.report(t, "mesh-build-node", "n1", "typescript", "tsc5", "1")
|
||||
b.setTrunk(t, map[string]string{repo: trunk})
|
||||
@@ -481,19 +493,19 @@ func startBed(t *testing.T) *bed {
|
||||
|
||||
func (b *bed) setTrunk(t *testing.T, heads map[string]string) {
|
||||
t.Helper()
|
||||
data, err := delivery.EncodeSource(delivery.Source{Group: "g", Heads: heads})
|
||||
data, err := meshdeliveryclient.EncodeSource(meshdelivery.SourceByMainValue{Group: "g", Heads: heads})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.buckets[delivery.SourceBucket].Put(b.ctx, delivery.SourceMainKey, data); err != nil {
|
||||
if _, err := b.buckets[check.SourceBucket].Put(b.ctx, meshdeliveryclient.SourceMainKey, data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bed) setOnline(t *testing.T, node string, online bool) {
|
||||
t.Helper()
|
||||
data, _ := delivery.EncodePresence(delivery.Presence{Online: online})
|
||||
if _, err := b.buckets[delivery.PresenceBucket].Put(b.ctx, node, data); err != nil {
|
||||
data, _ := json.Marshal(meshwatchdogs.PresenceByNodeValue{V: keys.Version, Online: online})
|
||||
if _, err := b.buckets[check.PresenceBucket].Put(b.ctx, node, data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -502,35 +514,35 @@ func (b *bed) setOnline(t *testing.T, node string, online bool) {
|
||||
// check ask (ADR 0567 §12); with no record the checker asks no check.
|
||||
func (b *bed) setNode(t *testing.T, node string) {
|
||||
t.Helper()
|
||||
data, err := delivery.EncodeNodeRecord(delivery.NodeRecord{V: 1, Node: node})
|
||||
data, err := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: node}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, _ := delivery.HaveNodeKey(node)
|
||||
if _, err := b.buckets[delivery.HaveBucket].Put(b.ctx, key, data); err != nil {
|
||||
key, _ := nodeengineclient.HaveNodeKey(node)
|
||||
if _, err := b.buckets[check.HaveBucket].Put(b.ctx, key, data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bed) setHealth(t *testing.T, node, module string) {
|
||||
t.Helper()
|
||||
data, err := delivery.EncodeHave(delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK})
|
||||
data, err := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, _ := delivery.HaveKey(node, module)
|
||||
if _, err := b.buckets[delivery.HaveBucket].Put(b.ctx, key, data); err != nil {
|
||||
key, _ := nodeengineclient.HaveKey(node, module)
|
||||
if _, err := b.buckets[check.HaveBucket].Put(b.ctx, key, data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bed) report(t *testing.T, module, node, language, version, recipe string) {
|
||||
t.Helper()
|
||||
data, err := delivery.EncodeToolchain(delivery.Toolchain{Language: language, Version: version, Recipe: recipe, At: time.Now().UTC()})
|
||||
data, err := buildstep.EncodeToolchain(buildstep.Toolchain{Language: language, Version: version, Recipe: recipe, At: time.Now().UTC()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, _ := delivery.ToolchainKey(node, language)
|
||||
key, _ := buildstep.ToolchainKey(node, language)
|
||||
if _, err := b.tools[module].Put(b.ctx, key, data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -540,30 +552,30 @@ func (b *bed) report(t *testing.T, module, node, language, version, recipe strin
|
||||
}
|
||||
|
||||
// candidate writes candidates.<group> as the indexer does (the SDK's EncodeCandidate is the indexer's own encoder).
|
||||
func (b *bed) candidate(t *testing.T, group string, pulls ...delivery.CandidatePull) string {
|
||||
func (b *bed) candidate(t *testing.T, group string, pulls ...meshcatalog.CandidatesByGroupValuePullsItem) string {
|
||||
t.Helper()
|
||||
heads := make([]delivery.Head, len(pulls))
|
||||
heads := make([]meshdeliveryclient.Head, len(pulls))
|
||||
for i, p := range pulls {
|
||||
heads[i] = p.Head
|
||||
heads[i] = meshdeliveryclient.Head{Repository: p.Repository, Number: int(p.Number), Head: p.Head}
|
||||
}
|
||||
hash, err := delivery.HeadsHash(heads)
|
||||
hash, err := meshdeliveryclient.HeadsHash(heads)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := delivery.EncodeCandidate(delivery.Candidate{V: delivery.Version, Group: group, HeadsHash: hash, Pulls: pulls, FPSet: delivery.FingerprintSet(pulls)})
|
||||
data, err := json.Marshal(meshcatalog.CandidatesByGroupValue{V: keys.Version, Group: group, Heads: hash, Pulls: pulls, Fpset: meshcatalogclient.FingerprintSet(pulls)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, _ := delivery.CandidatesKey(group)
|
||||
if _, err := b.buckets[delivery.CandidatesBucket].Put(b.ctx, key, data); err != nil {
|
||||
key, _ := meshcatalogclient.CandidatesKey(group)
|
||||
if _, err := b.buckets[check.CandidatesBucket].Put(b.ctx, key, data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
vk, _ := delivery.CheckerVerdictKey(group, hash)
|
||||
vk, _ := meshdeliverycheckerclient.VerdictKey(group, hash)
|
||||
return vk
|
||||
}
|
||||
|
||||
func pull(repository string, number int, head string) delivery.CandidatePull {
|
||||
return delivery.CandidatePull{Head: delivery.Head{Repository: repository, Number: number, Head: head}, Modules: []string{}}
|
||||
func pull(repository string, number int, head string) meshcatalog.CandidatesByGroupValuePullsItem {
|
||||
return meshcatalog.CandidatesByGroupValuePullsItem{Repository: repository, Number: int64(number), Head: head, Modules: []string{}}
|
||||
}
|
||||
|
||||
// start runs the program as the checker of a node.
|
||||
@@ -586,15 +598,15 @@ func (b *bed) start(t *testing.T, env map[string]string) {
|
||||
}
|
||||
|
||||
// verdict reads the verdict key until pred holds of it, within 20 s.
|
||||
func (b *bed) verdict(t *testing.T, key string, pred func(delivery.Verdict) bool) delivery.Verdict {
|
||||
func (b *bed) verdict(t *testing.T, key string, pred func(meshdeliverychecker.VerdictsByGroupHeadsValue) bool) meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
t.Helper()
|
||||
var last delivery.Verdict
|
||||
var last meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
e, err := b.buckets[delivery.CheckerVerdictsBucket].Get(b.ctx, key)
|
||||
e, err := b.buckets[check.CheckerVerdictsBucket].Get(b.ctx, key)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if last, err = delivery.DecodeVerdict(e.Value()); err == nil && pred(last) {
|
||||
if last, err = meshdeliverycheckerclient.DecodeVerdict(e.Value()); err == nil && pred(last) {
|
||||
return last
|
||||
}
|
||||
}
|
||||
@@ -602,22 +614,24 @@ func (b *bed) verdict(t *testing.T, key string, pred func(delivery.Verdict) bool
|
||||
return last
|
||||
}
|
||||
|
||||
func ended(v delivery.Verdict) bool { return v.State != delivery.VerdictRunning }
|
||||
func ended(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool {
|
||||
return v.State != meshdeliverycheckerclient.VerdictRunning
|
||||
}
|
||||
|
||||
// owed reads the status the checker owes on a head.
|
||||
func (b *bed) owed(t *testing.T, number int, head, state string) delivery.Owed {
|
||||
func (b *bed) owed(t *testing.T, number int, head, state string) meshdeliverychecker.OwedByRepositoryNumberHeadActValue {
|
||||
t.Helper()
|
||||
key, err := delivery.CheckerOwedKey(repo, number, head)
|
||||
key, err := check.CheckerOwedKey(repo, number, head)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var last delivery.Owed
|
||||
var last meshdeliverychecker.OwedByRepositoryNumberHeadActValue
|
||||
for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
e, err := b.buckets[delivery.CheckerOwedBucket].Get(b.ctx, key)
|
||||
e, err := b.buckets[check.CheckerOwedBucket].Get(b.ctx, key)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if last, err = delivery.DecodeCheckerOwed(e.Value()); err == nil && last.State == state {
|
||||
if last, err = check.DecodeCheckerOwed(e.Value()); err == nil && last.State == state {
|
||||
return last
|
||||
}
|
||||
}
|
||||
@@ -633,25 +647,25 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300
|
||||
// ADR 0603 §10: the candidate changes the module m, whose manifest the trunk holds; the builder has not built m's
|
||||
// candidate fingerprint yet, so the check waits on the builder's key before it composes.
|
||||
manifest := `{"module":"m"}`
|
||||
digest := delivery.ManifestDigest([]byte(manifest))
|
||||
stored, err := delivery.EncodeManifest(delivery.Manifest{Module: "m", JSON: manifest})
|
||||
digest := meshcatalogclient.ManifestDigest([]byte(manifest))
|
||||
stored, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "m", Manifest: manifest})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mkey, _ := delivery.ManifestsKey(digest)
|
||||
if _, err := b.buckets[delivery.ManifestsBucket].Put(b.ctx, mkey, stored); err != nil {
|
||||
mkey, _ := meshcatalogclient.ManifestsKey(digest)
|
||||
if _, err := b.buckets[check.ManifestsBucket].Put(b.ctx, mkey, stored); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
known, err := delivery.EncodeModule(delivery.Module{Repository: repo, Commit: trunk, Path: "modules/m", Manifest: digest, None2: "the fingerprint is computed with its chain"})
|
||||
known, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: repo, Commit: trunk, Path: "modules/m", Manifest: digest, None2: "the fingerprint is computed with its chain"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.buckets[delivery.ModulesBucket].Put(b.ctx, "m", known); err != nil {
|
||||
if _, err := b.buckets[check.ModulesBucket].Put(b.ctx, "m", known); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fp := "src2:" + strings.Repeat("f", 64)
|
||||
p := pull(repo, 7, head1)
|
||||
p.Modules, p.Fps = []string{"m"}, map[string]delivery.CandidateModule{"m": {FP2: fp, Manifest: digest}}
|
||||
p.Modules, p.Fps = []string{"m"}, map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"m": {Fp2: fp, Manifest: digest}}
|
||||
key := b.candidate(t, "feat/x", p)
|
||||
// ADR 0593 §7, §9 (D12): n1 and n2 are placed; n1's engine answers compose-candidate on the bus, n2's never does.
|
||||
asked := b.engine(t, "n1", "n2")
|
||||
@@ -660,34 +674,36 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300
|
||||
if got := asked(); len(got) != 0 {
|
||||
t.Fatalf("the engines were asked %+v before m's build ended", got)
|
||||
}
|
||||
bkey, _ := delivery.BuilderBuildKey("m", fp)
|
||||
building, _ := json.Marshal(delivery.BuilderBuild{V: 1, State: delivery.BuildBuilding})
|
||||
if _, err := b.buckets[delivery.BuilderBuildsBucket].Put(b.ctx, bkey, building); err != nil {
|
||||
bkey, _ := builds.BuilderBuildKey("m", fp)
|
||||
building, _ := json.Marshal(builds.BuilderBuild{V: 1, State: builds.BuildBuilding})
|
||||
if _, err := b.buckets[builds.BuilderBuildsBucket].Put(b.ctx, bkey, building); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := b.verdict(t, key, func(delivery.Verdict) bool { return true }); v.State != delivery.VerdictRunning || len(asked()) != 0 {
|
||||
if v := b.verdict(t, key, func(meshdeliverychecker.VerdictsByGroupHeadsValue) bool { return true }); v.State != meshdeliverycheckerclient.VerdictRunning || len(asked()) != 0 {
|
||||
t.Fatalf("verdict %s and %d asks while m builds: the check composes only once its build ended", v.State, len(asked()))
|
||||
}
|
||||
// m's build fails: the check fails naming it. Built again (builder_build_again writes the key done), the check asks
|
||||
// itself again for the same heads, with no recheck, and passes.
|
||||
failedBuild, _ := json.Marshal(delivery.BuilderBuild{V: 1, State: delivery.BuildFailed, Error: "go build: undefined: x"})
|
||||
if _, err := b.buckets[delivery.BuilderBuildsBucket].Put(b.ctx, bkey, failedBuild); err != nil {
|
||||
failedBuild, _ := json.Marshal(builds.BuilderBuild{V: 1, State: builds.BuildFailed, Error: "go build: undefined: x"})
|
||||
if _, err := b.buckets[builds.BuilderBuildsBucket].Put(b.ctx, bkey, failedBuild); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := b.verdict(t, key, ended); v.State != delivery.VerdictFail || v.Failed.Test != check.BuildFailedTest+"m" {
|
||||
if v := b.verdict(t, key, ended); v.State != meshdeliverycheckerclient.VerdictFail || v.Failed.Test != check.BuildFailedTest+"m" {
|
||||
t.Fatalf("verdict %+v, want it failed on m's build", v)
|
||||
}
|
||||
built, _ := json.Marshal(delivery.BuilderBuild{V: 1, State: delivery.BuildDone, Manifest: digest})
|
||||
if _, err := b.buckets[delivery.BuilderBuildsBucket].Put(b.ctx, bkey, built); err != nil {
|
||||
built, _ := json.Marshal(builds.BuilderBuild{V: 1, State: builds.BuildDone, Manifest: digest})
|
||||
if _, err := b.buckets[builds.BuilderBuildsBucket].Put(b.ctx, bkey, built); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.said(t, "the builds of m it failed on are done again, so it is checked again")
|
||||
v := b.verdict(t, key, func(v delivery.Verdict) bool { return v.State == delivery.VerdictPass })
|
||||
v := b.verdict(t, key, func(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool {
|
||||
return v.State == meshdeliverycheckerclient.VerdictPass
|
||||
})
|
||||
// The check asked again takes the packages the failed check ran from their kept verdicts.
|
||||
if v.State != delivery.VerdictPass || v.Levels[delivery.LevelUnit].Kept != 2 || v.Levels[delivery.LevelIntegration].Kept != 2 || v.Trunk[repo] != trunk {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass || v.Levels[buildstep.LevelUnit].Kept != 2 || v.Levels[buildstep.LevelIntegration].Kept != 2 || v.Trunk[repo] != trunk {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
if c := v.Composition; c == nil || c.State != delivery.CompositionPass || !slices.Equal(c.Machines, []string{"n1"}) ||
|
||||
if c := v.Composition; c == nil || c.State != meshdeliverycheckerclient.CompositionPass || !slices.Equal(c.Machines, []string{"n1"}) ||
|
||||
!strings.Contains(c.Words, "n2: "+nec.NoAnswer) || len(v.Machines) != 1 || v.Machines[0] != "n1" {
|
||||
t.Errorf("composition %+v machines %v", v.Composition, v.Machines)
|
||||
}
|
||||
@@ -704,7 +720,7 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300
|
||||
t.Errorf("the check ask named the nodes %v, want [n1] from the have's node record", names)
|
||||
}
|
||||
o := b.owed(t, 7, head1, "success")
|
||||
if o.Context != delivery.StatusContextCheck || !strings.HasPrefix(o.Text, "pass 111111111111 on aaaaaaaaaaaa") || !strings.Contains(o.Text, "unit 0 run, 2 kept") {
|
||||
if o.Context != meshdeliveryclient.StatusContextCheck || !strings.HasPrefix(o.Text, "pass 111111111111 on aaaaaaaaaaaa") || !strings.Contains(o.Text, "unit 0 run, 2 kept") {
|
||||
t.Errorf("owed = %+v", o)
|
||||
}
|
||||
// Two checks of the same heads: the one failed on m's build and the one it asked itself again; the second runs no test.
|
||||
@@ -715,17 +731,17 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300
|
||||
}
|
||||
// What passed is kept once under each level's digest.
|
||||
for _, k := range []string{"unit." + digU, "integration." + digI} {
|
||||
if _, err := b.buckets[delivery.CheckerKeptBucket].Get(b.ctx, k); err != nil {
|
||||
if _, err := b.buckets[check.CheckerKeptBucket].Get(b.ctx, k); err != nil {
|
||||
t.Errorf("kept.%s: %v", k, err)
|
||||
}
|
||||
}
|
||||
// The preview of the head merged onto the trunk, with the tree and the manifest digest the fetch named.
|
||||
pk, _ := delivery.PreviewKey(repo, head1, trunk)
|
||||
e, err := b.buckets[delivery.PreviewsBucket].Get(b.ctx, pk)
|
||||
pk, _ := meshdeliverycheckerclient.PreviewKey(repo, head1, trunk)
|
||||
e, err := b.buckets[check.PreviewsBucket].Get(b.ctx, pk)
|
||||
if err != nil {
|
||||
t.Fatalf("previews.%s: %v", pk, err)
|
||||
}
|
||||
pv, err := delivery.DecodePreview(e.Value())
|
||||
pv, err := meshdeliverycheckerclient.DecodePreview(e.Value())
|
||||
if err != nil || pv.Merge != merged || pv.Modules["modules/m"].Tree != "1111111111111111111111111111111111111111" {
|
||||
t.Errorf("preview = %+v, %v", pv, err)
|
||||
}
|
||||
@@ -744,7 +760,7 @@ func TestACheckOrACarryOfEqualInputsRunsNothing(t *testing.T) { // 1287/AC5, 128
|
||||
tests, kept := b.fake.count("mesh-build-go.build_go_test"), keptCount(t, b)
|
||||
second := b.candidate(t, "feat/y", pull(repo, 8, head2))
|
||||
v := b.verdict(t, second, ended)
|
||||
if v.State != delivery.VerdictPass || v.Levels[delivery.LevelUnit].Run != 0 || v.Levels[delivery.LevelUnit].Kept != 2 || v.Levels[delivery.LevelIntegration].Run != 0 {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass || v.Levels[buildstep.LevelUnit].Run != 0 || v.Levels[buildstep.LevelUnit].Kept != 2 || v.Levels[buildstep.LevelIntegration].Run != 0 {
|
||||
t.Fatalf("verdict = %+v, want everything taken from the kept verdicts", v)
|
||||
}
|
||||
if got := b.fake.count("mesh-build-go.build_go_test"); got != tests {
|
||||
@@ -756,22 +772,22 @@ func TestACheckOrACarryOfEqualInputsRunsNothing(t *testing.T) { // 1287/AC5, 128
|
||||
|
||||
// §3: the gate's turn on the repository holds a new check off; its delete claims it.
|
||||
at := time.Now().UTC()
|
||||
turn, err := delivery.EncodeTurn(delivery.Turn{Repository: repo, Waiting: []delivery.TurnWait{{Group: "fix/other", Heads: "0123456789ab", Approved: at, Since: at}}})
|
||||
turn, err := meshdeliveryclient.EncodeTurn(meshdelivery.TurnsByRepositoryValue{Repository: repo, Waiting: []meshdelivery.TurnsByRepositoryValueWaitingItem{{Group: "fix/other", Heads: "0123456789ab", Approved: at, Since: at}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tk, _ := delivery.TurnsKey(repo)
|
||||
turnRev, err := b.buckets[delivery.TurnsBucket].Put(b.ctx, tk, turn)
|
||||
tk, _ := meshdeliveryclient.TurnsKey(repo)
|
||||
turnRev, err := b.buckets[check.TurnsBucket].Put(b.ctx, tk, turn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.said(t, fmt.Sprintf("merge turn %s changed (revision %d)", tk, turnRev)) // the checker heard the turn
|
||||
third := b.candidate(t, "feat/z", pull(repo, 9, "3333333333333333333333333333333333333333"))
|
||||
b.said(t, third+": not claimed: the approved merge of fix/other") // it held the check off
|
||||
if _, err := b.buckets[delivery.CheckerVerdictsBucket].Get(b.ctx, third); !errors.Is(err, jetstream.ErrKeyNotFound) {
|
||||
if _, err := b.buckets[check.CheckerVerdictsBucket].Get(b.ctx, third); !errors.Is(err, jetstream.ErrKeyNotFound) {
|
||||
t.Fatalf("verdicts.%s exists (%v) while a merge turn holds its repository", third, err)
|
||||
}
|
||||
if err := b.buckets[delivery.TurnsBucket].Delete(b.ctx, tk); err != nil {
|
||||
if err := b.buckets[check.TurnsBucket].Delete(b.ctx, tk); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.verdict(t, third, ended)
|
||||
@@ -780,8 +796,8 @@ func TestACheckOrACarryOfEqualInputsRunsNothing(t *testing.T) { // 1287/AC5, 128
|
||||
tests = b.fake.count("mesh-build-go.build_go_test")
|
||||
const moved = "abababababababababababababababababababab"
|
||||
b.setTrunk(t, map[string]string{repo: moved})
|
||||
c := b.verdict(t, first, func(v delivery.Verdict) bool { return ended(v) && v.Trunk[repo] == moved })
|
||||
if c.State != delivery.VerdictPass || c.CarriedFrom[repo] != trunk || c.Levels[delivery.LevelUnit].Run != 0 || c.Levels[delivery.LevelIntegration].Run != 0 {
|
||||
c := b.verdict(t, first, func(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { return ended(v) && v.Trunk[repo] == moved })
|
||||
if c.State != meshdeliverycheckerclient.VerdictPass || c.CarriedFrom[repo] != trunk || c.Levels[buildstep.LevelUnit].Run != 0 || c.Levels[buildstep.LevelIntegration].Run != 0 {
|
||||
t.Fatalf("the carried verdict = %+v, want a pass carried from %s with no test run", c, trunk[:12])
|
||||
}
|
||||
if got := b.fake.count("mesh-build-go.build_go_test"); got != tests {
|
||||
@@ -794,7 +810,7 @@ func TestACheckOrACarryOfEqualInputsRunsNothing(t *testing.T) { // 1287/AC5, 128
|
||||
|
||||
func keptCount(t *testing.T, b *bed) int {
|
||||
t.Helper()
|
||||
keys, err := b.buckets[delivery.CheckerKeptBucket].Keys(b.ctx)
|
||||
keys, err := b.buckets[check.CheckerKeptBucket].Keys(b.ctx)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
@@ -809,7 +825,7 @@ func TestAHeadThatDoesNotMergeFailsNamingTheFilesAndRunsNoTest(t *testing.T) { /
|
||||
key := b.candidate(t, "feat/x", pull(repo, 7, head1))
|
||||
b.start(t, nil)
|
||||
v := b.verdict(t, key, ended)
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "files in conflict: a.go, sub/b.go") {
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(v.Failed.Words, "files in conflict: a.go, sub/b.go") {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
if o := b.owed(t, 7, head1, "failure"); !strings.HasPrefix(o.Text, "failed: n1: ") || !strings.Contains(o.Text, "files in conflict: a.go, sub/b.go") {
|
||||
@@ -818,10 +834,10 @@ func TestAHeadThatDoesNotMergeFailsNamingTheFilesAndRunsNoTest(t *testing.T) { /
|
||||
if n := b.fake.count("mesh-build-go.build_go_digest") + b.fake.count("mesh-build-go.build_go_test"); n != 0 {
|
||||
t.Errorf("%d digest and test steps ran after a conflict", n)
|
||||
}
|
||||
pk, _ := delivery.PreviewKey(repo, head1, trunk)
|
||||
if e, err := b.buckets[delivery.PreviewsBucket].Get(b.ctx, pk); err != nil {
|
||||
pk, _ := meshdeliverycheckerclient.PreviewKey(repo, head1, trunk)
|
||||
if e, err := b.buckets[check.PreviewsBucket].Get(b.ctx, pk); err != nil {
|
||||
t.Errorf("previews.%s: %v", pk, err)
|
||||
} else if pv, err := delivery.DecodePreview(e.Value()); err != nil || strings.Join(pv.Conflicts, ",") != "a.go,sub/b.go" || pv.Merge != "" {
|
||||
} else if pv, err := meshdeliverycheckerclient.DecodePreview(e.Value()); err != nil || strings.Join(pv.Conflicts, ",") != "a.go,sub/b.go" || pv.Merge != "" {
|
||||
t.Errorf("preview = %+v, %v, want the files in conflict alone", pv, err)
|
||||
}
|
||||
}
|
||||
@@ -834,13 +850,13 @@ func TestAnHqPullRequestGetsAVerdictAndAStatus(t *testing.T) {
|
||||
b.setTrunk(t, map[string]string{repo: trunk, "novox/hq": trunk})
|
||||
hq := b.candidate(t, "feat/hq", pull("novox/hq", 3, head1))
|
||||
b.start(t, nil)
|
||||
if v := b.verdict(t, hq, ended); v.State != delivery.VerdictPass {
|
||||
if v := b.verdict(t, hq, ended); v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("hq's verdict = %+v", v)
|
||||
}
|
||||
key, _ := delivery.CheckerOwedKey("novox/hq", 3, head1)
|
||||
key, _ := check.CheckerOwedKey("novox/hq", 3, head1)
|
||||
for deadline := time.Now().Add(20 * time.Second); ; time.Sleep(50 * time.Millisecond) {
|
||||
if e, err := b.buckets[delivery.CheckerOwedBucket].Get(b.ctx, key); err == nil {
|
||||
if o, err := delivery.DecodeCheckerOwed(e.Value()); err == nil && o.State == "success" {
|
||||
if e, err := b.buckets[check.CheckerOwedBucket].Get(b.ctx, key); err == nil {
|
||||
if o, err := check.DecodeCheckerOwed(e.Value()); err == nil && o.State == "success" {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -855,16 +871,16 @@ func TestAnHqPullRequestGetsAVerdictAndAStatus(t *testing.T) {
|
||||
func TestACheckLeftRunningIsTakenOverWhenTheCheckerStarts(t *testing.T) { // N6/AC2
|
||||
b := startBed(t)
|
||||
key := b.candidate(t, "feat/x", pull(repo, 7, head1))
|
||||
group, heads, _ := delivery.ParseCheckerVerdictKey(key)
|
||||
left, err := delivery.EncodeVerdict(delivery.Verdict{Group: group, Heads: heads, Trunk: map[string]string{repo: trunk}, State: delivery.VerdictRunning})
|
||||
group, heads, _ := meshdeliverycheckerclient.ParseVerdictKey(key)
|
||||
left, err := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: group, Heads: heads, Trunk: map[string]string{repo: trunk}, State: meshdeliverycheckerclient.VerdictRunning})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.buckets[delivery.CheckerVerdictsBucket].Create(b.ctx, key, left); err != nil {
|
||||
if _, err := b.buckets[check.CheckerVerdictsBucket].Create(b.ctx, key, left); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.start(t, nil)
|
||||
if v := b.verdict(t, key, ended); v.State != delivery.VerdictPass {
|
||||
if v := b.verdict(t, key, ended); v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Errorf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
@@ -877,7 +893,7 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing.
|
||||
b.stalePin.Store(true)
|
||||
key := b.candidate(t, "feat/x", pull(repo, 7, head1))
|
||||
b.start(t, nil)
|
||||
if v := b.verdict(t, key, ended); v.State != delivery.VerdictPass {
|
||||
if v := b.verdict(t, key, ended); v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
|
||||
@@ -888,12 +904,12 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing.
|
||||
b.setTrunk(t, map[string]string{repo: trunk, sdkRepo: trunk})
|
||||
// The member carries no preview of its merge yet, so its manifest is read at its head (the package new in the group).
|
||||
b.heads.Store(sdkRepo+" module.json "+head2, `{"module":"mesh-sdk","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"]}]}}`)
|
||||
asks, err := b.js.OrderedConsumer(b.ctx, "ASKS", jetstream.OrderedConsumerConfig{FilterSubjects: []string{meshdeliverychecker.HolderSubjectTagWanted(delivery.CheckerModule)}})
|
||||
asks, err := b.js.OrderedConsumer(b.ctx, "ASKS", jetstream.OrderedConsumerConfig{FilterSubjects: []string{meshdeliverychecker.HolderSubjectTagWanted(check.CheckerModule)}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sdk := pull(sdkRepo, 12, head2)
|
||||
sdk.Modules, sdk.Fps = []string{"mesh-sdk"}, map[string]delivery.CandidateModule{"mesh-sdk": {FP2: "src2:" + strings.Repeat("e", 64)}}
|
||||
sdk.Modules, sdk.Fps = []string{"mesh-sdk"}, map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"mesh-sdk": {Fp2: "src2:" + strings.Repeat("e", 64)}}
|
||||
key = b.candidate(t, "feat/x", pull(repo, 8, head1), sdk)
|
||||
msg, err := asks.Next(jetstream.FetchMaxWait(20 * time.Second))
|
||||
if err != nil {
|
||||
@@ -908,7 +924,7 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing.
|
||||
if want := (meshdeliverychecker.TagWantedHolderEvent{Repository: sdkRepo, Branch: "feat/x", Head: head2, Module: "mesh-sdk", Package: "go", Pull: 12}); wanted != want {
|
||||
t.Errorf("tag-wanted = %+v, want %+v", wanted, want)
|
||||
}
|
||||
if v, err := b.buckets[delivery.CheckerVerdictsBucket].Get(b.ctx, key); err == nil && !strings.Contains(string(v.Value()), `"running"`) {
|
||||
if v, err := b.buckets[check.CheckerVerdictsBucket].Get(b.ctx, key); err == nil && !strings.Contains(string(v.Value()), `"running"`) {
|
||||
t.Fatalf("the group was judged before its tagged version was published: %s", v.Value())
|
||||
}
|
||||
published, _ := json.Marshal(gopackageregistry.PublishedEvent{Holder: "gitea", Kind: "package", Name: "mesh-sdk/go", Module: check.SDKModulePath, Commit: head2, Tag: "feat-x", Version: "0.1.15-feat-x.1",
|
||||
@@ -916,7 +932,7 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing.
|
||||
if _, err := b.js.Publish(b.ctx, gopackageregistry.SubjectPublished, published); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := b.verdict(t, key, ended); v.State != delivery.VerdictPass {
|
||||
if v := b.verdict(t, key, ended); v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("the group's verdict = %+v", v)
|
||||
}
|
||||
b.fake.mu.Lock()
|
||||
@@ -928,7 +944,7 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing.
|
||||
|
||||
// DLV-C2, DLV-C6 (design 26 *Out of step*, ADR 0620): each check's end says one verdict per pull request on the stream
|
||||
// asks, at the definition's subject and in its body (decoded strictly): the lone pull request's, then the group's two.
|
||||
verdicts, err := b.js.OrderedConsumer(b.ctx, "ASKS", jetstream.OrderedConsumerConfig{FilterSubjects: []string{meshdeliverychecker.HolderSubjectVerdict(delivery.CheckerModule)}})
|
||||
verdicts, err := b.js.OrderedConsumer(b.ctx, "ASKS", jetstream.OrderedConsumerConfig{FilterSubjects: []string{meshdeliverychecker.HolderSubjectVerdict(check.CheckerModule)}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -949,8 +965,8 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing.
|
||||
}
|
||||
said[e.Pull] = e
|
||||
}
|
||||
for pull, want := range map[int64]meshdeliverychecker.VerdictHolderEvent{7: {Repository: repo, Pull: 7, Head: head1, Verdict: delivery.VerdictPass},
|
||||
8: {Repository: repo, Pull: 8, Head: head1, Verdict: delivery.VerdictPass}, 12: {Repository: sdkRepo, Pull: 12, Head: head2, Verdict: delivery.VerdictPass}} {
|
||||
for pull, want := range map[int64]meshdeliverychecker.VerdictHolderEvent{7: {Repository: repo, Pull: 7, Head: head1, Verdict: meshdeliverycheckerclient.VerdictPass},
|
||||
8: {Repository: repo, Pull: 8, Head: head1, Verdict: meshdeliverycheckerclient.VerdictPass}, 12: {Repository: sdkRepo, Pull: 12, Head: head2, Verdict: meshdeliverycheckerclient.VerdictPass}} {
|
||||
if got := said[pull]; got.Repository != want.Repository || got.Head != want.Head || got.Verdict != want.Verdict || len(got.OutOfStep) != 0 {
|
||||
t.Errorf("verdict of pull %d = %+v, want %+v", pull, got, want)
|
||||
}
|
||||
@@ -1009,7 +1025,7 @@ func TestATryIsJudgedAndItsEndWrittenWithNoStatusOwed(t *testing.T) {
|
||||
if b.fake.count("mesh-build-go.build_go_fetch") == 0 {
|
||||
t.Errorf("the try fetched nothing")
|
||||
}
|
||||
for _, name := range []string{delivery.CheckerOwedBucket, delivery.CheckerVerdictsBucket, delivery.PreviewsBucket} {
|
||||
for _, name := range []string{check.CheckerOwedBucket, check.CheckerVerdictsBucket, check.PreviewsBucket} {
|
||||
if keys, _ := b.buckets[name].Keys(b.ctx); len(keys) != 0 {
|
||||
t.Errorf("a try wrote %s: %v", name, keys)
|
||||
}
|
||||
@@ -1021,7 +1037,7 @@ func TestATryIsJudgedAndItsEndWrittenWithNoStatusOwed(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
steps, err := b.js.KeyValue(b.ctx, delivery.StepsBucket("mesh-build-go"))
|
||||
steps, err := b.js.KeyValue(b.ctx, buildrun.StepsBucket("mesh-build-go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -13,16 +13,19 @@ import (
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"mesh-delivery-checker/internal/check"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
)
|
||||
|
||||
// judgeDeps are the parts the judge of a check is made of.
|
||||
@@ -57,22 +60,22 @@ type judgeDeps struct {
|
||||
logf func(format string, args ...any)
|
||||
}
|
||||
|
||||
// keptPredefined reads the indexer's key predefined.list (delivery.PredefinedKept), bound once its state appears.
|
||||
// keptPredefined reads the indexer's key predefined.list (the seat mesh-catalog's state predefined), bound once its state appears.
|
||||
type keptPredefined struct{ kv jetstream.KeyValue }
|
||||
|
||||
func (k keptPredefined) PredefinedList(ctx context.Context) (json.RawMessage, error) {
|
||||
e, err := k.kv.Get(ctx, delivery.PredefinedListKey)
|
||||
e, err := k.kv.Get(ctx, meshcatalog.PredefinedByListKey())
|
||||
if errors.Is(err, jetstream.ErrKeyNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var kept delivery.PredefinedKept
|
||||
if err := json.Unmarshal(e.Value(), &kept); err != nil {
|
||||
kept, err := meshcatalog.DecodePredefinedByListValue(e.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kept.List, nil
|
||||
return json.Marshal(kept.List)
|
||||
}
|
||||
|
||||
// keptSeatDefinitions reads a pull request's entries of the indexer's seat definitions from its first view: each whose
|
||||
@@ -103,7 +106,7 @@ func seatDefinitionsOf(kv jetstream.KeyValue) keptSeatDefinitions {
|
||||
}
|
||||
|
||||
func (k keptSeatDefinitions) Unparsed(ctx context.Context, repository string, pull int) ([]check.UnparsedSeat, error) {
|
||||
suffix := ".pull." + delivery.EscapeName(repository) + "." + strconv.Itoa(pull)
|
||||
suffix := ".pull." + keys.EscapeName(repository) + "." + strconv.Itoa(pull)
|
||||
keys, err := k.keys(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -143,14 +146,14 @@ type nodeNames struct{ kv jetstream.KeyValue }
|
||||
|
||||
// openNodeNames opens the node-engine's have for the nodes' names alone: it is not watched, so no check waits on a node.
|
||||
func openNodeNames(ctx context.Context, js process.BucketOpener, logf func(string, ...any)) *nodeNames {
|
||||
return &nodeNames{kv: process.OptionalBucket(ctx, js, delivery.HaveBucket, "no check is asked until it is: the names check needs the nodes' names", logf)}
|
||||
return &nodeNames{kv: process.OptionalBucket(ctx, js, check.HaveBucket, "no check is asked until it is: the names check needs the nodes' names", logf)}
|
||||
}
|
||||
|
||||
func (n nodeNames) Names(ctx context.Context) ([]string, error) {
|
||||
if n.kv == nil {
|
||||
return nil, fmt.Errorf("the bucket %s could not be opened at the checker's start", delivery.HaveBucket)
|
||||
return nil, fmt.Errorf("the bucket %s could not be opened at the checker's start", check.HaveBucket)
|
||||
}
|
||||
lister, err := n.kv.ListKeysFiltered(ctx, "*."+delivery.HaveNodeWord)
|
||||
lister, err := n.kv.ListKeysFiltered(ctx, "*."+check.HaveNodeWord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -264,7 +267,7 @@ func (r *repoModules) Manifest(ctx context.Context, digest string) (catalogue.Ma
|
||||
if known {
|
||||
return m, nil
|
||||
}
|
||||
key, err := delivery.ManifestsKey(digest)
|
||||
key, err := meshcatalogclient.ManifestsKey(digest)
|
||||
if err != nil {
|
||||
return catalogue.Manifest{}, err
|
||||
}
|
||||
@@ -275,11 +278,11 @@ func (r *repoModules) Manifest(ctx context.Context, digest string) (catalogue.Ma
|
||||
if !found {
|
||||
return catalogue.Manifest{}, fmt.Errorf("the indexer keeps no manifest %s", digest)
|
||||
}
|
||||
kept, err := delivery.DecodeManifest(raw)
|
||||
kept, err := meshcatalogclient.DecodeManifest(raw)
|
||||
if err != nil {
|
||||
return catalogue.Manifest{}, err
|
||||
}
|
||||
if m, err = parseManifest(kept.JSON); err != nil {
|
||||
if m, err = parseManifest(kept.Manifest); err != nil {
|
||||
return catalogue.Manifest{}, err
|
||||
}
|
||||
r.mu.Lock()
|
||||
@@ -311,19 +314,19 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted
|
||||
return
|
||||
}
|
||||
r.mu.Unlock()
|
||||
m, err := delivery.DecodeModule(value)
|
||||
m, err := meshcatalogclient.DecodeModule(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if r.verdicts != nil {
|
||||
r.verdicts.put(key, m.VerdictOf(key))
|
||||
r.verdicts.put(key, meshcatalogclient.ModuleVerdict(key, m))
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.repoOf[key] = m.Repository
|
||||
r.typed[key] = m.Manifest
|
||||
r.commit[key] = m.Commit
|
||||
r.path[key] = m.Path
|
||||
r.named[key] = m.ArtifactPaths
|
||||
r.named[key] = m.Paths
|
||||
_, known := r.langs[m.Manifest]
|
||||
r.mu.Unlock()
|
||||
if m.Manifest == "" || known {
|
||||
@@ -331,10 +334,10 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted
|
||||
}
|
||||
set := map[string]bool{}
|
||||
var pkgs []string
|
||||
if mk, err := delivery.ManifestsKey(m.Manifest); err == nil {
|
||||
if mk, err := meshcatalogclient.ManifestsKey(m.Manifest); err == nil {
|
||||
if raw, _, found, err := r.manifests.Get(ctx, mk); err == nil && found {
|
||||
if kept, err := delivery.DecodeManifest(raw); err == nil {
|
||||
if man, err := parseManifest(kept.JSON); err == nil && man.Build != nil {
|
||||
if kept, err := meshcatalogclient.DecodeManifest(raw); err == nil {
|
||||
if man, err := parseManifest(kept.Manifest); err == nil && man.Build != nil {
|
||||
for _, a := range man.Build.Artifacts {
|
||||
if a.Language != "" {
|
||||
set[a.Language] = true
|
||||
@@ -365,7 +368,7 @@ func (r *repoModules) ModuleDir(module string) (string, bool) {
|
||||
}
|
||||
|
||||
// PackagePaths are the paths, relative to the repository, that the TypeScript package artifacts of a repository's modules
|
||||
// are built from, sorted: the named paths the indexer keeps in each module's value (delivery.Module.ArtifactPaths; only the
|
||||
// are built from, sorted: the named paths the indexer keeps in each module's value (meshcatalog.ModulesByModuleValue.ArtifactPaths; only the
|
||||
// indexer reads them from a manifest, the lead's decision L8), under the module's directory. None for a module whose value
|
||||
// holds no paths.
|
||||
func (r *repoModules) PackagePaths(repository string) []string {
|
||||
@@ -380,7 +383,7 @@ func (r *repoModules) PackagePaths(repository string) []string {
|
||||
for _, name := range r.tsPkgs[r.typed[module]] {
|
||||
for _, p := range r.named[module][name] {
|
||||
p = strings.Trim(strings.TrimPrefix(p, "./"), "/")
|
||||
if dir != "" && dir != delivery.RootPath {
|
||||
if dir != "" && dir != meshcatalogclient.RootPath {
|
||||
p = dir + "/" + p
|
||||
}
|
||||
out = append(out, p)
|
||||
@@ -420,16 +423,16 @@ func (r *repoModules) BuildModules(repository string) []string {
|
||||
// buildReader reads the builder's build of a module at a fingerprint (mesh-delivery-builder_builds).
|
||||
type buildReader struct{ kv kvStore }
|
||||
|
||||
func (r buildReader) Build(ctx context.Context, module, fp2 string) (delivery.BuilderBuild, bool, error) {
|
||||
key, err := delivery.BuilderBuildKey(module, fp2)
|
||||
func (r buildReader) Build(ctx context.Context, module, fp2 string) (builds.BuilderBuild, bool, error) {
|
||||
key, err := builds.BuilderBuildKey(module, fp2)
|
||||
if err != nil {
|
||||
return delivery.BuilderBuild{}, false, err
|
||||
return builds.BuilderBuild{}, false, err
|
||||
}
|
||||
value, _, found, err := r.kv.Get(ctx, key)
|
||||
if err != nil || !found {
|
||||
return delivery.BuilderBuild{}, false, err
|
||||
return builds.BuilderBuild{}, false, err
|
||||
}
|
||||
b, err := delivery.DecodeBuilderBuild(value)
|
||||
b, err := builds.DecodeBuilderBuild(value)
|
||||
return b, err == nil, err
|
||||
}
|
||||
|
||||
@@ -438,30 +441,30 @@ func (r buildReader) Build(ctx context.Context, module, fp2 string) (delivery.Bu
|
||||
// the fact and polls nothing (novox/hq ADR 0603 §10).
|
||||
type buildEnds struct{ kv jetstream.KeyValue }
|
||||
|
||||
func (e buildEnds) Ended(ctx context.Context, builds map[string]string) (map[string]delivery.BuilderBuild, error) {
|
||||
return e.until(ctx, builds, func(b delivery.BuilderBuild) bool {
|
||||
return b.State == delivery.BuildDone || b.State == delivery.BuildFailed
|
||||
func (e buildEnds) Ended(ctx context.Context, asked map[string]string) (map[string]builds.BuilderBuild, error) {
|
||||
return e.until(ctx, asked, func(b builds.BuilderBuild) bool {
|
||||
return b.State == builds.BuildDone || b.State == builds.BuildFailed
|
||||
})
|
||||
}
|
||||
|
||||
func (e buildEnds) Done(ctx context.Context, builds map[string]string) bool {
|
||||
done, err := e.until(ctx, builds, func(b delivery.BuilderBuild) bool { return b.State == delivery.BuildDone })
|
||||
return err == nil && len(done) == len(builds)
|
||||
func (e buildEnds) Done(ctx context.Context, asked map[string]string) bool {
|
||||
done, err := e.until(ctx, asked, func(b builds.BuilderBuild) bool { return b.State == builds.BuildDone })
|
||||
return err == nil && len(done) == len(asked)
|
||||
}
|
||||
|
||||
// until hears the builds' keys until each holds a build that ended holds, or ctx ends, and answers those that do, by module.
|
||||
func (e buildEnds) until(ctx context.Context, builds map[string]string, ended func(delivery.BuilderBuild) bool) (map[string]delivery.BuilderBuild, error) {
|
||||
func (e buildEnds) until(ctx context.Context, asked map[string]string, ended func(builds.BuilderBuild) bool) (map[string]builds.BuilderBuild, error) {
|
||||
byKey := map[string]string{}
|
||||
keys := make([]string, 0, len(builds))
|
||||
for module, fp2 := range builds {
|
||||
key, err := delivery.BuilderBuildKey(module, fp2)
|
||||
keys := make([]string, 0, len(asked))
|
||||
for module, fp2 := range asked {
|
||||
key, err := builds.BuilderBuildKey(module, fp2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byKey[key] = module
|
||||
keys = append(keys, key)
|
||||
}
|
||||
out := map[string]delivery.BuilderBuild{}
|
||||
out := map[string]builds.BuilderBuild{}
|
||||
if len(keys) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
@@ -485,7 +488,7 @@ func (e buildEnds) until(ctx context.Context, builds map[string]string, ended fu
|
||||
if !ours {
|
||||
continue
|
||||
}
|
||||
b, err := delivery.DecodeBuilderBuild(entry.Value())
|
||||
b, err := builds.DecodeBuilderBuild(entry.Value())
|
||||
if err == nil && ended(b) {
|
||||
out[module] = b
|
||||
} else {
|
||||
@@ -497,7 +500,7 @@ func (e buildEnds) until(ctx context.Context, builds map[string]string, ended fu
|
||||
}
|
||||
|
||||
// ManifestDigests is the manifest digest of every module the indexer holds, by module: the catalogue the indexer's judge
|
||||
// judges a change's manifests in (novox/hq ADR 0578 §2).
|
||||
// judges a change's manifests in (novox/hq ADR 0578 §2).8 §2).
|
||||
func (r *repoModules) ManifestDigests() map[string]string {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
@@ -12,8 +12,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
)
|
||||
@@ -27,7 +30,7 @@ func (m memManifests) Get(_ context.Context, key string) ([]byte, uint64, bool,
|
||||
|
||||
func putModule(t *testing.T, r *repoModules, name, repository, manifest string) {
|
||||
t.Helper()
|
||||
data, err := delivery.EncodeModule(delivery.Module{Repository: repository, Commit: strings.Repeat("a", 40), FP: "src1:" + strings.Repeat("a", 64), Manifest: delivery.ManifestDigest([]byte(manifest))})
|
||||
data, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: repository, Commit: strings.Repeat("a", 40), Fp: "src1:" + strings.Repeat("a", 64), Manifest: meshcatalogclient.ManifestDigest([]byte(manifest))})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -58,11 +61,11 @@ func TestARepositoryWithATypeScriptModuleIsJudgedByTheNodeBuildModuleToo(t *test
|
||||
tsManifest := string(tsRaw)
|
||||
store := memManifests{}
|
||||
for _, m := range []string{goManifest, tsManifest} {
|
||||
data, err := delivery.EncodeManifest(delivery.Manifest{Module: "demo", JSON: m})
|
||||
data, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "demo", Manifest: m})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store[delivery.ManifestDigest([]byte(m))] = data
|
||||
store[meshcatalogclient.ManifestDigest([]byte(m))] = data
|
||||
}
|
||||
r := &repoModules{manifests: store}
|
||||
putModule(t, r, "mesh-delivery-checker", "novox/mesh-delivery", goManifest)
|
||||
@@ -89,15 +92,15 @@ const sdkManifest = `{"module":"mesh-sdk","version":"1","slug":"sdk","build":{"a
|
||||
|
||||
func TestTheTypeScriptPackagesPathsComeFromTheIndexersModulesNotTheManifest(t *testing.T) { // ADR 0500 §5 with lead decision L8
|
||||
store := memManifests{}
|
||||
data, err := delivery.EncodeManifest(delivery.Manifest{Module: "mesh-sdk", JSON: sdkManifest})
|
||||
data, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "mesh-sdk", Manifest: sdkManifest})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store[delivery.ManifestDigest([]byte(sdkManifest))] = data
|
||||
store[meshcatalogclient.ManifestDigest([]byte(sdkManifest))] = data
|
||||
r := &repoModules{manifests: store}
|
||||
put := func(path string, paths map[string][]string) {
|
||||
v, err := delivery.EncodeModule(delivery.Module{Repository: "novox/mesh-sdk", Commit: strings.Repeat("a", 40), FP: "src1:" + strings.Repeat("a", 64),
|
||||
Manifest: delivery.ManifestDigest([]byte(sdkManifest)), Path: path, ArtifactPaths: paths})
|
||||
v, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: "novox/mesh-sdk", Commit: strings.Repeat("a", 40), Fp: "src1:" + strings.Repeat("a", 64),
|
||||
Manifest: meshcatalogclient.ManifestDigest([]byte(sdkManifest)), Path: path, Paths: paths})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -133,10 +136,11 @@ func TestThePublishedVersionsAreTheIndexersPackagesState(t *testing.T) {
|
||||
t.Errorf("read and empty, the known packages = %#v, want an empty map", got)
|
||||
}
|
||||
put := func(module, repository, dir, commit string) {
|
||||
key, _ := delivery.PackagesKey(module)
|
||||
raw, err := delivery.EncodePublishedPackages(delivery.PublishedPackages{Module: module, Repository: repository, Dir: dir,
|
||||
key := meshcatalog.PackagesByModuleKey(keys.EscapeName(module))
|
||||
now := time.Now().UTC()
|
||||
raw, err := json.Marshal(meshcatalog.PackagesByModuleValue{V: keys.Version, Module: module, Repository: repository, Dir: dir,
|
||||
Commit: commit, Requires: strings.Repeat("e", 64),
|
||||
Packages: []delivery.PackageDigest{{Package: module, Digest: strings.Repeat("d", 64)}}, At: time.Now().UTC()})
|
||||
Packages: []meshcatalog.PackagesByModuleValuePackagesItem{{Package: module, Digest: strings.Repeat("d", 64)}}, At: &now})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -146,30 +150,31 @@ func TestThePublishedVersionsAreTheIndexersPackagesState(t *testing.T) {
|
||||
put(sdk, "novox/mesh-sdk", "go", strings.Repeat("a", 40))
|
||||
put(build, "novox/mesh-catalog", "modules/mesh-build-go", strings.Repeat("b", 40))
|
||||
p.Put("broken", []byte("{"), false)
|
||||
if got := p.LatestPackages(); !reflect.DeepEqual(got, delivery.PackageVersions{sdk: strings.Repeat("a", 40), build: strings.Repeat("b", 40)}) {
|
||||
if got := p.LatestPackages(); !reflect.DeepEqual(got, buildstep.PackageVersions{sdk: strings.Repeat("a", 40), build: strings.Repeat("b", 40)}) {
|
||||
t.Errorf("latest packages = %v", got)
|
||||
}
|
||||
key, _ := delivery.PackagesKey(build)
|
||||
key := meshcatalog.PackagesByModuleKey(keys.EscapeName(build))
|
||||
p.Put(key, nil, true)
|
||||
if got := p.LatestPackages(); len(got) != 1 || got[sdk] == "" {
|
||||
t.Errorf("after a delete: %v", got)
|
||||
}
|
||||
// A series key names its package by its ecosystem name, the judge's known packages (DLV-I14, design 32), and is no
|
||||
// module's published version.
|
||||
series, err := delivery.EncodeSeriesPackage(delivery.SeriesPackage{Name: sdk, Package: "mesh-sdk/go", Series: "0.1", At: time.Now().UTC()})
|
||||
now := time.Now().UTC()
|
||||
series, err := json.Marshal(meshcatalog.PackagesByPackageSeriesValue{V: keys.Version, Name: sdk, Package: "mesh-sdk/go", Series: "0.1", At: &now})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.Put(delivery.SeriesPackageKey(sdk, "0.1"), series, false)
|
||||
p.Put(meshcatalog.PackagesByPackageSeriesKey(keys.EscapeName(sdk), "0.1"), series, false)
|
||||
if got := p.PackageNames(); !reflect.DeepEqual(got, map[string]string{sdk: "mesh-sdk/go"}) || len(p.LatestPackages()) != 1 {
|
||||
t.Errorf("the known packages = %v, the latest %v", got, p.LatestPackages())
|
||||
}
|
||||
t.Run("a pull request's unparsed seat definitions are its entries of the indexer's seat-definitions", func(t *testing.T) {
|
||||
state := map[string]string{
|
||||
"web-api.trunk": `{"seat":"web-api","source":"trunk","digest":"d"}`,
|
||||
"web-api.pull." + delivery.EscapeName("novox/web") + ".7": `{"seat":"web-api","source":"pull","module":"web-contracts","directory":"go","unparsed":"seat.json: bad"}`,
|
||||
"mail-api.pull." + delivery.EscapeName("novox/web") + ".7": `{"seat":"mail-api","source":"pull","digest":"e"}`,
|
||||
"web-api.pull." + delivery.EscapeName("novox/web") + ".17": `{"seat":"web-api","source":"pull","unparsed":"another pull request's"}`,
|
||||
"web-api.pull." + keys.EscapeName("novox/web") + ".7": `{"seat":"web-api","source":"pull","module":"web-contracts","directory":"go","unparsed":"seat.json: bad"}`,
|
||||
"mail-api.pull." + keys.EscapeName("novox/web") + ".7": `{"seat":"mail-api","source":"pull","digest":"e"}`,
|
||||
"web-api.pull." + keys.EscapeName("novox/web") + ".17": `{"seat":"web-api","source":"pull","unparsed":"another pull request's"}`,
|
||||
}
|
||||
k := keptSeatDefinitions{keys: func(context.Context) ([]string, error) { return slices.Collect(maps.Keys(state)), nil },
|
||||
get: func(_ context.Context, key string) ([]byte, error) { return []byte(state[key]), nil }}
|
||||
@@ -189,11 +194,11 @@ func TestAManifestFallingBackToAPredefinedKeyIsRead(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
manifest := string(raw)
|
||||
data, err := delivery.EncodeManifest(delivery.Manifest{Module: "xterm", JSON: manifest})
|
||||
data, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "xterm", Manifest: manifest})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store := memManifests{delivery.ManifestDigest([]byte(manifest)): data}
|
||||
store := memManifests{meshcatalogclient.ManifestDigest([]byte(manifest)): data}
|
||||
var said []string
|
||||
r := &repoModules{manifests: store, logf: func(f string, a ...any) { said = append(said, fmt.Sprintf(f, a...)) }}
|
||||
putModule(t, r, "xterm", "novox/mesh-catalog", manifest)
|
||||
@@ -208,8 +213,8 @@ func TestAManifestFallingBackToAPredefinedKeyIsRead(t *testing.T) {
|
||||
}
|
||||
// The sandbox's composer may be an older node-engine, which knows no verdict waits: it is handed a wait as a refusal.
|
||||
var held manifestVerdicts
|
||||
held.put("nats", &delivery.ManifestVerdict{Module: "nats", State: delivery.VerdictManifestWaits, Unknown: []string{"core-credentials"}, Judge: "j"})
|
||||
if v := held.all()["nats"]; v.State != delivery.VerdictRefused || len(v.Problems) != 1 || len(v.Unknown) != 0 {
|
||||
held.put("nats", &meshcatalog.VerdictsByDigestValue{Module: "nats", State: meshcatalogclient.VerdictManifestWaits, Unknown: []string{"core-credentials"}, Judge: "j"})
|
||||
if v := held.all()["nats"]; v.State != meshcatalogclient.VerdictRefused || len(v.Problems) != 1 || len(v.Unknown) != 0 {
|
||||
t.Errorf("a waiting verdict is handed to the sandbox as %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package main
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
)
|
||||
|
||||
// lazyTurns is the gate's merge turns as the checker reads them (novox/hq ADR 0548 §3), opened when the gate's bucket is
|
||||
@@ -33,18 +33,18 @@ func (l *lazyTurns) ensure() {
|
||||
if !l.said {
|
||||
l.said = true
|
||||
l.logf("NO MERGE TURN HOLDS A CHECK: the gate's bucket %s cannot be read (%v); every check is claimed as if no approved merge waited, "+
|
||||
"and the bucket is opened again before each claim until it is there", delivery.TurnsBucket, err)
|
||||
"and the bucket is opened again before each claim until it is there", check.TurnsBucket, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
l.opened = true
|
||||
if l.said {
|
||||
l.logf("the gate's merge turns are read from %s from now on: an approved merge's turn holds checks off its repositories again", delivery.TurnsBucket)
|
||||
l.logf("the gate's merge turns are read from %s from now on: an approved merge's turn holds checks off its repositories again", check.TurnsBucket)
|
||||
}
|
||||
}
|
||||
|
||||
// Holding opens the bucket when it is not yet, then answers what the turns hold.
|
||||
func (l *lazyTurns) Holding(repositories []string, heads string) (string, delivery.TurnWait, bool) {
|
||||
func (l *lazyTurns) Holding(repositories []string, heads string) (string, meshdelivery.TurnsByRepositoryValueWaitingItem, bool) {
|
||||
l.ensure()
|
||||
return l.turns.Holding(repositories, heads)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
sdkfindings "git.novox.be/novox/mesh-delivery/sdk/findings"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
@@ -19,23 +18,29 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
sdkfindings "git.novox.be/novox/mesh-delivery/sdk/findings"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
)
|
||||
|
||||
// eventSubject is the subject of an event of the checker's stream checks (the bus names the stream CHECKS): the indexer's
|
||||
// watch of the previews bucket and the forge bridge's watch of the owed bucket are woken by them.
|
||||
func eventSubject(event string) string {
|
||||
return process.ModuleStreamSubject(delivery.CheckerModule, event)
|
||||
return process.ModuleStreamSubject(check.CheckerModule, event)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -66,11 +71,11 @@ func durationEnv(getenv func(string) string, name string, def time.Duration) (ti
|
||||
}
|
||||
|
||||
// The checker's own states of its first views (views.StatusState) and of its findings, where the condition keeper raises a
|
||||
// first view unanswered past its bound (ADR 0615 §6); and the indexer's states it follows (delivery.CandidatesBucket,
|
||||
// first view unanswered past its bound (ADR 0615 §6); and the indexer's states it follows (check.CandidatesBucket,
|
||||
// ModulesBucket, ManifestsBucket).
|
||||
var (
|
||||
firstViewsBucket = delivery.CheckerModule + "_" + views.StatusState
|
||||
findingsBucket = delivery.CheckerModule + "_findings"
|
||||
firstViewsBucket = check.CheckerModule + "_" + views.StatusState
|
||||
findingsBucket = check.CheckerModule + "_findings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -120,7 +125,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
defer conn.Close()
|
||||
// Its own states: a check without one of them would be unwritten, so each stops the start, naming its grant (issue 1945).
|
||||
buckets := map[string]jetstream.KeyValue{}
|
||||
for _, name := range []string{delivery.CheckerVerdictsBucket, delivery.CheckerKeptBucket, delivery.CheckerOwedBucket, delivery.PreviewsBucket,
|
||||
for _, name := range []string{check.CheckerVerdictsBucket, check.CheckerKeptBucket, check.CheckerOwedBucket, check.PreviewsBucket,
|
||||
check.TriesBucket} {
|
||||
if buckets[name], err = process.EssentialBucket(ctx, js, "checker", name, "judge or keep a check"); err != nil {
|
||||
return err
|
||||
@@ -146,7 +151,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
// The module has one durable consumer of EVENTS: one hub pulls it for the views reader and for the registry seats'
|
||||
// published, started once both are registered.
|
||||
hub := process.NewEventHub(logger.Printf)
|
||||
reader := &views.ProcessReader{Module: delivery.CheckerModule, Call: caller.Call, Logf: logger.Printf, Hub: hub}
|
||||
reader := &views.ProcessReader{Module: check.CheckerModule, Call: caller.Call, Logf: logger.Printf, Hub: hub}
|
||||
seats := seatList{reader: followSeatList(seatlist.ProcessBus(caller.Call, hub), logger.Printf)}
|
||||
findings := process.AwaitBucket(ctx, js, conn, findingsBucket, "a first view unanswered past its bound is said in the log alone", logger.Printf)
|
||||
reader.Findings = func() views.FindingWriter { return sdkfindings.Writer(ctx, findings.KV) }
|
||||
@@ -157,14 +162,14 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
followed = errors.Join(followed, err)
|
||||
return p
|
||||
}
|
||||
candidates := followOwner(delivery.IndexerModule, candidatesState, "indexer_read_candidates", indexerViews)
|
||||
modules := followOwner(delivery.IndexerModule, modulesState, "indexer_read_modules", indexerViews)
|
||||
manifests := followOwner(delivery.IndexerModule, manifestsState, "indexer_read_manifests", indexerViews)
|
||||
packages := followOwner(delivery.IndexerModule, delivery.PackagesState, "indexer_read_packages", indexerViews)
|
||||
predefined := followOwner(delivery.IndexerModule, delivery.IndexerPredefinedState, "indexer_read_predefined", indexerViews)
|
||||
placement := followOwner(delivery.AssignerModule, delivery.PlacementState, "assigner_read_placement", assignerViews)
|
||||
candidates := followOwner(check.IndexerModule, candidatesState, "indexer_read_candidates", indexerViews)
|
||||
modules := followOwner(check.IndexerModule, modulesState, "indexer_read_modules", indexerViews)
|
||||
manifests := followOwner(check.IndexerModule, manifestsState, "indexer_read_manifests", indexerViews)
|
||||
packages := followOwner(check.IndexerModule, meshcatalog.StatePackages, "indexer_read_packages", indexerViews)
|
||||
predefined := followOwner(check.IndexerModule, check.IndexerPredefinedState, "indexer_read_predefined", indexerViews)
|
||||
placement := followOwner(buildrun.AssignerModule, meshmoduleassigner.StatePlacement, "assigner_read_placement", assignerViews)
|
||||
// The seventh: the indexer's seat definitions, so a pull request whose head's definition does not parse fails.
|
||||
seatDefs := followOwner(delivery.IndexerModule, seatDefinitionsState, "indexer_read_seat_definitions", indexerViews)
|
||||
seatDefs := followOwner(check.IndexerModule, seatDefinitionsState, "indexer_read_seat_definitions", indexerViews)
|
||||
if followed != nil {
|
||||
return followed
|
||||
}
|
||||
@@ -200,7 +205,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
// The state may not be on the bus yet when a delivery started the checker (the grants part creates it, issue 1790): it
|
||||
// is bound when the bus says it was created, and the latest hearing is written then.
|
||||
hearingKV := process.AwaitBucket(ctx, js, conn, check.HearingBucket, "checker_status cannot say whether DEAD_LETTERS is heard", logger.Printf)
|
||||
hearing := buildrun.NewDeadLetterHearing(ctx, buildrun.JetStreamDeadLetters{JS: js}, []string{delivery.AskGo, delivery.AskNode}, dead,
|
||||
hearing := buildrun.NewDeadLetterHearing(ctx, buildrun.JetStreamDeadLetters{JS: js}, []string{buildstep.AskGo, buildstep.AskNode}, dead,
|
||||
logger.Printf, buildrun.SayHearing(ctx, hearingKV, logger.Printf))
|
||||
defer hearing.Stop()
|
||||
// Each ask's step is heard by its own watch and, at every step end of its build module and every reconnect, read
|
||||
@@ -211,8 +216,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
asksKV := process.AwaitBucket(ctx, js, conn, check.AsksBucket, "checker_ask_again refuses every check, as it cannot tell whether its ask waits in a stream", logger.Printf)
|
||||
ch := &check.Checker{
|
||||
Asks: awaitedKV{asksKV},
|
||||
Verdicts: kvStore{buckets[delivery.CheckerVerdictsBucket]},
|
||||
Owed: kvStore{buckets[delivery.CheckerOwedBucket]},
|
||||
Verdicts: kvStore{buckets[check.CheckerVerdictsBucket]},
|
||||
Owed: kvStore{buckets[check.CheckerOwedBucket]},
|
||||
Trunks: trunks,
|
||||
Turns: lazy,
|
||||
Bound: checkBound,
|
||||
@@ -222,10 +227,10 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
}
|
||||
// The builder's builds say how the candidates' fingerprints stand (issue 1684); where its bucket is not there yet the line
|
||||
// says nothing of the builds, until the checker starts again.
|
||||
var builds check.BuildStates
|
||||
var buildStates check.BuildStates
|
||||
var ends check.BuildEnds
|
||||
if kv := process.OptionalBucket(ctx, js, delivery.BuilderBuildsBucket, "the line says nothing of the builds, and a check composes without waiting on them, until the checker starts again", logger.Printf); kv != nil {
|
||||
builds, ends = buildReader{kvStore{kv}}, buildEnds{kv}
|
||||
if kv := process.OptionalBucket(ctx, js, builds.BuilderBuildsBucket, "the line says nothing of the builds, and a check composes without waiting on them, until the checker starts again", logger.Printf); kv != nil {
|
||||
buildStates, ends = buildReader{kvStore{kv}}, buildEnds{kv}
|
||||
}
|
||||
if ends != nil {
|
||||
ch.Rebuilds = ends // a check failed on a build is asked again once that build is done again (ADR 0603 §10)
|
||||
@@ -276,8 +281,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
}
|
||||
ch.Judge = newJudge(judgeDeps{
|
||||
asks: buildrun.Asker{Publisher: asks, Waiter: waiter, Bound: stepBound}, tools: tools, trunks: trunks,
|
||||
kept: kvStore{buckets[delivery.CheckerKeptBucket]}, previews: kvStore{buckets[delivery.PreviewsBucket]},
|
||||
repos: repos, published: published, builds: builds, ends: ends, buildWait: set.buildWait, forge: forgeReads{call: caller.Call}, logf: logger.Printf,
|
||||
kept: kvStore{buckets[check.CheckerKeptBucket]}, previews: kvStore{buckets[check.PreviewsBucket]},
|
||||
repos: repos, published: published, builds: buildStates, ends: ends, buildWait: set.buildWait, forge: forgeReads{call: caller.Call}, logf: logger.Printf,
|
||||
announce: announce, compositions: compositions, tags: tags,
|
||||
nodes: openNodeNames(ctx, js, logger.Printf),
|
||||
predefined: keptPredefined{predefined},
|
||||
@@ -333,9 +338,9 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
// the assigner's own presence says whether a composition is being made (compositionWatch, ADR 0537 §4). A check is no
|
||||
// longer held for a node: its asks wait in the build seats' streams, which redeliver a lost node's ask (ADR 0525 §6,
|
||||
// issue 1912), so the node-engine's have is not watched here.
|
||||
if kv := process.OptionalBucket(ctx, js, delivery.PresenceBucket, "no node counts as online until the checker starts again", logger.Printf); kv != nil {
|
||||
if kv := process.OptionalBucket(ctx, js, check.PresenceBucket, "no node counts as online until the checker starts again", logger.Printf); kv != nil {
|
||||
if pw, err := settle(ctx, kv, func(e jetstream.KeyValueEntry) { presence.Put(e.Key(), e.Value(), !opOf(e)) }); err != nil {
|
||||
process.NotWatched(logger.Printf, delivery.PresenceBucket, err, "no node counts as online until the checker starts again")
|
||||
process.NotWatched(logger.Printf, check.PresenceBucket, err, "no node counts as online until the checker starts again")
|
||||
} else {
|
||||
go func() {
|
||||
errs <- follow(ctx, pw, func(e jetstream.KeyValueEntry) {
|
||||
@@ -348,18 +353,18 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
}
|
||||
// The gate's source says the trunk head of each repository, which a pull request is merged onto. Where it is not there
|
||||
// yet no trunk is known, so nothing is checked.
|
||||
if kv := process.OptionalBucket(ctx, js, delivery.SourceBucket, "no trunk is known, so nothing is checked until the checker starts again", logger.Printf); kv != nil {
|
||||
if kv := process.OptionalBucket(ctx, js, check.SourceBucket, "no trunk is known, so nothing is checked until the checker starts again", logger.Printf); kv != nil {
|
||||
sw, err := settle(ctx, kv, func(e jetstream.KeyValueEntry) {
|
||||
if e.Key() == delivery.SourceMainKey {
|
||||
if e.Key() == meshdeliveryclient.SourceMainKey {
|
||||
trunks.Put(e.Value(), !opOf(e))
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
process.NotWatched(logger.Printf, delivery.SourceBucket, err, "no trunk is known, so nothing is checked until the checker starts again")
|
||||
process.NotWatched(logger.Printf, check.SourceBucket, err, "no trunk is known, so nothing is checked until the checker starts again")
|
||||
} else {
|
||||
go func() {
|
||||
errs <- follow(ctx, sw, func(e jetstream.KeyValueEntry) {
|
||||
if e.Key() == delivery.SourceMainKey && trunks.Put(e.Value(), !opOf(e)) {
|
||||
if e.Key() == meshdeliveryclient.SourceMainKey && trunks.Put(e.Value(), !opOf(e)) {
|
||||
ch.TrunkMoved(ctx) // the trunk moved: its statuses go pending, and a verdict for the old trunk is judged again
|
||||
}
|
||||
})
|
||||
@@ -371,7 +376,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
// Where the gate's bucket is not there yet it is read as empty, and opened again before each claim until it is there
|
||||
// (lazyTurns), so a gate delivered after the checker started holds checks from its first turn on.
|
||||
lazy.open = func() error {
|
||||
kv, err := js.KeyValue(ctx, delivery.TurnsBucket)
|
||||
kv, err := js.KeyValue(ctx, check.TurnsBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -444,7 +449,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
}()
|
||||
// The checker's own verdicts, updates only, in one watch: one checker_ask_again wrote asks its check again
|
||||
// (Checker.VerdictWritten); a deleted key of heads a candidate holds is judged again (Checker.VerdictDeleted, issue 1733).
|
||||
vw, err := buckets[delivery.CheckerVerdictsBucket].WatchAll(ctx, jetstream.UpdatesOnly())
|
||||
vw, err := buckets[check.CheckerVerdictsBucket].WatchAll(ctx, jetstream.UpdatesOnly())
|
||||
if err != nil {
|
||||
return fmt.Errorf("the checker's verdicts cannot be watched, so no check can be asked again by hand and a deleted verdict would never be written again: %w", err)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestTheCheckerWatchesNoHave(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, s := range []string{"delivery.HaveBucket", "buildrun.HaveSet"} {
|
||||
for _, s := range []string{"check.HaveBucket", "buildrun.HaveSet"} {
|
||||
if strings.Contains(string(raw), s) {
|
||||
t.Errorf("the checker program still reads %s", s)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// publishedPackages is the indexer's state `packages` as the checker follows it (novox/hq ADR 0593 §2): by mesh Go
|
||||
@@ -11,13 +13,13 @@ import (
|
||||
// package's version; the checker reads it, so a Go check builds every mesh package at the version the mesh publishes
|
||||
// (check.Examiner.Published) and computes none.
|
||||
//
|
||||
// Its series keys (delivery.SeriesPackage, decoded with delivery.DecodeSeriesPackage) name each package by its name in its
|
||||
// Its series keys (meshcatalog.PackagesByPackageSeriesValue, decoded with meshcatalog.DecodePackagesByPackageSeriesValue) name each package by its name in its
|
||||
// own ecosystem and the `<module>/<artifact>` it is built from (design 32): the known packages the judge takes
|
||||
// (PackageNames).
|
||||
type publishedPackages struct {
|
||||
mu sync.Mutex
|
||||
byKey map[string]delivery.PublishedPackages
|
||||
series map[string]delivery.SeriesPackage
|
||||
byKey map[string]meshcatalog.PackagesByModuleValue
|
||||
series map[string]meshcatalog.PackagesByPackageSeriesValue
|
||||
unread map[string]string
|
||||
// answered is whether the state's first reading ended (Answered): until then the checker knows no package's name.
|
||||
answered bool
|
||||
@@ -36,7 +38,7 @@ func (p *publishedPackages) Put(key string, raw []byte, deleted bool) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.byKey == nil {
|
||||
p.byKey, p.series, p.unread = map[string]delivery.PublishedPackages{}, map[string]delivery.SeriesPackage{}, map[string]string{}
|
||||
p.byKey, p.series, p.unread = map[string]meshcatalog.PackagesByModuleValue{}, map[string]meshcatalog.PackagesByPackageSeriesValue{}, map[string]string{}
|
||||
}
|
||||
delete(p.byKey, key)
|
||||
delete(p.series, key)
|
||||
@@ -44,8 +46,8 @@ func (p *publishedPackages) Put(key string, raw []byte, deleted bool) {
|
||||
if deleted {
|
||||
return
|
||||
}
|
||||
if delivery.IsSeriesPackageKey(key) {
|
||||
s, err := delivery.DecodeSeriesPackage(raw)
|
||||
if isSeriesPackageKey(key) {
|
||||
s, err := meshcatalog.DecodePackagesByPackageSeriesValue(raw)
|
||||
if err != nil {
|
||||
p.unread[key] = err.Error()
|
||||
return
|
||||
@@ -53,7 +55,7 @@ func (p *publishedPackages) Put(key string, raw []byte, deleted bool) {
|
||||
p.series[key] = s
|
||||
return
|
||||
}
|
||||
v, err := delivery.DecodePublishedPackages(raw)
|
||||
v, err := meshcatalog.DecodePackagesByModuleValue(raw)
|
||||
if err != nil {
|
||||
p.unread[key] = err.Error()
|
||||
return
|
||||
@@ -64,10 +66,10 @@ func (p *publishedPackages) Put(key string, raw []byte, deleted bool) {
|
||||
// LatestPackages is the latest published version of every mesh Go module the indexer holds, by module path: the commit
|
||||
// its repository was published at. A module the state does not hold is left out: a check builds it at the version
|
||||
// go.mod names.
|
||||
func (p *publishedPackages) LatestPackages() delivery.PackageVersions {
|
||||
func (p *publishedPackages) LatestPackages() buildstep.PackageVersions {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
out := delivery.PackageVersions{}
|
||||
out := buildstep.PackageVersions{}
|
||||
for _, v := range p.byKey {
|
||||
if v.Module != "" && v.Commit != "" {
|
||||
out[v.Module] = v.Commit
|
||||
@@ -93,3 +95,7 @@ func (p *publishedPackages) PackageNames() map[string]string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// isSeriesPackageKey says a key of the indexer's state packages is a package's series (`<package>.<series>`): a module's key
|
||||
// is its path escaped, which holds no dot (keys.EscapeName).
|
||||
func isSeriesPackageKey(key string) bool { return strings.Contains(key, ".") }
|
||||
|
||||
@@ -11,11 +11,10 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry"
|
||||
)
|
||||
|
||||
// publishedSeats are the two package registry seats' published, by subject (their contracts in mesh-contracts): heard on
|
||||
@@ -46,7 +45,7 @@ func (t tagAsks) Want(ctx context.Context, w check.TagWanted) error {
|
||||
msg := nats.NewMsg(eventSubject(check.EventTagWanted))
|
||||
id := fmt.Sprintf("tag-wanted.%s.%s.%s.%s", w.Repository, w.Head, w.Module, w.Package)
|
||||
msg.Header.Set("x-event-id", id)
|
||||
msg.Header.Set("x-source", delivery.CheckerModule)
|
||||
msg.Header.Set("x-source", check.CheckerModule)
|
||||
msg.Header.Set("x-node", t.node)
|
||||
msg.Header.Set("x-time", t.now().UTC().Format(time.RFC3339Nano))
|
||||
msg.Header.Set("content-type", "application/json")
|
||||
@@ -65,7 +64,7 @@ func (t tagAsks) Say(ctx context.Context, v check.VerdictEvent) error {
|
||||
msg := nats.NewMsg(eventSubject(check.EventVerdict))
|
||||
id := fmt.Sprintf("verdict.%s.%d.%s.%s.%x", v.Repository, v.Pull, v.Head, v.Verdict, sha256.Sum256([]byte(strings.Join(v.OutOfStep, "\n"))))
|
||||
msg.Header.Set("x-event-id", id)
|
||||
msg.Header.Set("x-source", delivery.CheckerModule)
|
||||
msg.Header.Set("x-source", check.CheckerModule)
|
||||
msg.Header.Set("x-node", t.node)
|
||||
msg.Header.Set("x-time", t.now().UTC().Format(time.RFC3339Nano))
|
||||
msg.Header.Set("content-type", "application/json")
|
||||
|
||||
@@ -12,8 +12,6 @@ import (
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
)
|
||||
|
||||
@@ -58,7 +56,7 @@ func (w *toolchainWatch) ensure(module string) error {
|
||||
if w.watched[module] {
|
||||
return nil
|
||||
}
|
||||
name := delivery.ToolchainsBucket(module)
|
||||
name := buildrun.ToolchainsBucket(module)
|
||||
kv, err := w.js.KeyValue(w.ctx, name)
|
||||
switch {
|
||||
case errors.Is(err, jetstream.ErrBucketNotFound):
|
||||
|
||||
@@ -4,21 +4,29 @@ go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
require git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-20261104174638-125929dbc459
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-20261104174344-ae5266e77639
|
||||
git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry v0.0.0-20261104174344-ae5266e77639
|
||||
git.novox.be/novox/mesh-contracts/go/seats/meshconfig v0.0.0-20261104174344-ae5266e77639
|
||||
git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker v0.0.0-20261104174344-ae5266e77639
|
||||
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-20261104174344-ae5266e77639
|
||||
git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry v0.0.0-20261104174344-ae5266e77639
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun v0.0.0-00010101000000-000000000000
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
|
||||
@@ -6,7 +6,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
// One check per delivery (novox/hq ADR 0548): the checker's part. A merge turn holds new checks off its repository (§3);
|
||||
@@ -17,7 +21,7 @@ import (
|
||||
func turnOf(t testing.TB, repo, group, heads string) []byte {
|
||||
t.Helper()
|
||||
at := time.Date(2026, 10, 26, 9, 0, 0, 0, time.UTC)
|
||||
data, err := delivery.EncodeTurn(delivery.Turn{Repository: repo, Waiting: []delivery.TurnWait{{Group: group, Heads: heads, Approved: at, Since: at}}})
|
||||
data, err := meshdeliveryclient.EncodeTurn(meshdelivery.TurnsByRepositoryValue{Repository: repo, Waiting: []meshdelivery.TurnsByRepositoryValueWaitingItem{{Group: group, Heads: heads, Approved: at, Since: at}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -26,7 +30,7 @@ func turnOf(t testing.TB, repo, group, heads string) []byte {
|
||||
|
||||
func turnKey(t testing.TB, repo string) string {
|
||||
t.Helper()
|
||||
k, err := delivery.TurnsKey(repo)
|
||||
k, err := meshdeliveryclient.TurnsKey(repo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -45,10 +49,10 @@ func waitFor(t testing.TB, what string, ok func() bool) {
|
||||
}
|
||||
|
||||
// pullWithFP is a pull request touching the module m at the fingerprint `src2:` and 64 times the hex digit d.
|
||||
func pullWithFP(repo string, number int, head string, d string) delivery.CandidatePull {
|
||||
func pullWithFP(repo string, number int, head string, d string) meshcatalog.CandidatesByGroupValuePullsItem {
|
||||
p := pullOf(repo, number, head)
|
||||
p.Modules = []string{"m"}
|
||||
p.Fps = map[string]delivery.CandidateModule{"m": {FP2: "src2:" + strings.Repeat(d, 64)}}
|
||||
p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"m": {Fp2: "src2:" + strings.Repeat(d, 64)}}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -76,7 +80,7 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
}
|
||||
r.ch.Reconsider(ctx) // what the watch of turns does on the delete
|
||||
r.ch.Wait()
|
||||
if j.count() != 1 || r.verdict(t, keyOf(t, "feat/x", cand)).State != delivery.VerdictPass {
|
||||
if j.count() != 1 || r.verdict(t, keyOf(t, "feat/x", cand)).State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Errorf("judged %d times after the turn went, want once", j.count())
|
||||
}
|
||||
},
|
||||
@@ -86,7 +90,7 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
turns := &Turns{}
|
||||
r.ch.Turns = turns
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1))
|
||||
c, _ := delivery.DecodeCandidate(cand)
|
||||
c, _ := meshcatalog.DecodeCandidatesByGroupValue(cand)
|
||||
turns.Put(turnKey(t, repoSDK), turnOf(t, repoSDK, "fix/other", "0123456789ab"), false)
|
||||
turns.Put(turnKey(t, repoA), turnOf(t, repoA, "feat/x", GateHeads(c)), false)
|
||||
r.ch.Candidate(ctx, "feat%2Fx", cand)
|
||||
@@ -113,7 +117,7 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
r.ch.Reconsider(ctx)
|
||||
close(release)
|
||||
r.ch.Wait()
|
||||
if j.count() != 1 || r.verdict(t, keyOf(t, "feat/x", cand)).State != delivery.VerdictPass {
|
||||
if j.count() != 1 || r.verdict(t, keyOf(t, "feat/x", cand)).State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Errorf("judged %d times; the running check is not ended by the turn", j.count())
|
||||
}
|
||||
},
|
||||
@@ -144,7 +148,7 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
if j.count() != 2 || kinds[0] != KindNew || kinds[1] != KindTrunk || from[0] != nil || from[1][repoA] != trunkA {
|
||||
t.Fatalf("judged %d times as %v carried from %v; want a whole check, then one carry from %s", j.count(), kinds, from, trunkA[:12])
|
||||
}
|
||||
if v.State != delivery.VerdictPass || v.Trunk[repoA] != trunkA2 || v.CarriedFrom[repoA] != trunkA {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass || v.Trunk[repoA] != trunkA2 || v.CarriedFrom[repoA] != trunkA {
|
||||
t.Errorf("the verdict is %s on %v carried from %v; want a pass on the new trunk carried from the old", v.State, v.Trunk, v.CarriedFrom)
|
||||
}
|
||||
if s := owedState(t, r, repoA, 7, head1); s != "success" {
|
||||
@@ -160,7 +164,7 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
j := &judgeFunc{f: func(_ context.Context, c *Case, _ int) (Outcome, error) {
|
||||
kept = append(kept, c.Composition != nil)
|
||||
v := passing()
|
||||
v.Composition = &delivery.Composition{State: delivery.CompositionPass, SDK: "v0.1.14-x", FPSet: c.FPSet}
|
||||
v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionPass, Sdk: "v0.1.14-x", Fpset: c.FPSet}
|
||||
return Outcome{Verdict: v}, nil
|
||||
}}
|
||||
r := newRig(t, j)
|
||||
@@ -220,7 +224,7 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
j := &judgeFunc{f: func(_ context.Context, c *Case, call int) (Outcome, error) {
|
||||
kinds, from = append(kinds, c.Kind), append(from, c.CarriedFrom)
|
||||
if call == 2 {
|
||||
return Outcome{Verdict: delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: "ace",
|
||||
return Outcome{Verdict: meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: "ace",
|
||||
Words: repoA + "#7 " + ConflictWords + "go.mod"}}}, nil
|
||||
}
|
||||
return Outcome{Verdict: passing()}, nil
|
||||
@@ -260,7 +264,7 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
t.Errorf("the line names %s on %s first, want the head on the new trunk", head, trunk)
|
||||
}
|
||||
cs.CarriedFrom = map[string]string{repoA: trunkA2}
|
||||
pending := Line(delivery.Verdict{State: delivery.VerdictRunning}, cs, p, nil, nil, nil)
|
||||
pending := Line(meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, cs, p, nil, nil, nil)
|
||||
if !strings.HasPrefix(pending, "being carried to 111111111111 on aaaaaaaaaaaa | from abababababab") {
|
||||
t.Errorf("the pending line of a carry = %q", pending)
|
||||
}
|
||||
@@ -274,10 +278,10 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
r.ch.Candidate(ctx, "feat%2Fa", candA)
|
||||
r.ch.Candidate(ctx, "feat%2Fc", candC)
|
||||
r.ch.Wait()
|
||||
a, _ := delivery.DecodeCandidate(candA)
|
||||
c, _ := delivery.DecodeCandidate(candC)
|
||||
a, _ := meshcatalog.DecodeCandidatesByGroupValue(candA)
|
||||
c, _ := meshcatalog.DecodeCandidatesByGroupValue(candC)
|
||||
at := time.Date(2026, 10, 26, 9, 0, 0, 0, time.UTC)
|
||||
turn, err := delivery.EncodeTurn(delivery.Turn{Repository: repoA, Waiting: []delivery.TurnWait{
|
||||
turn, err := meshdeliveryclient.EncodeTurn(meshdelivery.TurnsByRepositoryValue{Repository: repoA, Waiting: []meshdelivery.TurnsByRepositoryValueWaitingItem{
|
||||
{Group: "feat/a", Heads: GateHeads(a), Approved: at, Since: at}, {Group: "feat/c", Heads: GateHeads(c), Approved: at.Add(time.Minute), Since: at}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -322,7 +326,7 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
if call == 1 {
|
||||
return Outcome{Verdict: passing()}, nil
|
||||
}
|
||||
return Outcome{Verdict: delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Test: "unit: m/a.TestX", Words: "want 4"}}}, nil
|
||||
return Outcome{Verdict: meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Test: "unit: m/a.TestX", Words: "want 4"}}}, nil
|
||||
}}
|
||||
r := newRig(t, j)
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1))
|
||||
@@ -332,7 +336,7 @@ func TestOneCheckPerDelivery(t *testing.T) {
|
||||
r.ch.TrunkMoved(ctx)
|
||||
r.ch.Wait()
|
||||
v := r.verdict(t, keyOf(t, "feat/x", cand))
|
||||
if v.State != delivery.VerdictFail || v.CarriedFrom != nil || !strings.HasPrefix(v.Failed.Words, "want 4") ||
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail || v.CarriedFrom != nil || !strings.HasPrefix(v.Failed.Words, "want 4") ||
|
||||
!strings.Contains(v.Failed.Words, repoA+" "+trunkA[:12]+" to "+trunkA2[:12]) {
|
||||
t.Errorf("the failed carry is %s carried from %v with words %q; want the test's words, then the move", v.State, v.CarriedFrom, v.Failed.Words)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
// **A check is asked again by hand** (the lost check of 2026-10-28; tools-are-how-a-module-is-spoken-to: every recovery
|
||||
@@ -23,7 +25,7 @@ import (
|
||||
// AsksState is the checker's state of the asks each running check put, by verdict key (CheckAsks); AsksBucket its bucket.
|
||||
const (
|
||||
AsksState = "asks"
|
||||
AsksBucket = delivery.CheckerModule + "_" + AsksState
|
||||
AsksBucket = CheckerModule + "_" + AsksState
|
||||
)
|
||||
|
||||
// CheckAsks are the asks a check put, in order, as the state asks keeps them while it runs; its end deletes the key.
|
||||
@@ -70,8 +72,8 @@ const AskedAgainWords = EndedWords + "asked again by hand (" + AskAgainTool + ")
|
||||
var errAskedAgain = errors.New("its verdict was asked again by hand (" + AskAgainTool + ")")
|
||||
|
||||
// IsAskedAgain says whether a verdict was written by AskAgainTool.
|
||||
func IsAskedAgain(v delivery.Verdict) bool {
|
||||
return IsEnded(v) && strings.HasPrefix(v.Failed.Words, AskedAgainWords)
|
||||
func IsAskedAgain(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool {
|
||||
return IsEnded(v) && strings.HasPrefix(failedOf(v).Words, AskedAgainWords)
|
||||
}
|
||||
|
||||
// VerdictEntry is the verdicts state as the ask-again tool writes it: a key's value and revision (nil and 0 for none),
|
||||
@@ -104,7 +106,7 @@ func (r Reader) AskAgain(w VerdictEntry, group, why string) (AskedAgain, error)
|
||||
if !ok {
|
||||
return AskedAgain{}, fmt.Errorf("no candidate is held for the group %q", group)
|
||||
}
|
||||
key, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash)
|
||||
key, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads)
|
||||
if err != nil {
|
||||
return AskedAgain{}, err
|
||||
}
|
||||
@@ -115,11 +117,11 @@ func (r Reader) AskAgain(w VerdictEntry, group, why string) (AskedAgain, error)
|
||||
if len(raw) == 0 {
|
||||
return AskedAgain{}, fmt.Errorf("%s holds no verdict: the checker claims its check when it looks at the candidate (%s)", key, StatusReadTool)
|
||||
}
|
||||
v, err := delivery.DecodeVerdict(raw)
|
||||
v, err := meshdeliverycheckerclient.DecodeVerdict(raw)
|
||||
if err != nil {
|
||||
return AskedAgain{}, fmt.Errorf("%s holds what is not a verdict: %w", key, err)
|
||||
}
|
||||
if v.State != delivery.VerdictRunning {
|
||||
if v.State != meshdeliverycheckerclient.VerdictRunning {
|
||||
return AskedAgain{}, fmt.Errorf("%s is %s, not running: a verdict stands until a push or a trunk move asks it again", key, v.State)
|
||||
}
|
||||
steps := r.readSteps()
|
||||
@@ -150,9 +152,9 @@ func (r Reader) AskAgain(w VerdictEntry, group, why string) (AskedAgain, error)
|
||||
}
|
||||
}
|
||||
words := AskedAgainWords + why
|
||||
ended := delivery.Verdict{Group: v.Group, Heads: v.Heads, Trunk: v.Trunk, State: delivery.VerdictFail,
|
||||
Failed: delivery.FailedFirst{Machine: "checker", Words: words}}
|
||||
data, err := delivery.EncodeVerdict(ended)
|
||||
ended := meshdeliverychecker.VerdictsByGroupHeadsValue{Group: v.Group, Heads: v.Heads, Trunk: v.Trunk, State: meshdeliverycheckerclient.VerdictFail,
|
||||
Failed: &FailedFirst{Machine: "checker", Words: words}}
|
||||
data, err := meshdeliverycheckerclient.EncodeVerdict(ended)
|
||||
if err != nil {
|
||||
return AskedAgain{}, err
|
||||
}
|
||||
@@ -168,7 +170,7 @@ func (r Reader) AskAgain(w VerdictEntry, group, why string) (AskedAgain, error)
|
||||
// the check of those heads running in this process, which then considers them again; with none running, the candidate
|
||||
// that holds them is considered at once. Any other verdict is the checker's own, and left.
|
||||
func (ch *Checker) VerdictWritten(ctx context.Context, key string, value []byte) {
|
||||
v, err := delivery.DecodeVerdict(value)
|
||||
v, err := meshdeliverycheckerclient.DecodeVerdict(value)
|
||||
if err != nil || !IsAskedAgain(v) {
|
||||
return
|
||||
}
|
||||
@@ -177,22 +179,22 @@ func (ch *Checker) VerdictWritten(ctx context.Context, key string, value []byte)
|
||||
if l, ok := ch.live[key]; ok {
|
||||
l.end(errAskedAgain)
|
||||
ch.mu.Unlock()
|
||||
ch.logf("%s: %s; the check running for it is ended and asked again", key, v.Failed.Words)
|
||||
ch.logf("%s: %s; the check running for it is ended and asked again", key, failedOf(v).Words)
|
||||
return
|
||||
}
|
||||
var group string
|
||||
var cand delivery.Candidate
|
||||
var cand meshcatalog.CandidatesByGroupValue
|
||||
for k, c := range ch.known {
|
||||
if vk, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash); err == nil && vk == key {
|
||||
if vk, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads); err == nil && vk == key {
|
||||
group, cand = k, c
|
||||
break
|
||||
}
|
||||
}
|
||||
ch.mu.Unlock()
|
||||
if group == "" {
|
||||
ch.logf("%s: %s, but no candidate holds these heads, so nothing is asked", key, v.Failed.Words)
|
||||
ch.logf("%s: %s, but no candidate holds these heads, so nothing is asked", key, failedOf(v).Words)
|
||||
return
|
||||
}
|
||||
ch.logf("%s: %s; asked again", key, v.Failed.Words)
|
||||
ch.logf("%s: %s; asked again", key, failedOf(v).Words)
|
||||
ch.consider(ctx, group, cand)
|
||||
}
|
||||
|
||||
@@ -7,28 +7,18 @@ import (
|
||||
|
||||
// A change that adds or modifies a compiled program fails (issue 1577): it is build output, `go build` in a module's folder
|
||||
// writes one beside go.mod, and `git add -A` takes it. The build module's fetch step, which holds the merged clone, reads the
|
||||
// first bytes of each changed file and reports the programs (mesh-sdk buildstep.MergeInto); the checker judges the answer.
|
||||
|
||||
// NoProgramsReported is the warning of a build module whose fetch answered no programs field: an older module, which cannot
|
||||
// tell, so the check does not refuse a program it did not look for.
|
||||
const NoProgramsReported = "the build module did not report programs: a compiled program in the change is not refused"
|
||||
// first bytes of each changed file and reports the programs (the build modules' fetch tools); the checker judges the answer.
|
||||
|
||||
// checkBinariesOf fails a pull request whose change adds or modifies a compiled program, naming each file. It judges the
|
||||
// first fetch answer of the pull request, whichever build module gave it; a build module that reports no programs leaves a
|
||||
// warning on the check and passes it.
|
||||
// first fetch answer of the pull request, whichever build module gave it. A fetch that made the merge and names no program has
|
||||
// none: the answer's `programs` is an omitted empty list (B2g), and `merge` tells a preview was made.
|
||||
func (st *state) checkBinariesOf(t *target) verdictPtr {
|
||||
key := fmt.Sprintf("%s#%d", t.pull.Repository, t.pull.Number)
|
||||
if t.fetched.Merge == "" {
|
||||
return nil // no merge was made: nothing was changed to judge
|
||||
}
|
||||
if t.fetched.Programs == nil {
|
||||
st.c.note(fmt.Sprintf("%s: %s (%s)", key, NoProgramsReported, t.module))
|
||||
st.c.warn(key, fmt.Sprintf("a compiled program is not refused: %s reported no programs", t.module))
|
||||
st.j.logf("%s: %s (%s)", key, NoProgramsReported, t.module)
|
||||
return nil
|
||||
}
|
||||
var found []string
|
||||
for _, p := range *t.fetched.Programs {
|
||||
for _, p := range t.fetched.Programs {
|
||||
if p.Commit != "" {
|
||||
found = append(found, fmt.Sprintf("%s: a compiled program added in %s and removed later: it stays in history; squash the branch (issue 1577)", p.Path, short12(p.Commit)))
|
||||
continue
|
||||
|
||||
@@ -1,52 +1,46 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
func TestAChangeWhoseFetchReportsProgramsFailsNamingEachFile(t *testing.T) { // 1577
|
||||
prog := func(p ...string) *[]ProgramFile {
|
||||
out := []ProgramFile{}
|
||||
prog := func(p ...string) []buildstep.Program {
|
||||
var out []buildstep.Program // no program is the omitted empty list of the typed answer
|
||||
for _, f := range p {
|
||||
path, commit, _ := strings.Cut(f, "@") // `path@commit`: added by a commit of the branch and removed later
|
||||
out = append(out, ProgramFile{Path: path, Kind: "ELF", Commit: commit})
|
||||
out = append(out, buildstep.Program{Path: path, Kind: "ELF", Commit: commit})
|
||||
}
|
||||
return &out
|
||||
return out
|
||||
}
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
programs *[]ProgramFile
|
||||
programs []buildstep.Program
|
||||
fail []string // paths named; none passes
|
||||
warned bool // the build module reported no programs field
|
||||
}{
|
||||
{"none found", prog(), nil, false},
|
||||
{"one program", prog("modules/m/m"), []string{"modules/m/m"}, false},
|
||||
{"two programs", prog("a/x", "b/y"), []string{"a/x", "b/y"}, false},
|
||||
{"added and removed in the branch", prog("gone/prog@0123456789abcdef0123"), []string{"gone/prog: a compiled program added in 0123456789ab and removed later: it stays in history; squash the branch (issue 1577)"}, false},
|
||||
{"an older build module reports nothing", nil, nil, true},
|
||||
{"none found", prog(), nil},
|
||||
{"one program", prog("modules/m/m"), []string{"modules/m/m"}},
|
||||
{"two programs", prog("a/x", "b/y"), []string{"a/x", "b/y"}},
|
||||
{"added and removed in the branch", prog("gone/prog@0123456789abcdef0123"), []string{"gone/prog: a compiled program added in 0123456789ab and removed later: it stays in history; squash the branch (issue 1577)"}},
|
||||
} {
|
||||
b := newBench(t)
|
||||
b.fetch.Merge, b.fetch.Programs = "mmmm", c.programs
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1))
|
||||
v := b.judge(t, cs).Verdict
|
||||
if got := slices.ContainsFunc(cs.Notes(), func(n string) bool { return strings.Contains(n, NoProgramsReported) }); got != c.warned {
|
||||
t.Errorf("%s: warned = %v, want %v (%v)", c.name, got, c.warned, cs.Notes())
|
||||
}
|
||||
line := Line(v, cs, cs.Pulls[0], nil, nil, nil)
|
||||
if got := strings.Contains(line, "a compiled program is not refused: mesh-build-go reported no programs"); got != c.warned {
|
||||
t.Errorf("%s: the status line holds the warning = %v, want %v: %s", c.name, got, c.warned, line)
|
||||
if len(cs.Notes()) > 0 {
|
||||
t.Errorf("%s: notes = %v: a merge with no program is not a fetch that reported none", c.name, cs.Notes())
|
||||
}
|
||||
if len(c.fail) == 0 {
|
||||
if v.State != delivery.VerdictPass {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Errorf("%s: verdict = %+v", c.name, v)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if v.State != delivery.VerdictFail {
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail {
|
||||
t.Errorf("%s: verdict = %+v, want a failure", c.name, v)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// ReadWorkers is how many values a read tool asks the bus for at once. Each value is a round trip from the tool through the
|
||||
@@ -60,7 +60,7 @@ func capKeys(keys []string, narrow string) ([]string, string) {
|
||||
// longer id is the later and equal lengths compare as text. A key that is no step key goes last.
|
||||
func newestFirst(keys []string) {
|
||||
id := func(k string) (string, bool) {
|
||||
_, id, err := delivery.ParseStepKey(k)
|
||||
_, id, err := buildstep.ParseStepKey(k)
|
||||
return id, err == nil
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
|
||||
@@ -41,10 +41,10 @@ func (st *state) buildsOfTheCheck(ctx context.Context) (map[string]string, []str
|
||||
readable := true
|
||||
for _, p := range st.c.Pulls {
|
||||
for module, cm := range p.Fps {
|
||||
if cm.FP2 == "" || cm.Manifest == "" {
|
||||
if cm.Fp2 == "" || cm.Manifest == "" {
|
||||
continue // the builder builds no fingerprint of it (candidates.go: none of the new form, or no manifest)
|
||||
}
|
||||
wait[module] = cm.FP2
|
||||
wait[module] = cm.Fp2
|
||||
if !readable || st.j.Read == nil {
|
||||
readable = false
|
||||
continue
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
)
|
||||
|
||||
// The states of a build the status line says (ADR 0484 clause 7).
|
||||
@@ -22,18 +24,18 @@ const (
|
||||
// BuildStates reads the builder's build of a module at a fingerprint of the new form (builds.<module>.<fingerprint>): the
|
||||
// value and whether the key exists.
|
||||
type BuildStates interface {
|
||||
Build(ctx context.Context, module, fp2 string) (delivery.BuilderBuild, bool, error)
|
||||
Build(ctx context.Context, module, fp2 string) (builds.BuilderBuild, bool, error)
|
||||
}
|
||||
|
||||
// NoteOf is the state of a build as the line says it: done is built, failed is failed, and one queued, building or not
|
||||
// begun is waiting.
|
||||
func NoteOf(b delivery.BuilderBuild, found bool) string {
|
||||
func NoteOf(b builds.BuilderBuild, found bool) string {
|
||||
switch {
|
||||
case !found:
|
||||
return BuildWaiting
|
||||
case b.State == delivery.BuildDone:
|
||||
case b.State == builds.BuildDone:
|
||||
return BuildBuilt
|
||||
case b.State == delivery.BuildFailed:
|
||||
case b.State == builds.BuildFailed:
|
||||
return BuildFailed
|
||||
}
|
||||
return BuildWaiting
|
||||
@@ -48,8 +50,8 @@ func (j *Examiner) buildNotes(ctx context.Context, c *Case) []BuildNote {
|
||||
fps := map[string]string{}
|
||||
for _, p := range c.Pulls {
|
||||
for module, cm := range p.Fps {
|
||||
if cm.FP2 != "" {
|
||||
fps[module] = cm.FP2
|
||||
if cm.Fp2 != "" {
|
||||
fps[module] = cm.Fp2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +81,7 @@ const DefaultBuildWait = 30 * time.Minute
|
||||
type BuildEnds interface {
|
||||
// Ended waits until the build of every module at its fingerprint (module to fingerprint) is done or failed, or ctx ends,
|
||||
// and answers the builds that ended, by module. An error is only builds that cannot be heard at all.
|
||||
Ended(ctx context.Context, builds map[string]string) (map[string]delivery.BuilderBuild, error)
|
||||
Ended(ctx context.Context, builds map[string]string) (map[string]builds.BuilderBuild, error)
|
||||
// Done waits until the build of every module at its fingerprint is done (a failed one built again, builder_build_again),
|
||||
// and says whether they all are; false when ctx ends first or they cannot be heard.
|
||||
Done(ctx context.Context, builds map[string]string) bool
|
||||
@@ -91,19 +93,19 @@ const BuildFailedTest = "build: "
|
||||
|
||||
// failedBuilds are the candidate's builds a failed verdict names as failed (BuildFailedTest), by module, at the fingerprint
|
||||
// the case carries for it; none for a verdict that names no failed build.
|
||||
func failedBuilds(v delivery.Verdict, cs *Case) map[string]string {
|
||||
if v.State != delivery.VerdictFail {
|
||||
func failedBuilds(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case) map[string]string {
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail {
|
||||
return nil
|
||||
}
|
||||
out := map[string]string{}
|
||||
for _, f := range append([]delivery.FailedFirst{v.Failed}, v.Failures...) {
|
||||
for _, f := range append([]FailedFirst{failedOf(v)}, failedList(v.Failures)...) {
|
||||
module, named := strings.CutPrefix(f.Test, BuildFailedTest)
|
||||
if !named {
|
||||
continue
|
||||
}
|
||||
for _, p := range cs.Pulls {
|
||||
if cm, ok := p.Fps[module]; ok && cm.FP2 != "" {
|
||||
out[module] = cm.FP2
|
||||
if cm, ok := p.Fps[module]; ok && cm.Fp2 != "" {
|
||||
out[module] = cm.Fp2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,13 +126,13 @@ func (j *Examiner) buildWait() time.Duration {
|
||||
// builds of the candidate and composes with it. A failed build fails the check, naming the module and the builder's words;
|
||||
// a build not ended within the bound is named, and the check composes without it. It answers the words of the composition's
|
||||
// warnings: what the check composes without waiting on its build, and why.
|
||||
func (st *state) awaitBuilds(ctx context.Context, v *delivery.Verdict) []string {
|
||||
func (st *state) awaitBuilds(ctx context.Context, v *meshdeliverychecker.VerdictsByGroupHeadsValue) []string {
|
||||
wait, words := st.buildsOfTheCheck(ctx)
|
||||
if len(wait) == 0 {
|
||||
return words
|
||||
}
|
||||
modules := slices.Sorted(maps.Keys(wait))
|
||||
if v.State == delivery.VerdictFail {
|
||||
if v.State == meshdeliverycheckerclient.VerdictFail {
|
||||
// The check has failed already: its red status is never held back by a build (the lead, 2026-11-03).
|
||||
return append(words, fmt.Sprintf("the check failed before its builds were waited on, so it composes at once, without waiting on the builds of %s (ADR 0603 §10)", strings.Join(modules, ", ")))
|
||||
}
|
||||
@@ -154,10 +156,10 @@ func (st *state) awaitBuilds(ctx context.Context, v *delivery.Verdict) []string
|
||||
switch {
|
||||
case !done:
|
||||
late = append(late, module)
|
||||
case b.State == delivery.BuildFailed:
|
||||
f := delivery.FailedFirst{Test: BuildFailedTest + module, Words: fmt.Sprintf("the candidate's build at %s failed (rebuild with builder_build_again; the check asks itself again when the build is done): %s", wait[module], b.Error)}
|
||||
if v.State != delivery.VerdictFail {
|
||||
v.State, v.Failed = delivery.VerdictFail, f
|
||||
case b.State == builds.BuildFailed:
|
||||
f := FailedFirst{Test: BuildFailedTest + module, Words: fmt.Sprintf("the candidate's build at %s failed (rebuild with builder_build_again; the check asks itself again when the build is done): %s", wait[module], b.Error)}
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail {
|
||||
v.State, v.Failed = meshdeliverycheckerclient.VerdictFail, &f
|
||||
continue
|
||||
}
|
||||
addFailure(v, f)
|
||||
|
||||
@@ -9,7 +9,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// Pull is one pull request the checker judges: its head, the trunk head of its repository it is merged onto and the
|
||||
@@ -20,7 +24,7 @@ type Pull struct {
|
||||
Head string
|
||||
Trunk string
|
||||
Modules []string
|
||||
Fps map[string]delivery.CandidateModule
|
||||
Fps map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue
|
||||
}
|
||||
|
||||
// Case is one check: a candidate's pull requests that are judged (those of a repository whose trunk is not known are left
|
||||
@@ -48,7 +52,7 @@ type Case struct {
|
||||
// seriesOf are, by pull request, the changes of a package series it makes (series.go), named in its status line.
|
||||
seriesOf map[string][]string
|
||||
// seriesChanges are, by pull request, the same changes as structures (series.go), named in the verdict and its event.
|
||||
seriesChanges map[string][]delivery.SeriesChange
|
||||
seriesChanges map[string][]meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem
|
||||
notes []string
|
||||
// asks are the ids of the asks the check put on the build seats' streams, in order: a try names them (Try.Asks).
|
||||
asks []string
|
||||
@@ -56,7 +60,7 @@ type Case struct {
|
||||
// the check waits for it: the node that took it, the phase it says, its end. A try writes them into its record, so a
|
||||
// running try names its asks and where each stands (issue 1890). Called from the check's goroutines; it must not block
|
||||
// for long.
|
||||
Watch func(askID string, step *delivery.Step)
|
||||
Watch func(askID string, step *buildstep.Step)
|
||||
// Progress says what the check is doing, so a check that ran past its bound names the step it was stuck at.
|
||||
Progress *Progress
|
||||
// Kind is what asked the check (KindNew, KindNodes, KindTrunk): a carry after a trunk or sibling move is asked on the
|
||||
@@ -65,11 +69,11 @@ type Case struct {
|
||||
// CarriedFrom are, for the carry of a verdict that passed, the trunk and sibling commits it was judged on (ADR 0548 §5);
|
||||
// nil for a whole check.
|
||||
CarriedFrom map[string]string
|
||||
// FPSet is the candidate's fingerprint set (Candidate.FPSet): a composition names the set it judged.
|
||||
// FPSet is the candidate's fingerprint set (Candidate.Fpset): a composition names the set it judged.
|
||||
FPSet string
|
||||
// Composition is, for a carry, the composition that passed on the same fingerprint set: it is kept and not asked again,
|
||||
// since no fingerprint of a module it composed changed (ADR 0548 §5). Nil composes.
|
||||
Composition *delivery.Composition
|
||||
Composition *meshdeliverychecker.VerdictsByGroupHeadsValueComposition
|
||||
}
|
||||
|
||||
// Asked records the id of an ask the check put.
|
||||
@@ -110,18 +114,18 @@ func (p *Progress) Now() (stage, node string) {
|
||||
// The checker judges pull requests into main only (ADR 0484 clause 11): the pull message carries no base, a merge into
|
||||
// another branch is a closed message, so every candidate is into the trunk; a repository whose trunk head is not known has
|
||||
// none to merge onto. It answers nil when no pull request is left to judge.
|
||||
func CaseOf(c delivery.Candidate, trunk map[string]string) *Case {
|
||||
key, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash)
|
||||
func CaseOf(c meshcatalog.CandidatesByGroupValue, trunk map[string]string) *Case {
|
||||
key, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
cs := &Case{Key: key, Group: c.Group, HeadsHash: c.HeadsHash, Heads: GateHeads(c), Trunk: map[string]string{}, Progress: &Progress{}, FPSet: c.FPSet}
|
||||
cs := &Case{Key: key, Group: c.Group, HeadsHash: c.Heads, Heads: GateHeads(c), Trunk: map[string]string{}, Progress: &Progress{}, FPSet: c.Fpset}
|
||||
for _, p := range c.Pulls {
|
||||
switch head, known := trunk[p.Repository]; {
|
||||
case !known || head == "":
|
||||
cs.Left = append(cs.Left, fmt.Sprintf("%s#%d: %s", p.Repository, p.Number, UntrunkedWordsOf([]string{p.Repository})))
|
||||
default:
|
||||
cs.Pulls = append(cs.Pulls, Pull{Repository: p.Repository, Number: p.Number, Head: p.Head.Head, Trunk: head, Modules: p.Modules, Fps: p.Fps})
|
||||
cs.Pulls = append(cs.Pulls, Pull{Repository: p.Repository, Number: int(p.Number), Head: p.Head, Trunk: head, Modules: p.Modules, Fps: p.Fps})
|
||||
cs.Trunk[p.Repository] = head
|
||||
}
|
||||
}
|
||||
@@ -151,8 +155,8 @@ func UntrunkedWordsOf(repos []string) string {
|
||||
}
|
||||
|
||||
// IsUntrunked says whether a verdict says its heads cannot be judged for want of a trunk head (UntrunkedWords).
|
||||
func IsUntrunked(v delivery.Verdict) bool {
|
||||
return v.State == delivery.VerdictFail && v.Failed.Machine == "checker" && strings.HasPrefix(v.Failed.Words, UntrunkedWords)
|
||||
func IsUntrunked(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool {
|
||||
return v.State == meshdeliverycheckerclient.VerdictFail && failedOf(v).Machine == "checker" && strings.HasPrefix(failedOf(v).Words, UntrunkedWords)
|
||||
}
|
||||
|
||||
// **A candidate outside the mesh is not the checker's** (sweep 2026-10-30 item 7): the forge carries repositories the mesh
|
||||
@@ -168,7 +172,7 @@ const OutsideWords = "outside the mesh: the gate's source.main names no trunk he
|
||||
|
||||
// Outside says every pull request of c is of a repository trunk names no head of and touches no module. Never with
|
||||
// trunk unknown (nil): before the gate's source is read nothing is outside.
|
||||
func Outside(c delivery.Candidate, trunk map[string]string) bool {
|
||||
func Outside(c meshcatalog.CandidatesByGroupValue, trunk map[string]string) bool {
|
||||
if trunk == nil || len(c.Pulls) == 0 {
|
||||
return false
|
||||
}
|
||||
@@ -182,11 +186,11 @@ func Outside(c delivery.Candidate, trunk map[string]string) bool {
|
||||
|
||||
// leftOf are the pull requests of a candidate whose repository has no trunk head known, with Trunk empty, in repository and
|
||||
// number order; nil when every repository's is known.
|
||||
func leftOf(c delivery.Candidate, trunk map[string]string) []Pull {
|
||||
func leftOf(c meshcatalog.CandidatesByGroupValue, trunk map[string]string) []Pull {
|
||||
var out []Pull
|
||||
for _, p := range c.Pulls {
|
||||
if trunk[p.Repository] == "" {
|
||||
out = append(out, Pull{Repository: p.Repository, Number: p.Number, Head: p.Head.Head, Modules: p.Modules, Fps: p.Fps})
|
||||
out = append(out, Pull{Repository: p.Repository, Number: int(p.Number), Head: p.Head, Modules: p.Modules, Fps: p.Fps})
|
||||
}
|
||||
}
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
@@ -199,15 +203,15 @@ func leftOf(c delivery.Candidate, trunk map[string]string) []Pull {
|
||||
}
|
||||
|
||||
// GateHeads is the hash the gate matches the members an Approve covers by (mesh-catalog mesh-delivery-gate onestatus.go
|
||||
// headsOf): delivery.HeadsHash over every pull request of the candidate, hq's included, as
|
||||
// headsOf): meshdeliveryclient.Heads over every pull request of the candidate, hq's included, as
|
||||
// `<owner/name lowercase>#<number>=<full head sha lowercase>`. A lone pull request is a group of one. Empty for a candidate
|
||||
// with no pull request.
|
||||
func GateHeads(c delivery.Candidate) string {
|
||||
heads := make([]delivery.Head, 0, len(c.Pulls))
|
||||
func GateHeads(c meshcatalog.CandidatesByGroupValue) string {
|
||||
heads := make([]meshdeliveryclient.Head, 0, len(c.Pulls))
|
||||
for _, p := range c.Pulls {
|
||||
heads = append(heads, delivery.Head{Repository: strings.ToLower(p.Repository), Number: p.Number, Head: strings.ToLower(p.Head.Head)})
|
||||
heads = append(heads, meshdeliveryclient.Head{Repository: strings.ToLower(p.Repository), Number: int(p.Number), Head: strings.ToLower(p.Head)})
|
||||
}
|
||||
hash, err := delivery.HeadsHash(heads)
|
||||
hash, err := meshdeliveryclient.HeadsHash(heads)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -227,7 +231,7 @@ type Trunks struct {
|
||||
func (t *Trunks) Put(value []byte, deleted bool) bool {
|
||||
var heads map[string]string
|
||||
if !deleted {
|
||||
s, err := delivery.DecodeSource(value)
|
||||
s, err := meshdeliveryclient.DecodeSource(value)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -11,7 +11,14 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
// DefaultBound is how long a check may run before it is ended failed naming the step it was stuck at (ADR 0484, issue 1678):
|
||||
@@ -38,7 +45,7 @@ type Announcer func(ctx context.Context, event, key string)
|
||||
// Outcome is what judging a case gives: the verdict (state, failure, levels, times, composition; the checker adds the
|
||||
// group, the heads and the trunk) and, for the line, what the check knows of the builds of the modules it touches.
|
||||
type Outcome struct {
|
||||
Verdict delivery.Verdict
|
||||
Verdict meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
Builds []BuildNote
|
||||
// Slowest names the slowest tests of a level that went over its bound ("unit pkg.Test 1.2s").
|
||||
Slowest []string
|
||||
@@ -80,7 +87,7 @@ type Checker struct {
|
||||
// Turns are the gate's merge turns (ADR 0548 §3): no check is claimed of a repository a turn names, but the check of the
|
||||
// waiting delivery's own heads; nil holds none.
|
||||
Turns interface {
|
||||
Holding(repositories []string, heads string) (string, delivery.TurnWait, bool)
|
||||
Holding(repositories []string, heads string) (string, meshdelivery.TurnsByRepositoryValueWaitingItem, bool)
|
||||
}
|
||||
// Bound is how long one check may run; none when 0 (DefaultBound).
|
||||
Bound time.Duration
|
||||
@@ -105,17 +112,17 @@ type Checker struct {
|
||||
once sync.Once
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
known map[string]delivery.Candidate // by candidates key
|
||||
inflight map[string]bool // by verdict key
|
||||
again map[string]bool // by verdict key: asked again while it ran
|
||||
tried map[string]bool // by verdict key: looked at since this process started
|
||||
epoch uint64 // changes of the nodes seen since this process started
|
||||
nodesFail map[string]uint64 // by verdict key: the epoch a failure of the nodes was written at
|
||||
held map[string]string // by verdict key: the turn that holds its check off, as last said
|
||||
live map[string]liveCheck // by verdict key: the check running in this process, ended when its heads go
|
||||
settling map[string]*window // by candidates key: the window its heads settle in
|
||||
rebuilt map[string]bool // by verdict key: every failed build its verdict names is done again
|
||||
awaiting map[string]bool // by verdict key: its failed builds are heard (awaitRebuilt)
|
||||
known map[string]meshcatalog.CandidatesByGroupValue // by candidates key
|
||||
inflight map[string]bool // by verdict key
|
||||
again map[string]bool // by verdict key: asked again while it ran
|
||||
tried map[string]bool // by verdict key: looked at since this process started
|
||||
epoch uint64 // changes of the nodes seen since this process started
|
||||
nodesFail map[string]uint64 // by verdict key: the epoch a failure of the nodes was written at
|
||||
held map[string]string // by verdict key: the turn that holds its check off, as last said
|
||||
live map[string]liveCheck // by verdict key: the check running in this process, ended when its heads go
|
||||
settling map[string]*window // by candidates key: the window its heads settle in
|
||||
rebuilt map[string]bool // by verdict key: every failed build its verdict names is done again
|
||||
awaiting map[string]bool // by verdict key: its failed builds are heard (awaitRebuilt)
|
||||
// outsideSaid are the candidates outside the mesh said in the log, by group and heads.
|
||||
outsideSaid map[string]bool
|
||||
}
|
||||
@@ -155,8 +162,8 @@ const DefaultSettle = 15 * time.Second
|
||||
const EndedWords = "ended: "
|
||||
|
||||
// IsEnded says whether a verdict was ended without being judged (EndedWords).
|
||||
func IsEnded(v delivery.Verdict) bool {
|
||||
return v.State == delivery.VerdictFail && v.Failed.Machine == "checker" && strings.HasPrefix(v.Failed.Words, EndedWords)
|
||||
func IsEnded(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool {
|
||||
return v.State == meshdeliverycheckerclient.VerdictFail && failedOf(v).Machine == "checker" && strings.HasPrefix(failedOf(v).Words, EndedWords)
|
||||
}
|
||||
|
||||
// superseded is the cause a running check is ended with: the heads that replaced its own, or none when its group went.
|
||||
@@ -171,7 +178,7 @@ func (s superseded) Error() string {
|
||||
|
||||
func (ch *Checker) init() {
|
||||
ch.once.Do(func() {
|
||||
ch.known, ch.inflight, ch.again, ch.tried = map[string]delivery.Candidate{}, map[string]bool{}, map[string]bool{}, map[string]bool{}
|
||||
ch.known, ch.inflight, ch.again, ch.tried = map[string]meshcatalog.CandidatesByGroupValue{}, map[string]bool{}, map[string]bool{}, map[string]bool{}
|
||||
ch.nodesFail, ch.held = map[string]uint64{}, map[string]string{}
|
||||
ch.live, ch.settling = map[string]liveCheck{}, map[string]*window{}
|
||||
ch.rebuilt, ch.awaiting = map[string]bool{}, map[string]bool{}
|
||||
@@ -216,19 +223,19 @@ func (ch *Checker) Wait() { ch.wg.Wait() }
|
||||
// checked. A value that is not a candidate is said and left.
|
||||
func (ch *Checker) Candidate(ctx context.Context, key string, value []byte) {
|
||||
ch.init()
|
||||
c, err := delivery.DecodeCandidate(value)
|
||||
c, err := meshcatalog.DecodeCandidatesByGroupValue(value)
|
||||
if err != nil {
|
||||
ch.logf("candidates.%s holds what is not a candidate, left: %v", key, err)
|
||||
return
|
||||
}
|
||||
vkey, _ := delivery.CheckerVerdictKey(c.Group, c.HeadsHash)
|
||||
vkey, _ := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads)
|
||||
ch.mu.Lock()
|
||||
prev, had := ch.known[key]
|
||||
ch.known[key] = c
|
||||
// A check of heads this candidate no longer holds is ended at once (issue 1890): a member's new head, a member joining
|
||||
// or leaving.
|
||||
ch.endOthers(key, vkey, GateHeads(c))
|
||||
moved := !had || prev.HeadsHash != c.HeadsHash
|
||||
moved := !had || prev.Heads != c.Heads
|
||||
if ch.Settle > 0 && (moved || ch.settling[key] != nil) {
|
||||
// The group's heads changed: its check waits until they have been the same for Settle, so a group whose members
|
||||
// are announced one after another is checked once, whole. A later change starts the window again. The wait is a
|
||||
@@ -279,9 +286,9 @@ func (ch *Checker) VerdictDeleted(ctx context.Context, key string) {
|
||||
return
|
||||
}
|
||||
var group string
|
||||
var cand delivery.Candidate
|
||||
var cand meshcatalog.CandidatesByGroupValue
|
||||
for k, c := range ch.known {
|
||||
if vk, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash); err == nil && vk == key {
|
||||
if vk, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads); err == nil && vk == key {
|
||||
group, cand = k, c
|
||||
break
|
||||
}
|
||||
@@ -324,7 +331,7 @@ func (ch *Checker) NodesChanged(ctx context.Context) {
|
||||
func (ch *Checker) Reconsider(ctx context.Context) {
|
||||
ch.init()
|
||||
ch.mu.Lock()
|
||||
known := make(map[string]delivery.Candidate, len(ch.known))
|
||||
known := make(map[string]meshcatalog.CandidatesByGroupValue, len(ch.known))
|
||||
for k, c := range ch.known {
|
||||
known[k] = c
|
||||
}
|
||||
@@ -341,7 +348,7 @@ func (ch *Checker) heads() map[string]string {
|
||||
return ch.Trunks.Heads()
|
||||
}
|
||||
|
||||
func (ch *Checker) consider(ctx context.Context, key string, c delivery.Candidate) {
|
||||
func (ch *Checker) consider(ctx context.Context, key string, c meshcatalog.CandidatesByGroupValue) {
|
||||
heads := ch.heads()
|
||||
if Outside(c, heads) {
|
||||
ch.outside(ctx, c)
|
||||
@@ -422,10 +429,10 @@ func (ch *Checker) consider(ctx context.Context, key string, c delivery.Candidat
|
||||
// UntrunkedWords, the members whose trunk is known too, as their group is not judged. It is logged once per head: when a
|
||||
// status or the verdict is written, never at a look that finds them already said. Once the trunk head is known the verdict
|
||||
// is stale (it names no trunk) and the heads are judged whole (prepare, claim).
|
||||
func (ch *Checker) untrunked(ctx context.Context, c delivery.Candidate, left []Pull) {
|
||||
key, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash)
|
||||
func (ch *Checker) untrunked(ctx context.Context, c meshcatalog.CandidatesByGroupValue, left []Pull) {
|
||||
key, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads)
|
||||
if err != nil {
|
||||
ch.logf("candidates of %s: no verdict key for heads %q, so it cannot be said that they cannot be judged: %v", c.Group, c.HeadsHash, err)
|
||||
ch.logf("candidates of %s: no verdict key for heads %q, so it cannot be said that they cannot be judged: %v", c.Group, c.Heads, err)
|
||||
return
|
||||
}
|
||||
repos := make([]string, 0, len(left))
|
||||
@@ -433,37 +440,37 @@ func (ch *Checker) untrunked(ctx context.Context, c delivery.Candidate, left []P
|
||||
repos = append(repos, p.Repository)
|
||||
}
|
||||
words := UntrunkedWordsOf(repos)
|
||||
v := delivery.Verdict{Group: c.Group, Heads: GateHeads(c), Trunk: map[string]string{}, State: delivery.VerdictFail,
|
||||
Failed: delivery.FailedFirst{Machine: "checker", Words: words}}
|
||||
data, err := delivery.EncodeVerdict(v)
|
||||
v := meshdeliverychecker.VerdictsByGroupHeadsValue{Group: c.Group, Heads: GateHeads(c), Trunk: map[string]string{}, State: meshdeliverycheckerclient.VerdictFail,
|
||||
Failed: &FailedFirst{Machine: "checker", Words: words}}
|
||||
data, err := meshdeliverycheckerclient.EncodeVerdict(v)
|
||||
if err != nil {
|
||||
ch.logf("%s: %s, and the verdict cannot say so: %v", key, words, err)
|
||||
return
|
||||
}
|
||||
said, err := putOnce(ctx, ch.Verdicts, key, data, func(cur []byte) bool {
|
||||
held, err := delivery.DecodeVerdict(cur)
|
||||
return err == nil && IsUntrunked(held) && held.Failed.Words == words // the same words stand; anything else is replaced
|
||||
held, err := meshdeliverycheckerclient.DecodeVerdict(cur)
|
||||
return err == nil && IsUntrunked(held) && failedOf(held).Words == words // the same words stand; anything else is replaced
|
||||
})
|
||||
if err != nil {
|
||||
ch.logf("%s: %s, and the verdict cannot say so: %v", key, words, err)
|
||||
}
|
||||
for _, cp := range c.Pulls {
|
||||
okey, err := delivery.CheckerOwedKey(cp.Repository, cp.Number, cp.Head.Head)
|
||||
okey, err := CheckerOwedKey(cp.Repository, int(cp.Number), cp.Head)
|
||||
if err != nil {
|
||||
ch.logf("%s: %v", key, err)
|
||||
continue
|
||||
}
|
||||
o := delivery.Owed{Act: delivery.OwedStatus, Context: delivery.StatusContextCheck, State: "error",
|
||||
o := meshdeliverychecker.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: meshdeliveryclient.StatusContextCheck, State: "error",
|
||||
Target: strings.TrimSuffix(ch.Link, "/") + "/" + key, Text: clipLine(words, MaxLine)}
|
||||
data, err := delivery.EncodeCheckerOwed(o)
|
||||
data, err := EncodeCheckerOwed(o)
|
||||
if err != nil {
|
||||
ch.logf("%s: the status of %s cannot be written: %v", key, okey, err)
|
||||
continue
|
||||
}
|
||||
wrote, err := putOnce(ctx, ch.Owed, okey, data, func(cur []byte) bool {
|
||||
held, err := delivery.DecodeCheckerOwed(cur)
|
||||
o.V = delivery.Version
|
||||
return err == nil && delivery.SameOwed(held, o)
|
||||
held, err := DecodeCheckerOwed(cur)
|
||||
o.V = keys.Version
|
||||
return err == nil && held == o
|
||||
})
|
||||
if err != nil {
|
||||
ch.logf("%s: the status of %s cannot be written: %v", key, okey, err)
|
||||
@@ -488,11 +495,11 @@ func (ch *Checker) untrunked(ctx context.Context, c delivery.Candidate, left []P
|
||||
// outside takes a candidate outside the mesh (case.go Outside): no verdict, and every status owed for its pull requests
|
||||
// taken away, so the forge module stops writing one into a repository the mesh's forge account may not write. Said once
|
||||
// per heads in the log.
|
||||
func (ch *Checker) outside(ctx context.Context, c delivery.Candidate) {
|
||||
func (ch *Checker) outside(ctx context.Context, c meshcatalog.CandidatesByGroupValue) {
|
||||
var names []string
|
||||
for _, p := range c.Pulls {
|
||||
names = append(names, fmt.Sprintf("%s#%d@%s", p.Repository, p.Number, short12(p.Head.Head)))
|
||||
key, err := delivery.CheckerOwedKey(p.Repository, p.Number, p.Head.Head)
|
||||
names = append(names, fmt.Sprintf("%s#%d@%s", p.Repository, p.Number, short12(p.Head)))
|
||||
key, err := CheckerOwedKey(p.Repository, int(p.Number), p.Head)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -508,7 +515,7 @@ func (ch *Checker) outside(ctx context.Context, c delivery.Candidate) {
|
||||
ch.logf("%s: the status owed of a pull request outside the mesh could not be taken away: %v", key, err)
|
||||
}
|
||||
}
|
||||
said := c.Group + "@" + c.HeadsHash
|
||||
said := c.Group + "@" + c.Heads
|
||||
ch.mu.Lock()
|
||||
first := !ch.outsideSaid[said]
|
||||
ch.outsideSaid[said] = true
|
||||
@@ -538,9 +545,9 @@ func (ch *Checker) prepare(ctx context.Context, group string, cs *Case) {
|
||||
if err != nil || !found {
|
||||
return
|
||||
}
|
||||
v, err := delivery.DecodeVerdict(value)
|
||||
v, err := meshdeliverycheckerclient.DecodeVerdict(value)
|
||||
switch {
|
||||
case err != nil || v.State == delivery.VerdictRunning:
|
||||
case err != nil || v.State == meshdeliverycheckerclient.VerdictRunning:
|
||||
return
|
||||
case IsNodesFailure(v):
|
||||
cs.Kind = KindNodes
|
||||
@@ -555,9 +562,9 @@ func (ch *Checker) prepare(ctx context.Context, group string, cs *Case) {
|
||||
return // ended unjudged when its heads went (issue 1890), or never judged for want of a trunk head (issue 1422): checked whole
|
||||
}
|
||||
cs.Kind = KindTrunk
|
||||
if v.State == delivery.VerdictPass && ch.stale(v, cs) {
|
||||
if v.State == meshdeliverycheckerclient.VerdictPass && ch.stale(v, cs) {
|
||||
cs.CarriedFrom = judgedOn(v)
|
||||
if c := v.Composition; c != nil && c.State == delivery.CompositionPass && c.FPSet != "" && c.FPSet == cs.FPSet {
|
||||
if c := v.Composition; c != nil && c.State == meshdeliverycheckerclient.CompositionPass && c.Fpset != "" && c.Fpset == cs.FPSet {
|
||||
kept := *c
|
||||
cs.Composition = &kept
|
||||
}
|
||||
@@ -566,7 +573,7 @@ func (ch *Checker) prepare(ctx context.Context, group string, cs *Case) {
|
||||
|
||||
// stale says whether a verdict was judged on commits that are no longer the case's: a trunk it merged moved, or a sibling it
|
||||
// read at its trunk's head (ADR 0548 §6).
|
||||
func (ch *Checker) stale(v delivery.Verdict, cs *Case) bool {
|
||||
func (ch *Checker) stale(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case) bool {
|
||||
return !sameTrunk(v.Trunk, cs.Trunk) || besideMoved(v.Beside, ch.besideNow())
|
||||
}
|
||||
|
||||
@@ -603,7 +610,7 @@ func besideMoved(beside, now map[string]string) bool {
|
||||
}
|
||||
|
||||
// judgedOn are the trunk and sibling commits a verdict was judged on, by repository.
|
||||
func judgedOn(v delivery.Verdict) map[string]string {
|
||||
func judgedOn(v meshdeliverychecker.VerdictsByGroupHeadsValue) map[string]string {
|
||||
out := make(map[string]string, len(v.Trunk)+len(v.Beside))
|
||||
for r, c := range v.Beside {
|
||||
out[r] = c
|
||||
@@ -616,7 +623,7 @@ func judgedOn(v delivery.Verdict) map[string]string {
|
||||
|
||||
// carried is what a carry's verdict names as carried from (ADR 0548 §5): the commits it was judged on before, of the
|
||||
// repositories the new verdict still judges or reads; nil when none of them moved.
|
||||
func carried(from map[string]string, v delivery.Verdict) map[string]string {
|
||||
func carried(from map[string]string, v meshdeliverychecker.VerdictsByGroupHeadsValue) map[string]string {
|
||||
out, moved := map[string]string{}, false
|
||||
for r, c := range from {
|
||||
now, ok := v.Trunk[r]
|
||||
@@ -636,7 +643,7 @@ func carried(from map[string]string, v delivery.Verdict) map[string]string {
|
||||
|
||||
// moveWords say a carry's moves, for the words of a failure: "carried from <old12> to <new12> of <repository>", each moved
|
||||
// repository, sorted.
|
||||
func moveWords(from map[string]string, v delivery.Verdict) string {
|
||||
func moveWords(from map[string]string, v meshdeliverychecker.VerdictsByGroupHeadsValue) string {
|
||||
var moves []string
|
||||
for r, c := range from {
|
||||
now, ok := v.Trunk[r]
|
||||
@@ -651,7 +658,7 @@ func moveWords(from map[string]string, v delivery.Verdict) string {
|
||||
return strings.Join(moves, ", ")
|
||||
}
|
||||
|
||||
func (ch *Checker) candidate(key string, fallback delivery.Candidate) delivery.Candidate {
|
||||
func (ch *Checker) candidate(key string, fallback meshcatalog.CandidatesByGroupValue) meshcatalog.CandidatesByGroupValue {
|
||||
ch.mu.Lock()
|
||||
defer ch.mu.Unlock()
|
||||
if c, ok := ch.known[key]; ok {
|
||||
@@ -670,8 +677,8 @@ type claimed struct{ rev uint64 }
|
||||
// - a pass or a failure for the trunk heads the case has stands; one for other trunk heads (the trunk moved) is taken
|
||||
// again by compare-and-set on the same key, and so is a failure of the nodes after the nodes changed.
|
||||
func (ch *Checker) claim(ctx context.Context, cs *Case, first bool, epoch uint64) (*claimed, error) {
|
||||
running := delivery.Verdict{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: delivery.VerdictRunning}
|
||||
data, err := delivery.EncodeVerdict(running)
|
||||
running := meshdeliverychecker.VerdictsByGroupHeadsValue{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: meshdeliverycheckerclient.VerdictRunning}
|
||||
data, err := meshdeliverycheckerclient.EncodeVerdict(running)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -692,22 +699,22 @@ func (ch *Checker) claim(ctx context.Context, cs *Case, first bool, epoch uint64
|
||||
}
|
||||
return &claimed{rev}, nil
|
||||
}
|
||||
cur, err := delivery.DecodeVerdict(value)
|
||||
cur, err := meshdeliverycheckerclient.DecodeVerdict(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s holds what is not a verdict: %w", cs.Key, err)
|
||||
}
|
||||
retake := false
|
||||
switch {
|
||||
case cur.State == delivery.VerdictRunning:
|
||||
case cur.State == meshdeliverycheckerclient.VerdictRunning:
|
||||
if first {
|
||||
return &claimed{rev}, nil
|
||||
}
|
||||
return nil, nil
|
||||
case IsEnded(cur), ch.stale(cur, cs):
|
||||
retake = true // heads held again after their check was ended unjudged (issue 1890), or a trunk that moved
|
||||
case cur.State == delivery.VerdictFail && ch.takeRebuilt(cs.Key):
|
||||
case cur.State == meshdeliverycheckerclient.VerdictFail && ch.takeRebuilt(cs.Key):
|
||||
retake = true // every failed build it names is done again (ADR 0603 §10)
|
||||
case cur.State == delivery.VerdictFail && IsNodesFailure(cur):
|
||||
case cur.State == meshdeliverycheckerclient.VerdictFail && IsNodesFailure(cur):
|
||||
ch.mu.Lock()
|
||||
at, ok := ch.nodesFail[cs.Key]
|
||||
ch.mu.Unlock()
|
||||
@@ -775,15 +782,15 @@ const ConflictWords = "does not merge into the trunk: files in conflict: "
|
||||
|
||||
// IsConflictFailure says whether a verdict failed because its head did not merge onto its trunk (ADR 0548 §7): it has no
|
||||
// pass to carry, so its next check, at a push or at a later trunk move, is a whole check of its head.
|
||||
func IsConflictFailure(v delivery.Verdict) bool {
|
||||
return v.State == delivery.VerdictFail && strings.Contains(v.Failed.Words, ConflictWords)
|
||||
func IsConflictFailure(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool {
|
||||
return v.State == meshdeliverycheckerclient.VerdictFail && strings.Contains(failedOf(v).Words, ConflictWords)
|
||||
}
|
||||
|
||||
// IsNodesFailure says whether a verdict failed because a part of the mesh the check reads was not heard (UnheardWords):
|
||||
// it is judged again when the nodes change. No check fails for want of a build node any more: its asks wait in the build
|
||||
// seats' streams (ADR 0525 §6, issue 1912).
|
||||
func IsNodesFailure(v delivery.Verdict) bool {
|
||||
return v.State == delivery.VerdictFail && strings.Contains(v.Failed.Words, UnheardWords)
|
||||
func IsNodesFailure(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool {
|
||||
return v.State == meshdeliverycheckerclient.VerdictFail && strings.Contains(failedOf(v).Words, UnheardWords)
|
||||
}
|
||||
|
||||
// try claims the check, judges it within its bound under run and writes the verdict and the owed statuses. A check that ends
|
||||
@@ -808,14 +815,14 @@ func (ch *Checker) try(ctx, run context.Context, key string, cs *Case) {
|
||||
}
|
||||
began := ch.now()
|
||||
if cs.Watch == nil && ch.Asks != nil {
|
||||
cs.Watch = func(_ string, step *delivery.Step) {
|
||||
cs.Watch = func(_ string, step *buildstep.Step) {
|
||||
if step == nil { // put: kept before its wait starts
|
||||
ch.recordAsks(ctx, cs.Key, cs.Asks())
|
||||
}
|
||||
}
|
||||
defer ch.forgetAsks(ctx, cs.Key)
|
||||
}
|
||||
ch.writeOwed(ctx, cs, delivery.Verdict{State: delivery.VerdictRunning}, Outcome{})
|
||||
ch.writeOwed(ctx, cs, meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, Outcome{})
|
||||
out := ch.judge(run, cs)
|
||||
// An ask the build seat gave up on, or whose step the build module's next process ended because it was replaced under
|
||||
// it, judged nothing (issue 734, IsLostAsk): the check is asked again under the same claim, its status pending, and after
|
||||
@@ -823,11 +830,11 @@ func (ch *Checker) try(ctx, run context.Context, key string, cs *Case) {
|
||||
// read at the next nudge (HeardEnds) and judged.
|
||||
for try := 1; IsLostAsk(out.Verdict) && run.Err() == nil; try++ {
|
||||
if try >= LostAskTries {
|
||||
out.Verdict.Failed.Words = strings.TrimSpace(out.Verdict.Failed.Words + "\nasked " + fmt.Sprint(LostAskTries) +
|
||||
out.Verdict.Failed.Words = strings.TrimSpace(failedOf(out.Verdict).Words + "\nasked " + fmt.Sprint(LostAskTries) +
|
||||
" times and given up each time: " + UnheardWords)
|
||||
break
|
||||
}
|
||||
ch.logf("%s: %s; asked again (%d of %d)", cs.Key, out.Verdict.Failed.Words, try+1, LostAskTries)
|
||||
ch.logf("%s: %s; asked again (%d of %d)", cs.Key, failedOf(out.Verdict).Words, try+1, LostAskTries)
|
||||
out = ch.judge(run, cs)
|
||||
}
|
||||
if ctx.Err() != nil || errors.Is(context.Cause(run), errAskedAgain) {
|
||||
@@ -851,23 +858,26 @@ func (ch *Checker) try(ctx, run context.Context, key string, cs *Case) {
|
||||
if cs.CarriedFrom != nil {
|
||||
// A carry (ADR 0548 §5): a pass names the commits it was carried from; a failure names the move it failed on.
|
||||
switch v.State {
|
||||
case delivery.VerdictPass:
|
||||
case meshdeliverycheckerclient.VerdictPass:
|
||||
v.CarriedFrom = carried(cs.CarriedFrom, v)
|
||||
case delivery.VerdictFail:
|
||||
case meshdeliverycheckerclient.VerdictFail:
|
||||
if moves := moveWords(cs.CarriedFrom, v); moves != "" {
|
||||
v.Failed.Words = strings.TrimSpace(v.Failed.Words + "\nfailed on the carry over the move of " + moves + ": the move changed what these tests read")
|
||||
v.Failed.Words = strings.TrimSpace(failedOf(v).Words + "\nfailed on the carry over the move of " + moves + ": the move changed what these tests read")
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.Took.Queue == 0 && !cs.Queued.IsZero() && began.After(cs.Queued) {
|
||||
if tookOf(v).Queue == 0 && !cs.Queued.IsZero() && began.After(cs.Queued) {
|
||||
if v.Took == nil {
|
||||
v.Took = &meshdeliverychecker.VerdictsByGroupHeadsValueTook{}
|
||||
}
|
||||
v.Took.Queue = began.Sub(cs.Queued).Milliseconds()
|
||||
}
|
||||
data, err := delivery.EncodeVerdict(v)
|
||||
data, err := meshdeliverycheckerclient.EncodeVerdict(v)
|
||||
if err != nil {
|
||||
ch.logf("%s: the verdict cannot be written: %v", cs.Key, err)
|
||||
v = delivery.Verdict{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: delivery.VerdictFail,
|
||||
Failed: delivery.FailedFirst{Machine: "checker", Words: "the verdict could not be written: " + err.Error()}}
|
||||
if data, err = delivery.EncodeVerdict(v); err != nil {
|
||||
v = meshdeliverychecker.VerdictsByGroupHeadsValue{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: meshdeliverycheckerclient.VerdictFail,
|
||||
Failed: &FailedFirst{Machine: "checker", Words: "the verdict could not be written: " + err.Error()}}
|
||||
if data, err = meshdeliverycheckerclient.EncodeVerdict(v); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -902,9 +912,9 @@ func (ch *Checker) try(ctx, run context.Context, key string, cs *Case) {
|
||||
// be taken back (buildrun.Asker has no withdraw), and are no longer waited for. No status is written: the heads that
|
||||
// replaced these have their own check, whose pending status stands.
|
||||
func (ch *Checker) end(ctx context.Context, cs *Case, cl *claimed, why superseded) {
|
||||
v := delivery.Verdict{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: delivery.VerdictFail,
|
||||
Failed: delivery.FailedFirst{Machine: "checker", Words: why.Error()}}
|
||||
data, err := delivery.EncodeVerdict(v)
|
||||
v := meshdeliverychecker.VerdictsByGroupHeadsValue{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: meshdeliverycheckerclient.VerdictFail,
|
||||
Failed: &FailedFirst{Machine: "checker", Words: why.Error()}}
|
||||
data, err := meshdeliverycheckerclient.EncodeVerdict(v)
|
||||
if err == nil {
|
||||
_, err = ch.Verdicts.Update(ctx, cs.Key, data, cl.rev)
|
||||
}
|
||||
@@ -926,32 +936,32 @@ func (ch *Checker) judge(ctx context.Context, cs *Case) Outcome {
|
||||
|
||||
var errBound = errors.New("the check ran past its bound")
|
||||
|
||||
func failure(machine, words string) delivery.Verdict {
|
||||
return delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: machine, Words: words}}
|
||||
func failure(machine, words string) meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
return meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: machine, Words: words}}
|
||||
}
|
||||
|
||||
// writeOwed writes, for each pull request judged, the status the forge bridge performs: pending while the check runs, success
|
||||
// or failure at its end (ADR 0484 clause 2). A key already holding the same value is not written again, so the bridge
|
||||
// performs one act for each change; an older head's key is never touched.
|
||||
func (ch *Checker) writeOwed(ctx context.Context, cs *Case, v delivery.Verdict, out Outcome) {
|
||||
func (ch *Checker) writeOwed(ctx context.Context, cs *Case, v meshdeliverychecker.VerdictsByGroupHeadsValue, out Outcome) {
|
||||
for _, p := range cs.Pulls {
|
||||
key, err := delivery.CheckerOwedKey(p.Repository, p.Number, p.Head)
|
||||
key, err := CheckerOwedKey(p.Repository, int(p.Number), p.Head)
|
||||
if err != nil {
|
||||
ch.logf("%s: %v", cs.Key, err)
|
||||
continue
|
||||
}
|
||||
state := map[string]string{delivery.VerdictRunning: "pending", delivery.VerdictPass: "success", delivery.VerdictFail: "failure"}[v.State]
|
||||
o := delivery.Owed{Act: delivery.OwedStatus, Context: delivery.StatusContextCheck, State: state,
|
||||
state := map[string]string{meshdeliverycheckerclient.VerdictRunning: "pending", meshdeliverycheckerclient.VerdictPass: "success", meshdeliverycheckerclient.VerdictFail: "failure"}[v.State]
|
||||
o := meshdeliverychecker.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: meshdeliveryclient.StatusContextCheck, State: state,
|
||||
Target: strings.TrimSuffix(ch.Link, "/") + "/" + cs.Key, Text: Line(v, cs, p, out.Builds, out.Slowest, out.Judged)}
|
||||
data, err := delivery.EncodeCheckerOwed(o)
|
||||
data, err := EncodeCheckerOwed(o)
|
||||
if err != nil {
|
||||
ch.logf("%s: the status of %s cannot be written: %v", cs.Key, key, err)
|
||||
continue
|
||||
}
|
||||
wrote, err := putOnce(ctx, ch.Owed, key, data, func(cur []byte) bool {
|
||||
held, err := delivery.DecodeCheckerOwed(cur)
|
||||
o.V = delivery.Version
|
||||
return err == nil && delivery.SameOwed(held, o)
|
||||
held, err := DecodeCheckerOwed(cur)
|
||||
o.V = keys.Version
|
||||
return err == nil && held == o
|
||||
})
|
||||
if err != nil {
|
||||
ch.logf("%s: the status of %s cannot be written: %v", cs.Key, key, err)
|
||||
@@ -997,7 +1007,7 @@ func (ch *Checker) Sweep(ctx context.Context) error {
|
||||
ch.mu.Lock()
|
||||
current := map[string]bool{}
|
||||
for _, c := range ch.known {
|
||||
if k, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash); err == nil {
|
||||
if k, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads); err == nil {
|
||||
current[k] = true
|
||||
}
|
||||
}
|
||||
@@ -1010,13 +1020,13 @@ func (ch *Checker) Sweep(ctx context.Context) error {
|
||||
if err != nil || !found {
|
||||
continue
|
||||
}
|
||||
cur, err := delivery.DecodeVerdict(value)
|
||||
if err != nil || cur.State != delivery.VerdictRunning {
|
||||
cur, err := meshdeliverycheckerclient.DecodeVerdict(value)
|
||||
if err != nil || cur.State != meshdeliverycheckerclient.VerdictRunning {
|
||||
continue
|
||||
}
|
||||
cur.State = delivery.VerdictFail
|
||||
cur.Failed = delivery.FailedFirst{Machine: "checker", Words: EndedWords + "closed at start: left running, and no candidate holds these heads any more, so nothing will check them"}
|
||||
data, err := delivery.EncodeVerdict(cur)
|
||||
cur.State = meshdeliverycheckerclient.VerdictFail
|
||||
cur.Failed = &FailedFirst{Machine: "checker", Words: EndedWords + "closed at start: left running, and no candidate holds these heads any more, so nothing will check them"}
|
||||
data, err := meshdeliverycheckerclient.EncodeVerdict(cur)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1035,7 +1045,7 @@ func (ch *Checker) Sweep(ctx context.Context) error {
|
||||
func (ch *Checker) TrunkMoved(ctx context.Context) {
|
||||
ch.init()
|
||||
ch.mu.Lock()
|
||||
known := make(map[string]delivery.Candidate, len(ch.known))
|
||||
known := make(map[string]meshcatalog.CandidatesByGroupValue, len(ch.known))
|
||||
for k, c := range ch.known {
|
||||
known[k] = c
|
||||
}
|
||||
@@ -1050,23 +1060,23 @@ func (ch *Checker) TrunkMoved(ctx context.Context) {
|
||||
if err != nil || !found {
|
||||
continue // never judged: no status to put out of date
|
||||
}
|
||||
v, err := delivery.DecodeVerdict(value)
|
||||
v, err := meshdeliverycheckerclient.DecodeVerdict(value)
|
||||
if err == nil && !ch.stale(v, cs) {
|
||||
continue
|
||||
}
|
||||
if err == nil && v.State == delivery.VerdictPass {
|
||||
if err == nil && v.State == meshdeliverycheckerclient.VerdictPass {
|
||||
cs.CarriedFrom = judgedOn(v) // the status says it is being carried to the new commit (ADR 0548 §6)
|
||||
}
|
||||
ch.writeOwed(ctx, cs, delivery.Verdict{State: delivery.VerdictRunning}, Outcome{})
|
||||
ch.writeOwed(ctx, cs, meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, Outcome{})
|
||||
}
|
||||
ch.Reconsider(ctx)
|
||||
}
|
||||
|
||||
// owePending owes mesh/check pending on the heads of a verdict judged on commits that moved while it ran: a pass says it is
|
||||
// being carried to the trunk's head now (ADR 0548 §6), anything else that it is checked again.
|
||||
func (ch *Checker) owePending(ctx context.Context, judged *Case, v delivery.Verdict) {
|
||||
func (ch *Checker) owePending(ctx context.Context, judged *Case, v meshdeliverychecker.VerdictsByGroupHeadsValue) {
|
||||
next := judged
|
||||
ckey, err := delivery.CandidatesKey(v.Group)
|
||||
ckey, err := meshcatalogclient.CandidatesKey(v.Group)
|
||||
ch.mu.Lock()
|
||||
c, ok := ch.known[ckey]
|
||||
ch.mu.Unlock()
|
||||
@@ -1075,10 +1085,10 @@ func (ch *Checker) owePending(ctx context.Context, judged *Case, v delivery.Verd
|
||||
next = now
|
||||
}
|
||||
}
|
||||
if v.State == delivery.VerdictPass {
|
||||
if v.State == meshdeliverycheckerclient.VerdictPass {
|
||||
next.CarriedFrom = judgedOn(v)
|
||||
}
|
||||
ch.writeOwed(ctx, next, delivery.Verdict{State: delivery.VerdictRunning}, Outcome{})
|
||||
ch.writeOwed(ctx, next, meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, Outcome{})
|
||||
}
|
||||
|
||||
// movedSince says whether a trunk the case was judged on is no longer the trunk's head.
|
||||
@@ -1134,7 +1144,7 @@ func (ch *Checker) hearFailedBuilds(ctx context.Context, group string, cs *Case)
|
||||
if err != nil || !found {
|
||||
return
|
||||
}
|
||||
if v, err := delivery.DecodeVerdict(value); err == nil {
|
||||
if v, err := meshdeliverycheckerclient.DecodeVerdict(value); err == nil {
|
||||
if builds := failedBuilds(v, cs); builds != nil {
|
||||
ch.awaitRebuilt(ctx, group, cs.Key, builds)
|
||||
}
|
||||
|
||||
@@ -9,8 +9,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
const hqRepo = "novox/hq"
|
||||
@@ -27,30 +33,30 @@ const (
|
||||
)
|
||||
|
||||
// candidateOf is a candidate of a group with the given pull requests, as the indexer writes one.
|
||||
func candidateOf(t testing.TB, group string, pulls ...delivery.CandidatePull) []byte {
|
||||
func candidateOf(t testing.TB, group string, pulls ...meshcatalog.CandidatesByGroupValuePullsItem) []byte {
|
||||
t.Helper()
|
||||
heads := make([]delivery.Head, len(pulls))
|
||||
heads := make([]meshdeliveryclient.Head, len(pulls))
|
||||
for i, p := range pulls {
|
||||
heads[i] = p.Head
|
||||
heads[i] = meshdeliveryclient.Head{Repository: p.Repository, Number: int(p.Number), Head: p.Head}
|
||||
}
|
||||
hash, err := delivery.HeadsHash(heads)
|
||||
hash, err := meshdeliveryclient.HeadsHash(heads)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := delivery.Candidate{V: delivery.Version, Group: group, HeadsHash: hash, Pulls: pulls, FPSet: delivery.FingerprintSet(pulls)}
|
||||
data, err := delivery.EncodeCandidate(c)
|
||||
c := meshcatalog.CandidatesByGroupValue{V: keys.Version, Group: group, Heads: hash, Pulls: pulls, Fpset: meshcatalogclient.FingerprintSet(pulls)}
|
||||
data, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func pullOf(repo string, number int, head string) delivery.CandidatePull {
|
||||
return delivery.CandidatePull{Head: delivery.Head{Repository: repo, Number: number, Head: head}, Modules: []string{}}
|
||||
func pullOf(repo string, number int, head string) meshcatalog.CandidatesByGroupValuePullsItem {
|
||||
return meshcatalog.CandidatesByGroupValuePullsItem{Repository: repo, Number: int64(number), Head: head, Modules: []string{}}
|
||||
}
|
||||
|
||||
// newRepoPullOf is a pull request of a new repository of the mesh: its trunk not named yet, its first module added.
|
||||
func newRepoPullOf(repo string, number int, head string) delivery.CandidatePull {
|
||||
func newRepoPullOf(repo string, number int, head string) meshcatalog.CandidatesByGroupValuePullsItem {
|
||||
p := pullOf(repo, number, head)
|
||||
p.Modules = []string{"mod-u"}
|
||||
return p
|
||||
@@ -73,9 +79,9 @@ func (j *judgeFunc) Judge(ctx context.Context, c *Case) (Outcome, error) {
|
||||
|
||||
func (j *judgeFunc) count() int { j.mu.Lock(); defer j.mu.Unlock(); return j.calls }
|
||||
|
||||
func passing() delivery.Verdict {
|
||||
return delivery.Verdict{State: delivery.VerdictPass, Levels: map[string]delivery.LevelResult{
|
||||
delivery.LevelUnit: {Run: 2, Kept: 3}, delivery.LevelIntegration: {Run: 1}}}
|
||||
func passing() meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
return meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictPass, Levels: map[string]meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{
|
||||
buildstep.LevelUnit: {Run: 2, Kept: 3}, buildstep.LevelIntegration: {Run: 1}}}
|
||||
}
|
||||
|
||||
func passJudge() *judgeFunc {
|
||||
@@ -99,20 +105,20 @@ func newRig(t testing.TB, j Judge) *rig {
|
||||
|
||||
func (r *rig) setTrunks(t testing.TB, heads map[string]string) bool {
|
||||
t.Helper()
|
||||
data, err := delivery.EncodeSource(delivery.Source{Group: "g", Heads: heads})
|
||||
data, err := meshdeliveryclient.EncodeSource(meshdelivery.SourceByMainValue{Group: "g", Heads: heads})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return r.trunks.Put(data, false)
|
||||
}
|
||||
|
||||
func (r *rig) verdict(t testing.TB, key string) delivery.Verdict {
|
||||
func (r *rig) verdict(t testing.TB, key string) meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
t.Helper()
|
||||
raw, _, found, _ := r.verdicts.Get(context.Background(), key)
|
||||
if !found {
|
||||
t.Fatalf("no verdict %s; the bucket holds %v", key, r.verdicts.vals)
|
||||
}
|
||||
v, err := delivery.DecodeVerdict(raw)
|
||||
v, err := meshdeliverycheckerclient.DecodeVerdict(raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -121,11 +127,11 @@ func (r *rig) verdict(t testing.TB, key string) delivery.Verdict {
|
||||
|
||||
func keyOf(t testing.TB, group string, value []byte) string {
|
||||
t.Helper()
|
||||
c, err := delivery.DecodeCandidate(value)
|
||||
c, err := meshcatalog.DecodeCandidatesByGroupValue(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
k, err := delivery.CheckerVerdictKey(group, c.HeadsHash)
|
||||
k, err := meshdeliverycheckerclient.VerdictKey(group, c.Heads)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -139,7 +145,7 @@ func TestACandidateNotYetCheckedGetsItsVerdictKeyCreatedOnceAndIsJudged(t *testi
|
||||
r.ch.Candidate(ctx, "feat%2Fx", cand)
|
||||
r.ch.Wait()
|
||||
key := keyOf(t, "feat/x", cand)
|
||||
if v := r.verdict(t, key); v.State != delivery.VerdictPass || v.Group != "feat/x" || v.Trunk[repoA] != trunkA {
|
||||
if v := r.verdict(t, key); v.State != meshdeliverycheckerclient.VerdictPass || v.Group != "feat/x" || v.Trunk[repoA] != trunkA {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
if r.verdicts.creates[key] != 1 || j.count() != 1 {
|
||||
@@ -171,14 +177,14 @@ func TestASecondInstancesCreateIsRefusedAndItDoesNothing(t *testing.T) { // N6/A
|
||||
r := newRig(t, j)
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1))
|
||||
key := keyOf(t, "feat/x", cand)
|
||||
other, _ := delivery.EncodeVerdict(delivery.Verdict{Group: "feat/x", Heads: strings.SplitN(key, ".", 2)[1], State: delivery.VerdictRunning})
|
||||
other, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "feat/x", Heads: strings.SplitN(key, ".", 2)[1], State: meshdeliverycheckerclient.VerdictRunning})
|
||||
r.ch.Verdicts = racyKV{r.verdicts, other}
|
||||
r.ch.Candidate(ctx, "feat%2Fx", cand)
|
||||
r.ch.Wait()
|
||||
if j.count() != 0 {
|
||||
t.Errorf("the instance that lost the create judged %d times, want none", j.count())
|
||||
}
|
||||
if v := r.verdict(t, key); v.State != delivery.VerdictRunning {
|
||||
if v := r.verdict(t, key); v.State != meshdeliverycheckerclient.VerdictRunning {
|
||||
t.Errorf("the other instance's key was changed: %+v", v)
|
||||
}
|
||||
}
|
||||
@@ -188,13 +194,13 @@ func TestARestartWithARunningKeyJudgesItAgain(t *testing.T) { // N6/AC2
|
||||
r := newRig(t, j)
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1))
|
||||
key := keyOf(t, "feat/x", cand)
|
||||
left, _ := delivery.EncodeVerdict(delivery.Verdict{Group: "feat/x", Heads: strings.SplitN(key, ".", 2)[1], Trunk: map[string]string{repoA: trunkA}, State: delivery.VerdictRunning})
|
||||
left, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "feat/x", Heads: strings.SplitN(key, ".", 2)[1], Trunk: map[string]string{repoA: trunkA}, State: meshdeliverycheckerclient.VerdictRunning})
|
||||
if _, err := r.verdicts.Create(ctx, key, left); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.ch.Candidate(ctx, "feat%2Fx", cand)
|
||||
r.ch.Wait()
|
||||
if j.count() != 1 || r.verdict(t, key).State != delivery.VerdictPass {
|
||||
if j.count() != 1 || r.verdict(t, key).State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("judged %d times, verdict %+v, want the left check judged once and passed", j.count(), r.verdict(t, key))
|
||||
}
|
||||
}
|
||||
@@ -212,7 +218,7 @@ func TestACheckPastItsBoundFailsNamingTheStuckStep(t *testing.T) { // N6/AC3
|
||||
r.ch.Candidate(ctx, "feat%2Fx", cand)
|
||||
r.ch.Wait()
|
||||
v := r.verdict(t, keyOf(t, "feat/x", cand))
|
||||
if v.State != delivery.VerdictFail || v.Failed.Machine != "shanks" || !strings.Contains(v.Failed.Words, "stuck at: test the unit level of mesh-delivery") {
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail || v.Failed.Machine != "shanks" || !strings.Contains(v.Failed.Words, "stuck at: test the unit level of mesh-delivery") {
|
||||
t.Fatalf("verdict = %+v, want a failure naming shanks and the stuck step", v)
|
||||
}
|
||||
}
|
||||
@@ -242,7 +248,7 @@ func TestAnUnheardForgeFailsWithTheReasonAndIsJudgedAgainWhenTheNodesChange(t *t
|
||||
}
|
||||
r.ch.NodesChanged(ctx)
|
||||
r.ch.Wait()
|
||||
if j.count() != 2 || r.verdict(t, key).State != delivery.VerdictPass {
|
||||
if j.count() != 2 || r.verdict(t, key).State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("after the nodes changed: judged %d times, verdict %+v", j.count(), r.verdict(t, key))
|
||||
}
|
||||
}
|
||||
@@ -275,7 +281,7 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi
|
||||
t.Errorf("said %d times in the log, want once for the head: %q", n, logged)
|
||||
}
|
||||
reader := func() Reader {
|
||||
ckey, _ := delivery.CandidatesKey("feat/y")
|
||||
ckey, _ := meshcatalogclient.CandidatesKey("feat/y")
|
||||
return Reader{CandidateState: fakeState{ckey: cand}.read(), VerdictState: fakeState(r.verdicts.vals).read(),
|
||||
Trunks: func() (map[string]string, error) { return r.trunks.Heads(), nil }}
|
||||
}
|
||||
@@ -294,7 +300,7 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi
|
||||
r.setTrunks(t, map[string]string{repoA: trunkA, repoSDK: trunkS, unknown: trunkA2})
|
||||
r.ch.TrunkMoved(ctx)
|
||||
r.ch.Wait()
|
||||
if v := r.verdict(t, key); j.count() != 1 || v.State != delivery.VerdictPass || v.Trunk[unknown] != trunkA2 {
|
||||
if v := r.verdict(t, key); j.count() != 1 || v.State != meshdeliverycheckerclient.VerdictPass || v.Trunk[unknown] != trunkA2 {
|
||||
t.Errorf("judged %d times, verdict %+v, want one pass on %s", j.count(), v, trunkA2)
|
||||
}
|
||||
if s := owedState(t, r, unknown, 4, head1); s != "success" {
|
||||
@@ -306,15 +312,15 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi
|
||||
// running verdict left for heads that cannot be judged is replaced; a verdict key deleted is judged again on its delete.
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
pulls []delivery.CandidatePull
|
||||
pulls []meshcatalog.CandidatesByGroupValuePullsItem
|
||||
running bool // a running verdict is left under the key first
|
||||
deleted bool // the key is deleted after its verdict
|
||||
judged int
|
||||
want string // the verdict's state, and for a failure the repository it names
|
||||
}{
|
||||
{name: "a member the checker cannot claim", pulls: []delivery.CandidatePull{pullOf(repoA, 9, head2), pullOf("novox/other", 2, head2)}, want: "novox/other"},
|
||||
{name: "a running verdict left for heads that cannot be judged", pulls: []delivery.CandidatePull{newRepoPullOf(unknown, 5, head2)}, running: true, want: unknown},
|
||||
{name: "a verdict key deleted", pulls: []delivery.CandidatePull{pullOf(repoA, 5, head2)}, deleted: true, judged: 2, want: delivery.VerdictPass},
|
||||
{name: "a member the checker cannot claim", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 9, head2), pullOf("novox/other", 2, head2)}, want: "novox/other"},
|
||||
{name: "a running verdict left for heads that cannot be judged", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{newRepoPullOf(unknown, 5, head2)}, running: true, want: unknown},
|
||||
{name: "a verdict key deleted", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 5, head2)}, deleted: true, judged: 2, want: meshdeliverycheckerclient.VerdictPass},
|
||||
} {
|
||||
t.Run(row.name, func(t *testing.T) {
|
||||
j := passJudge()
|
||||
@@ -322,8 +328,8 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi
|
||||
cand := candidateOf(t, "feat/z", row.pulls...)
|
||||
key := keyOf(t, "feat/z", cand)
|
||||
if row.running {
|
||||
group, heads, _ := delivery.ParseCheckerVerdictKey(key)
|
||||
raw, _ := delivery.EncodeVerdict(delivery.Verdict{Group: group, Heads: heads, State: delivery.VerdictRunning})
|
||||
group, heads, _ := meshdeliverycheckerclient.ParseVerdictKey(key)
|
||||
raw, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: group, Heads: heads, State: meshdeliverycheckerclient.VerdictRunning})
|
||||
_, _ = r.verdicts.Create(ctx, key, raw)
|
||||
}
|
||||
r.ch.Candidate(ctx, "feat%2Fz", cand)
|
||||
@@ -334,8 +340,8 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi
|
||||
r.ch.Wait()
|
||||
}
|
||||
v := r.verdict(t, key)
|
||||
if row.want == delivery.VerdictPass {
|
||||
if v.State != delivery.VerdictPass || j.count() != row.judged {
|
||||
if row.want == meshdeliverycheckerclient.VerdictPass {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass || j.count() != row.judged {
|
||||
t.Errorf("verdict %s after %d judgings, want a pass after %d", v.State, j.count(), row.judged)
|
||||
}
|
||||
return
|
||||
@@ -344,7 +350,7 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi
|
||||
t.Errorf("verdict %+v after %d judgings, want a failure naming %s and none judged", v, j.count(), row.want)
|
||||
}
|
||||
for _, p := range row.pulls {
|
||||
if s := owedState(t, r, p.Repository, p.Number, p.Head.Head); s != "error" {
|
||||
if s := owedState(t, r, p.Repository, int(p.Number), p.Head); s != "error" {
|
||||
t.Errorf("%s#%d owes %q, want error", p.Repository, p.Number, s)
|
||||
}
|
||||
}
|
||||
@@ -391,7 +397,7 @@ func TestATrunkMoveRewritesTheSameVerdictKeyAndTheHeadsStatus(t *testing.T) { //
|
||||
r.ch.Candidate(ctx, "feat%2Fx", cand)
|
||||
r.ch.Wait()
|
||||
_, rev1, _, _ := r.verdicts.Get(ctx, key)
|
||||
owedKey, _ := delivery.CheckerOwedKey(repoA, 7, head1)
|
||||
owedKey, _ := CheckerOwedKey(repoA, 7, head1)
|
||||
_, orev1, _, _ := r.owed.Get(ctx, owedKey)
|
||||
if !r.setTrunks(t, map[string]string{repoA: trunkA2, repoSDK: trunkS}) {
|
||||
t.Fatal("the trunks did not change")
|
||||
@@ -427,12 +433,12 @@ func TestTheStatusIsPendingWhileTheCheckRunsAndEndsSuccessOrFailure(t *testing.T
|
||||
var seen []string
|
||||
var r *rig
|
||||
j := &judgeFunc{f: func(ctx context.Context, c *Case, _ int) (Outcome, error) {
|
||||
key, _ := delivery.CheckerOwedKey(repoA, 7, head1)
|
||||
key, _ := CheckerOwedKey(repoA, 7, head1)
|
||||
raw, _, _, _ := r.owed.Get(ctx, key)
|
||||
o, _ := delivery.DecodeCheckerOwed(raw)
|
||||
o, _ := DecodeCheckerOwed(raw)
|
||||
seen = append(seen, o.State)
|
||||
c.series(repoA+"#7", []string{"web/pkg series 0.1 → 0.2"}, []delivery.SeriesChange{{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}})
|
||||
return Outcome{Verdict: delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Test: "m/a.TestX"}},
|
||||
c.series(repoA+"#7", []string{"web/pkg series 0.1 → 0.2"}, []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}})
|
||||
return Outcome{Verdict: meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Test: "m/a.TestX"}},
|
||||
OutOfStep: map[string][]string{repoA + "#7": {"go/seats/x/contract.go"}}}, nil
|
||||
}}
|
||||
r = newRig(t, j)
|
||||
@@ -440,19 +446,19 @@ func TestTheStatusIsPendingWhileTheCheckRunsAndEndsSuccessOrFailure(t *testing.T
|
||||
r.ch.Says = says
|
||||
r.ch.Candidate(ctx, "feat/x", candidateOf(t, "feat/x", pullOf(repoA, 7, head1)))
|
||||
r.ch.Wait()
|
||||
key, _ := delivery.CheckerOwedKey(repoA, 7, head1)
|
||||
key, _ := CheckerOwedKey(repoA, 7, head1)
|
||||
raw, _, _, _ := r.owed.Get(ctx, key)
|
||||
o, err := delivery.DecodeCheckerOwed(raw)
|
||||
o, err := DecodeCheckerOwed(raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(seen) != 1 || seen[0] != "pending" || o.State != "failure" || o.Context != delivery.StatusContextCheck || !strings.HasPrefix(o.Target, "https://view.invalid/checks/") {
|
||||
if len(seen) != 1 || seen[0] != "pending" || o.State != "failure" || o.Context != meshdeliveryclient.StatusContextCheck || !strings.HasPrefix(o.Target, "https://view.invalid/checks/") {
|
||||
t.Errorf("while running %v, at the end %+v", seen, o)
|
||||
}
|
||||
// DLV-C2 (design 26 *Out of step*): the check's end says one verdict event for the pull request, naming its out-of-step
|
||||
// files; the running status says none.
|
||||
if len(says.said) != 1 || says.said[0].Repository != repoA || says.said[0].Pull != 7 || says.said[0].Head != head1 ||
|
||||
says.said[0].Verdict != delivery.VerdictFail || strings.Join(says.said[0].OutOfStep, ",") != "go/seats/x/contract.go" ||
|
||||
says.said[0].Verdict != meshdeliverycheckerclient.VerdictFail || strings.Join(says.said[0].OutOfStep, ",") != "go/seats/x/contract.go" ||
|
||||
// The join (A2j finding 2): and its series changes, as the seat's event names them.
|
||||
len(says.said[0].Series) != 1 || says.said[0].Series[0] != (meshdeliverychecker.VerdictHolderEventSeriesItem{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}) {
|
||||
t.Errorf("verdict events %+v", says.said)
|
||||
@@ -480,14 +486,14 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) {
|
||||
r := newRig(t, j)
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1))
|
||||
live := keyOf(t, "feat/x", cand)
|
||||
dead, _ := delivery.CheckerVerdictKey("feat/gone", "0123456789ab")
|
||||
dead, _ := meshdeliverycheckerclient.VerdictKey("feat/gone", "0123456789ab")
|
||||
for _, k := range []string{live, dead} {
|
||||
group, heads, _ := delivery.ParseCheckerVerdictKey(k)
|
||||
raw, _ := delivery.EncodeVerdict(delivery.Verdict{Group: group, Heads: heads, State: delivery.VerdictRunning})
|
||||
group, heads, _ := meshdeliverycheckerclient.ParseVerdictKey(k)
|
||||
raw, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: group, Heads: heads, State: meshdeliverycheckerclient.VerdictRunning})
|
||||
_, _ = r.verdicts.Create(ctx, k, raw)
|
||||
}
|
||||
r.ch.init()
|
||||
var c delivery.Candidate
|
||||
var c meshcatalog.CandidatesByGroupValue
|
||||
_ = json.Unmarshal(cand, &c)
|
||||
r.ch.known["feat%2Fx"] = c
|
||||
if err := r.ch.Sweep(ctx); err != nil {
|
||||
@@ -496,7 +502,7 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) {
|
||||
if v := r.verdict(t, dead); !IsEnded(v) || !strings.Contains(v.Failed.Words, "closed at start") {
|
||||
t.Errorf("the dead key = %+v", v)
|
||||
}
|
||||
if v := r.verdict(t, live); v.State != delivery.VerdictRunning {
|
||||
if v := r.verdict(t, live); v.State != meshdeliverycheckerclient.VerdictRunning {
|
||||
t.Errorf("the live key = %+v, want it left for the check to take over", v)
|
||||
}
|
||||
|
||||
@@ -519,7 +525,7 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) {
|
||||
r.ch.Candidate(ctx, "feat%2Fx", alone)
|
||||
<-started
|
||||
r.ch.Candidate(ctx, "feat%2Fx", both)
|
||||
var bothC delivery.Candidate
|
||||
var bothC meshcatalog.CandidatesByGroupValue
|
||||
_ = json.Unmarshal(both, &bothC)
|
||||
select {
|
||||
case err := <-ended:
|
||||
@@ -533,10 +539,10 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) {
|
||||
if v := r.verdict(t, live); !IsEnded(v) || v.Failed.Words != EndedWords+"superseded by "+GateHeads(bothC) {
|
||||
t.Errorf("the superseded verdict = %+v", v.Failed)
|
||||
}
|
||||
if v := r.verdict(t, keyOf(t, "feat/x", both)); v.State != delivery.VerdictPass {
|
||||
if v := r.verdict(t, keyOf(t, "feat/x", both)); v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Errorf("the group's verdict = %s", v.State)
|
||||
}
|
||||
okey, _ := delivery.CheckerOwedKey(repoA, 7, head1)
|
||||
okey, _ := CheckerOwedKey(repoA, 7, head1)
|
||||
if raw, _, _, _ := r.owed.Get(ctx, okey); !strings.Contains(string(raw), `"success"`) {
|
||||
t.Errorf("the head's status = %s, want the group's success, never the ended check's", raw)
|
||||
}
|
||||
@@ -554,7 +560,7 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) {
|
||||
// The member leaves again: the ended heads are held again and checked whole.
|
||||
r.ch.Candidate(ctx, "feat%2Fx", alone)
|
||||
r.ch.Wait()
|
||||
if v := r.verdict(t, live); v.State != delivery.VerdictPass || j.count() != 3 {
|
||||
if v := r.verdict(t, live); v.State != meshdeliverycheckerclient.VerdictPass || j.count() != 3 {
|
||||
t.Errorf("the heads held again = %s after %d judgings, want a pass of a third", v.State, j.count())
|
||||
}
|
||||
|
||||
@@ -575,7 +581,7 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) {
|
||||
if _, _, found, _ := r.verdicts.Get(ctx, keyOf(t, "feat/y", sdk)); found || j.count() != 1 {
|
||||
t.Errorf("the partial set has a verdict (%v) or the judge ran %d times, want the whole set alone", found, j.count())
|
||||
}
|
||||
if v := r.verdict(t, keyOf(t, "feat/y", whole)); v.State != delivery.VerdictPass {
|
||||
if v := r.verdict(t, keyOf(t, "feat/y", whole)); v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Errorf("the whole set = %s", v.State)
|
||||
}
|
||||
}
|
||||
@@ -588,7 +594,7 @@ func TestTheForgeBridgeIsToldOnceForEachChangeOfAStatus(t *testing.T) { // 1683
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1))
|
||||
r.ch.Candidate(ctx, "feat%2Fx", cand)
|
||||
r.ch.Wait()
|
||||
key, _ := delivery.CheckerOwedKey(repoA, 7, head1)
|
||||
key, _ := CheckerOwedKey(repoA, 7, head1)
|
||||
want := []string{EventOwedWritten + " " + key, EventOwedWritten + " " + key} // pending, then success
|
||||
if len(told) != 2 || told[0] != want[0] || told[1] != want[1] {
|
||||
t.Fatalf("told %v, want the status said when pending and when success", told)
|
||||
@@ -609,8 +615,8 @@ func TestACandidateOutsideTheMeshIsJudgedNothingAndOwesNothing(t *testing.T) {
|
||||
var logged []string
|
||||
r.ch.Logf = func(f string, a ...any) { logged = append(logged, fmt.Sprintf(f, a...)) }
|
||||
const theirs = "someone/own-app"
|
||||
okey, _ := delivery.CheckerOwedKey(theirs, 55, head1)
|
||||
before, _ := delivery.EncodeCheckerOwed(delivery.Owed{Act: delivery.OwedStatus, Context: delivery.StatusContextCheck, State: "error", Text: "cannot be judged"})
|
||||
okey, _ := CheckerOwedKey(theirs, 55, head1)
|
||||
before, _ := EncodeCheckerOwed(meshdeliverychecker.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: meshdeliveryclient.StatusContextCheck, State: "error", Text: "cannot be judged"})
|
||||
if _, err := r.owed.Create(ctx, okey, before); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -629,7 +635,7 @@ func TestACandidateOutsideTheMeshIsJudgedNothingAndOwesNothing(t *testing.T) {
|
||||
if n := countWith(logged, "outside the mesh"); n != 1 {
|
||||
t.Errorf("said %d times in the log, want once: %q", n, logged)
|
||||
}
|
||||
ckey, _ := delivery.CandidatesKey("feat/theirs")
|
||||
ckey, _ := meshcatalogclient.CandidatesKey("feat/theirs")
|
||||
st, err := Reader{CandidateState: fakeState{ckey: cand}.read(), VerdictState: fakeState(r.verdicts.vals).read(),
|
||||
Trunks: func() (map[string]string, error) { return r.trunks.Heads(), nil }}.Status()
|
||||
if err != nil {
|
||||
|
||||
@@ -3,26 +3,40 @@ package check
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
// withComposition is v with the check's composition taken in: a failed one fails the check, naming its machine.
|
||||
func withComposition(v *delivery.Verdict, comp delivery.CompositionVerdict) {
|
||||
v.Composition = comp.Composition()
|
||||
func withComposition(v *meshdeliverychecker.VerdictsByGroupHeadsValue, comp meshwants.CompositionsByGroupHeadsValue) {
|
||||
v.Composition = compositionOf(comp)
|
||||
if v.Took == nil {
|
||||
v.Took = &meshdeliverychecker.VerdictsByGroupHeadsValueTook{}
|
||||
}
|
||||
v.Took.Composition = comp.Took
|
||||
if comp.State == delivery.CompositionPass && len(comp.Warnings) > 0 && v.Composition != nil {
|
||||
if comp.State == meshdeliverycheckerclient.CompositionPass && len(comp.Warnings) > 0 && v.Composition != nil {
|
||||
v.Composition.Words = strings.Join(comp.Warnings, "; ") // passed with a warning (ADR 0593 §9)
|
||||
}
|
||||
if comp.State == delivery.CompositionFail {
|
||||
if comp.State == meshdeliverycheckerclient.CompositionFail {
|
||||
machine := comp.Machine
|
||||
if machine == "" {
|
||||
machine = "the composition"
|
||||
}
|
||||
f := delivery.FailedFirst{Machine: machine, Words: comp.Words}
|
||||
if v.State == delivery.VerdictFail {
|
||||
f := FailedFirst{Machine: machine, Words: comp.Words}
|
||||
if v.State == meshdeliverycheckerclient.VerdictFail {
|
||||
addFailure(v, f) // the tests failed too: the composition is named after them (the operator, 2026-11-02)
|
||||
return
|
||||
}
|
||||
v.State, v.Failed = delivery.VerdictFail, f
|
||||
v.State, v.Failed = meshdeliverycheckerclient.VerdictFail, &f
|
||||
}
|
||||
}
|
||||
|
||||
// compositionOf is what the checker writes into its verdict from the assigner's composition answer c: nil while c waits (the
|
||||
// check is still running).
|
||||
func compositionOf(c meshwants.CompositionsByGroupHeadsValue) *meshdeliverychecker.VerdictsByGroupHeadsValueComposition {
|
||||
if c.State == compositionWaits {
|
||||
return nil
|
||||
}
|
||||
return &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: c.State, Words: c.Words, Sdk: c.Sdk, Machines: c.Machines}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,28 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
buildsrec "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// fakeCompositions answers a check's composition from memory, recording the keys asked.
|
||||
type fakeCompositions struct {
|
||||
v delivery.CompositionVerdict
|
||||
v meshwants.CompositionsByGroupHeadsValue
|
||||
err error
|
||||
asked []string
|
||||
// waits answers only when its context ends.
|
||||
waits bool
|
||||
}
|
||||
|
||||
func (f *fakeCompositions) Compose(ctx context.Context, c *Case) (delivery.CompositionVerdict, error) {
|
||||
func (f *fakeCompositions) Compose(ctx context.Context, c *Case) (meshwants.CompositionsByGroupHeadsValue, error) {
|
||||
f.asked = append(f.asked, c.Key)
|
||||
if f.waits {
|
||||
<-ctx.Done()
|
||||
return delivery.CompositionVerdict{}, ctx.Err()
|
||||
return meshwants.CompositionsByGroupHeadsValue{}, ctx.Err()
|
||||
}
|
||||
return f.v, f.err
|
||||
}
|
||||
@@ -35,12 +39,12 @@ func (f *fakeCompositions) Compose(ctx context.Context, c *Case) (delivery.Compo
|
||||
func TestAPassingCheckTakesTheAssignersComposition(t *testing.T) {
|
||||
t.Run("a change to a build module or base names the modules built on it", aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt)
|
||||
b := newBench(t)
|
||||
f := &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionPass, SDK: "v0.1.14-x", Machines: []string{"g14"}, Took: 40}}
|
||||
f := &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.CompositionPass, Sdk: "v0.1.14-x", Machines: []string{"g14"}, Took: 40}}
|
||||
b.ex.Compositions = f
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1))
|
||||
cs.FPSet = "fps-of-the-candidate"
|
||||
v := b.judge(t, cs).Verdict
|
||||
if v.State != delivery.VerdictPass || v.Composition == nil || v.Composition.State != delivery.CompositionPass || v.Composition.SDK != "v0.1.14-x" || v.Took.Composition != 40 {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionPass || v.Composition.Sdk != "v0.1.14-x" || v.Took.Composition != 40 {
|
||||
t.Fatalf("verdict = %+v, composition %+v", v, v.Composition)
|
||||
}
|
||||
if len(f.asked) != 1 || f.asked[0] != cs.Key {
|
||||
@@ -49,32 +53,32 @@ func TestAPassingCheckTakesTheAssignersComposition(t *testing.T) {
|
||||
if !compositionMentions(v, "passed") {
|
||||
t.Errorf("the line says %q", compositionWords(v.Composition, v.Took.Composition))
|
||||
}
|
||||
if v.Composition.FPSet == "" || v.Composition.FPSet != cs.FPSet {
|
||||
t.Errorf("the composition names the fingerprint set %q, want the candidate's %q (ADR 0548 §5)", v.Composition.FPSet, cs.FPSet)
|
||||
if v.Composition.Fpset == "" || v.Composition.Fpset != cs.FPSet {
|
||||
t.Errorf("the composition names the fingerprint set %q, want the candidate's %q (ADR 0548 §5)", v.Composition.Fpset, cs.FPSet)
|
||||
}
|
||||
// ADR 0548 §5: a carry whose fingerprints did not change keeps the composition it passed and asks the assigner nothing.
|
||||
carry := caseOf(t, pullOf(repoA, 7, head1))
|
||||
carry.CarriedFrom, carry.Composition = map[string]string{repoA: trunkA2}, v.Composition
|
||||
if kept := b.judge(t, carry).Verdict; kept.Composition == nil || kept.Composition.SDK != "v0.1.14-x" || len(f.asked) != 1 {
|
||||
if kept := b.judge(t, carry).Verdict; kept.Composition == nil || kept.Composition.Sdk != "v0.1.14-x" || len(f.asked) != 1 {
|
||||
t.Errorf("the carry's composition is %+v and the assigner was asked %d times; want the kept one and no second ask", kept.Composition, len(f.asked))
|
||||
}
|
||||
}
|
||||
|
||||
func compositionMentions(v delivery.Verdict, word string) bool {
|
||||
func compositionMentions(v meshdeliverychecker.VerdictsByGroupHeadsValue, word string) bool {
|
||||
return strings.Contains(compositionWords(v.Composition, v.Took.Composition), word)
|
||||
}
|
||||
|
||||
// 1297/AC2 at the checker: a failed composition fails the check, naming the machine it failed at and its words.
|
||||
func TestAFailedCompositionFailsTheCheck(t *testing.T) {
|
||||
b := newBench(t)
|
||||
b.ex.Compositions = &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionFail, SDK: "x",
|
||||
b.ex.Compositions = &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.CompositionFail, Sdk: "x",
|
||||
Machine: "g14", Words: "g14 runs web, which the change removes from its source"}}
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if v.State != delivery.VerdictFail || v.Failed.Machine != "g14" || v.Composition == nil || v.Composition.State != delivery.CompositionFail {
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail || v.Failed.Machine != "g14" || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionFail {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
v.Group, v.Heads = "feat/x", "0123456789ab" // the checker names them when it writes the verdict
|
||||
if _, err := delivery.EncodeVerdict(v); err != nil {
|
||||
if _, err := meshdeliverycheckerclient.EncodeVerdict(v); err != nil {
|
||||
t.Errorf("the verdict cannot be written: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -85,11 +89,11 @@ func TestACompositionNotHadIsNotRun(t *testing.T) {
|
||||
f := &fakeCompositions{err: errors.New("the check's bound passed")}
|
||||
b.ex.Compositions = f
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if v.State != delivery.VerdictPass || v.Composition == nil || v.Composition.State != delivery.CompositionNotRun {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionNotRun {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
b2 := newBench(t)
|
||||
f2 := &fakeCompositions{v: delivery.CompositionVerdict{State: delivery.CompositionFail, Words: "w", SDK: "x"}}
|
||||
f2 := &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{State: meshdeliverycheckerclient.CompositionFail, Words: "w", Sdk: "x"}}
|
||||
b2.ex.Compositions = f2
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1))
|
||||
cs.Try = &TryOptions{}
|
||||
@@ -113,12 +117,12 @@ type fakeEnds struct {
|
||||
early bool
|
||||
}
|
||||
|
||||
func (f *fakeEnds) Ended(_ context.Context, builds map[string]string) (map[string]delivery.BuilderBuild, error) {
|
||||
f.asked = builds
|
||||
func (f *fakeEnds) Ended(_ context.Context, asked map[string]string) (map[string]buildsrec.BuilderBuild, error) {
|
||||
f.asked = asked
|
||||
f.early = f.composed != nil && len(f.composed.asked) > 0
|
||||
out := map[string]delivery.BuilderBuild{}
|
||||
for module, fp := range builds {
|
||||
if b, ok := f.builds[module+" "+fp]; ok && (b.State == delivery.BuildDone || b.State == delivery.BuildFailed) {
|
||||
out := map[string]buildsrec.BuilderBuild{}
|
||||
for module, fp := range asked {
|
||||
if b, ok := f.builds[module+" "+fp]; ok && (b.State == buildsrec.BuildDone || b.State == buildsrec.BuildFailed) {
|
||||
out[module] = b
|
||||
}
|
||||
}
|
||||
@@ -154,12 +158,12 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) {
|
||||
fp := "src2:" + dig("f")
|
||||
goBuild := &catalogue.Build{Artifacts: []catalogue.Artifact{{Name: "bin", Kind: catalogue.ArtifactBundle, Language: "go"}}}
|
||||
tsBuild := &catalogue.Build{Artifacts: []catalogue.Artifact{{Name: "app", Kind: catalogue.ArtifactBundle, Language: "typescript"}}}
|
||||
done := delivery.BuilderBuild{State: delivery.BuildDone}
|
||||
done := buildsrec.BuilderBuild{State: buildsrec.BuildDone}
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
manifest map[string]catalogue.Manifest
|
||||
builds map[string]delivery.BuilderBuild // by module, at fp; nil leaves the check without the builds' ends
|
||||
runs knownRuns // nil: the trunk holds every module
|
||||
builds map[string]buildsrec.BuilderBuild // by module, at fp; nil leaves the check without the builds' ends
|
||||
runs knownRuns // nil: the trunk holds every module
|
||||
want []string
|
||||
waited string // the modules waited on, sorted
|
||||
failed string // the failure's test, when the check fails on a build
|
||||
@@ -169,37 +173,37 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) {
|
||||
{name: "the Go build module invokes a verb no running build serves",
|
||||
manifest: map[string]catalogue.Manifest{"mesh-build-go": {Module: "mesh-build-go", Build: goBuild,
|
||||
Invokes: []string{"seat:package-registry.publish", "seat:artifact-store.put"}}, "web": {Module: "web", Build: goBuild}},
|
||||
builds: map[string]delivery.BuilderBuild{"mesh-build-go": done}, served: []string{"seat:artifact-store.put"},
|
||||
builds: map[string]buildsrec.BuilderBuild{"mesh-build-go": done}, served: []string{"seat:artifact-store.put"},
|
||||
want: []string{"the changed build module mesh-build-go invokes seat:package-registry.publish, which no running build serves, " +
|
||||
"so the check cannot run it: web, which it builds, are composed from their manifests (ADR 0614 §7)"}, waited: "mesh-build-go"},
|
||||
{name: "the Go build module changes",
|
||||
manifest: map[string]catalogue.Manifest{"mesh-build-go": {Module: "mesh-build-go", Build: goBuild}, "web": {Module: "web", Build: goBuild},
|
||||
"mail": {Module: "mail", Build: goBuild}, "docs": {Module: "docs"}},
|
||||
builds: map[string]delivery.BuilderBuild{"mesh-build-go": done, "docs": done},
|
||||
builds: map[string]buildsrec.BuilderBuild{"mesh-build-go": done, "docs": done},
|
||||
want: []string{"the build module mesh-build-go: mail, web, which it builds"}, waited: "docs, mesh-build-go"},
|
||||
{name: "the Node build module changes",
|
||||
manifest: map[string]catalogue.Manifest{"mesh-build-node": {Module: "mesh-build-node", Build: goBuild}, "panel": {Module: "panel", Build: tsBuild},
|
||||
"web": {Module: "web", Build: goBuild}},
|
||||
builds: map[string]delivery.BuilderBuild{"mesh-build-node": done, "web": done},
|
||||
builds: map[string]buildsrec.BuilderBuild{"mesh-build-node": done, "web": done},
|
||||
want: []string{"the build module mesh-build-node: panel, which it builds"}, waited: "mesh-build-node, web"},
|
||||
{name: "a base changes",
|
||||
manifest: map[string]catalogue.Manifest{"image": {Module: "image"},
|
||||
"web": {Module: "web", Build: &catalogue.Build{On: []catalogue.BuildsOn{{Arg: "BASE", Module: "image", Artifact: "img"}}}}},
|
||||
builds: map[string]delivery.BuilderBuild{"image": done},
|
||||
builds: map[string]buildsrec.BuilderBuild{"image": done},
|
||||
want: []string{"alters image, a base of web", "waits on the build of image alone"}, waited: "image"},
|
||||
{name: "neither changes", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}},
|
||||
builds: map[string]delivery.BuilderBuild{"web": done}, waited: "web"},
|
||||
builds: map[string]buildsrec.BuilderBuild{"web": done}, waited: "web"},
|
||||
{name: "a build fails", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}, "mail": {Module: "mail", Build: goBuild}},
|
||||
builds: map[string]delivery.BuilderBuild{"web": {State: delivery.BuildFailed, Error: "go build: undefined: x"}, "mail": done},
|
||||
builds: map[string]buildsrec.BuilderBuild{"web": {State: buildsrec.BuildFailed, Error: "go build: undefined: x"}, "mail": done},
|
||||
waited: "mail, web", failed: "build: web"},
|
||||
{name: "the tests fail", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}},
|
||||
builds: map[string]delivery.BuilderBuild{"web": {State: delivery.BuildBuilding}}, tests: true,
|
||||
builds: map[string]buildsrec.BuilderBuild{"web": {State: buildsrec.BuildBuilding}}, tests: true,
|
||||
want: []string{"composes at once, without waiting on the builds of web"}},
|
||||
{name: "a build does not end within the bound", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}},
|
||||
builds: map[string]delivery.BuilderBuild{"web": {State: delivery.BuildBuilding}},
|
||||
builds: map[string]buildsrec.BuilderBuild{"web": {State: buildsrec.BuildBuilding}},
|
||||
want: []string{"the builds of web did not end within the build-wait-bound"}, waited: "web"},
|
||||
{name: "a module the trunk does not hold", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}, "new": {Module: "new", Build: goBuild}},
|
||||
builds: map[string]delivery.BuilderBuild{"web": done}, runs: knownRuns{"web"},
|
||||
builds: map[string]buildsrec.BuilderBuild{"web": done}, runs: knownRuns{"web"},
|
||||
want: []string{"new: the trunk holds no record of it yet"}, waited: "web"},
|
||||
{name: "the builds cannot be heard", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}},
|
||||
want: []string{"the builds of web cannot be heard"}},
|
||||
@@ -212,7 +216,7 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
comps := &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionPass, SDK: "x", Machines: []string{"g14"}}}
|
||||
comps := &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.CompositionPass, Sdk: "x", Machines: []string{"g14"}}}
|
||||
b.ex.Compositions = comps
|
||||
var ends *fakeEnds
|
||||
if row.builds != nil {
|
||||
@@ -228,10 +232,10 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) {
|
||||
if row.served != nil {
|
||||
b.ex.Serving = fixedServing(row.served)
|
||||
}
|
||||
read, fps := fixedManifests{}, map[string]delivery.CandidateModule{}
|
||||
read, fps := fixedManifests{}, map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{}
|
||||
for module, m := range row.manifest {
|
||||
d := dig(string(rune('a' + len(read))))
|
||||
read[d], fps[module] = m, delivery.CandidateModule{FP2: fp, Manifest: d}
|
||||
read[d], fps[module] = m, meshcatalog.CandidatesByGroupValuePullsItemFpsValue{Fp2: fp, Manifest: d}
|
||||
}
|
||||
b.ex.Read = read
|
||||
p := pullOf(catalogueRepository, 9, head1)
|
||||
@@ -246,12 +250,12 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case row.failed != "" && (v.State != delivery.VerdictFail || v.Failed.Test != row.failed || !strings.Contains(v.Failed.Words, "undefined: x") ||
|
||||
case row.failed != "" && (v.State != meshdeliverycheckerclient.VerdictFail || v.Failed.Test != row.failed || !strings.Contains(v.Failed.Words, "undefined: x") ||
|
||||
!strings.Contains(v.Failed.Words, "the check asks itself again when the build is done")):
|
||||
t.Errorf("%s: the verdict is %s failing %+v, want it failed naming %s, the builder's words and how it is asked again", row.name, v.State, v.Failed, row.failed)
|
||||
case row.tests && v.State != delivery.VerdictFail:
|
||||
case row.tests && v.State != meshdeliverycheckerclient.VerdictFail:
|
||||
t.Errorf("%s: %+v, want the tests' failure", row.name, v)
|
||||
case row.failed == "" && !row.tests && v.State != delivery.VerdictPass:
|
||||
case row.failed == "" && !row.tests && v.State != meshdeliverycheckerclient.VerdictPass:
|
||||
t.Errorf("%s: %+v", row.name, v)
|
||||
}
|
||||
words := compositionWords(v.Composition, v.Took.Composition)
|
||||
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// 1930/AC3: given a check ask whose stored end keeps the digest's packages as a list of {package, digest} (issue 1930),
|
||||
@@ -19,17 +20,17 @@ func TestKeptVerdictsAreReadFromTheDigestsListForm(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b := newBench(t)
|
||||
var stored []string
|
||||
b.asks.Answer = func(a delivery.StepAsk) delivery.Step {
|
||||
b.asks.Answer = func(a buildstep.StepAsk) buildstep.Step {
|
||||
s := b.answer(a)
|
||||
if tc.old && s.State == delivery.StepPassed {
|
||||
if tc.old && s.State == buildstep.StepPassed {
|
||||
s.Result = oldDigestForm(t, s.Result)
|
||||
}
|
||||
stored = append(stored, string(s.Result))
|
||||
return s
|
||||
}
|
||||
first := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
u := first.Levels[delivery.LevelUnit]
|
||||
if first.State != delivery.VerdictPass || u.Run == 0 || u.Kept != 0 {
|
||||
u := first.Levels[buildstep.LevelUnit]
|
||||
if first.State != meshdeliverycheckerclient.VerdictPass || u.Run == 0 || u.Kept != 0 {
|
||||
t.Fatalf("the first check = %+v", first)
|
||||
}
|
||||
if len(stored) == 0 {
|
||||
@@ -41,7 +42,7 @@ func TestKeptVerdictsAreReadFromTheDigestsListForm(t *testing.T) {
|
||||
}
|
||||
b.steps.calls = nil
|
||||
second := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if s := second.Levels[delivery.LevelUnit]; second.State != delivery.VerdictPass || s.Kept != u.Run || s.Run != 0 {
|
||||
if s := second.Levels[buildstep.LevelUnit]; second.State != meshdeliverycheckerclient.VerdictPass || s.Kept != u.Run || s.Run != 0 {
|
||||
t.Fatalf("the second check's unit level = %+v, want %d kept and none run", s, u.Run)
|
||||
}
|
||||
if n := len(b.steps.tool("test")); n != 0 {
|
||||
|
||||
@@ -9,10 +9,15 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// **A check's composition is the checker's, made from what each node's engine answers** (novox/hq ADR 0593 §7, §9).
|
||||
@@ -35,7 +40,7 @@ type EngineAsker interface {
|
||||
|
||||
// Sandbox runs a compose job on the Go build seat and answers the composer's verdict.
|
||||
type Sandbox interface {
|
||||
Compose(ctx context.Context, job delivery.ComposeJob) (composer.Verdict, error)
|
||||
Compose(ctx context.Context, job buildstep.ComposeJob) (composer.Verdict, error)
|
||||
}
|
||||
|
||||
// EngineCompositions is the checker's composition of a check (Examiner.Compositions).
|
||||
@@ -54,7 +59,7 @@ type EngineCompositions struct {
|
||||
// Manifests are the indexer's trunk manifest digests by module, and Verdicts its refusals of them: the catalogue the
|
||||
// sandbox composes with, before the case's own manifests are laid over.
|
||||
Manifests TrunkManifests
|
||||
Verdicts func() map[string]delivery.ManifestVerdict
|
||||
Verdicts func() map[string]meshcatalog.VerdictsByDigestValue
|
||||
}
|
||||
|
||||
// composerHead is the node-engine member's head of a case, or empty.
|
||||
@@ -66,16 +71,16 @@ func composerHead(c *Case) string {
|
||||
}
|
||||
|
||||
// Compose asks every placed node and judges their answers (JudgeEngines). An error is only a case it cannot ask at all.
|
||||
func (e EngineCompositions) Compose(ctx context.Context, c *Case) (delivery.CompositionVerdict, error) {
|
||||
func (e EngineCompositions) Compose(ctx context.Context, c *Case) (meshwants.CompositionsByGroupHeadsValue, error) {
|
||||
began := time.Now()
|
||||
key, err := delivery.CompositionKey(c.Group, c.HeadsHash)
|
||||
key, err := nodeengineclient.CompositionKey(c.Group, c.Heads)
|
||||
if err != nil {
|
||||
return delivery.CompositionVerdict{}, err
|
||||
return meshwants.CompositionsByGroupHeadsValue{}, err
|
||||
}
|
||||
var nodes []string
|
||||
if e.Placed != nil {
|
||||
if nodes, err = e.Placed(); err != nil {
|
||||
return delivery.CompositionVerdict{}, fmt.Errorf("the placement cannot be read, so no node can be asked: %w", err)
|
||||
return meshwants.CompositionsByGroupHeadsValue{}, fmt.Errorf("the placement cannot be read, so no node can be asked: %w", err)
|
||||
}
|
||||
}
|
||||
bound := e.Bound
|
||||
@@ -126,7 +131,7 @@ func (e EngineCompositions) Compose(ctx context.Context, c *Case) (delivery.Comp
|
||||
} else if e.Composer != nil && e.Composer() != "" {
|
||||
composerName = e.Composer()
|
||||
}
|
||||
v := JudgeEngines(c.Group, c.HeadsHash, composerName, nodes, answers, silent)
|
||||
v := JudgeEngines(c.Group, c.Heads, composerName, nodes, answers, silent)
|
||||
if head != "" && e.Sandbox == nil {
|
||||
v.Warnings = append(v.Warnings, "the group changes the node-engine's composition code and was composed by the running engines")
|
||||
}
|
||||
@@ -138,8 +143,8 @@ func (e EngineCompositions) Compose(ctx context.Context, c *Case) (delivery.Comp
|
||||
// answer is refused, naming each node and each module refused with its words (the first refusing node is the machine);
|
||||
// passed otherwise. Every node in nodes without an answer (silent, by node with why, for the journal only) is named
|
||||
// with nec.NoAnswer as a warning. composer names what composed, for the line. Pure.
|
||||
func JudgeEngines(group, heads, composerName string, nodes []string, answers map[string]ne.ComposeCandidateAnswer, silent map[string]string) delivery.CompositionVerdict {
|
||||
v := delivery.CompositionVerdict{Group: group, Heads: heads, State: delivery.CompositionPass, SDK: composerName}
|
||||
func JudgeEngines(group, heads, composerName string, nodes []string, answers map[string]ne.ComposeCandidateAnswer, silent map[string]string) meshwants.CompositionsByGroupHeadsValue {
|
||||
v := meshwants.CompositionsByGroupHeadsValue{Group: group, Heads: heads, State: meshdeliverycheckerclient.CompositionPass, Sdk: composerName}
|
||||
sorted := append([]string(nil), nodes...)
|
||||
sort.Strings(sorted)
|
||||
var fails []string
|
||||
@@ -177,7 +182,7 @@ func JudgeEngines(group, heads, composerName string, nodes []string, answers map
|
||||
fails = append(fails, "not composed on "+node+": "+strings.Join(words, "; "))
|
||||
}
|
||||
if len(fails) > 0 {
|
||||
v.State, v.Words = delivery.CompositionFail, strings.Join(fails, "; ")
|
||||
v.State, v.Words = meshdeliverycheckerclient.CompositionFail, strings.Join(fails, "; ")
|
||||
}
|
||||
if len(sorted) == 0 {
|
||||
v.Warnings = append(v.Warnings, "no node is placed, so the change was composed on none")
|
||||
@@ -232,7 +237,7 @@ func (e EngineCompositions) inSandbox(ctx context.Context, c *Case, head, node s
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
job := delivery.ComposeJob{Fetch: map[string]any{"repository": nodeEngineRepo, "commit": head}, Dir: delivery.RootPath,
|
||||
job := buildstep.ComposeJob{Fetch: buildstep.Fetch{Repository: nodeEngineRepo, Commit: head}, Dir: meshcatalogclient.RootPath,
|
||||
Package: composer.Package, Request: raw, Reader: "the node-engine head of " + c.Group}
|
||||
verdict, err := e.Sandbox.Compose(ctx, job)
|
||||
if err != nil {
|
||||
|
||||
@@ -9,10 +9,15 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// fakeEngines answers compose-candidate by node from memory; a node it holds no answer for never answers.
|
||||
@@ -45,12 +50,12 @@ func (f *fakeEngines) ComposeCandidate(ctx context.Context, node string, ask ne.
|
||||
// fakeSandbox answers a compose job with a verdict, recording the job.
|
||||
type fakeSandbox struct {
|
||||
mu sync.Mutex
|
||||
jobs []delivery.ComposeJob
|
||||
jobs []buildstep.ComposeJob
|
||||
v func(node string) composer.Verdict
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeSandbox) Compose(_ context.Context, job delivery.ComposeJob) (composer.Verdict, error) {
|
||||
func (f *fakeSandbox) Compose(_ context.Context, job buildstep.ComposeJob) (composer.Verdict, error) {
|
||||
var req composer.Request
|
||||
if err := json.Unmarshal(job.Request, &req); err != nil {
|
||||
return composer.Verdict{}, err
|
||||
@@ -80,8 +85,8 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
Own: ne.Module{Module: nec.Own, State: nec.ModuleComposed, Digest: "sha256:" + strings.Repeat("b", 64)}}
|
||||
}
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1))
|
||||
key, _ := delivery.CompositionKey(cs.Group, cs.HeadsHash)
|
||||
compose := func(e *fakeEngines, nodes ...string) delivery.CompositionVerdict {
|
||||
key, _ := nodeengineclient.CompositionKey(cs.Group, cs.HeadsHash)
|
||||
compose := func(e *fakeEngines, nodes ...string) meshwants.CompositionsByGroupHeadsValue {
|
||||
t.Helper()
|
||||
ec := EngineCompositions{Asker: e, Placed: func() ([]string, error) { return nodes, nil }, Bound: 50 * time.Millisecond,
|
||||
Composer: func() string { return strings.Repeat("7", 40) }}
|
||||
@@ -99,16 +104,16 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
{Module: "mail", State: nec.ModuleHeld, Words: "held as today"}}
|
||||
e := &fakeEngines{answers: map[string]ne.ComposeCandidateAnswer{"anchor": composed("anchor"), "laptop": refusing}}
|
||||
v := compose(e, "anchor", "laptop")
|
||||
if v.State != delivery.CompositionFail || v.Machine != "laptop" || !strings.Contains(v.Words, "not composed on laptop: web: a required value is unset") ||
|
||||
if v.State != meshdeliverycheckerclient.CompositionFail || v.Machine != "laptop" || !strings.Contains(v.Words, "not composed on laptop: web: a required value is unset") ||
|
||||
strings.Contains(v.Words, "mail") || len(v.Machines) != 2 {
|
||||
t.Errorf("verdict = %+v", v)
|
||||
}
|
||||
if e.asked["anchor"].Candidate != key || e.asked["laptop"].Input {
|
||||
t.Errorf("asked %+v, want the check's candidate %s and no input", e.asked, key)
|
||||
}
|
||||
var ver delivery.Verdict
|
||||
var ver meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
withComposition(&ver, v)
|
||||
if ver.State != delivery.VerdictFail || ver.Failed.Machine != "laptop" {
|
||||
if ver.State != meshdeliverycheckerclient.VerdictFail || ver.Failed.Machine != "laptop" {
|
||||
t.Errorf("the check's verdict = %+v", ver)
|
||||
}
|
||||
})
|
||||
@@ -122,7 +127,7 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
if took := time.Since(began); took > 2*time.Second {
|
||||
t.Errorf("the nodes were not asked in parallel within the bound: %s", took)
|
||||
}
|
||||
if v.State != delivery.CompositionPass || v.SDK != strings.Repeat("7", 40) || len(v.Machines) != 1 {
|
||||
if v.State != meshdeliverycheckerclient.CompositionPass || v.Sdk != strings.Repeat("7", 40) || len(v.Machines) != 1 {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
for _, node := range []string{"ace", "g14", "laptop"} {
|
||||
@@ -130,16 +135,16 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
t.Errorf("%s is not named: %v", node, v.Warnings)
|
||||
}
|
||||
}
|
||||
var ver delivery.Verdict
|
||||
ver.State = delivery.VerdictPass
|
||||
var ver meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
ver.State = meshdeliverycheckerclient.VerdictPass
|
||||
withComposition(&ver, v)
|
||||
if ver.State != delivery.VerdictPass || !strings.Contains(composition(ver.Composition, 0, false), "laptop: "+nec.NoAnswer) {
|
||||
if ver.State != meshdeliverycheckerclient.VerdictPass || !strings.Contains(composition(ver.Composition, 0, false), "laptop: "+nec.NoAnswer) {
|
||||
t.Errorf("the line = %q", composition(ver.Composition, 0, false))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("a node-engine member is composed by the composer at its head over each node's input", func(t *testing.T) {
|
||||
c, err := delivery.DecodeCandidate(candidateOf(t, "feat/x", pullOf(nodeEngineRepo, 3, head2), pullOf(repoA, 7, head1)))
|
||||
c, err := meshcatalog.DecodeCandidatesByGroupValue(candidateOf(t, "feat/x", pullOf(nodeEngineRepo, 3, head2), pullOf(repoA, 7, head1)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -163,11 +168,11 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
job := sb.jobs[0]
|
||||
var req composer.Request
|
||||
_ = json.Unmarshal(job.Request, &req)
|
||||
if job.Fetch["repository"] != nodeEngineRepo || job.Fetch["commit"] != head2 || job.Package != composer.Package ||
|
||||
if job.Fetch.Repository != nodeEngineRepo || job.Fetch.Commit != head2 || job.Package != composer.Package ||
|
||||
string(req.Input) != `{"nodes":[{"name":"anchor"}]}` || req.Cases[0].Manifests["web"].Digest == "" {
|
||||
t.Errorf("job = %+v, request %+v", job, req)
|
||||
}
|
||||
if v.State != delivery.CompositionFail || v.SDK != head2 || !strings.Contains(v.Words, "the new composer refuses it") {
|
||||
if v.State != meshdeliverycheckerclient.CompositionFail || v.Sdk != head2 || !strings.Contains(v.Words, "the new composer refuses it") {
|
||||
t.Errorf("verdict = %+v", v)
|
||||
}
|
||||
// ADR 0593 §9 (ADR 0601 §4's kind): a node the composer did not judge is named as no answer with its words, by the
|
||||
@@ -185,7 +190,7 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
}
|
||||
v, err = ec.Compose(context.Background(), host)
|
||||
named := len(v.Warnings) == 1 && strings.Contains(v.Warnings[0], "anchor: "+nec.NoAnswer+" (its node-engine sends no running state for web)")
|
||||
if err != nil || (v.State == delivery.CompositionPass) != row.pass || named != row.pass {
|
||||
if err != nil || (v.State == meshdeliverycheckerclient.CompositionPass) != row.pass || named != row.pass {
|
||||
t.Errorf("%+v: verdict %+v, %v", row.m, v, err)
|
||||
}
|
||||
}
|
||||
@@ -193,7 +198,7 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
// run fails the composition, naming the head, never passing as a silent node.
|
||||
sb.err = errors.New("the build of mesh-compose failed")
|
||||
v, err = ec.Compose(context.Background(), host)
|
||||
if err != nil || v.State != delivery.CompositionFail || !strings.Contains(v.Words, head2) ||
|
||||
if err != nil || v.State != meshdeliverycheckerclient.CompositionFail || !strings.Contains(v.Words, head2) ||
|
||||
!strings.Contains(v.Words, "the build of mesh-compose failed") || len(v.Warnings) != 0 {
|
||||
t.Errorf("a composer that cannot run: %+v, %v", v, err)
|
||||
}
|
||||
@@ -208,7 +213,7 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
||||
b := newBench(t)
|
||||
b.ex.Compositions = ec
|
||||
v := b.judge(t, cs).Verdict
|
||||
if v.Composition == nil || v.Composition.State != delivery.CompositionNotRun || !strings.Contains(v.Composition.Words, "no such bucket") {
|
||||
if v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionNotRun || !strings.Contains(v.Composition.Words, "no such bucket") {
|
||||
t.Errorf("the verdict's composition = %+v", v.Composition)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -6,23 +6,26 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// failingResult is a level's test result in which the named tests failed, each in its own package.
|
||||
func failingResult(t *testing.T, tests int, failed ...Failure) json.RawMessage {
|
||||
t.Helper()
|
||||
raw, err := json.Marshal(Tested{Level: delivery.LevelIntegration, Tests: tests, Elapsed: 2, Failed: failed, Passed: len(failed) == 0})
|
||||
raw, err := json.Marshal(Tested{Level: buildstep.LevelIntegration, Tests: tests, Elapsed: 2, Failed: failed, Passed: len(failed) == 0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func judgeLevel(t *testing.T, tests ...json.RawMessage) (*delivery.Verdict, *state) {
|
||||
func judgeLevel(t *testing.T, tests ...json.RawMessage) (*meshdeliverychecker.VerdictsByGroupHeadsValue, *state) {
|
||||
t.Helper()
|
||||
st := &state{j: &Examiner{}, c: &Case{}, levels: map[string]*acc{delivery.LevelUnit: {}, delivery.LevelIntegration: {}}, nodes: map[string]bool{}}
|
||||
v, _ := st.level(t.Context(), &target{pull: Pull{Repository: "novox/x"}}, delivery.LevelAnswer{Level: delivery.LevelIntegration, Tests: tests})
|
||||
st := &state{j: &Examiner{}, c: &Case{}, levels: map[string]*acc{buildstep.LevelUnit: {}, buildstep.LevelIntegration: {}}, nodes: map[string]bool{}}
|
||||
v, _ := st.level(t.Context(), &target{pull: Pull{Repository: "novox/x"}}, buildstep.LevelAnswer{Level: buildstep.LevelIntegration, Tests: tests})
|
||||
return settled(joinFailures(v)), st
|
||||
}
|
||||
|
||||
@@ -48,29 +51,29 @@ func TestTwoFailingTestsInTwoPackagesAreTwoEntries(t *testing.T) {
|
||||
if len(v.Failures) != 2 || v.Failed.Test != "integration: x/a.TestA" || !strings.Contains(v.Failed.Words, "a_test.go:3: logged") {
|
||||
t.Errorf("%s: verdict %+v", name, v)
|
||||
}
|
||||
if run := st.levels[delivery.LevelIntegration].run; run != 10 {
|
||||
if run := st.levels[buildstep.LevelIntegration].run; run != 10 {
|
||||
t.Errorf("%s: %d tests counted, want all 10", name, run)
|
||||
}
|
||||
if _, err := delivery.EncodeVerdict(delivery.Verdict{Group: "g", Heads: "0123456789ab", State: v.State, Failed: v.Failed, Failures: v.Failures}); err != nil {
|
||||
if _, err := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "g", Heads: "0123456789ab", State: v.State, Failed: v.Failed, Failures: v.Failures}); err != nil {
|
||||
t.Errorf("%s: the verdict is not written: %v", name, err)
|
||||
}
|
||||
}
|
||||
// Design 26 *Out of step* (DLV-C2): the out-of-step files the compare step names, in each result, are the outcome's for
|
||||
// the pull request, each once and sorted; a level naming none leaves the outcome with none.
|
||||
step := func(files ...string) json.RawMessage {
|
||||
raw, _ := json.Marshal(Tested{Level: delivery.LevelUnit, Tests: 1, Passed: false, Why: "out of step", OutOfStep: files})
|
||||
raw, _ := json.Marshal(Tested{Level: buildstep.LevelUnit, Tests: 1, Passed: false, Why: "out of step", OutOfStep: files})
|
||||
return raw
|
||||
}
|
||||
_, st := judgeLevel(t, step("go/seats/b/contract.go", "go/seats/a/contract.go"), step("go/seats/a/contract.go"))
|
||||
if got := st.outcome(delivery.Verdict{State: delivery.VerdictFail}).OutOfStep["novox/x#0"]; strings.Join(got, ",") != "go/seats/a/contract.go,go/seats/b/contract.go" {
|
||||
if got := st.outcome(meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail}).OutOfStep["novox/x#0"]; strings.Join(got, ",") != "go/seats/a/contract.go,go/seats/b/contract.go" {
|
||||
t.Errorf("out of step %v", got)
|
||||
}
|
||||
if _, st := judgeLevel(t, failingResult(t, 2, a)); st.outcome(delivery.Verdict{State: delivery.VerdictFail}).OutOfStep != nil {
|
||||
if _, st := judgeLevel(t, failingResult(t, 2, a)); st.outcome(meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail}).OutOfStep != nil {
|
||||
t.Errorf("a level naming no out-of-step file has %v", st.outOfStep)
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1921: past delivery.MaxFailures the list names the first fifty and how many more, the status line says how many
|
||||
// Issue 1921: past meshdeliverycheckerclient.MaxFailures the list names the first fifty and how many more, the status line says how many
|
||||
// failed and names the first, cut so "and N more" stays whole, and each entry's line is cut to MaxFailureLine.
|
||||
func TestFailuresAreCappedWithHowManyMore(t *testing.T) {
|
||||
var fs []Failure
|
||||
@@ -78,7 +81,7 @@ func TestFailuresAreCappedWithHowManyMore(t *testing.T) {
|
||||
fs = append(fs, Failure{Package: fmt.Sprintf("x/p%d", i), Test: "TestT", Cause: strings.Repeat("y", 500)})
|
||||
}
|
||||
v, _ := judgeLevel(t, failingResult(t, 60, fs...))
|
||||
if len(v.Failures) != delivery.MaxFailures+1 || v.Failures[delivery.MaxFailures].Test != "and 10 more" {
|
||||
if len(v.Failures) != meshdeliverycheckerclient.MaxFailures+1 || v.Failures[meshdeliverycheckerclient.MaxFailures].Test != "and 10 more" {
|
||||
t.Fatalf("%d entries, last %+v", len(v.Failures), v.Failures[len(v.Failures)-1])
|
||||
}
|
||||
if len(v.Failures[0].Words) > MaxFailureLine {
|
||||
@@ -93,14 +96,14 @@ func TestFailuresAreCappedWithHowManyMore(t *testing.T) {
|
||||
t.Errorf("line %q", line)
|
||||
}
|
||||
// A composition failing after them is counted in the last entry (the operator, 2026-11-02).
|
||||
withComposition(v, delivery.CompositionVerdict{State: delivery.CompositionFail, Machine: "g14", Words: "route-proxy is defined twice"})
|
||||
withComposition(v, meshwants.CompositionsByGroupHeadsValue{State: meshdeliverycheckerclient.CompositionFail, Machine: "g14", Words: "route-proxy is defined twice"})
|
||||
if n := failedCount(*v); n != 61 || v.Failed.Test != "integration: x/p0.TestT" {
|
||||
t.Errorf("after the composition failedCount = %d, failed %+v", n, v.Failed)
|
||||
}
|
||||
|
||||
// A verdict that names no list (a conflict, a refused step, a verdict written before issue 1921) is one failed line, as
|
||||
// before.
|
||||
one := delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: "g14", Words: "files in conflict: go.mod\nmore"}}
|
||||
one := meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: "g14", Words: "files in conflict: go.mod\nmore"}}
|
||||
if got := failedLines(one); len(got) != 1 || got[0] != "g14: files in conflict: go.mod" {
|
||||
t.Fatalf("%q", got)
|
||||
}
|
||||
|
||||
@@ -20,10 +20,17 @@ import (
|
||||
|
||||
gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshconfig"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// captured reads a result the build module's own code marshalled (testdata/captured/README).
|
||||
@@ -80,10 +87,10 @@ func (f *fakeSteps) tool(name string) []stepCall {
|
||||
return out
|
||||
}
|
||||
|
||||
type entries []delivery.NodeToolchain
|
||||
type entries []buildstep.NodeToolchain
|
||||
|
||||
func (e entries) Entries(language string) []delivery.NodeToolchain {
|
||||
var out []delivery.NodeToolchain
|
||||
func (e entries) Entries(language string) []buildstep.NodeToolchain {
|
||||
var out []buildstep.NodeToolchain
|
||||
for _, x := range e {
|
||||
if x.Language == language {
|
||||
out = append(out, x)
|
||||
@@ -104,8 +111,8 @@ func (l langs) BuildModules(repository string) []string {
|
||||
func goEntries(nodes ...string) entries {
|
||||
var e entries
|
||||
for _, n := range nodes {
|
||||
e = append(e, delivery.NodeToolchain{Node: n, Toolchain: delivery.Toolchain{Language: "go", Version: "go1.27.1", Recipe: "7"}})
|
||||
e = append(e, delivery.NodeToolchain{Node: n, Toolchain: delivery.Toolchain{Language: "typescript", Version: "node24", Recipe: "3"}})
|
||||
e = append(e, buildstep.NodeToolchain{Node: n, Toolchain: buildstep.Toolchain{Language: "go", Version: "go1.27.1", Recipe: "7"}})
|
||||
e = append(e, buildstep.NodeToolchain{Node: n, Toolchain: buildstep.Toolchain{Language: "typescript", Version: "node24", Recipe: "3"}})
|
||||
}
|
||||
return e
|
||||
}
|
||||
@@ -140,8 +147,8 @@ var toolPrefix = map[string]string{buildrun.GoBuildModule: "build_go_", buildrun
|
||||
|
||||
// answer runs a check ask as the build module that pulls it does (gobuild.RunAsk), with the fake tools on shanks and the
|
||||
// bench's kept verdicts, and answers the step it writes.
|
||||
func (b *bench) answer(a delivery.StepAsk) delivery.Step {
|
||||
module, _ := delivery.BuildModuleOf(a.Language)
|
||||
func (b *bench) answer(a buildstep.StepAsk) buildstep.Step {
|
||||
module, _ := buildstep.BuildModuleOf(a.Language)
|
||||
const node = "shanks"
|
||||
tool := func(name string) gobuild.StepFunc {
|
||||
return func(ctx context.Context, args map[string]any) (any, error) {
|
||||
@@ -155,7 +162,7 @@ func (b *bench) answer(a delivery.StepAsk) delivery.Step {
|
||||
steps := gobuild.AskSteps{Fetch: tool("fetch"), Test: tool("test"),
|
||||
Kept: func(ctx context.Context, level, digest string) (int, bool, error) {
|
||||
k, found, err := (Kept{Store: b.kept}).Lookup(ctx, level, digest)
|
||||
return k.Tests, found, err
|
||||
return int(k.Tests), found, err
|
||||
}}
|
||||
if module == buildrun.GoBuildModule {
|
||||
steps.Digest = tool("digest")
|
||||
@@ -164,16 +171,16 @@ func (b *bench) answer(a delivery.StepAsk) delivery.Step {
|
||||
if len(a.Needs) > 0 {
|
||||
if b.declines > 0 {
|
||||
b.declines--
|
||||
result, _ := json.Marshal(delivery.StepDeclined{Declined: true, Needs: a.Needs, Lacks: a.Needs})
|
||||
return delivery.Step{ID: a.ID, Tool: a.Kind + " ask", Node: "ace", State: delivery.StepRefused, Started: began, Ended: began,
|
||||
result, _ := json.Marshal(buildstep.StepDeclined{Declined: true, Needs: a.Needs, Lacks: a.Needs})
|
||||
return buildstep.Step{ID: a.ID, Tool: a.Kind + " ask", Node: "ace", State: buildstep.StepRefused, Started: began, Ended: began,
|
||||
Reason: "declined", Result: result}
|
||||
}
|
||||
steps.Has = func(string) bool { return true }
|
||||
}
|
||||
out, err := gobuild.RunAsk(context.Background(), steps, a)
|
||||
step := delivery.Step{ID: a.ID, Tool: a.Kind + " ask", Node: node, State: delivery.StepPassed, Started: began, Ended: began.Add(2 * time.Second)}
|
||||
step := buildstep.Step{ID: a.ID, Tool: a.Kind + " ask", Node: node, State: buildstep.StepPassed, Started: began, Ended: began.Add(2 * time.Second)}
|
||||
if err != nil {
|
||||
step.State, step.Reason = delivery.StepFailed, err.Error()
|
||||
step.State, step.Reason = buildstep.StepFailed, err.Error()
|
||||
return step
|
||||
}
|
||||
step.Result, _ = json.Marshal(out)
|
||||
@@ -188,8 +195,8 @@ func newBench(t testing.TB) *bench {
|
||||
b.fetch.Modules, b.fetch.Changed, b.fetch.Trees = []string{"."}, []string{"README.md"}, nil
|
||||
one := captured[Digested](t, "go-digest")
|
||||
b.digest = map[string]Digested{
|
||||
delivery.LevelUnit: {Level: "unit", Packages: map[string]string{"fixture/slow": digA}},
|
||||
delivery.LevelIntegration: {Level: "integration", Packages: map[string]string{"fixture/slow": digB}},
|
||||
buildstep.LevelUnit: {Level: "unit", Packages: map[string]string{"fixture/slow": digA}},
|
||||
buildstep.LevelIntegration: {Level: "integration", Packages: map[string]string{"fixture/slow": digB}},
|
||||
}
|
||||
_ = one
|
||||
slow := captured[Tested](t, "go-test-slow")
|
||||
@@ -212,10 +219,10 @@ func newBench(t testing.TB) *bench {
|
||||
return b
|
||||
}
|
||||
|
||||
func caseOf(t testing.TB, pulls ...delivery.CandidatePull) *Case {
|
||||
func caseOf(t testing.TB, pulls ...meshcatalog.CandidatesByGroupValuePullsItem) *Case {
|
||||
t.Helper()
|
||||
raw := candidateOf(t, "feat/x", pulls...)
|
||||
c, err := delivery.DecodeCandidate(raw)
|
||||
c, err := meshcatalog.DecodeCandidatesByGroupValue(raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -257,10 +264,10 @@ func TestACleanMergeIsFetchedAtItsTrunkAndBothLevelsPass(t *testing.T) { // 1679
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1))
|
||||
out := b.judge(t, cs)
|
||||
v := out.Verdict
|
||||
if v.State != delivery.VerdictPass || v.Levels[delivery.LevelUnit].Run != 2 || v.Levels[delivery.LevelIntegration].Run != 2 {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass || v.Levels[buildstep.LevelUnit].Run != 2 || v.Levels[buildstep.LevelIntegration].Run != 2 {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
if v.Composition == nil || v.Composition.State != delivery.CompositionNotRun {
|
||||
if v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionNotRun {
|
||||
t.Errorf("composition = %+v, want not run", v.Composition)
|
||||
}
|
||||
if len(v.Machines) != 1 || v.Machines[0] != "shanks" {
|
||||
@@ -292,7 +299,7 @@ func TestACleanMergeIsFetchedAtItsTrunkAndBothLevelsPass(t *testing.T) { // 1679
|
||||
out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
asks := b.asks.Asks()
|
||||
if c.fails != "" {
|
||||
if out.Verdict.State != delivery.VerdictFail || len(asks) != 0 || !strings.Contains(out.Verdict.Failed.Words, c.fails) {
|
||||
if out.Verdict.State != meshdeliverycheckerclient.VerdictFail || len(asks) != 0 || !strings.Contains(out.Verdict.Failed.Words, c.fails) {
|
||||
t.Errorf("names %v, %v: verdict %+v, %d asks", c.names, c.err, out.Verdict, len(asks))
|
||||
}
|
||||
} else if len(asks) == 0 || strings.Join(asks[0].Check.NodeNames, ",") != "anchor,laptop" {
|
||||
@@ -311,7 +318,7 @@ func (n namesOf) Names(context.Context) ([]string, error) { return n.names, n.er
|
||||
|
||||
func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1
|
||||
b := newBench(t)
|
||||
b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/slow": digA, "fixture/other": digC}}
|
||||
b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/slow": digA, "fixture/other": digC}}
|
||||
if _, err := (Kept{Store: b.kept}).Keep(context.Background(), "unit", digC, keptValue("fixture/other", 40)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -320,7 +327,7 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1
|
||||
if len(tests) != 2 || strings.Join(packagesOf(tests[0]), ",") != "fixture/slow" || tests[0].args["level"] != "unit" {
|
||||
t.Fatalf("test calls = %+v, want the unit level of fixture/slow alone, then the integration level", tests)
|
||||
}
|
||||
if u := out.Verdict.Levels[delivery.LevelUnit]; u.Run != 2 || u.Kept != 40 {
|
||||
if u := out.Verdict.Levels[buildstep.LevelUnit]; u.Run != 2 || u.Kept != 40 {
|
||||
t.Errorf("unit = %+v, want 2 run and 40 kept", u)
|
||||
}
|
||||
|
||||
@@ -340,11 +347,11 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
b.digest[delivery.LevelIntegration] = Digested{Level: "integration",
|
||||
Needs: []delivery.PackageNeeds{{Package: "fixture/node", Needs: []string{delivery.NeedNodeBuildNode}}}}
|
||||
b.digest[buildstep.LevelIntegration] = Digested{Level: "integration",
|
||||
Needs: []buildstep.PackageNeeds{{Package: "fixture/node", Needs: []string{buildstep.NeedNodeBuildNode}}}}
|
||||
out = b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
asks := b.asks.Asks()
|
||||
if out.Verdict.State != delivery.VerdictPass || len(asks) != 3 || len(asks[0].Needs) != 0 {
|
||||
if out.Verdict.State != meshdeliverycheckerclient.VerdictPass || len(asks) != 3 || len(asks[0].Needs) != 0 {
|
||||
t.Fatalf("verdict %+v, asks %+v: want a pass of three asks, the first naming no need", out.Verdict, asks)
|
||||
}
|
||||
for _, a := range asks { // ADR 0584: every check ask names the agreed toolchain, so a node off it declines
|
||||
@@ -353,7 +360,7 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1
|
||||
}
|
||||
}
|
||||
for _, a := range asks[1:] {
|
||||
if strings.Join(a.Needs, ",") != delivery.NeedNodeBuildNode || strings.Join(a.Check.Levels, ",") != delivery.LevelIntegration || strings.Join(a.Check.Packages, ",") != "fixture/node" {
|
||||
if strings.Join(a.Needs, ",") != buildstep.NeedNodeBuildNode || strings.Join(a.Check.Levels, ",") != buildstep.LevelIntegration || strings.Join(a.Check.Packages, ",") != "fixture/node" {
|
||||
t.Errorf("a placed ask %+v, want the integration level needing node-build-node", a)
|
||||
}
|
||||
}
|
||||
@@ -362,13 +369,13 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1
|
||||
t.Errorf("test calls = %+v, want the unit level, the integration level of fixture/slow and fixture/outside, then fixture/node alone", tests)
|
||||
}
|
||||
slow := captured[Tested](t, "go-test-slow")
|
||||
if i := out.Verdict.Levels[delivery.LevelIntegration]; i.Run != 2*slow.Tests || i.Kept != 0 {
|
||||
if i := out.Verdict.Levels[buildstep.LevelIntegration]; int(i.Run) != 2*slow.Tests || i.Kept != 0 {
|
||||
t.Errorf("integration = %+v, want %d run and nothing counted kept", i, 2*slow.Tests)
|
||||
}
|
||||
// And where no node has the need, the check fails once the declines outlast the bound, naming the need.
|
||||
b.kept, b.declines = newMemKV(), 1000
|
||||
b.ex.Kept, b.ex.DeclineBound = Kept{Store: b.kept}, 200*time.Millisecond
|
||||
if v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict; v.State != delivery.VerdictFail || !strings.Contains(fmt.Sprint(v), "needs a node with node-build-node") {
|
||||
if v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict; v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(failedOf(v).Words, "needs a node with node-build-node") {
|
||||
t.Errorf("with no node having the need: %+v", v)
|
||||
}
|
||||
}
|
||||
@@ -376,7 +383,7 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1
|
||||
func TestAReAskRunsOnlyWhatFailedAndWhatChanged(t *testing.T) { // 1288/AC2, AC3
|
||||
b := newBench(t)
|
||||
failing := captured[Tested](t, "go-test-failing")
|
||||
b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/failing/bad": digA, "fixture/failing/good": digB}}
|
||||
b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/failing/bad": digA, "fixture/failing/good": digB}}
|
||||
b.test = func(c stepCall) (any, error) {
|
||||
if c.args["level"] == "unit" {
|
||||
return failing, nil
|
||||
@@ -385,7 +392,7 @@ func TestAReAskRunsOnlyWhatFailedAndWhatChanged(t *testing.T) { // 1288/AC2, AC3
|
||||
}
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1))
|
||||
first := b.judge(t, cs).Verdict
|
||||
if first.State != delivery.VerdictFail || first.Failed.Test != "unit: fixture/failing/bad.TestThreeIsFour" {
|
||||
if first.State != meshdeliverycheckerclient.VerdictFail || first.Failed.Test != "unit: fixture/failing/bad.TestThreeIsFour" {
|
||||
t.Fatalf("first = %+v", first)
|
||||
}
|
||||
if _, _, found, _ := b.kept.Get(context.Background(), "unit."+digB); !found {
|
||||
@@ -402,7 +409,7 @@ func TestAReAskRunsOnlyWhatFailedAndWhatChanged(t *testing.T) { // 1288/AC2, AC3
|
||||
t.Fatalf("the re-ask ran %+v, want only fixture/failing/bad", tests)
|
||||
}
|
||||
// The trunk moved and one digest changed: that package runs, the unchanged one is taken.
|
||||
b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/failing/bad": digA, "fixture/failing/good": digD}}
|
||||
b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/failing/bad": digA, "fixture/failing/good": digD}}
|
||||
b.steps.calls = nil
|
||||
b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
tests = b.steps.tool("test")
|
||||
@@ -421,7 +428,7 @@ func TestANodePackageIsNeverKeptAndRunsEveryCheck(t *testing.T) { // 1288/AC4, 1
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1))
|
||||
for i := 0; i < 2; i++ {
|
||||
v := b.judge(t, cs).Verdict
|
||||
if v.State != delivery.VerdictPass || v.Levels[delivery.LevelUnit].Kept != 0 || v.Levels[delivery.LevelUnit].Run != 1 {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass || v.Levels[buildstep.LevelUnit].Kept != 0 || v.Levels[buildstep.LevelUnit].Run != 1 {
|
||||
t.Fatalf("round %d: verdict = %+v", i, v)
|
||||
}
|
||||
}
|
||||
@@ -445,11 +452,11 @@ func TestANodePackageIsNeverKeptAndRunsEveryCheck(t *testing.T) { // 1288/AC4, 1
|
||||
func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) {
|
||||
for _, conflicts := range []bool{false, true} {
|
||||
b := newBench(t)
|
||||
comps := &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionFail, SDK: "x",
|
||||
comps := &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.CompositionFail, Sdk: "x",
|
||||
Machine: "g14", Words: "route-proxy is defined twice"}, waits: conflicts}
|
||||
b.ex.Compositions = comps
|
||||
b.fetch.Changed = []string{"modules/m/internal/b/b_test.go"}
|
||||
b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"m/internal/a": digA, "m/internal/b": digB}}
|
||||
b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"m/internal/a": digA, "m/internal/b": digB}}
|
||||
conflict := captured[Fetched](t, "go-fetch-conflict")
|
||||
b.steps.handlers["build_go_fetch"] = func(c stepCall) (any, error) {
|
||||
if conflicts && c.args["repository"] == repoSDK {
|
||||
@@ -467,9 +474,9 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) {
|
||||
return captured[Tested](t, "go-test-slow"), nil
|
||||
}
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))
|
||||
done := make(chan delivery.Verdict, 1)
|
||||
done := make(chan meshdeliverychecker.VerdictsByGroupHeadsValue, 1)
|
||||
go func() { done <- b.judge(t, cs).Verdict }()
|
||||
var v delivery.Verdict
|
||||
var v meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
select {
|
||||
case v = <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
@@ -477,14 +484,14 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) {
|
||||
}
|
||||
var got []string
|
||||
for _, f := range v.Failures {
|
||||
got = append(got, firstFailed(delivery.Verdict{Failed: f}))
|
||||
got = append(got, firstFailed(meshdeliverychecker.VerdictsByGroupHeadsValue{Failed: &FailedFirst{Test: f.Test, Machine: f.Machine, Words: f.Words}}))
|
||||
}
|
||||
tests := []string{"unit: fixture/failing/bad.TestThreeIsFour", "integration: fixture/failing/bad.TestThreeIsFour"}
|
||||
want := append(append(slices.Clone(tests), tests...), "g14: route-proxy is defined twice") // both members fail their tests
|
||||
if conflicts {
|
||||
want = append([]string{"shanks: novox/mesh-sdk#12 does not merge into the trunk: files in conflict: a.go"}, tests...)
|
||||
}
|
||||
if v.State != delivery.VerdictFail || len(got) != len(want) {
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail || len(got) != len(want) {
|
||||
t.Fatalf("conflicts %v: verdict %+v: failures\n%s\nwant\n%s", conflicts, v, strings.Join(got, "\n"), strings.Join(want, "\n"))
|
||||
}
|
||||
for i := range want {
|
||||
@@ -494,9 +501,9 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) {
|
||||
}
|
||||
keys, _ := b.prev.Keys(context.Background())
|
||||
switch {
|
||||
case !conflicts && (len(keys) != 2 || v.Composition == nil || v.Composition.State != delivery.CompositionFail):
|
||||
case !conflicts && (len(keys) != 2 || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionFail):
|
||||
t.Errorf("tests alone failed: previews %v, composition %+v, want both previews and the composition read", keys, v.Composition)
|
||||
case conflicts && (len(comps.asked) != 0 || v.Composition == nil || v.Composition.State != delivery.CompositionNotRun ||
|
||||
case conflicts && (len(comps.asked) != 0 || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionNotRun ||
|
||||
!strings.Contains(v.Composition.Words, "novox/mesh-sdk#12")):
|
||||
t.Errorf("a member did not merge: asked %v, composition %+v, want not run naming novox/mesh-sdk#12", comps.asked, v.Composition)
|
||||
}
|
||||
@@ -510,7 +517,7 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) {
|
||||
t.Errorf("the unit level ran %v and %d digests, want the changed test's package, then the rest, at both levels", unit, len(b.steps.tool("digest")))
|
||||
}
|
||||
v.Group, v.Heads = "feat/x", "0123456789ab"
|
||||
if _, err := delivery.EncodeVerdict(v); err != nil {
|
||||
if _, err := meshdeliverycheckerclient.EncodeVerdict(v); err != nil {
|
||||
t.Errorf("conflicts %v: the verdict cannot be written: %v", conflicts, err)
|
||||
}
|
||||
if line := Line(v, cs, cs.Pulls[0], nil, nil, nil); !strings.HasPrefix(line, fmt.Sprintf("failed (%d): %s", len(want), want[0][:20])) ||
|
||||
@@ -523,9 +530,9 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) {
|
||||
func TestTheChangesOwnPassingTestRunsFirstAndTheRestFollow(t *testing.T) { // 1289/AC3
|
||||
b := newBench(t)
|
||||
b.fetch.Changed = []string{"modules/m/internal/b/b_test.go", "modules/m/internal/a/a.go"}
|
||||
b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"m/internal/a": digA, "m/internal/b": digB}}
|
||||
b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"m/internal/a": digA, "m/internal/b": digB}}
|
||||
out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
if out.Verdict.State != delivery.VerdictPass {
|
||||
if out.Verdict.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", out.Verdict)
|
||||
}
|
||||
var unit []string
|
||||
@@ -544,7 +551,7 @@ func TestAHeadThatDoesNotMergeEndsFailedNamingTheFilesAndRunsNoTest(t *testing.T
|
||||
b.fetch = captured[Fetched](t, "go-fetch-conflict")
|
||||
out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
f := out.Verdict.Failed
|
||||
if out.Verdict.State != delivery.VerdictFail || f.Machine != "shanks" || !strings.Contains(f.Words, "files in conflict: a.go, sub/b.go") {
|
||||
if out.Verdict.State != meshdeliverycheckerclient.VerdictFail || f.Machine != "shanks" || !strings.Contains(f.Words, "files in conflict: a.go, sub/b.go") {
|
||||
t.Fatalf("verdict = %+v", out.Verdict)
|
||||
}
|
||||
if n := len(b.steps.tool("digest")) + len(b.steps.tool("test")); n != 0 {
|
||||
@@ -556,7 +563,7 @@ func TestALevelWhoseDigestIsNotNamedRunsWholeAndKeepsNothing(t *testing.T) { //
|
||||
b := newBench(t)
|
||||
b.steps.handlers["build_go_digest"] = func(stepCall) (any, error) { return nil, fmt.Errorf("go list failed") }
|
||||
out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
if out.Verdict.State != delivery.VerdictPass {
|
||||
if out.Verdict.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", out.Verdict)
|
||||
}
|
||||
tests := b.steps.tool("test")
|
||||
@@ -580,7 +587,7 @@ func TestALevelOverItsBoundIsNamedWithItsSlowestTestsAndKeepsItsVerdict(t *testi
|
||||
}
|
||||
out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
v := out.Verdict
|
||||
if v.State != delivery.VerdictPass || strings.Join(v.OverBound, ",") != "unit,integration" {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass || strings.Join(v.OverBound, ",") != "unit,integration" {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
if len(out.Slowest) == 0 || !strings.Contains(out.Slowest[0], "TestTakesAWhile") {
|
||||
@@ -594,7 +601,7 @@ func TestSkippedTestsAreCountedPerLevel(t *testing.T) { // 1300/AC6, 1251
|
||||
return testedWith(t, "go-test-slow", func(r *Tested) { r.Level = c.args["level"].(string); r.Skipped = 3 }), nil
|
||||
}
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if v.Levels[delivery.LevelUnit].Skipped != 3 || v.Levels[delivery.LevelIntegration].Skipped != 3 {
|
||||
if v.Levels[buildstep.LevelUnit].Skipped != 3 || v.Levels[buildstep.LevelIntegration].Skipped != 3 {
|
||||
t.Errorf("levels = %+v", v.Levels)
|
||||
}
|
||||
}
|
||||
@@ -603,7 +610,7 @@ func TestAGroupIsOneVerdictAndEachMembersTestsReadTheOthersAtTheirHeads(t *testi
|
||||
b := newBench(t)
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))
|
||||
v := b.judge(t, cs).Verdict
|
||||
if v.State != delivery.VerdictPass {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
fetch := b.steps.tool("fetch")
|
||||
@@ -618,7 +625,7 @@ func TestAGroupIsOneVerdictAndEachMembersTestsReadTheOthersAtTheirHeads(t *testi
|
||||
}
|
||||
found := false
|
||||
for _, r := range forA {
|
||||
if r.Repository == repoSDK && r.Commit == head2 && r.Ref == "refs/pull/12/head" {
|
||||
if r.Repository == repoSDK && r.Commit == head2 {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
@@ -637,17 +644,17 @@ const (
|
||||
|
||||
func TestACleanMergeWritesItsPreviewWithTheTreesManifestsAndLocks(t *testing.T) { // 1445/AC1
|
||||
b := newBench(t)
|
||||
b.fetch.Trees = map[string]MergeTree{
|
||||
b.fetch.Trees = map[string]buildstep.ModuleTree{
|
||||
"modules/avahi": {Tree: "7777777777777777777777777777777777777777", Manifest: manA, Locks: map[string]string{"go.sum": "ff00"}},
|
||||
"modules/bare": {Tree: "8888888888888888888888888888888888888888"}, // no manifest digest: left out
|
||||
}
|
||||
b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
key, _ := delivery.PreviewKey(repoA, head1, trunkA)
|
||||
key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, trunkA)
|
||||
raw, _, found, _ := b.prev.Get(context.Background(), key)
|
||||
if !found {
|
||||
t.Fatalf("no preview %s; the bucket holds %v", key, b.prev.vals)
|
||||
}
|
||||
pv, err := delivery.DecodePreview(raw)
|
||||
pv, err := meshdeliverycheckerclient.DecodePreview(raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -661,12 +668,12 @@ func TestAHeadThatDoesNotMergeWritesAPreviewOfItsConflictsAlone(t *testing.T) {
|
||||
b := newBench(t)
|
||||
b.fetch = captured[Fetched](t, "go-fetch-conflict")
|
||||
b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
key, _ := delivery.PreviewKey(repoA, head1, trunkA)
|
||||
key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, trunkA)
|
||||
raw, _, found, _ := b.prev.Get(context.Background(), key)
|
||||
if !found {
|
||||
t.Fatalf("no preview for a head that does not merge; the bucket holds %v", b.prev.vals)
|
||||
}
|
||||
pv, err := delivery.DecodePreview(raw)
|
||||
pv, err := meshdeliverycheckerclient.DecodePreview(raw)
|
||||
if err != nil || strings.Join(pv.Conflicts, ",") != "a.go,sub/b.go" || pv.Merge != "" || len(pv.Modules) != 0 {
|
||||
t.Errorf("preview = %+v, %v, want the files in conflict and no merge", pv, err)
|
||||
}
|
||||
@@ -677,12 +684,12 @@ func TestAPreviewIsWrittenOnceAndAnExistingKeyStands(t *testing.T) {
|
||||
b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
b.fetch.Merge = "5656565656565656565656565656565656565656"
|
||||
b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
key, _ := delivery.PreviewKey(repoA, head1, trunkA)
|
||||
key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, trunkA)
|
||||
if b.prev.creates[key] != 1 {
|
||||
t.Errorf("the preview was created %d times, want once", b.prev.creates[key])
|
||||
}
|
||||
raw, _, _, _ := b.prev.Get(context.Background(), key)
|
||||
if pv, _ := delivery.DecodePreview(raw); pv.Merge == b.fetch.Merge {
|
||||
if pv, _ := meshdeliverycheckerclient.DecodePreview(raw); pv.Merge == b.fetch.Merge {
|
||||
t.Error("the second merge commit replaced the first")
|
||||
}
|
||||
}
|
||||
@@ -693,15 +700,15 @@ func TestTheIndexerIsToldOfAPreviewOnceWhenItIsCreated(t *testing.T) { // 1445/A
|
||||
b.ex.Announce = func(_ context.Context, event, key string) { told = append(told, event+" "+key) }
|
||||
b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
key, _ := delivery.PreviewKey(repoA, head1, trunkA)
|
||||
key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, trunkA)
|
||||
if len(told) != 1 || told[0] != EventPreviewWritten+" "+key {
|
||||
t.Errorf("told %v, want once of %s", told, key)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeBuilds map[string]delivery.BuilderBuild
|
||||
type fakeBuilds map[string]builds.BuilderBuild
|
||||
|
||||
func (f fakeBuilds) Build(_ context.Context, module, fp2 string) (delivery.BuilderBuild, bool, error) {
|
||||
func (f fakeBuilds) Build(_ context.Context, module, fp2 string) (builds.BuilderBuild, bool, error) {
|
||||
b, ok := f[module+" "+fp2]
|
||||
return b, ok, nil
|
||||
}
|
||||
@@ -712,13 +719,13 @@ func TestTheOutcomeSaysIfEachCandidatesModuleIsBuiltFailedOrWaiting(t *testing.T
|
||||
fpB := "src2:" + strings.Repeat("b", 64)
|
||||
fpC := "src2:" + strings.Repeat("c", 64)
|
||||
b.ex.Builds = fakeBuilds{
|
||||
"avahi " + fpA: {State: delivery.BuildDone},
|
||||
"mesh-tools " + fpB: {State: delivery.BuildFailed, Error: "boom"},
|
||||
"avahi " + fpA: {State: builds.BuildDone},
|
||||
"mesh-tools " + fpB: {State: builds.BuildFailed, Error: "boom"},
|
||||
// netcheck has no key yet
|
||||
}
|
||||
p := pullOf(repoA, 7, head1)
|
||||
p.Modules = []string{"avahi", "mesh-tools", "netcheck", "nofp"}
|
||||
p.Fps = map[string]delivery.CandidateModule{"avahi": {FP2: fpA}, "mesh-tools": {FP2: fpB}, "netcheck": {FP2: fpC}, "nofp": {None2: "toolchain not yet reported"}}
|
||||
p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"avahi": {Fp2: fpA}, "mesh-tools": {Fp2: fpB}, "netcheck": {Fp2: fpC}, "nofp": {None2: "toolchain not yet reported"}}
|
||||
out := b.judge(t, caseOf(t, p))
|
||||
var got []string
|
||||
for _, n := range out.Builds {
|
||||
@@ -732,7 +739,7 @@ func TestTheOutcomeSaysIfEachCandidatesModuleIsBuiltFailedOrWaiting(t *testing.T
|
||||
// trunksOf are trunk heads of every repository, as the gate's source names them.
|
||||
func trunksOf(t *testing.T, heads map[string]string) *Trunks {
|
||||
t.Helper()
|
||||
data, err := delivery.EncodeSource(delivery.Source{Group: "g", Heads: heads})
|
||||
data, err := meshdeliveryclient.EncodeSource(meshdelivery.SourceByMainValue{Group: "g", Heads: heads})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -808,8 +815,8 @@ func TestACoreRepositorysPassingPackageIsKeptUnderADigestCoveringItsSiblings(t *
|
||||
// The same siblings again: everything is taken, nothing runs.
|
||||
b.steps.calls = nil
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if n := len(b.steps.tool("test")); n != 0 || v.Levels[delivery.LevelUnit].Run != 0 || v.Levels[delivery.LevelUnit].Kept == 0 {
|
||||
t.Fatalf("%d test steps, unit %+v, want none run and the kept verdict taken", n, v.Levels[delivery.LevelUnit])
|
||||
if n := len(b.steps.tool("test")); n != 0 || v.Levels[buildstep.LevelUnit].Run != 0 || v.Levels[buildstep.LevelUnit].Kept == 0 {
|
||||
t.Fatalf("%d test steps, unit %+v, want none run and the kept verdict taken", n, v.Levels[buildstep.LevelUnit])
|
||||
}
|
||||
// A sibling's commit moves: a new digest, so the package runs again and is kept under it.
|
||||
b.ex.Trunks = trunksOf(t, map[string]string{repoSDK: trunkS2, "novox/mesh-catalog": trunkA})
|
||||
@@ -917,15 +924,15 @@ func TestNoStepOfACheckCarriesPublish(t *testing.T) { // ADR 0500 §4, 1309 (the
|
||||
func TestAnAskWhoseStepNeverEndsEndsTheCheckLoudly(t *testing.T) {
|
||||
b := newBench(t)
|
||||
// A node took the ask and its step stays running: the bound runs from its start (review round 2 item 1).
|
||||
b.asks.Answer = func(a delivery.StepAsk) delivery.Step {
|
||||
return delivery.Step{ID: a.ID, Tool: "check ask", Node: "shanks", State: delivery.StepRunning, Started: time.Now()}
|
||||
b.asks.Answer = func(a buildstep.StepAsk) buildstep.Step {
|
||||
return buildstep.Step{ID: a.ID, Tool: "check ask", Node: "shanks", State: buildstep.StepRunning, Started: time.Now()}
|
||||
}
|
||||
b.ex.Asks = buildrun.Asker{Publisher: b.asks, Waiter: b.asks, Bound: 20 * time.Millisecond}
|
||||
out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
if f := out.Verdict.Failed; out.Verdict.State != delivery.VerdictFail || !strings.Contains(f.Words, "on shanks did not end within") {
|
||||
if f := out.Verdict.Failed; out.Verdict.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(f.Words, "on shanks did not end within") {
|
||||
t.Fatalf("verdict = %+v", out.Verdict)
|
||||
}
|
||||
if n := len(b.asks.Asks()); n != 1 || b.asks.Asks()[0].Kind != delivery.AskCheck || b.asks.Asks()[0].Check == nil {
|
||||
if n := len(b.asks.Asks()); n != 1 || b.asks.Asks()[0].Kind != buildstep.AskCheck || b.asks.Asks()[0].Check == nil {
|
||||
t.Fatalf("asks = %+v, want one check ask", b.asks.Asks())
|
||||
}
|
||||
}
|
||||
@@ -936,16 +943,20 @@ func TestACheckIsOneAskAndAReJudgeGoesOnTheRecheckStream(t *testing.T) {
|
||||
b := newBench(t)
|
||||
b.judge(t, caseOf(t, pullOf(repoA, 7, head1)))
|
||||
asks := b.asks.Asks()
|
||||
if len(asks) != 1 || asks[0].Kind != delivery.AskCheck || asks[0].Language != delivery.AskGo || asks[0].Asker != delivery.CheckerModule {
|
||||
if len(asks) != 1 || asks[0].Kind != buildstep.AskCheck || asks[0].Language != buildstep.AskGo || asks[0].Asker != CheckerModule {
|
||||
t.Fatalf("asks = %+v, want one Go check ask", asks)
|
||||
}
|
||||
if j := asks[0].Check; j == nil || !j.Kept || strings.Join(j.Levels, ",") != "unit,integration" || j.Fetch["repository"] != repoA {
|
||||
if j := asks[0].Check; j == nil || !j.Kept || strings.Join(j.Levels, ",") != "unit,integration" || j.Fetch.Repository != repoA {
|
||||
t.Fatalf("the job = %+v", asks[0].Check)
|
||||
}
|
||||
// B2g: the job carries the toolchain the checker agreed (ADR 0584), with its language, so a node on another declines it.
|
||||
if tc := asks[0].Check.Toolchain; tc == nil || *tc != (buildstep.ToolchainUsed{Language: "go", Version: "go1.27.1", Recipe: "7"}) {
|
||||
t.Errorf("the job's toolchain = %+v, want the agreed go toolchain", tc)
|
||||
}
|
||||
cs := caseOf(t, pullOf(repoA, 7, head1))
|
||||
cs.Kind = KindTrunk
|
||||
b.judge(t, cs)
|
||||
if asks := b.asks.Asks(); len(asks) != 2 || asks[1].Kind != delivery.AskRecheck {
|
||||
if asks := b.asks.Asks(); len(asks) != 2 || asks[1].Kind != buildstep.AskRecheck {
|
||||
t.Fatalf("asks = %+v, want the re-judge on recheck", asks)
|
||||
}
|
||||
}
|
||||
@@ -971,7 +982,7 @@ func TestACheckAskWaitingInItsStreamIsNotBounded(t *testing.T) {
|
||||
default:
|
||||
}
|
||||
b.asks.End(answer(b.asks.Asks()[0])) // a node took it and ran it
|
||||
if out := <-done; out.Verdict.State != delivery.VerdictPass || len(b.asks.Asks()) != 1 {
|
||||
if out := <-done; out.Verdict.State != meshdeliverycheckerclient.VerdictPass || len(b.asks.Asks()) != 1 {
|
||||
t.Fatalf("verdict %+v after %d asks, want a pass from the one ask", out.Verdict, len(b.asks.Asks()))
|
||||
}
|
||||
}
|
||||
@@ -981,13 +992,13 @@ func TestACheckAskWaitingInItsStreamIsNotBounded(t *testing.T) {
|
||||
func TestASecondCheckOfTheSameHeadsKeepsWhatTheFirstPassed(t *testing.T) {
|
||||
b := newBench(t)
|
||||
first := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
u := first.Levels[delivery.LevelUnit]
|
||||
if first.State != delivery.VerdictPass || u.Run == 0 || u.Kept != 0 {
|
||||
u := first.Levels[buildstep.LevelUnit]
|
||||
if first.State != meshdeliverycheckerclient.VerdictPass || u.Run == 0 || u.Kept != 0 {
|
||||
t.Fatalf("the first check = %+v", first)
|
||||
}
|
||||
b.steps.calls = nil
|
||||
second := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if s := second.Levels[delivery.LevelUnit]; second.State != delivery.VerdictPass || s.Kept != u.Run || s.Run != 0 {
|
||||
if s := second.Levels[buildstep.LevelUnit]; second.State != meshdeliverycheckerclient.VerdictPass || s.Kept != u.Run || s.Run != 0 {
|
||||
t.Fatalf("the second check's unit level = %+v, want %d kept and none run", s, u.Run)
|
||||
}
|
||||
if n := len(b.steps.tool("test")); n != 0 {
|
||||
@@ -1171,9 +1182,9 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
if row.seats != "" {
|
||||
b.ex.SeatList = fakeSeatList{read: row.seats == "read"}
|
||||
}
|
||||
var judged []delivery.ComposeJob
|
||||
var judged []buildstep.ComposeJob
|
||||
answer := b.asks.Answer
|
||||
b.asks.Answer = func(a delivery.StepAsk) delivery.Step {
|
||||
b.asks.Answer = func(a buildstep.StepAsk) buildstep.Step {
|
||||
if a.Compose == nil {
|
||||
return answer(a)
|
||||
}
|
||||
@@ -1188,8 +1199,8 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
verdict, _ := json.Marshal(composer.Verdict{V: composer.Version, SDK: "exe:1", Cases: []composer.CaseVerdict{{Name: JudgeCase,
|
||||
Manifests: map[string]composer.ManifestRead{"web": read, "mail": {Accepted: row.untouched == "", Refused: row.untouched}}},
|
||||
{Name: JudgeTrunk, Manifests: map[string]composer.ManifestRead{"web": {Accepted: true}, "mail": {Accepted: row.trunkMail == "", Refused: row.trunkMail}}}}})
|
||||
result, _ := json.Marshal(delivery.ComposeAnswer{Key: "k", Verdict: verdict})
|
||||
return delivery.Step{ID: a.ID, State: delivery.StepPassed, Result: result}
|
||||
result, _ := json.Marshal(buildstep.ComposeAnswer{Key: "k", Verdict: verdict})
|
||||
return buildstep.Step{ID: a.ID, State: buildstep.StepPassed, Result: result}
|
||||
}
|
||||
p := pullOf(repoA, 7, head1)
|
||||
if row.ownJudge {
|
||||
@@ -1197,15 +1208,15 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
b.fetch.Changed = []string{JudgeDir + "/judge/judge.go"}
|
||||
}
|
||||
p.Modules = []string{"web"}
|
||||
p.Fps = map[string]delivery.CandidateModule{"web": {FP2: "src2:" + strings.Repeat("f", 64), Manifest: web}}
|
||||
p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"web": {Fp2: "src2:" + strings.Repeat("f", 64), Manifest: web}}
|
||||
cs := caseOf(t, p)
|
||||
v := b.judge(t, cs).Verdict
|
||||
if line := Line(v, cs, cs.Pulls[0], nil, nil, nil); row.seriesWords != "" && !strings.Contains(line, row.seriesWords) || row.seriesWords == "" && strings.Contains(line, "series") {
|
||||
t.Errorf("%s: the line %q, want it to name %q", row.name, line, row.seriesWords)
|
||||
}
|
||||
// The join (A2j finding 2): the verdict names each series change as {module, artifact, from, to}.
|
||||
if want := []delivery.SeriesChange(nil); row.series == "0.2" {
|
||||
if want = []delivery.SeriesChange{{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}}; !reflect.DeepEqual(v.Series, want) {
|
||||
if want := []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem(nil); row.series == "0.2" {
|
||||
if want = []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}}; !reflect.DeepEqual(v.Series, want) {
|
||||
t.Errorf("%s: the verdict's series %+v, want %+v", row.name, v.Series, want)
|
||||
}
|
||||
} else if len(v.Series) != 0 {
|
||||
@@ -1217,18 +1228,18 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
t.Errorf("%s: the case says %v, want the new indexer's manifest said accepted by the trunk's manifest validator", row.name, cs.notes)
|
||||
}
|
||||
if row.seats == "unread" {
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, row.fails) || len(judged) != 0 {
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(v.Failed.Words, row.fails) || len(judged) != 0 {
|
||||
t.Errorf("%s: %+v, judged %d, want a fail saying %q before any judge", row.name, v.Failed, len(judged), row.fails)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if row.ownJudge && (len(judged) != 2 || judged[0].Fetch["commit"] != head1 || judged[1].Fetch["commit"] != trunk) {
|
||||
if row.ownJudge && (len(judged) != 2 || judged[0].Fetch.Commit != head1 || judged[1].Fetch.Commit != trunk) {
|
||||
t.Fatalf("%s: judged %+v, want the head's judge and then the trunk's", row.name, judged)
|
||||
}
|
||||
if row.ownJudge {
|
||||
judged = judged[1:]
|
||||
}
|
||||
if len(judged) != 1 || judged[0].Fetch["repository"] != DeliveryRepository || judged[0].Fetch["commit"] != trunk ||
|
||||
if len(judged) != 1 || judged[0].Fetch.Repository != DeliveryRepository || judged[0].Fetch.Commit != trunk ||
|
||||
judged[0].Package != JudgePackage || judged[0].Dir != JudgeDir {
|
||||
t.Fatalf("%s: judged %+v (verdict %+v)", row.name, judged, v)
|
||||
}
|
||||
@@ -1250,9 +1261,9 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
req.Packages, req.Input, row.known)
|
||||
}
|
||||
switch {
|
||||
case row.fails == "" && v.State != delivery.VerdictPass:
|
||||
case row.fails == "" && v.State != meshdeliverycheckerclient.VerdictPass:
|
||||
t.Errorf("%s: %+v", row.name, v)
|
||||
case row.fails != "" && (v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, row.fails)):
|
||||
case row.fails != "" && (v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(v.Failed.Words, row.fails)):
|
||||
t.Errorf("%s: %+v, want a fail saying %q", row.name, v.Failed, row.fails)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
)
|
||||
|
||||
// The failure the build module reported for group r4g on 2026-10-23: the test logged a line, then failed with a multi-line
|
||||
@@ -53,7 +53,7 @@ func TestWordsLongerThanTheBoundKeepTheirLastLines(t *testing.T) { // 1901/AC1
|
||||
if !strings.HasSuffix(v.Failed.Words, "the cause is here") || len(v.Failed.Words) > MaxWords+200 {
|
||||
t.Errorf("%d bytes, ends %q", len(v.Failed.Words), v.Failed.Words[len(v.Failed.Words)-30:])
|
||||
}
|
||||
_ = delivery.VerdictFail
|
||||
_ = meshdeliverycheckerclient.VerdictFail
|
||||
}
|
||||
|
||||
// An older build module sends no "cause": its answer is still accepted and the verdict's words are its output, as before
|
||||
@@ -85,7 +85,7 @@ func TestAnOlderBuildModulesFailureIsAcceptedWithItsWholeOutput(t *testing.T) {
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
v := levelFailure(&target{module: c.module}, "unit", Tested{By: c.by, Failed: []Failure{{Package: "modules/gitea", Test: "fails", Cause: c.cause, Output: c.output}}})
|
||||
if v.State != delivery.VerdictFail {
|
||||
if v.State != meshdeliverycheckerclient.VerdictFail {
|
||||
t.Errorf("1901/AC8: the verdict is %q, want it failed", v.State)
|
||||
}
|
||||
line := firstFailed(*v)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
)
|
||||
|
||||
// What the gate reads of the checker (R4e, lead's interface with dev-r4e-gate): the verdict's heads are the gate's hash over
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
func owedState(t testing.TB, r *rig, repo string, number int, head string) string {
|
||||
t.Helper()
|
||||
key, err := delivery.CheckerOwedKey(repo, number, head)
|
||||
key, err := CheckerOwedKey(repo, number, head)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func owedState(t testing.TB, r *rig, repo string, number int, head string) strin
|
||||
if !found {
|
||||
return ""
|
||||
}
|
||||
o, err := delivery.DecodeCheckerOwed(raw)
|
||||
o, err := DecodeCheckerOwed(raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func TestTheVerdictsHeadsAreTheGatesHashOverLowercaseHeads(t *testing.T) { // ga
|
||||
cand := candidateOf(t, "feat/x", pullOf("Novox/Mesh-Delivery", 7, upper))
|
||||
r.ch.Candidate(ctx, "feat%2Fx", cand)
|
||||
r.ch.Wait()
|
||||
want, _ := delivery.HeadsHash([]delivery.Head{{Repository: repoA, Number: 7, Head: head1}})
|
||||
want, _ := meshdeliveryclient.HeadsHash([]meshdeliveryclient.Head{{Repository: repoA, Number: 7, Head: head1}})
|
||||
if v := r.verdict(t, keyOf(t, "feat/x", cand)); v.Heads != want {
|
||||
t.Errorf("heads = %q, want the gate's hash of the lone pull request, a group of one, %q", v.Heads, want)
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func TestAGroupsVerdictHeadsCoverEveryMember(t *testing.T) { // gate (b): hq's m
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2), pullOf(hqRepo, 3, head2))
|
||||
r.ch.Candidate(ctx, "feat%2Fx", cand)
|
||||
r.ch.Wait()
|
||||
want, _ := delivery.HeadsHash([]delivery.Head{{Repository: repoA, Number: 7, Head: head1}, {Repository: repoSDK, Number: 12, Head: head2},
|
||||
want, _ := meshdeliveryclient.HeadsHash([]meshdeliveryclient.Head{{Repository: repoA, Number: 7, Head: head1}, {Repository: repoSDK, Number: 12, Head: head2},
|
||||
{Repository: hqRepo, Number: 3, Head: head2}})
|
||||
if v := r.verdict(t, keyOf(t, "feat/x", cand)); v.Heads != want {
|
||||
t.Errorf("heads = %q, want %q", v.Heads, want)
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||
)
|
||||
@@ -29,7 +29,7 @@ type ManifestReader interface {
|
||||
|
||||
// refuseCycle fails the check for a cycle of the change's module graph, naming it; nil when the case touches no manifest,
|
||||
// when nothing reads manifests, or when there is none. A manifest that cannot be read fails the check, said, never passes it.
|
||||
func (st *state) refuseCycle(ctx context.Context) *delivery.Verdict {
|
||||
func (st *state) refuseCycle(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
if st.j.Manifests == nil || st.j.Read == nil || st.c.Try != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// goModule is the build module the Go repositories are judged by.
|
||||
@@ -48,20 +50,20 @@ const (
|
||||
)
|
||||
|
||||
// verdictPtr is a verdict of failure a step of the check ends with.
|
||||
type verdictPtr = *delivery.Verdict
|
||||
type verdictPtr = *meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
|
||||
func failed(machine, words string) verdictPtr { return fail(machine, words) }
|
||||
|
||||
// Asks puts a check's ask on its build seat's stream and waits for its step (buildrun.Asker; ADR 0525 §1, §6): the
|
||||
// checker chooses no node, starts no step and holds none; whichever build node pulls the ask runs the check.
|
||||
type Asks interface {
|
||||
Put(ctx context.Context, a delivery.StepAsk) error
|
||||
Wait(ctx context.Context, a delivery.StepAsk) (delivery.Step, error)
|
||||
Put(ctx context.Context, a buildstep.StepAsk) error
|
||||
Wait(ctx context.Context, a buildstep.StepAsk) (buildstep.Step, error)
|
||||
}
|
||||
|
||||
// ToolchainEntries are the nodes' toolchain entries of a language (buildrun.ToolchainSet).
|
||||
type ToolchainEntries interface {
|
||||
Entries(language string) []delivery.NodeToolchain
|
||||
Entries(language string) []buildstep.NodeToolchain
|
||||
}
|
||||
|
||||
// BuildModules says which build modules judge a repository: the Go one always, the Node one when the repository holds a
|
||||
@@ -71,7 +73,7 @@ type BuildModules interface {
|
||||
}
|
||||
|
||||
// askLanguage is the build seat language of each build module the checker asks.
|
||||
var askLanguage = map[string]string{buildrun.GoBuildModule: delivery.AskGo, buildrun.NodeBuildModule: delivery.AskNode}
|
||||
var askLanguage = map[string]string{buildrun.GoBuildModule: buildstep.AskGo, buildrun.NodeBuildModule: buildstep.AskNode}
|
||||
|
||||
// Examiner judges a case through the build seats (ADR 0484 clauses 3, 4, 10, 13; ADR 0525 §1, §3): for each pull request
|
||||
// and build module it puts one check ask whose job fetches the head merged onto its trunk head, then runs the unit level
|
||||
@@ -97,9 +99,9 @@ type Examiner struct {
|
||||
// indexer's modules (novox/hq ADR 0573 §2): a check builds against those, or against a member's head for a package
|
||||
// its group publishes (§4), whatever go.mod names. Nil names none, and go.mod's versions are built.
|
||||
Published interface {
|
||||
LatestPackages() delivery.PackageVersions
|
||||
LatestPackages() buildstep.PackageVersions
|
||||
}
|
||||
// KnownPackages names the packages the indexer's packages state knows (its series keys, delivery.SeriesPackage), by the
|
||||
// KnownPackages names the packages the indexer's packages state knows (its series keys, meshcatalog.PackagesByPackageSeriesValue), by the
|
||||
// name in its own ecosystem, each with the `<module>/<artifact>` it is built from: the names a manifest's `packages` is
|
||||
// judged against (manifests_judge.go, design 32). Nil, or PackageNames answering nil while the state is not read yet,
|
||||
// hands the judge no names, and it judges none; an empty map is a state read and empty, against which every name fails.
|
||||
@@ -126,7 +128,7 @@ type Examiner struct {
|
||||
ModuleDir(module string) (string, bool)
|
||||
}
|
||||
// Packages names, of a repository, the paths (repository-relative) its TypeScript package artifacts are built from, from
|
||||
// the indexer's modules (delivery.Module.ArtifactPaths, lead decision L8); nil serves no group package.
|
||||
// the indexer's modules (meshcatalog.ModulesByModuleValue.ArtifactPaths, lead decision L8); nil serves no group package.
|
||||
Packages interface {
|
||||
PackagePaths(repository string) []string
|
||||
}
|
||||
@@ -169,7 +171,7 @@ type Examiner struct {
|
||||
|
||||
// CompositionReader makes a check's composition verdict; an error is only a case that cannot be composed at all.
|
||||
type CompositionReader interface {
|
||||
Compose(ctx context.Context, c *Case) (delivery.CompositionVerdict, error)
|
||||
Compose(ctx context.Context, c *Case) (meshwants.CompositionsByGroupHeadsValue, error)
|
||||
}
|
||||
|
||||
func (j *Examiner) now() time.Time {
|
||||
@@ -185,6 +187,15 @@ func (j *Examiner) logf(format string, args ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
// fetchBeside are the siblings as the fetch is asked to place them.
|
||||
func fetchBeside(refs []BesideRef) []buildstep.FetchBeside {
|
||||
out := make([]buildstep.FetchBeside, 0, len(refs))
|
||||
for _, r := range refs {
|
||||
out = append(out, buildstep.FetchBeside{Repository: r.Repository, Commit: r.Commit})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// besideLines are the siblings as the digest step echoes them: `<owner/repo> <commit>`, sorted.
|
||||
func besideLines(refs []BesideRef) []string {
|
||||
out := make([]string, 0, len(refs))
|
||||
@@ -203,10 +214,10 @@ type target struct {
|
||||
node string
|
||||
toolchain string
|
||||
// agreed is the toolchain the ask names, so a node off it declines the check (ADR 0584); nil when none is agreed.
|
||||
agreed *delivery.ToolchainUsed
|
||||
agreed *buildstep.ToolchainUsed
|
||||
fetched Fetched
|
||||
// fetchArgs are the arguments of the fetch its check ask runs.
|
||||
fetchArgs map[string]any
|
||||
fetchArgs buildstep.Fetch
|
||||
// priority are the Go packages (import paths) or the Node module directories whose test files the merge changes.
|
||||
changed []string
|
||||
// beside are the lines `<owner/repo> <commit>` of the siblings the workspace was given, sorted: its tests may read them,
|
||||
@@ -279,7 +290,7 @@ type state struct {
|
||||
// Judge runs the case (it is the Judge of a Checker). A failure of the change, of a merge or of a step is a verdict; an error is only a case that cannot
|
||||
// be judged at all.
|
||||
func (j *Examiner) Judge(ctx context.Context, c *Case) (Outcome, error) {
|
||||
st := &state{j: j, c: c, levels: map[string]*acc{delivery.LevelUnit: {}, delivery.LevelIntegration: {}}, nodes: map[string]bool{},
|
||||
st := &state{j: j, c: c, levels: map[string]*acc{buildstep.LevelUnit: {}, buildstep.LevelIntegration: {}}, nodes: map[string]bool{},
|
||||
beside: map[string]string{}}
|
||||
// Every member and level runs to its end, and every manifest the change touches is judged by the indexer's judge (ADR
|
||||
// 0578 §2), also after a failure: the verdict names every failure, not only the first (the operator, 2026-11-02).
|
||||
@@ -288,12 +299,12 @@ func (j *Examiner) Judge(ctx context.Context, c *Case) (Outcome, error) {
|
||||
if v := settled(joinFailures(tests, st.judgeManifests(ctx), st.refuseCycle(ctx), st.judgeRenames(ctx), st.refuseUnparsed(ctx))); v != nil {
|
||||
return st.finish(ctx, *v), nil
|
||||
}
|
||||
return st.finish(ctx, delivery.Verdict{State: delivery.VerdictPass}), nil
|
||||
return st.finish(ctx, meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictPass}), nil
|
||||
}
|
||||
|
||||
// finish is the outcome of the verdict with the state of the builds the candidate's modules carry and the assigner's
|
||||
// composition of it, read also when the tests failed, so a failed composition is named beside them.
|
||||
func (st *state) finish(ctx context.Context, v delivery.Verdict) Outcome {
|
||||
func (st *state) finish(ctx context.Context, v meshdeliverychecker.VerdictsByGroupHeadsValue) Outcome {
|
||||
out := st.outcome(v)
|
||||
st.composed(ctx, &out.Verdict)
|
||||
out.Builds = st.j.buildNotes(ctx, st.c)
|
||||
@@ -303,7 +314,7 @@ func (st *state) finish(ctx context.Context, v delivery.Verdict) Outcome {
|
||||
// composed makes the check's composition from the engines' answers into its verdict (ADR 0593 §7, §9): a failed
|
||||
// composition fails the check, naming the machine it failed at; a node that did not answer is a warning. A try is not
|
||||
// composed (it has no candidate); a composition that cannot be made leaves it not run, said.
|
||||
func (st *state) composed(ctx context.Context, v *delivery.Verdict) {
|
||||
func (st *state) composed(ctx context.Context, v *meshdeliverychecker.VerdictsByGroupHeadsValue) {
|
||||
if st.c.Composition != nil && st.c.Try == nil {
|
||||
// A carry whose candidate's fingerprints did not change keeps the composition it passed (ADR 0548 §5).
|
||||
kept := *st.c.Composition
|
||||
@@ -316,7 +327,7 @@ func (st *state) composed(ctx context.Context, v *delivery.Verdict) {
|
||||
}
|
||||
if len(st.unpreviewed) > 0 {
|
||||
// An engine would only say it cannot read a manifest the indexer will not have: the check does not ask it.
|
||||
v.Composition = &delivery.Composition{State: delivery.CompositionNotRun, Words: "no preview of a merge of " +
|
||||
v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionNotRun, Words: "no preview of a merge of " +
|
||||
strings.Join(st.unpreviewed, ", ") + " (its head does not merge or its fetch did not answer), so the change cannot be composed"}
|
||||
return
|
||||
}
|
||||
@@ -325,41 +336,41 @@ func (st *state) composed(ctx context.Context, v *delivery.Verdict) {
|
||||
if err != nil {
|
||||
// Said in the verdict, never left as a composition not yet run (review finding 4).
|
||||
st.j.logf("%s: the composition could not be made, so it is said as not run: %v", st.c.Key, err)
|
||||
v.Composition = &delivery.Composition{State: delivery.CompositionNotRun, Words: "not made: " + err.Error()}
|
||||
v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionNotRun, Words: "not made: " + err.Error()}
|
||||
return
|
||||
}
|
||||
comp.Warnings = append(comp.Warnings, words...) // ADR 0603 §10
|
||||
withComposition(v, comp)
|
||||
if v.Composition != nil && v.Composition.State != delivery.CompositionNotRun {
|
||||
v.Composition.FPSet = st.c.FPSet // a carry keeps it while the fingerprints stay the same (ADR 0548 §5)
|
||||
if v.Composition != nil && v.Composition.State != meshdeliverycheckerclient.CompositionNotRun {
|
||||
v.Composition.Fpset = st.c.FPSet // a carry keeps it while the fingerprints stay the same (ADR 0548 §5)
|
||||
}
|
||||
}
|
||||
|
||||
// outcome fills the verdict's levels, times, machines and composition from what ran.
|
||||
func (st *state) outcome(v delivery.Verdict) Outcome {
|
||||
func (st *state) outcome(v meshdeliverychecker.VerdictsByGroupHeadsValue) Outcome {
|
||||
v.Series = st.c.allSeries() // the series changes, as structures (A2j finding 2)
|
||||
if v.State == delivery.VerdictPass || len(v.Levels) == 0 {
|
||||
v.Levels = map[string]delivery.LevelResult{}
|
||||
if v.State == meshdeliverycheckerclient.VerdictPass || len(v.Levels) == 0 {
|
||||
v.Levels = map[string]meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{}
|
||||
for level, a := range st.levels {
|
||||
v.Levels[level] = delivery.LevelResult{Run: a.run, Kept: a.kept, Skipped: a.skipped}
|
||||
v.Levels[level] = meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{Run: int64(a.run), Kept: int64(a.kept), Skipped: int64(a.skipped)}
|
||||
}
|
||||
}
|
||||
ms := func(s float64) int64 { return int64(s * 1000) }
|
||||
u, i := st.levels[delivery.LevelUnit], st.levels[delivery.LevelIntegration]
|
||||
v.Took = delivery.Took{Clone: st.clone.Milliseconds(), Unit: ms(u.elapsed), Lint: ms(u.lint), IntegrationSetup: ms(i.setup), IntegrationRun: ms(i.elapsed)}
|
||||
u, i := st.levels[buildstep.LevelUnit], st.levels[buildstep.LevelIntegration]
|
||||
v.Took = &meshdeliverychecker.VerdictsByGroupHeadsValueTook{Clone: st.clone.Milliseconds(), Unit: ms(u.elapsed), Lint: ms(u.lint), IntegrationSetup: ms(i.setup), IntegrationRun: ms(i.elapsed)}
|
||||
for node := range st.nodes {
|
||||
v.Machines = append(v.Machines, node)
|
||||
}
|
||||
sort.Strings(v.Machines)
|
||||
for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} {
|
||||
for _, level := range []string{buildstep.LevelUnit, buildstep.LevelIntegration} {
|
||||
if st.levels[level].over {
|
||||
v.OverBound = append(v.OverBound, level)
|
||||
}
|
||||
}
|
||||
// The composition is made from the engines' answers (ADR 0593 §7): until it is, a verdict says it was not run (ADR 0484 clause 6).
|
||||
v.Composition = &delivery.Composition{State: delivery.CompositionNotRun}
|
||||
v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionNotRun}
|
||||
var slow []string
|
||||
for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} {
|
||||
for _, level := range []string{buildstep.LevelUnit, buildstep.LevelIntegration} {
|
||||
a := st.levels[level]
|
||||
if !a.over {
|
||||
continue
|
||||
@@ -402,7 +413,7 @@ func (st *state) stepOutOfStep(p Pull, files []string) {
|
||||
}
|
||||
|
||||
// fail is a verdict of failure at a machine.
|
||||
func fail(machine, words string) *delivery.Verdict {
|
||||
func fail(machine, words string) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
v := failure(machine, words)
|
||||
return &v
|
||||
}
|
||||
@@ -412,14 +423,14 @@ func (st *state) levelsOf() []string {
|
||||
if st.c.Try != nil && st.c.Try.Level != "" {
|
||||
return []string{st.c.Try.Level} // a try of one level runs that level alone
|
||||
}
|
||||
return []string{delivery.LevelUnit, delivery.LevelIntegration}
|
||||
return []string{buildstep.LevelUnit, buildstep.LevelIntegration}
|
||||
}
|
||||
|
||||
// judgeAll puts one check ask per pull request and build module the repository needs, the mesh-sdk member first, and
|
||||
// judges each answer. A head that does not merge, a pin that is not allowed or a level that fails is a failure of that
|
||||
// member; every other member is still judged, and the verdict names every failure (the operator, 2026-11-02), unjoined
|
||||
// to its first lines (settled does that). Only a check whose context ended stops.
|
||||
func (st *state) judgeAll(ctx context.Context) *delivery.Verdict {
|
||||
func (st *state) judgeAll(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
// The mesh-sdk member first: whether its merge changes the TypeScript package's paths decides whether the other members'
|
||||
// Node fetches take the group's package (ADR 0500 §5).
|
||||
pulls := slices.Clone(st.c.Pulls)
|
||||
@@ -437,12 +448,12 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict {
|
||||
if v := st.tagged(ctx); v != nil {
|
||||
return v
|
||||
}
|
||||
var groupPackage map[string]any
|
||||
var failures []*delivery.Verdict
|
||||
var groupPackage *buildstep.FetchPackage
|
||||
var failures []*meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
for _, p := range pulls {
|
||||
if ctx.Err() != nil {
|
||||
if len(failures) == 0 { // an ask that ended with the context said it already
|
||||
failures = append(failures, fail(delivery.CheckerModule, ctx.Err().Error()))
|
||||
failures = append(failures, fail(CheckerModule, ctx.Err().Error()))
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -450,16 +461,16 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict {
|
||||
modules := st.j.Modules.BuildModules(p.Repository)
|
||||
for _, bm := range modules {
|
||||
t := &target{pull: p, module: bm}
|
||||
if tc, none := delivery.AgreedToolchain(st.j.Toolchains.Entries(buildrun.LanguageOf[bm])); none == "" {
|
||||
if tc, none := buildstep.AgreedToolchain(st.j.Toolchains.Entries(buildrun.LanguageOf[bm])); none == "" {
|
||||
t.toolchain = tc.Version
|
||||
t.agreed = &delivery.ToolchainUsed{Version: tc.Version, Recipe: tc.Recipe}
|
||||
t.agreed = &buildstep.ToolchainUsed{Language: buildrun.LanguageOf[bm], Version: tc.Version, Recipe: tc.Recipe}
|
||||
}
|
||||
args := map[string]any{"repository": p.Repository, "commit": p.Head, "ref": refOf(p.Number), "base": p.Trunk}
|
||||
args := buildstep.Fetch{Repository: p.Repository, Commit: p.Head, Ref: refOf(p.Number), Base: p.Trunk}
|
||||
if bm == buildrun.NodeBuildModule {
|
||||
if np, ok := st.tags.nodePackageFor(p.Repository); ok {
|
||||
args["package"] = map[string]any{"name": np.Name, "version": np.Version} // the group's tagged version (ADR 0607 §3)
|
||||
args.Package = &buildstep.FetchPackage{Name: np.Name, Version: np.Version} // the group's tagged version (ADR 0607 §3)
|
||||
} else if groupPackage != nil && p.Repository != SDKRepository {
|
||||
args["package"] = groupPackage
|
||||
args.Package = groupPackage
|
||||
}
|
||||
}
|
||||
if bm == buildrun.GoBuildModule {
|
||||
@@ -469,7 +480,7 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict {
|
||||
continue
|
||||
}
|
||||
if len(beside) > 0 {
|
||||
args["beside"] = beside
|
||||
args.Beside = fetchBeside(beside)
|
||||
t.beside = besideLines(beside)
|
||||
besideAtTrunk(st.c, beside, st.beside)
|
||||
}
|
||||
@@ -497,7 +508,7 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict {
|
||||
// TypeScript version is heard, the form the running checker hands while the new build modules are delivered. Its
|
||||
// removal is a subtask of the milestone after this one's delivery (issue 1966).
|
||||
if p.Repository == SDKRepository && len(st.c.Pulls) > 1 && len(st.tags.node) == 0 && st.j.changesPackage(p, merged) {
|
||||
groupPackage = map[string]any{"repository": p.Repository, "commit": p.Head, "ref": refOf(p.Number)}
|
||||
groupPackage = &buildstep.FetchPackage{Repository: p.Repository, Commit: p.Head, Ref: refOf(p.Number)}
|
||||
}
|
||||
}
|
||||
return joinFailures(failures...)
|
||||
@@ -511,17 +522,17 @@ const DefaultDeclineBound = 30 * time.Minute
|
||||
// each level's digest, kept verdicts and tests. When the answer leaves packages unmet,
|
||||
// their tests needing what that node lacks (issue 890), the passes are kept and the levels from the first such one are
|
||||
// asked again of a node that has it (placed).
|
||||
func (st *state) ask(ctx context.Context, t *target, merged *[]Fetched) *delivery.Verdict {
|
||||
func (st *state) ask(ctx context.Context, t *target, merged *[]Fetched) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
return st.askNeeding(ctx, t, merged, st.levelsOf(), nil, nil)
|
||||
}
|
||||
|
||||
// askNeeding is ask with the levels to run, what the node must have and the packages it tests: none for the first ask
|
||||
// of a target; for the placed one the unmet packages' needs and those packages alone, so no package the first ask judged
|
||||
// is run or counted again (ADR 0552 §4).
|
||||
func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, levels, needs, packages []string) *delivery.Verdict {
|
||||
func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, levels, needs, packages []string) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
p := t.pull
|
||||
placed := len(needs) > 0
|
||||
job := &delivery.CheckJob{Fetch: t.fetchArgs, Levels: levels, Kept: true, Packages: packages, Toolchain: t.agreed}
|
||||
job := &buildstep.CheckJob{Fetch: t.fetchArgs, Levels: levels, Kept: true, Packages: packages, Toolchain: t.agreed}
|
||||
if st.j.Nodes != nil {
|
||||
names, err := st.j.Nodes.Names(ctx)
|
||||
if err == nil && len(names) == 0 {
|
||||
@@ -538,16 +549,16 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
if st.c.Try != nil && st.c.Try.Run != "" {
|
||||
job.Run, job.Kept = st.c.Try.Run, false // a try of some tests runs them alone: a partial run proves no package
|
||||
}
|
||||
kind := delivery.AskCheck
|
||||
kind := buildstep.AskCheck
|
||||
if st.c.Kind == KindTrunk {
|
||||
kind = delivery.AskRecheck // a re-judge after a trunk move goes after every new head's check (ADR 0536 §4)
|
||||
kind = buildstep.AskRecheck // a re-judge after a trunk move goes after every new head's check (ADR 0536 §4)
|
||||
}
|
||||
a := delivery.StepAsk{ID: buildstep.NewID(), Kind: kind, Language: askLanguage[t.module], Asker: delivery.CheckerModule,
|
||||
a := buildstep.StepAsk{ID: buildstep.NewID(), Kind: kind, Language: askLanguage[t.module], Asker: CheckerModule,
|
||||
At: st.j.now().UTC(), Check: job, Needs: needs}
|
||||
if a.Language == delivery.AskGo {
|
||||
if a.Language == buildstep.AskGo {
|
||||
a.Packages = st.packagesOf(p.Repository)
|
||||
}
|
||||
var step delivery.Step
|
||||
var step buildstep.Step
|
||||
var firstDecline time.Time
|
||||
for {
|
||||
what := fmt.Sprintf("the %s ask %s of %s#%d", kind, a.ID, p.Repository, p.Number)
|
||||
@@ -563,7 +574,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
if watch := st.c.Watch; watch != nil {
|
||||
id := a.ID
|
||||
watch(id, nil) // put: a try names it at once, not only when it ends (issue 1890)
|
||||
waiting = buildrun.WithStepSeen(ctx, func(s delivery.Step) { watch(id, &s) })
|
||||
waiting = buildrun.WithStepSeen(ctx, func(s buildstep.Step) { watch(id, &s) })
|
||||
}
|
||||
var err error
|
||||
step, err = st.j.Asks.Wait(waiting, a)
|
||||
@@ -573,7 +584,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
}
|
||||
return fail(t.module, fmt.Sprintf("the check of %s#%d: %v", p.Repository, p.Number, err))
|
||||
}
|
||||
d, declined := delivery.DeclinedOf(step)
|
||||
d, declined := buildstep.DeclinedOf(step)
|
||||
if !declined {
|
||||
break
|
||||
}
|
||||
@@ -598,7 +609,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
st.j.logf("%s#%d: the ask %s was declined on %s, which lacks %s; asked again", p.Repository, p.Number, a.ID, step.Node, strings.Join(d.Lacks, ", "))
|
||||
a.ID, a.At = buildstep.NewID(), now.UTC()
|
||||
}
|
||||
var failures []*delivery.Verdict
|
||||
var failures []*meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
// The fetch and the digests are what of the ask's time its levels' running did not take (Took.Clone).
|
||||
took := time.Duration(0)
|
||||
if !step.Started.IsZero() && step.Ended.After(step.Started) {
|
||||
@@ -607,15 +618,15 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
t.node = step.Node
|
||||
st.nodes[step.Node] = true
|
||||
st.c.Progress.Set(fmt.Sprintf("judge the check of %s#%d", p.Repository, p.Number), step.Node)
|
||||
if step.State != delivery.StepPassed {
|
||||
if step.State != buildstep.StepPassed {
|
||||
return fail(step.Node, fmt.Sprintf("the check of %s#%d %s: %s", p.Repository, p.Number, step.State, step.Reason))
|
||||
}
|
||||
var answer delivery.CheckAnswer
|
||||
var answer buildstep.CheckAnswer
|
||||
if err := json.Unmarshal(step.Result, &answer); err != nil {
|
||||
return fail(step.Node, fmt.Sprintf("the check of %s#%d answered what is not a check: %s", p.Repository, p.Number, step.Result))
|
||||
}
|
||||
if err := json.Unmarshal(answer.Fetch, &t.fetched); err != nil || t.fetched.Workspace == "" {
|
||||
return fail(step.Node, fmt.Sprintf("the fetch of %s#%d answered no workspace: %s", p.Repository, p.Number, answer.Fetch))
|
||||
if t.fetched = answer.Fetch; t.fetched.Workspace == "" {
|
||||
return fail(step.Node, fmt.Sprintf("the fetch of %s#%d answered no workspace: %s", p.Repository, p.Number, step.Result))
|
||||
}
|
||||
if len(t.fetched.Conflicts) > 0 {
|
||||
if st.c.Try == nil {
|
||||
@@ -650,7 +661,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
}
|
||||
}
|
||||
st.clone += max(0, took-time.Duration(ran*float64(time.Second)))
|
||||
if unmet := delivery.UnmetNeeds(answer.Levels); len(unmet) > 0 && !placed {
|
||||
if unmet := buildstep.UnmetNeeds(answer.Levels); len(unmet) > 0 && !placed {
|
||||
// Every level ran, its passes kept, and some packages were left to a node with what their tests need: the levels
|
||||
// from the first that left one are asked of such a node, which takes the kept passes and tests those (issue 890).
|
||||
var again, packages []string
|
||||
@@ -673,7 +684,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
}
|
||||
|
||||
// unmetNames names the unmet packages and their needs.
|
||||
func unmetNames(unmet []delivery.PackageNeeds) string {
|
||||
func unmetNames(unmet []buildstep.PackageNeeds) string {
|
||||
var out []string
|
||||
for _, u := range unmet {
|
||||
out = append(out, u.Package+" (needs "+strings.Join(u.Needs, ", ")+")")
|
||||
@@ -682,7 +693,7 @@ func unmetNames(unmet []delivery.PackageNeeds) string {
|
||||
}
|
||||
|
||||
// unmetOf names the unmet packages of every level.
|
||||
func unmetOf(levels []delivery.LevelAnswer) string {
|
||||
func unmetOf(levels []buildstep.LevelAnswer) string {
|
||||
var out []string
|
||||
for _, la := range levels {
|
||||
if len(la.Unmet) > 0 {
|
||||
@@ -696,7 +707,7 @@ func unmetOf(levels []delivery.LevelAnswer) string {
|
||||
// of the packages that passed, the kept ones taken are counted, and every test result is read and counted, also after
|
||||
// one that did not pass, so the verdict names every failed test of the level (issue 1921). It answers the level's
|
||||
// running time (its tests' and setup's).
|
||||
func (st *state) level(ctx context.Context, t *target, la delivery.LevelAnswer) (*delivery.Verdict, float64) {
|
||||
func (st *state) level(ctx context.Context, t *target, la buildstep.LevelAnswer) (*meshdeliverychecker.VerdictsByGroupHeadsValue, float64) {
|
||||
level := la.Level
|
||||
a := st.levels[level]
|
||||
if a == nil {
|
||||
@@ -930,18 +941,18 @@ func firstLine(s string) string {
|
||||
const MaxFailureLine = 300
|
||||
|
||||
// levelFailure turns a level that did not pass into a verdict: Failed names what failed first with its words whole,
|
||||
// and Failures every failed test of the level's results with its first line, at most delivery.MaxFailures and an entry
|
||||
// and Failures every failed test of the level's results with its first line, at most meshdeliverycheckerclient.MaxFailures and an entry
|
||||
// saying how many more (issue 1921).
|
||||
func levelFailure(t *target, level string, results ...Tested) *delivery.Verdict {
|
||||
v := delivery.Verdict{State: delivery.VerdictFail}
|
||||
var all []delivery.FailedFirst
|
||||
func levelFailure(t *target, level string, results ...Tested) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
v := meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail}
|
||||
var all []FailedFirst
|
||||
for _, res := range results {
|
||||
if len(res.Failed) == 0 {
|
||||
why := res.Why
|
||||
if why == "" {
|
||||
why = "the " + level + " level did not pass"
|
||||
}
|
||||
all = append(all, delivery.FailedFirst{Test: level + ": " + t.pull.Repository, Words: why})
|
||||
all = append(all, FailedFirst{Test: level + ": " + t.pull.Repository, Words: why})
|
||||
continue
|
||||
}
|
||||
for _, f := range res.Failed {
|
||||
@@ -949,75 +960,76 @@ func levelFailure(t *target, level string, results ...Tested) *delivery.Verdict
|
||||
if t.module == buildrun.NodeBuildModule && f.Package == "" {
|
||||
name = res.Module
|
||||
}
|
||||
all = append(all, delivery.FailedFirst{Test: level + ": " + name, Words: failureWordsAfter(f, hidWords(t.module, res.By, f))})
|
||||
all = append(all, FailedFirst{Test: level + ": " + name, Words: failureWordsAfter(f, hidWords(t.module, res.By, f))})
|
||||
}
|
||||
}
|
||||
if len(all) == 0 { // no result: a level said failed with nothing to name
|
||||
all = append(all, delivery.FailedFirst{Test: level + ": " + t.pull.Repository, Words: "the " + level + " level did not pass"})
|
||||
all = append(all, FailedFirst{Test: level + ": " + t.pull.Repository, Words: "the " + level + " level did not pass"})
|
||||
}
|
||||
v.Failed = all[0]
|
||||
v.Failures = all // whole: settled cuts them to their first lines once every failure of the check is joined
|
||||
first := all[0]
|
||||
v.Failed = &first
|
||||
v.Failures = failureItems(all) // whole: settled cuts them to their first lines once every failure of the check is joined
|
||||
return &v
|
||||
}
|
||||
|
||||
// joinFailures is one failed verdict of every failure of vs, in the order given (a nil one passed), unsettled: Failed is
|
||||
// the first whole, and Failures every failure whole, or none when the one failure is no level's (a conflict, a refused
|
||||
// step: one line, as before issue 1921). Nil when none failed.
|
||||
func joinFailures(vs ...*delivery.Verdict) *delivery.Verdict {
|
||||
var out *delivery.Verdict
|
||||
var all []delivery.FailedFirst
|
||||
func joinFailures(vs ...*meshdeliverychecker.VerdictsByGroupHeadsValue) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
var out *meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
var all []FailedFirst
|
||||
listed := false
|
||||
for _, v := range vs {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if out == nil {
|
||||
out = &delivery.Verdict{State: delivery.VerdictFail, Failed: v.Failed}
|
||||
out = &meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: v.Failed}
|
||||
}
|
||||
if len(v.Failures) > 0 {
|
||||
all, listed = append(all, v.Failures...), true
|
||||
all, listed = append(all, failedList(v.Failures)...), true
|
||||
continue
|
||||
}
|
||||
all = append(all, v.Failed)
|
||||
all = append(all, failedOf(*v))
|
||||
}
|
||||
if out != nil && (listed || len(all) > 1) {
|
||||
out.Failures = all
|
||||
out.Failures = failureItems(all)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// settled is a joined verdict as it is written: each failure of its list cut to its first line, at most
|
||||
// delivery.MaxFailures and then how many more (failureLines).
|
||||
func settled(v *delivery.Verdict) *delivery.Verdict {
|
||||
// meshdeliverycheckerclient.MaxFailures and then how many more (failureLines).
|
||||
func settled(v *meshdeliverychecker.VerdictsByGroupHeadsValue) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
if v != nil && len(v.Failures) > 0 {
|
||||
v.Failures = failureLines(v.Failures)
|
||||
v.Failures = failureItems(failureLines(failedList(v.Failures)))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// addFailure is a settled list of failures with one more at its end: the first failure is put first when the list is
|
||||
// empty (a verdict of one failure keeps none), and past delivery.MaxFailures the last entry counts one more.
|
||||
func addFailure(v *delivery.Verdict, f delivery.FailedFirst) {
|
||||
// empty (a verdict of one failure keeps none), and past meshdeliverycheckerclient.MaxFailures the last entry counts one more.
|
||||
func addFailure(v *meshdeliverychecker.VerdictsByGroupHeadsValue, f FailedFirst) {
|
||||
if len(v.Failures) == 0 {
|
||||
v.Failures = failureLines([]delivery.FailedFirst{v.Failed})
|
||||
v.Failures = failureItems(failureLines([]FailedFirst{failedOf(*v)}))
|
||||
}
|
||||
if n := len(v.Failures); n > delivery.MaxFailures {
|
||||
v.Failures[n-1].Test = fmt.Sprintf("and %d more", failedCount(*v)-delivery.MaxFailures+1)
|
||||
if n := len(v.Failures); n > meshdeliverycheckerclient.MaxFailures {
|
||||
v.Failures[n-1].Test = fmt.Sprintf("and %d more", failedCount(*v)-meshdeliverycheckerclient.MaxFailures+1)
|
||||
return
|
||||
}
|
||||
v.Failures = append(v.Failures, failureLines([]delivery.FailedFirst{f})...)
|
||||
v.Failures = append(v.Failures, failureItems(failureLines([]FailedFirst{f}))...)
|
||||
}
|
||||
|
||||
// failureLines is every failure with the first line of its words, cut to MaxFailureLine, at most delivery.MaxFailures
|
||||
// failureLines is every failure with the first line of its words, cut to MaxFailureLine, at most meshdeliverycheckerclient.MaxFailures
|
||||
// and then one entry saying how many more failed.
|
||||
func failureLines(all []delivery.FailedFirst) []delivery.FailedFirst {
|
||||
n := min(len(all), delivery.MaxFailures)
|
||||
out := make([]delivery.FailedFirst, 0, n+1)
|
||||
func failureLines(all []FailedFirst) []FailedFirst {
|
||||
n := min(len(all), meshdeliverycheckerclient.MaxFailures)
|
||||
out := make([]FailedFirst, 0, n+1)
|
||||
for _, f := range all[:n] {
|
||||
out = append(out, delivery.FailedFirst{Test: f.Test, Machine: f.Machine, Words: clipLine(firstLine(f.Words), MaxFailureLine)})
|
||||
out = append(out, FailedFirst{Test: f.Test, Machine: f.Machine, Words: clipLine(firstLine(f.Words), MaxFailureLine)})
|
||||
}
|
||||
if more := len(all) - n; more > 0 {
|
||||
out = append(out, delivery.FailedFirst{Test: fmt.Sprintf("and %d more", more)})
|
||||
out = append(out, FailedFirst{Test: fmt.Sprintf("and %d more", more)})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// The judged_by answer of mesh-build-go's digest and test steps (ADR 0535 §5; the milestone file's interface for issue
|
||||
@@ -84,7 +85,7 @@ func TestAnInstalledModuleWithoutTheRunnerJudgesAsTodayAndTheLineSaysSo(t *testi
|
||||
b := candidateBench(t, false)
|
||||
cs := caseOf(t, pullOf(catalogueRepository, 9, head1))
|
||||
out := b.judge(t, cs)
|
||||
if out.Verdict.State != delivery.VerdictPass {
|
||||
if out.Verdict.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", out.Verdict)
|
||||
}
|
||||
// As today: the digests key kept verdicts, and the levels are asked without the runner the module ignored.
|
||||
@@ -112,7 +113,7 @@ func TestACandidateJudgedCheckTakesNoKeptVerdictAndWritesNone(t *testing.T) { //
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := b.judge(t, caseOf(t, pullOf(catalogueRepository, 9, head1)))
|
||||
if out.Verdict.State != delivery.VerdictPass {
|
||||
if out.Verdict.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", out.Verdict)
|
||||
}
|
||||
tests := b.steps.tool("test")
|
||||
@@ -124,7 +125,7 @@ func TestACandidateJudgedCheckTakesNoKeptVerdictAndWritesNone(t *testing.T) { //
|
||||
t.Errorf("the %v level was asked for %v, want it whole", c.args["level"], c.args["packages"])
|
||||
}
|
||||
}
|
||||
if u := out.Verdict.Levels[delivery.LevelUnit]; u.Kept != 0 {
|
||||
if u := out.Verdict.Levels[buildstep.LevelUnit]; u.Kept != 0 {
|
||||
t.Errorf("unit = %+v, want nothing taken", u)
|
||||
}
|
||||
if keys, _ := b.kept.Keys(context.Background()); len(keys) != 1 || keys[0] != "unit."+digA {
|
||||
@@ -148,8 +149,8 @@ func TestTheLineNamesTheCandidateThatJudged(t *testing.T) { // 1898/AC6
|
||||
}
|
||||
// A long failing line keeps the program it was judged by; only the lesser parts go.
|
||||
v := out.Verdict
|
||||
v.State = delivery.VerdictFail
|
||||
v.Failed = delivery.FailedFirst{Test: "unit: git.novox.be/novox/mesh-catalog/modules/mesh-build-go/cmd/mesh-build-go.TestADigestFoldsTheSiblings", Words: strings.Repeat("x", 60)}
|
||||
v.State = meshdeliverycheckerclient.VerdictFail
|
||||
v.Failed = &FailedFirst{Test: "unit: git.novox.be/novox/mesh-catalog/modules/mesh-build-go/cmd/mesh-build-go.TestADigestFoldsTheSiblings", Words: strings.Repeat("x", 60)}
|
||||
if l := Line(v, cs, p, out.Builds, out.Slowest, out.Judged); !strings.Contains(l, "judged by the candidate's") {
|
||||
t.Errorf("the long line %q dropped the program", l)
|
||||
}
|
||||
@@ -174,7 +175,7 @@ func TestACandidateThatDoesNotCompileFailsTheCheckWithTheCompilersWords(t *testi
|
||||
b.steps.handlers["build_go_digest"] = func(stepCall) (any, error) { return nil, errorString(refusal) }
|
||||
b.test = func(stepCall) (any, error) { return nil, errorString(refusal) }
|
||||
out := b.judge(t, caseOf(t, pullOf(catalogueRepository, 9, head1)))
|
||||
if out.Verdict.State != delivery.VerdictFail || !strings.Contains(out.Verdict.Failed.Words, "undefined: x") {
|
||||
if out.Verdict.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(out.Verdict.Failed.Words, "undefined: x") {
|
||||
t.Fatalf("verdict = %+v, want a failure naming the compile error", out.Verdict)
|
||||
}
|
||||
if n := len(b.steps.tool("test")); n != 1 {
|
||||
@@ -195,9 +196,9 @@ func TestTheStatusTheForgeShowsNamesTheProgramThatJudged(t *testing.T) { // 1898
|
||||
r := newRig(t, j)
|
||||
r.ch.Candidate(ctx, "feat/x", candidateOf(t, "feat/x", pullOf(repoA, 7, head1)))
|
||||
r.ch.Wait()
|
||||
key, _ := delivery.CheckerOwedKey(repoA, 7, head1)
|
||||
key, _ := CheckerOwedKey(repoA, 7, head1)
|
||||
raw, _, _, _ := r.owed.Get(ctx, key)
|
||||
o, err := delivery.DecodeCheckerOwed(raw)
|
||||
o, err := DecodeCheckerOwed(raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
// Kept is the checker's bucket of kept verdicts (ADR 0444, ADR 0484 clause 13): a passing verdict per package and level,
|
||||
@@ -17,31 +18,31 @@ import (
|
||||
type Kept struct{ Store KV }
|
||||
|
||||
// Lookup is the kept verdict of a level under a digest, or none.
|
||||
func (k Kept) Lookup(ctx context.Context, level, digest string) (delivery.Kept, bool, error) {
|
||||
key, err := delivery.KeptKey(level, digest)
|
||||
func (k Kept) Lookup(ctx context.Context, level, digest string) (meshdeliverychecker.KeptByLevelDigestValue, bool, error) {
|
||||
key, err := meshdeliverycheckerclient.KeptKey(level, digest)
|
||||
if err != nil {
|
||||
return delivery.Kept{}, false, err
|
||||
return meshdeliverychecker.KeptByLevelDigestValue{}, false, err
|
||||
}
|
||||
value, _, found, err := k.Store.Get(ctx, key)
|
||||
if err != nil || !found {
|
||||
return delivery.Kept{}, false, err
|
||||
return meshdeliverychecker.KeptByLevelDigestValue{}, false, err
|
||||
}
|
||||
v, err := delivery.DecodeKept(value)
|
||||
v, err := meshdeliverycheckerclient.DecodeKept(value)
|
||||
if err != nil {
|
||||
return delivery.Kept{}, false, fmt.Errorf("%s holds what is not a kept verdict: %w", key, err)
|
||||
return meshdeliverychecker.KeptByLevelDigestValue{}, false, fmt.Errorf("%s holds what is not a kept verdict: %w", key, err)
|
||||
}
|
||||
return v, true, nil
|
||||
}
|
||||
|
||||
// Keep writes v under level and digest by a create. It answers false, and writes nothing, when the key exists: the first
|
||||
// value stands.
|
||||
func (k Kept) Keep(ctx context.Context, level, digest string, v delivery.Kept) (bool, error) {
|
||||
key, err := delivery.KeptKey(level, digest)
|
||||
func (k Kept) Keep(ctx context.Context, level, digest string, v meshdeliverychecker.KeptByLevelDigestValue) (bool, error) {
|
||||
key, err := meshdeliverycheckerclient.KeptKey(level, digest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
v.Level = level
|
||||
data, err := delivery.EncodeKept(v)
|
||||
data, err := meshdeliverycheckerclient.EncodeKept(v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -77,8 +78,8 @@ func (k Kept) KeepPasses(ctx context.Context, repository, level, toolchain strin
|
||||
if failedIn(res, p.Package) {
|
||||
continue
|
||||
}
|
||||
made, err := k.Keep(ctx, level, digest, delivery.Kept{State: delivery.VerdictPass, Repository: repository, Package: p.Package,
|
||||
Level: level, Tests: p.Tests, Took: int64(p.Elapsed * 1000), Toolchain: toolchain, At: at})
|
||||
made, err := k.Keep(ctx, level, digest, meshdeliverychecker.KeptByLevelDigestValue{State: meshdeliverycheckerclient.VerdictPass, Repository: repository, Package: p.Package,
|
||||
Level: level, Tests: int64(p.Tests), Took: int64(p.Elapsed * 1000), Toolchain: toolchain, At: at.UTC().Format(time.RFC3339)})
|
||||
if err != nil {
|
||||
return kept, err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
var at0 = time.Date(2026, 10, 21, 19, 0, 0, 0, time.UTC)
|
||||
@@ -16,23 +18,23 @@ const (
|
||||
digB = "bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22"
|
||||
)
|
||||
|
||||
func keptValue(pkg string, tests int) delivery.Kept {
|
||||
return delivery.Kept{State: delivery.VerdictPass, Repository: "novox/mesh-delivery", Package: pkg, Tests: tests, Took: 120, Toolchain: "go1.26.0", At: at0}
|
||||
func keptValue(pkg string, tests int) meshdeliverychecker.KeptByLevelDigestValue {
|
||||
return meshdeliverychecker.KeptByLevelDigestValue{State: meshdeliverycheckerclient.VerdictPass, Repository: "novox/mesh-delivery", Package: pkg, Tests: int64(tests), Took: 120, Toolchain: "go1.26.0", At: at0.UTC().Format(time.RFC3339)}
|
||||
}
|
||||
|
||||
func TestAPassIsKeptByACreateUnderItsLevelAndDigest(t *testing.T) { // 1287/AC1
|
||||
ctx, st := context.Background(), newMemKV()
|
||||
k := Kept{Store: st}
|
||||
made, err := k.Keep(ctx, delivery.LevelUnit, digA, keptValue("m/a", 3))
|
||||
made, err := k.Keep(ctx, buildstep.LevelUnit, digA, keptValue("m/a", 3))
|
||||
if err != nil || !made {
|
||||
t.Fatalf("Keep = %v, %v, want made", made, err)
|
||||
}
|
||||
key, _ := delivery.KeptKey(delivery.LevelUnit, digA)
|
||||
key, _ := meshdeliverycheckerclient.KeptKey(buildstep.LevelUnit, digA)
|
||||
if st.creates[key] != 1 {
|
||||
t.Fatalf("the key %s was created %d times, want once", key, st.creates[key])
|
||||
}
|
||||
got, found, err := k.Lookup(ctx, delivery.LevelUnit, digA)
|
||||
if err != nil || !found || got.Package != "m/a" || got.Tests != 3 || got.Level != delivery.LevelUnit || got.Toolchain != "go1.26.0" {
|
||||
got, found, err := k.Lookup(ctx, buildstep.LevelUnit, digA)
|
||||
if err != nil || !found || got.Package != "m/a" || got.Tests != 3 || got.Level != buildstep.LevelUnit || got.Toolchain != "go1.26.0" {
|
||||
t.Fatalf("Lookup = %+v, %v, %v", got, found, err)
|
||||
}
|
||||
}
|
||||
@@ -40,14 +42,14 @@ func TestAPassIsKeptByACreateUnderItsLevelAndDigest(t *testing.T) { // 1287/AC1
|
||||
func TestASecondKeepUnderTheSameDigestIsRefusedAndTheFirstStands(t *testing.T) { // 1287/AC2
|
||||
ctx, st := context.Background(), newMemKV()
|
||||
k := Kept{Store: st}
|
||||
if made, err := k.Keep(ctx, delivery.LevelUnit, digA, keptValue("m/a", 3)); err != nil || !made {
|
||||
if made, err := k.Keep(ctx, buildstep.LevelUnit, digA, keptValue("m/a", 3)); err != nil || !made {
|
||||
t.Fatal(made, err)
|
||||
}
|
||||
made, err := k.Keep(ctx, delivery.LevelUnit, digA, keptValue("m/other", 9))
|
||||
made, err := k.Keep(ctx, buildstep.LevelUnit, digA, keptValue("m/other", 9))
|
||||
if err != nil || made {
|
||||
t.Fatalf("the second Keep = %v, %v, want refused without an error", made, err)
|
||||
}
|
||||
got, _, _ := k.Lookup(ctx, delivery.LevelUnit, digA)
|
||||
got, _, _ := k.Lookup(ctx, buildstep.LevelUnit, digA)
|
||||
if got.Package != "m/a" || got.Tests != 3 {
|
||||
t.Errorf("the first value did not stand: %+v", got)
|
||||
}
|
||||
@@ -103,14 +105,14 @@ func TestOnlyTheSkippingPackageOfAResultIsWithheld(t *testing.T) { // issue 890
|
||||
func TestAKeptValueOfAnotherDigestIsNotRead(t *testing.T) { // 1287/AC7
|
||||
ctx, st := context.Background(), newMemKV()
|
||||
k := Kept{Store: st}
|
||||
if _, err := k.Keep(ctx, delivery.LevelUnit, digA, keptValue("m/a", 3)); err != nil {
|
||||
if _, err := k.Keep(ctx, buildstep.LevelUnit, digA, keptValue("m/a", 3)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A toolchain change changes the digest, so the new digest finds nothing and the old key is not deleted.
|
||||
if _, found, _ := k.Lookup(ctx, delivery.LevelUnit, digB); found {
|
||||
if _, found, _ := k.Lookup(ctx, buildstep.LevelUnit, digB); found {
|
||||
t.Error("a digest that was not kept was found")
|
||||
}
|
||||
if _, found, _ := k.Lookup(ctx, delivery.LevelIntegration, digA); found {
|
||||
if _, found, _ := k.Lookup(ctx, buildstep.LevelIntegration, digA); found {
|
||||
t.Error("a kept unit verdict was found at the integration level")
|
||||
}
|
||||
if keys, _ := st.Keys(ctx); len(keys) != 1 {
|
||||
@@ -121,8 +123,8 @@ func TestAKeptValueOfAnotherDigestIsNotRead(t *testing.T) { // 1287/AC7
|
||||
func TestAKeptVerdictThatIsNotAPassIsRefused(t *testing.T) {
|
||||
k := Kept{Store: newMemKV()}
|
||||
v := keptValue("m/a", 1)
|
||||
v.State = delivery.VerdictFail
|
||||
if _, err := k.Keep(context.Background(), delivery.LevelUnit, digA, v); err == nil {
|
||||
v.State = meshdeliverycheckerclient.VerdictFail
|
||||
if _, err := k.Keep(context.Background(), buildstep.LevelUnit, digA, v); err == nil {
|
||||
t.Error("a failure was kept")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// MaxLine is the most characters a status's line has: the forge shows a status's description in a short space, and the full
|
||||
@@ -33,8 +35,8 @@ func short12(s string) string {
|
||||
}
|
||||
|
||||
// firstFailed is what failed first, in words: a conflict, a refused step or a test, named before anything else.
|
||||
func firstFailed(v delivery.Verdict) string {
|
||||
f := v.Failed
|
||||
func firstFailed(v meshdeliverychecker.VerdictsByGroupHeadsValue) string {
|
||||
f := failedOf(v)
|
||||
switch {
|
||||
case f.Test != "":
|
||||
if w := firstWordsLine(f.Words); w != "" {
|
||||
@@ -51,7 +53,7 @@ func firstFailed(v delivery.Verdict) string {
|
||||
|
||||
// failedLines is everything a verdict names as failed, one line each: every failed test with its first line (issue
|
||||
// 1921), or what failed first when the verdict names no list (a conflict, a refused step, an older verdict).
|
||||
func failedLines(v delivery.Verdict) []string {
|
||||
func failedLines(v meshdeliverychecker.VerdictsByGroupHeadsValue) []string {
|
||||
if len(v.Failures) == 0 {
|
||||
if f := firstFailed(v); f != "" {
|
||||
return []string{f}
|
||||
@@ -60,7 +62,7 @@ func failedLines(v delivery.Verdict) []string {
|
||||
}
|
||||
lines := make([]string, 0, len(v.Failures))
|
||||
for _, f := range v.Failures {
|
||||
lines = append(lines, firstFailed(delivery.Verdict{Failed: f}))
|
||||
lines = append(lines, firstFailed(meshdeliverychecker.VerdictsByGroupHeadsValue{Failed: &FailedFirst{Machine: f.Machine, Test: f.Test, Words: f.Words}}))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
@@ -68,7 +70,7 @@ func failedLines(v delivery.Verdict) []string {
|
||||
// failedHead is the head of a failed verdict's line: what failed first, and when more did, how many failed and "and N
|
||||
// more" after it (the operator, 2026-11-02), so a line cut to MaxLine still says that more failed. The words of the first
|
||||
// are cut to fit room characters when room is not negative; the count and "and N more" are never cut.
|
||||
func failedHead(v delivery.Verdict, room int) string {
|
||||
func failedHead(v meshdeliverychecker.VerdictsByGroupHeadsValue, room int) string {
|
||||
first := firstFailed(v)
|
||||
n := failedCount(v)
|
||||
if n <= 1 {
|
||||
@@ -93,13 +95,13 @@ func clipRunes(s string, n int) string {
|
||||
return string(r[:n-1]) + "…"
|
||||
}
|
||||
|
||||
// failedCount is how many failures a verdict names, the ones after delivery.MaxFailures counted too.
|
||||
func failedCount(v delivery.Verdict) int {
|
||||
// failedCount is how many failures a verdict names, the ones after meshdeliverycheckerclient.MaxFailures counted too.
|
||||
func failedCount(v meshdeliverychecker.VerdictsByGroupHeadsValue) int {
|
||||
n := len(v.Failures)
|
||||
if n > delivery.MaxFailures {
|
||||
if n > meshdeliverycheckerclient.MaxFailures {
|
||||
var more int
|
||||
if _, err := fmt.Sscanf(v.Failures[n-1].Test, "and %d more", &more); err == nil {
|
||||
return delivery.MaxFailures + more
|
||||
return meshdeliverycheckerclient.MaxFailures + more
|
||||
}
|
||||
}
|
||||
return n
|
||||
@@ -150,7 +152,7 @@ func (p pieces) join(drop int) string {
|
||||
return joinSet(parts...)
|
||||
}
|
||||
|
||||
func levelWords(name string, r delivery.LevelResult) string {
|
||||
func levelWords(name string, r meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue) string {
|
||||
s := fmt.Sprintf("%s %d run, %d kept", name, r.Run, r.Kept)
|
||||
if r.Skipped > 0 {
|
||||
s += fmt.Sprintf(", %d skipped", r.Skipped)
|
||||
@@ -160,14 +162,16 @@ func levelWords(name string, r delivery.LevelResult) string {
|
||||
|
||||
// compositionWords says the composition: not yet run, or once the engines answered (ADR 0593 §7) passed, with the nodes
|
||||
// that gave no answer, or failed, with the time it took and the machines it composed (issue 1300).
|
||||
func compositionWords(c *delivery.Composition, took int64) string { return composition(c, took, false) }
|
||||
func compositionWords(c *meshdeliverychecker.VerdictsByGroupHeadsValueComposition, took int64) string {
|
||||
return composition(c, took, false)
|
||||
}
|
||||
|
||||
// composition is compositionWords; short counts the machines and leaves the SDK commit to the report.
|
||||
func composition(c *delivery.Composition, took int64, short bool) string {
|
||||
if c != nil && c.State == delivery.CompositionNotRun && c.Words != "" {
|
||||
func composition(c *meshdeliverychecker.VerdictsByGroupHeadsValueComposition, took int64, short bool) string {
|
||||
if c != nil && c.State == meshdeliverycheckerclient.CompositionNotRun && c.Words != "" {
|
||||
return "composition not judged: " + c.Words
|
||||
}
|
||||
if c == nil || c.State == delivery.CompositionNotRun {
|
||||
if c == nil || c.State == meshdeliverycheckerclient.CompositionNotRun {
|
||||
return "composition not yet run"
|
||||
}
|
||||
in := ""
|
||||
@@ -186,14 +190,14 @@ func composition(c *delivery.Composition, took int64, short bool) string {
|
||||
}
|
||||
}
|
||||
warned := ""
|
||||
if c.State == delivery.CompositionPass && c.Words != "" {
|
||||
if c.State == meshdeliverycheckerclient.CompositionPass && c.Words != "" {
|
||||
warned = " (" + c.Words + ")" // a node that did not answer (ADR 0593 §9)
|
||||
}
|
||||
switch {
|
||||
case c.State == delivery.CompositionPass && short:
|
||||
case c.State == meshdeliverycheckerclient.CompositionPass && short:
|
||||
return "composition passed" + in + on + warned // what it composed with is in the report
|
||||
case c.State == delivery.CompositionPass:
|
||||
return "composition passed" + in + " with " + short12(c.SDK) + on + warned
|
||||
case c.State == meshdeliverycheckerclient.CompositionPass:
|
||||
return "composition passed" + in + " with " + short12(c.Sdk) + on + warned
|
||||
}
|
||||
return "composition failed" + in + on + ": " + c.Words
|
||||
}
|
||||
@@ -211,7 +215,7 @@ func buildWords(builds []BuildNote) string {
|
||||
|
||||
// carriedWords say the commits a verdict was carried from, for the line of pull request p (ADR 0548 §8): its own trunk's
|
||||
// commit as twelve characters, a sibling's with its name before it; only those that moved, sorted. Empty when none did.
|
||||
func carriedWords(from map[string]string, v delivery.Verdict, cs *Case, p Pull) string {
|
||||
func carriedWords(from map[string]string, v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case, p Pull) string {
|
||||
var own string
|
||||
var others []string
|
||||
for r, old := range from {
|
||||
@@ -243,7 +247,7 @@ func carriedWords(from map[string]string, v delivery.Verdict, cs *Case, p Pull)
|
||||
}
|
||||
|
||||
// parts makes the pieces of the line of a pull request of a case.
|
||||
func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) pieces {
|
||||
func parts(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) pieces {
|
||||
var pc pieces
|
||||
pc.judged = judged[pullKey(p)]
|
||||
pc.series = seriesWords(cs, p)
|
||||
@@ -252,7 +256,7 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s
|
||||
}
|
||||
commits := short12(p.Head) + " on " + short12(p.Trunk)
|
||||
switch v.State {
|
||||
case delivery.VerdictRunning:
|
||||
case meshdeliverycheckerclient.VerdictRunning:
|
||||
pc.head = "checking " + commits
|
||||
if cs != nil && cs.CarriedFrom != nil {
|
||||
// Out of date and being carried to the new commit (ADR 0548 §6): pending, and saying so.
|
||||
@@ -261,7 +265,7 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s
|
||||
pc.carried = "from " + w
|
||||
}
|
||||
}
|
||||
case delivery.VerdictPass:
|
||||
case meshdeliverycheckerclient.VerdictPass:
|
||||
pc.head = "pass " + commits
|
||||
if w := carriedWords(v.CarriedFrom, v, cs, p); w != "" {
|
||||
pc.carried = "carried from " + w // the counts that follow say what the carry ran and kept
|
||||
@@ -271,16 +275,16 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s
|
||||
pc.commits = commits
|
||||
}
|
||||
var counts []string
|
||||
for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} {
|
||||
for _, level := range []string{buildstep.LevelUnit, buildstep.LevelIntegration} {
|
||||
if r, ok := v.Levels[level]; ok {
|
||||
counts = append(counts, levelWords(level, r))
|
||||
}
|
||||
}
|
||||
pc.counts = strings.Join(counts, " | ")
|
||||
var short []string
|
||||
for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} {
|
||||
for _, level := range []string{buildstep.LevelUnit, buildstep.LevelIntegration} {
|
||||
if r, ok := v.Levels[level]; ok {
|
||||
name := map[string]string{delivery.LevelUnit: "unit", delivery.LevelIntegration: "int"}[level]
|
||||
name := map[string]string{buildstep.LevelUnit: "unit", buildstep.LevelIntegration: "int"}[level]
|
||||
w := fmt.Sprintf("%s %d run %d kept", name, r.Run, r.Kept)
|
||||
if r.Skipped > 0 {
|
||||
w += fmt.Sprintf(" %d skipped", r.Skipped)
|
||||
@@ -289,8 +293,8 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s
|
||||
}
|
||||
}
|
||||
pc.shortCounts = strings.Join(short, " | ")
|
||||
if v.State != delivery.VerdictRunning {
|
||||
t := v.Took
|
||||
if v.State != meshdeliverycheckerclient.VerdictRunning {
|
||||
t := tookOf(v)
|
||||
var times []string
|
||||
for _, x := range []struct {
|
||||
name string
|
||||
@@ -329,13 +333,13 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s
|
||||
}
|
||||
}
|
||||
pc.builds = buildWords(builds)
|
||||
if v.State != delivery.VerdictRunning {
|
||||
pc.composition = compositionWords(v.Composition, v.Took.Composition)
|
||||
pc.shortComposition = composition(v.Composition, v.Took.Composition, true)
|
||||
if v.State != meshdeliverycheckerclient.VerdictRunning {
|
||||
pc.composition = compositionWords(v.Composition, tookOf(v).Composition)
|
||||
pc.shortComposition = composition(v.Composition, tookOf(v).Composition, true)
|
||||
}
|
||||
if cs != nil && len(cs.Pulls) > 1 {
|
||||
pc.group = fmt.Sprintf("group %s of %d pull requests", cs.Group, len(cs.Pulls))
|
||||
if v.State != delivery.VerdictPass {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
pc.group = fmt.Sprintf("%s#%d in group %s", p.Repository, p.Number, cs.Group)
|
||||
}
|
||||
}
|
||||
@@ -343,7 +347,7 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s
|
||||
}
|
||||
|
||||
// Report is the full line of a pull request's status, with no limit: what the read tools and the full report say.
|
||||
func Report(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) string {
|
||||
func Report(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) string {
|
||||
return parts(v, cs, p, builds, slowest, judged).join(0)
|
||||
}
|
||||
|
||||
@@ -355,9 +359,9 @@ func Report(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []
|
||||
// builds, then the group drop first, then the times are given in their compact form, then dropped, and the line is cut at last.
|
||||
// The program that judged is never dropped: a line naming it gives the counts in their compact form, then drops them and
|
||||
// what follows, and is cut in its first part rather than lose it.
|
||||
func Line(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) string {
|
||||
func Line(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) string {
|
||||
pc := parts(v, cs, p, builds, slowest, judged)
|
||||
if v.State == delivery.VerdictFail {
|
||||
if v.State == meshdeliverycheckerclient.VerdictFail {
|
||||
// The head gives way first to the commits and what is never dropped: its count and "and N more" stay whole.
|
||||
kept := joinSet(pc.commits, pc.carried, pc.warning, pc.judged, pc.series)
|
||||
pc.head = failedHead(v, MaxLine-utf8.RuneCountInString(" | "+kept))
|
||||
@@ -379,7 +383,7 @@ func Line(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []st
|
||||
return string(head[:room]) + "…" + rest
|
||||
}
|
||||
}
|
||||
if v.State == delivery.VerdictFail {
|
||||
if v.State == meshdeliverycheckerclient.VerdictFail {
|
||||
// A failure's head, sized above, and the commits fit: the counts and the composition are in the report.
|
||||
if s := joinSet(pc.head, pc.commits, pc.carried, pc.warning, pc.judged, pc.series); utf8.RuneCountInString(s) <= MaxLine {
|
||||
return s
|
||||
|
||||
@@ -6,19 +6,22 @@ import (
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
func lineCase(t testing.TB, pulls ...delivery.CandidatePull) (*Case, Pull) {
|
||||
func lineCase(t testing.TB, pulls ...meshcatalog.CandidatesByGroupValuePullsItem) (*Case, Pull) {
|
||||
cs := caseOf(t, pulls...)
|
||||
return cs, cs.Pulls[0]
|
||||
}
|
||||
|
||||
func passVerdict() delivery.Verdict {
|
||||
return delivery.Verdict{State: delivery.VerdictPass,
|
||||
Levels: map[string]delivery.LevelResult{delivery.LevelUnit: {Run: 12, Kept: 301}, delivery.LevelIntegration: {Run: 3, Kept: 40}},
|
||||
Took: delivery.Took{Queue: 800, Clone: 4100, Unit: 400, IntegrationSetup: 3200, IntegrationRun: 12000},
|
||||
Composition: &delivery.Composition{State: delivery.CompositionNotRun}}
|
||||
func passVerdict() meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
return meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictPass,
|
||||
Levels: map[string]meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{buildstep.LevelUnit: {Run: 12, Kept: 301}, buildstep.LevelIntegration: {Run: 3, Kept: 40}},
|
||||
Took: &meshdeliverychecker.VerdictsByGroupHeadsValueTook{Queue: 800, Clone: 4100, Unit: 400, IntegrationSetup: 3200, IntegrationRun: 12000},
|
||||
Composition: &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionNotRun}}
|
||||
}
|
||||
|
||||
func TestAPassOpensWithTheVerdictAndSaysTheCommitsTheCountsTheTimesAndThatTheCompositionWasNotRun(t *testing.T) { // 1300/AC1
|
||||
@@ -44,17 +47,17 @@ func TestAPassOpensWithTheVerdictAndSaysTheCommitsTheCountsTheTimesAndThatTheCom
|
||||
|
||||
func TestAFailureNamesTheTestTheConflictOrTheRefusedStepFirst(t *testing.T) { // 1300/AC2
|
||||
cs, p := lineCase(t, pullOf(repoA, 7, head1))
|
||||
long := delivery.FailedFirst{Test: "integration: " + strings.Repeat("pkg/", 50) + "TestSomething", Words: strings.Repeat("because ", 40)}
|
||||
long := FailedFirst{Test: "integration: " + strings.Repeat("pkg/", 50) + "TestSomething", Words: strings.Repeat("because ", 40)}
|
||||
for name, c := range map[string]struct {
|
||||
v delivery.Verdict
|
||||
v meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
want string
|
||||
}{
|
||||
"test": {delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Test: "unit: m/a.TestX", Words: "want 4"}}, "failed: unit: m/a.TestX: want 4"},
|
||||
"conflict": {delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: "shanks", Words: "novox/mesh-delivery#7 does not merge into the trunk: files in conflict: go.mod"}}, "failed: shanks: novox/mesh-delivery#7 does not merge into the trunk: files in conflict: go.mod"},
|
||||
"refused": {delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: "mesh-build-go", Words: "no go build node qualifies: builders disagree"}}, "failed: mesh-build-go: no go build node qualifies"},
|
||||
"test": {meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Test: "unit: m/a.TestX", Words: "want 4"}}, "failed: unit: m/a.TestX: want 4"},
|
||||
"conflict": {meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: "shanks", Words: "novox/mesh-delivery#7 does not merge into the trunk: files in conflict: go.mod"}}, "failed: shanks: novox/mesh-delivery#7 does not merge into the trunk: files in conflict: go.mod"},
|
||||
"refused": {meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: "mesh-build-go", Words: "no go build node qualifies: builders disagree"}}, "failed: mesh-build-go: no go build node qualifies"},
|
||||
// ADR 0591: a first failure longer than the forge's limit is shortened; "failed (N)" and "and N-1 more" are never cut.
|
||||
"many, the first too long": {delivery.Verdict{State: delivery.VerdictFail, Failed: long, Failures: []delivery.FailedFirst{long, {Machine: "g14", Words: "route-proxy is defined twice"},
|
||||
{Test: "unit: m/b.TestY", Words: "boom"}}}, "failed (3): integration: pkg/pkg/"},
|
||||
"many, the first too long": {meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &long, Failures: failureItems([]FailedFirst{long, {Machine: "g14", Words: "route-proxy is defined twice"},
|
||||
{Test: "unit: m/b.TestY", Words: "boom"}})}, "failed (3): integration: pkg/pkg/"},
|
||||
} {
|
||||
line := Line(c.v, cs, p, nil, nil, nil)
|
||||
if more := len(c.v.Failures) - 1; more > 0 && (!strings.Contains(line, fmt.Sprintf("…; and %d more | 111111111111", more)) || utf8.RuneCountInString(line) > MaxLine) {
|
||||
@@ -79,7 +82,7 @@ func TestALevelOverItsBoundIsNamedWithItsSlowestTestsAndTheVerdictIsNotChanged(t
|
||||
func TestSkippedTestsAreSaidAndNeverAFlatPass(t *testing.T) { // 1300/AC6
|
||||
cs, p := lineCase(t, pullOf(repoA, 7, head1))
|
||||
v := passVerdict()
|
||||
v.Levels[delivery.LevelUnit] = delivery.LevelResult{Run: 12, Kept: 301, Skipped: 2}
|
||||
v.Levels[buildstep.LevelUnit] = meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{Run: 12, Kept: 301, Skipped: 2}
|
||||
if line := Line(v, cs, p, nil, nil, nil); !strings.Contains(line, "unit 12 run, 301 kept, 2 skipped") {
|
||||
t.Errorf("line = %q", line)
|
||||
}
|
||||
@@ -108,7 +111,7 @@ func TestTheBuildsOfTheTouchedModulesAreSaidAndDroppedFirstWhenTheLineIsTooLong(
|
||||
t.Errorf("line %q is too long", line)
|
||||
}
|
||||
short := passVerdict()
|
||||
short.Took = delivery.Took{}
|
||||
short.Took = &meshdeliverychecker.VerdictsByGroupHeadsValueTook{}
|
||||
if l := Line(short, cs, p, builds[:1], nil, nil); !strings.Contains(l, "builds: avahi built") {
|
||||
t.Errorf("a short line keeps its builds: %q", l)
|
||||
}
|
||||
@@ -116,7 +119,7 @@ func TestTheBuildsOfTheTouchedModulesAreSaidAndDroppedFirstWhenTheLineIsTooLong(
|
||||
|
||||
func TestALineIsNeverLongerThanTheLimit(t *testing.T) { // 1300/AC1
|
||||
cs, p := lineCase(t, pullOf(repoA, 7, head1))
|
||||
v := delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Test: "integration: " + strings.Repeat("pkg/", 50) + ".TestSomething", Words: strings.Repeat("because ", 40)}}
|
||||
v := meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Test: "integration: " + strings.Repeat("pkg/", 50) + ".TestSomething", Words: strings.Repeat("because ", 40)}}
|
||||
if line := Line(v, cs, p, nil, nil, nil); utf8.RuneCountInString(line) > MaxLine || !strings.HasPrefix(line, "failed: integration: ") {
|
||||
t.Errorf("line = %q (%d)", line, utf8.RuneCountInString(line))
|
||||
}
|
||||
@@ -124,7 +127,7 @@ func TestALineIsNeverLongerThanTheLimit(t *testing.T) { // 1300/AC1
|
||||
|
||||
func TestARunningCheckIsPending(t *testing.T) {
|
||||
cs, p := lineCase(t, pullOf(repoA, 7, head1))
|
||||
if line := Line(delivery.Verdict{State: delivery.VerdictRunning}, cs, p, nil, nil, nil); line != "checking 111111111111 on aaaaaaaaaaaa" {
|
||||
if line := Line(meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, cs, p, nil, nil, nil); line != "checking 111111111111 on aaaaaaaaaaaa" {
|
||||
t.Errorf("line = %q", line)
|
||||
}
|
||||
}
|
||||
@@ -132,7 +135,7 @@ func TestARunningCheckIsPending(t *testing.T) {
|
||||
func TestThePassedCompositionIsSaidOnlyWhenItWasRun(t *testing.T) { // ADR 0484 clause 6
|
||||
cs, p := lineCase(t, pullOf(repoA, 7, head1))
|
||||
v := passVerdict()
|
||||
v.Composition = &delivery.Composition{State: delivery.CompositionPass, SDK: "afbe371003a570f088be4a394d83aa7a9458f7ab"}
|
||||
v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionPass, Sdk: "afbe371003a570f088be4a394d83aa7a9458f7ab"}
|
||||
if r := Report(v, cs, p, nil, nil, nil); !strings.Contains(r, "composition passed with afbe371003a5") {
|
||||
t.Errorf("report = %q", r)
|
||||
}
|
||||
@@ -146,7 +149,7 @@ func TestTheCompositionsTimeAndMachinesAreSaidOnceTheAssignerReportsThem(t *test
|
||||
cs, p := lineCase(t, pullOf(repoA, 7, head1))
|
||||
v := passVerdict()
|
||||
v.Took.Composition = 2400
|
||||
v.Composition = &delivery.Composition{State: delivery.CompositionPass, SDK: "afbe371003a570f088be4a394d83aa7a9458f7ab", Machines: []string{"g14", "ace", "novox", "shanks"}}
|
||||
v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionPass, Sdk: "afbe371003a570f088be4a394d83aa7a9458f7ab", Machines: []string{"g14", "ace", "novox", "shanks"}}
|
||||
r := Report(v, cs, p, nil, nil, nil)
|
||||
for _, want := range []string{"composition passed in 2.4s with afbe371003a5 on 4 machines: ace, g14, novox, shanks"} {
|
||||
if !strings.Contains(r, want) {
|
||||
@@ -156,8 +159,8 @@ func TestTheCompositionsTimeAndMachinesAreSaidOnceTheAssignerReportsThem(t *test
|
||||
if l := Line(v, cs, p, nil, nil, nil); !strings.Contains(l, "composition passed in 2.4s") || !strings.Contains(l, "on 4 machines") || utf8.RuneCountInString(l) > MaxLine {
|
||||
t.Errorf("line = %q", l)
|
||||
}
|
||||
v.State, v.Failed = delivery.VerdictFail, delivery.FailedFirst{Machine: "novox", Words: "composition refused"}
|
||||
v.Composition = &delivery.Composition{State: delivery.CompositionFail, Words: "novox: two seats claim dns", Machines: []string{"novox"}}
|
||||
v.State, v.Failed = meshdeliverycheckerclient.VerdictFail, &FailedFirst{Machine: "novox", Words: "composition refused"}
|
||||
v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionFail, Words: "novox: two seats claim dns", Machines: []string{"novox"}}
|
||||
if r := Report(v, cs, p, nil, nil, nil); !strings.Contains(r, "composition failed in 2.4s on novox: novox: two seats claim dns") {
|
||||
t.Errorf("failed composition report = %q", r)
|
||||
}
|
||||
@@ -169,7 +172,7 @@ func TestTheLineSaysTheLintTime(t *testing.T) {
|
||||
test := b.test
|
||||
b.test = func(c stepCall) (any, error) {
|
||||
r, err := test(c)
|
||||
if res, ok := r.(Tested); ok && res.Level == delivery.LevelUnit {
|
||||
if res, ok := r.(Tested); ok && res.Level == buildstep.LevelUnit {
|
||||
res.Lint = 1.5
|
||||
return res, err
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ package check
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
sdkrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
|
||||
)
|
||||
|
||||
// **An ask the build seat gave up on ends its step** (novox/hq issue 734): mesh-sdk buildrun.LostAskWaiter hands the
|
||||
@@ -22,18 +23,18 @@ const LostAskTries = 3
|
||||
|
||||
// IsLostAsk says whether a verdict failed because its ask judged nothing: the build seat gave it up (buildrun.LostWords), or
|
||||
// the build module that ran its step was replaced under it, and its next process ended the step it left running
|
||||
// (buildstep.RestartReason; a delivery of the build module, as on 2026-10-28). Neither is the change's failure: the check
|
||||
// (sdkrun.RestartReason; a delivery of the build module, as on 2026-10-28). Neither is the change's failure: the check
|
||||
// is asked again, as for a given-up ask.
|
||||
func IsLostAsk(v delivery.Verdict) bool {
|
||||
return v.State == delivery.VerdictFail && (strings.Contains(v.Failed.Words, buildrun.LostWords) ||
|
||||
strings.Contains(v.Failed.Words, buildstep.RestartReason))
|
||||
func IsLostAsk(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool {
|
||||
return v.State == meshdeliverycheckerclient.VerdictFail && (strings.Contains(failedOf(v).Words, buildrun.LostWords) ||
|
||||
strings.Contains(failedOf(v).Words, sdkrun.RestartReason))
|
||||
}
|
||||
|
||||
// IsNotTaken says whether a verdict failed because no build node took its ask within the bound (buildrun.NotTakenWords).
|
||||
func IsNotTaken(v delivery.Verdict) bool {
|
||||
return v.State == delivery.VerdictFail && strings.Contains(v.Failed.Words, buildrun.NotTakenWords)
|
||||
func IsNotTaken(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool {
|
||||
return v.State == meshdeliverycheckerclient.VerdictFail && strings.Contains(failedOf(v).Words, buildrun.NotTakenWords)
|
||||
}
|
||||
|
||||
// HearingBucket is the checker's state hearing: whether it hears DEAD_LETTERS (buildrun.Hearing under
|
||||
// buildrun.HearingKey), which checker_status says (dead_letters).
|
||||
const HearingBucket = delivery.CheckerModule + "_" + buildrun.HearingState
|
||||
const HearingBucket = CheckerModule + "_" + buildrun.HearingState
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
sdkrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// losingAsks is the build seat over a bus that gives up the first `lose` asks put (DEAD_LETTERS keeps them, as on
|
||||
@@ -18,7 +20,7 @@ import (
|
||||
// gives up (an ask no node takes).
|
||||
type losingAsks struct {
|
||||
*buildrun.FakeAsks
|
||||
answer func(delivery.StepAsk) delivery.Step
|
||||
answer func(buildstep.StepAsk) buildstep.Step
|
||||
dead *buildrun.DeadLetters
|
||||
never bool
|
||||
|
||||
@@ -47,7 +49,7 @@ func (l *losingAsks) PublishAsk(ctx context.Context, subject, id string, data []
|
||||
}
|
||||
go l.dead.Heard(g)
|
||||
default:
|
||||
a, err := delivery.DecodeStepAsk(data)
|
||||
a, err := buildstep.DecodeStepAsk(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -146,32 +148,33 @@ func TestAnAskGivenUpOrNeverTakenEndsItsTryAsErrorAndItsHeadIsAskedAnew(t *testi
|
||||
}
|
||||
|
||||
// troubled is the build seat as a check meets it when its step's end does not reach its wait: the first `restarts` asks'
|
||||
// steps are ended by the build module's next process (buildstep.RestartReason), and the first `deaf` asks' watches hear
|
||||
// steps are ended by the build module's next process (sdkrun.RestartReason), and the first `deaf` asks' watches hear
|
||||
// nothing though their steps end (the lost check of 2026-10-28). Its steps state is read through AskSteps and Steps.
|
||||
type troubled struct {
|
||||
*buildrun.FakeAsks
|
||||
answer func(delivery.StepAsk) delivery.Step
|
||||
answer func(buildstep.StepAsk) buildstep.Step
|
||||
|
||||
mu sync.Mutex
|
||||
restarts, deaf int
|
||||
deafIDs map[string]bool
|
||||
steps map[string]delivery.Step
|
||||
steps map[string]buildstep.Step
|
||||
}
|
||||
|
||||
func (tr *troubled) PublishAsk(ctx context.Context, subject, id string, data []byte) error {
|
||||
if err := tr.FakeAsks.PublishAsk(ctx, subject, id, data); err != nil {
|
||||
return err
|
||||
}
|
||||
a, err := delivery.DecodeStepAsk(data)
|
||||
a, err := buildstep.DecodeStepAsk(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
step := tr.answer(a)
|
||||
step.What = buildstep.AskWhat(a)
|
||||
read, _ := buildstep.AskAs[buildstep.Ask](a)
|
||||
step.What = sdkrun.AskWhat(read)
|
||||
tr.mu.Lock()
|
||||
if tr.restarts > 0 {
|
||||
tr.restarts--
|
||||
step.State, step.Reason, step.Result = delivery.StepFailed, buildstep.RestartReason, nil
|
||||
step.State, step.Reason, step.Result = buildstep.StepFailed, sdkrun.RestartReason, nil
|
||||
}
|
||||
deaf := tr.deaf > 0
|
||||
if deaf {
|
||||
@@ -186,21 +189,21 @@ func (tr *troubled) PublishAsk(ctx context.Context, subject, id string, data []b
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *troubled) WatchAsk(ctx context.Context, module, askID string) (<-chan delivery.Step, error) {
|
||||
func (tr *troubled) WatchAsk(ctx context.Context, module, askID string) (<-chan buildstep.Step, error) {
|
||||
tr.mu.Lock()
|
||||
deaf := tr.deafIDs[askID]
|
||||
tr.mu.Unlock()
|
||||
if deaf {
|
||||
return make(chan delivery.Step), nil // open, and silent for good
|
||||
return make(chan buildstep.Step), nil // open, and silent for good
|
||||
}
|
||||
return tr.FakeAsks.WatchAsk(ctx, module, askID)
|
||||
}
|
||||
|
||||
func (tr *troubled) AskSteps(_ context.Context, _, askID string) ([]delivery.Step, error) {
|
||||
func (tr *troubled) AskSteps(_ context.Context, _, askID string) ([]buildstep.Step, error) {
|
||||
tr.mu.Lock()
|
||||
defer tr.mu.Unlock()
|
||||
if s, ok := tr.steps[askID]; ok {
|
||||
return []delivery.Step{s}, nil
|
||||
return []buildstep.Step{s}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -212,9 +215,9 @@ func (tr *troubled) Steps(running bool) StateRead {
|
||||
vals := map[string]json.RawMessage{}
|
||||
for id, s := range tr.steps {
|
||||
if running {
|
||||
s.State, s.Ended, s.Started = delivery.StepRunning, time.Time{}, time.Now()
|
||||
s.State, s.Ended, s.Started = buildstep.StepRunning, time.Time{}, time.Now()
|
||||
}
|
||||
raw, _ := delivery.EncodeStep(s)
|
||||
raw, _ := buildstep.EncodeStep(s)
|
||||
vals[s.Node+"."+id] = raw
|
||||
}
|
||||
return StateRead{Keys: func() ([]string, error) {
|
||||
@@ -262,13 +265,13 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) {
|
||||
state string
|
||||
asks int
|
||||
}{
|
||||
{name: "given up once", lose: 1, state: delivery.VerdictPass, asks: 2},
|
||||
{name: "given up every time", lose: LostAskTries, state: delivery.VerdictFail, asks: LostAskTries},
|
||||
{name: "its build module replaced under it", restarts: 1, state: delivery.VerdictPass, asks: 2},
|
||||
{name: "replaced under every ask", restarts: LostAskTries, state: delivery.VerdictFail, asks: LostAskTries},
|
||||
{name: "its watch deaf, a step of its build module ends", deaf: 1, how: "nudge", state: delivery.VerdictPass, asks: 1},
|
||||
{name: "its watch deaf, asked again by hand", deaf: 1, how: "hand", state: delivery.VerdictPass, asks: 2},
|
||||
{name: "its watch deaf, asked again by hand after its build module replaced it", deaf: 1, restarts: 1, how: "hand", state: delivery.VerdictPass, asks: 2},
|
||||
{name: "given up once", lose: 1, state: meshdeliverycheckerclient.VerdictPass, asks: 2},
|
||||
{name: "given up every time", lose: LostAskTries, state: meshdeliverycheckerclient.VerdictFail, asks: LostAskTries},
|
||||
{name: "its build module replaced under it", restarts: 1, state: meshdeliverycheckerclient.VerdictPass, asks: 2},
|
||||
{name: "replaced under every ask", restarts: LostAskTries, state: meshdeliverycheckerclient.VerdictFail, asks: LostAskTries},
|
||||
{name: "its watch deaf, a step of its build module ends", deaf: 1, how: "nudge", state: meshdeliverycheckerclient.VerdictPass, asks: 1},
|
||||
{name: "its watch deaf, asked again by hand", deaf: 1, how: "hand", state: meshdeliverycheckerclient.VerdictPass, asks: 2},
|
||||
{name: "its watch deaf, asked again by hand after its build module replaced it", deaf: 1, restarts: 1, how: "hand", state: meshdeliverycheckerclient.VerdictPass, asks: 2},
|
||||
} {
|
||||
t.Run(row.name, func(t *testing.T) {
|
||||
b := newBench(t)
|
||||
@@ -280,7 +283,7 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) {
|
||||
asked = func() int { return len(l.Asks()) }
|
||||
} else {
|
||||
tr = &troubled{FakeAsks: &buildrun.FakeAsks{}, answer: b.answer, restarts: row.restarts, deaf: row.deaf,
|
||||
deafIDs: map[string]bool{}, steps: map[string]delivery.Step{}}
|
||||
deafIDs: map[string]bool{}, steps: map[string]buildstep.Step{}}
|
||||
heard = &buildrun.HeardEnds{Inner: tr, Read: tr}
|
||||
b.ex.Asks = buildrun.Asker{Publisher: tr, Waiter: heard, Bound: 5 * time.Second}
|
||||
asked = func() int { return len(tr.Asks()) }
|
||||
@@ -295,7 +298,7 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) {
|
||||
for deadline := time.Now().Add(10 * time.Second); heard.Waiting() == 0 && time.Now().Before(deadline); {
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
}
|
||||
if v := r.verdict(t, key); v.State != delivery.VerdictRunning {
|
||||
if v := r.verdict(t, key); v.State != meshdeliverycheckerclient.VerdictRunning {
|
||||
t.Fatalf("a check whose step ended unheard = %s, want running until it is read", v.State)
|
||||
}
|
||||
}
|
||||
@@ -305,7 +308,7 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) {
|
||||
heard.Nudge(buildrun.GoBuildModule)
|
||||
case "hand":
|
||||
cands := newMemKV()
|
||||
ckey, _ := delivery.CandidatesKey("feat/x")
|
||||
ckey, _ := meshcatalogclient.CandidatesKey("feat/x")
|
||||
_, _ = cands.Create(ctx, ckey, cand)
|
||||
none := StateRead{Keys: func() ([]string, error) { return nil, nil }, Get: func(string) (json.RawMessage, error) { return nil, nil }}
|
||||
rd := Reader{CandidateState: kvRead(cands), VerdictState: kvRead(r.verdicts), AskState: kvRead(asksKept),
|
||||
@@ -336,7 +339,7 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) {
|
||||
if _, _, kept, _ := asksKept.Get(ctx, key); kept {
|
||||
t.Errorf("the asks of the ended check are still kept under %s", key)
|
||||
}
|
||||
if row.state == delivery.VerdictFail && (!IsLostAsk(v) || !IsNodesFailure(v)) {
|
||||
if row.state == meshdeliverycheckerclient.VerdictFail && (!IsLostAsk(v) || !IsNodesFailure(v)) {
|
||||
t.Errorf("a check that judged nothing every time = %q, want lost and judged again when the nodes change", v.Failed.Words)
|
||||
}
|
||||
if row.how == "hand" {
|
||||
|
||||
@@ -9,10 +9,11 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/packagename"
|
||||
)
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
// A change's manifests are judged by the indexer's judge (novox/hq ADR 0578 §2): the checker asks the Go build seat to
|
||||
// build the judge's command (mesh-delivery's modules/mesh-delivery-indexer, cmd/mesh-judge) at mesh-delivery's trunk, or
|
||||
// at the group's mesh-delivery member head when that member changes the judge, and to run it in its sandbox over the
|
||||
// catalogue as the change leaves it, exactly as the composer is run (delivery.ComposeJob, ADR 0482 §4). A manifest the
|
||||
// catalogue as the change leaves it, exactly as the composer is run (buildstep.ComposeJob, ADR 0482 §4). A manifest the
|
||||
// change touches that the judge refuses fails the check, naming the manifest and each problem. The checker links no
|
||||
// judge of its own.
|
||||
|
||||
@@ -106,7 +107,7 @@ func judgeRequest(trunk map[string]string, pulls []Pull) (req composer.Request,
|
||||
// judgeManifests runs the indexer's judge over the case's manifests and fails the check for each touched manifest it
|
||||
// refuses. A case that touches no manifest, or an examiner that reads no trunk manifests, judges nothing; a judge that
|
||||
// cannot be asked or does not answer fails the check, said, never passes it.
|
||||
func (st *state) judgeManifests(ctx context.Context) *delivery.Verdict {
|
||||
func (st *state) judgeManifests(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
if st.j.Manifests == nil || st.c.Try != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -195,14 +196,14 @@ func (st *state) judgeManifests(ctx context.Context) *delivery.Verdict {
|
||||
|
||||
// runJudge asks the Go build seat to build the judge at the commit and run it over the request, and reads its change and
|
||||
// trunk cases; a failed verdict when it cannot be asked or does not answer.
|
||||
func (st *state) runJudge(ctx context.Context, req composer.Request, touched []string, commit, at string) (change, trunk composer.CaseVerdict, v *delivery.Verdict) {
|
||||
func (st *state) runJudge(ctx context.Context, req composer.Request, touched []string, commit, at string) (change, trunk composer.CaseVerdict, v *meshdeliverychecker.VerdictsByGroupHeadsValue) {
|
||||
raw, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return change, trunk, fail(goModule, err.Error())
|
||||
}
|
||||
job := delivery.ComposeJob{Fetch: map[string]any{"repository": DeliveryRepository, "commit": commit}, Dir: JudgeDir,
|
||||
job := buildstep.ComposeJob{Fetch: buildstep.Fetch{Repository: DeliveryRepository, Commit: commit}, Dir: JudgeDir,
|
||||
Package: JudgePackage, Request: raw, Reader: "the indexer's judge at " + at}
|
||||
a := delivery.StepAsk{ID: buildstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: delivery.CheckerModule,
|
||||
a := buildstep.StepAsk{ID: buildstep.NewID(), Kind: buildstep.AskCheck, Language: buildstep.AskGo, Asker: CheckerModule,
|
||||
At: st.j.now().UTC(), Compose: &job}
|
||||
st.c.Progress.Set(fmt.Sprintf("the judge ask %s of the manifests of %s", a.ID, strings.Join(touched, ", ")), "")
|
||||
st.c.Asked(a.ID)
|
||||
@@ -231,7 +232,7 @@ const CatalogSeat = "mesh-catalog"
|
||||
// as well. The verdicts read are the trunk's manifest validator's: its change case, where refused, held (not accepted
|
||||
// with no reason) or with a field set aside (a field the running manifest validator does not know waits, ADR 0601 §3)
|
||||
// each fail it, naming the module and the problems.
|
||||
func (st *state) refuseUnmovableIndexer(ctx context.Context, req composer.Request, touched []string, cv composer.CaseVerdict, judgedAt string) *delivery.Verdict {
|
||||
func (st *state) refuseUnmovableIndexer(ctx context.Context, req composer.Request, touched []string, cv composer.CaseVerdict, judgedAt string) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
if st.j.Read == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -258,7 +259,7 @@ func (st *state) refuseUnmovableIndexer(ctx context.Context, req composer.Reques
|
||||
if trunk == "" {
|
||||
return fail(goModule, "the new indexer cannot be judged by the trunk's manifest validator: the trunk head of "+DeliveryRepository+" is not known")
|
||||
}
|
||||
var v *delivery.Verdict
|
||||
var v *meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
judgedAt = "mesh-delivery's trunk " + trunk
|
||||
if cv, _, v = st.runJudge(ctx, req, touched, trunk, judgedAt); v != nil {
|
||||
return v
|
||||
@@ -285,11 +286,11 @@ func (st *state) refuseUnmovableIndexer(ctx context.Context, req composer.Reques
|
||||
}
|
||||
|
||||
// judgeAnswer reads the judge's change and trunk cases from its step, or why it cannot.
|
||||
func judgeAnswer(step delivery.Step) (change, trunk composer.CaseVerdict, why string) {
|
||||
if step.State != delivery.StepPassed {
|
||||
func judgeAnswer(step buildstep.Step) (change, trunk composer.CaseVerdict, why string) {
|
||||
if step.State != buildstep.StepPassed {
|
||||
return change, trunk, fmt.Sprintf("its step %s: %s", step.State, step.Reason)
|
||||
}
|
||||
var a delivery.ComposeAnswer
|
||||
var a buildstep.ComposeAnswer
|
||||
if err := json.Unmarshal(step.Result, &a); err != nil {
|
||||
return change, trunk, "its step's answer cannot be read: " + err.Error()
|
||||
}
|
||||
@@ -400,7 +401,7 @@ func (st *state) knownPackages(ctx context.Context) []string {
|
||||
}
|
||||
dirs := []string{d.dir}
|
||||
if d.dir == "" {
|
||||
dirs = []string{"modules/" + d.module, delivery.RootPath}
|
||||
dirs = []string{"modules/" + d.module, meshcatalogclient.RootPath}
|
||||
}
|
||||
var why []string
|
||||
for _, dir := range dirs {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
// The holders and buckets the checker names. No contract names a holder (novox/hq ADR 0625 §4, ADR 0634 §1), so these are its
|
||||
// own constants, each a state of a seat (the seat's state name beside the holder's module name) and each declared in its
|
||||
// manifest (module.json: its own states, its `reads`); a unit row checks them against the manifest (TestTheNamesAreTheManifests).
|
||||
const (
|
||||
// CheckerModule is this part's module; its states verdicts, kept, owed and previews are the seat mesh-delivery-checker's.
|
||||
CheckerModule = "mesh-delivery-checker"
|
||||
CheckerVerdictsBucket = CheckerModule + "_" + checker.StateVerdicts
|
||||
CheckerKeptBucket = CheckerModule + "_" + checker.StateKept
|
||||
CheckerOwedBucket = CheckerModule + "_" + checker.StateOwed
|
||||
PreviewsBucket = CheckerModule + "_" + checker.StatePreviews
|
||||
|
||||
// GateModule holds the seat mesh-delivery's states source and turns, which the checker reads (reads: mesh-delivery-gate.source,
|
||||
// mesh-delivery-gate.turns).
|
||||
GateModule = "mesh-delivery-gate"
|
||||
SourceBucket = GateModule + "_" + meshdelivery.StateSource
|
||||
TurnsBucket = GateModule + "_" + meshdelivery.StateTurns
|
||||
|
||||
// IndexerModule holds the seat mesh-catalog's states, which the checker follows by their views (consumes:
|
||||
// mesh-delivery-indexer.views.*).
|
||||
IndexerModule = meshcatalogclient.Module
|
||||
CandidatesBucket = IndexerModule + "_" + meshcatalog.StateCandidates
|
||||
ModulesBucket = IndexerModule + "_" + meshcatalog.StateModules
|
||||
ManifestsBucket = IndexerModule + "_" + meshcatalog.StateManifests
|
||||
IndexerPredefinedState = meshcatalog.StatePredefined
|
||||
|
||||
// WatchmanModule holds the seat mesh-watchdogs' presence (reads: mesh-delivery-watchman.presence).
|
||||
WatchmanModule = "mesh-delivery-watchman"
|
||||
PresenceBucket = WatchmanModule + "_" + meshwatchdogs.StatePresence
|
||||
|
||||
// HaveBucket is the node-engine's state have (reads: node-engine.have); a node's own key ends in HaveNodeWord.
|
||||
HaveBucket = "node-engine_" + nodeengine.StateHave
|
||||
HaveNodeWord = "node"
|
||||
|
||||
// compositionWaits is the state of the assigner's composition answer that waits for what it composes from.
|
||||
compositionWaits = "waits"
|
||||
)
|
||||
|
||||
// FailedFirst names what failed: a test (its package and name) or a machine, with the words of the failure. It is the shape of
|
||||
// the verdict's `failed` and of each entry of its `failures` (the seat's two types of it have the same fields), so the check
|
||||
// judges with one and converts at the verdict's edge (failureItems, failedList).
|
||||
type FailedFirst = checker.VerdictsByGroupHeadsValueFailed
|
||||
|
||||
// failedOf is the first failure a verdict names: the zero value of a verdict that names none.
|
||||
func failedOf(v checker.VerdictsByGroupHeadsValue) FailedFirst {
|
||||
if v.Failed == nil {
|
||||
return FailedFirst{}
|
||||
}
|
||||
return *v.Failed
|
||||
}
|
||||
|
||||
// tookOf is the time of each part of a check a verdict names: the zero value of a verdict that names none.
|
||||
func tookOf(v checker.VerdictsByGroupHeadsValue) checker.VerdictsByGroupHeadsValueTook {
|
||||
if v.Took == nil {
|
||||
return checker.VerdictsByGroupHeadsValueTook{}
|
||||
}
|
||||
return *v.Took
|
||||
}
|
||||
|
||||
func failureItems(list []FailedFirst) []checker.VerdictsByGroupHeadsValueFailuresItem {
|
||||
if list == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]checker.VerdictsByGroupHeadsValueFailuresItem, len(list))
|
||||
for i, f := range list {
|
||||
out[i] = checker.VerdictsByGroupHeadsValueFailuresItem(f)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func failedList(items []checker.VerdictsByGroupHeadsValueFailuresItem) []FailedFirst {
|
||||
if items == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]FailedFirst, len(items))
|
||||
for i, f := range items {
|
||||
out[i] = FailedFirst(f)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
// The checker's owed state holds the one act it owes, the status mesh/check on a pull request at a head: the gate's owed act
|
||||
// with the context mesh/check. Its value has the shape of the gate's (the seat mesh-delivery's owed), so its rules and its
|
||||
// codec are the mesh-delivery client's; only the checker reads it, so the checker's own narrowing to its one act lives here.
|
||||
|
||||
// CheckerOwedAct is the act the checker owes.
|
||||
const CheckerOwedAct = meshdeliveryclient.OwedStatus + "=" + meshdeliveryclient.StatusContextCheck
|
||||
|
||||
// CheckerOwedKey is the key, in the checker's owed state, of the status the checker owes on a pull request at a head.
|
||||
func CheckerOwedKey(repository string, number int, head string) (string, error) {
|
||||
return meshdeliveryclient.OwedKey(repository, number, head, CheckerOwedAct)
|
||||
}
|
||||
|
||||
func checkCheckerOwed(o meshdeliverychecker.OwedByRepositoryNumberHeadActValue) error {
|
||||
if o.Act != meshdeliveryclient.OwedStatus || o.Context != meshdeliveryclient.StatusContextCheck {
|
||||
return fmt.Errorf("the checker owes only a status with the context %s, not the act %q with the context %q", meshdeliveryclient.StatusContextCheck, o.Act, o.Context)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeCheckerOwed is o as the gate's owed value, refused for any act but the checker's status.
|
||||
func EncodeCheckerOwed(o meshdeliverychecker.OwedByRepositoryNumberHeadActValue) ([]byte, error) {
|
||||
if err := checkCheckerOwed(o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return meshdeliveryclient.EncodeOwed(meshdelivery.OwedByRepositoryNumberHeadActValue(o))
|
||||
}
|
||||
|
||||
// DecodeCheckerOwed reads an owed act of the checker's state.
|
||||
func DecodeCheckerOwed(data []byte) (meshdeliverychecker.OwedByRepositoryNumberHeadActValue, error) {
|
||||
g, err := meshdeliveryclient.DecodeOwed(data)
|
||||
if err != nil {
|
||||
return meshdeliverychecker.OwedByRepositoryNumberHeadActValue{}, err
|
||||
}
|
||||
o := meshdeliverychecker.OwedByRepositoryNumberHeadActValue(g)
|
||||
if err := checkCheckerOwed(o); err != nil {
|
||||
return meshdeliverychecker.OwedByRepositoryNumberHeadActValue{}, err
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"errors"
|
||||
"maps"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
||||
)
|
||||
|
||||
// **A check builds against the group's own package heads, not against what a member names** (novox/hq ADR 0573 §2, §4;
|
||||
// issue 1585). Every Go check ask carries the version of each mesh package to build against (delivery.StepAsk.Packages):
|
||||
// issue 1585). Every Go check ask carries the version of each mesh package to build against (buildstep.StepAsk.Packages):
|
||||
// the head of the group's member that publishes it, else its latest published version as the indexer holds it. The build
|
||||
// module writes them into the workspace, whatever version or commit a member's go.mod names, so no member is refused
|
||||
// for naming no version of the group's package, an older head of it, a merge commit or a commit on no pull request: that
|
||||
@@ -37,8 +38,8 @@ type Forge interface {
|
||||
// also a package new in the group, which has no published version yet), else that member's head (mesh-sdk's Go module
|
||||
// whenever mesh-sdk is a member). A package of repository itself comes with the tree the check fetched, and is left out.
|
||||
// Nil for none.
|
||||
func (st *state) packagesOf(repository string) delivery.PackageVersions {
|
||||
versions := delivery.PackageVersions{}
|
||||
func (st *state) packagesOf(repository string) buildstep.PackageVersions {
|
||||
versions := buildstep.PackageVersions{}
|
||||
if st.j.Published != nil {
|
||||
maps.Copy(versions, st.j.Published.LatestPackages())
|
||||
}
|
||||
@@ -51,8 +52,8 @@ func (st *state) packagesOf(repository string) delivery.PackageVersions {
|
||||
versions[pkg] = ""
|
||||
}
|
||||
for pkg := range versions {
|
||||
repo, _, ok := delivery.PackageRepository(pkg)
|
||||
if !ok || delivery.SameRepository(repo, repository) {
|
||||
repo, _, ok := trunk.PackageRepository(pkg)
|
||||
if !ok || trunk.SameRepository(repo, repository) {
|
||||
delete(versions, pkg)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -11,21 +11,22 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// published is the latest published version of each mesh package, as the indexer's modules say (Examiner.Published).
|
||||
type published delivery.PackageVersions
|
||||
type published buildstep.PackageVersions
|
||||
|
||||
func (p published) LatestPackages() delivery.PackageVersions {
|
||||
return maps.Clone(delivery.PackageVersions(p))
|
||||
func (p published) LatestPackages() buildstep.PackageVersions {
|
||||
return maps.Clone(buildstep.PackageVersions(p))
|
||||
}
|
||||
|
||||
// packagesOfFetch is the package versions the fetch of repository was handed (delivery.StepAsk.Packages, issue 1585).
|
||||
func packagesOfFetch(t *testing.T, b *bench, repository string) delivery.PackageVersions {
|
||||
// packagesOfFetch is the package versions the fetch of repository was handed (buildstep.StepAsk.Packages, issue 1585).
|
||||
func packagesOfFetch(t *testing.T, b *bench, repository string) buildstep.PackageVersions {
|
||||
t.Helper()
|
||||
for _, f := range b.steps.tool("fetch") {
|
||||
if f.args["repository"] == repository {
|
||||
@@ -50,7 +51,7 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes
|
||||
b.fetch.Modules = []string{"modules/m"}
|
||||
b.ex.Published = published{SDKModulePath: trunkS, contract: trunkA}
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict
|
||||
if v.State != delivery.VerdictPass {
|
||||
if v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
if got := packagesOfFetch(t, b, repoA); len(got) != 1 || got[SDKModulePath] != head2 {
|
||||
@@ -61,7 +62,7 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes
|
||||
}
|
||||
b = newBench(t)
|
||||
b.ex.Published = published{SDKModulePath: trunkS, contract: trunkA}
|
||||
if v := b.judge(t, caseOf(t, pullOf("novox/mesh-catalog", 7, head1))).Verdict; v.State != delivery.VerdictPass {
|
||||
if v := b.judge(t, caseOf(t, pullOf("novox/mesh-catalog", 7, head1))).Verdict; v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("a pull request alone: verdict = %+v", v)
|
||||
}
|
||||
if got := packagesOfFetch(t, b, "novox/mesh-catalog"); len(got) != 2 || got[SDKModulePath] != trunkS || got[contract] != trunkA {
|
||||
@@ -76,27 +77,27 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes
|
||||
{Name: "go", Kind: catalogue.ArtifactPackage, Language: "go", Paths: []string{"go"}},
|
||||
{Name: "go-gopackageregistry", Kind: catalogue.ArtifactPackage, Language: "go", Paths: []string{"go/seats/gopackageregistry"}}}}}
|
||||
digest, contractsDigest := strings.Repeat("c", 64), strings.Repeat("d", 64)
|
||||
sdkPull := func() delivery.CandidatePull {
|
||||
sdkPull := func() meshcatalog.CandidatesByGroupValuePullsItem {
|
||||
p := pullOf(repoSDK, 12, head2)
|
||||
p.Modules = []string{"mesh-sdk"}
|
||||
p.Fps = map[string]delivery.CandidateModule{"mesh-sdk": {FP2: "src2:" + strings.Repeat("f", 64), Manifest: digest}}
|
||||
p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"mesh-sdk": {Fp2: "src2:" + strings.Repeat("f", 64), Manifest: digest}}
|
||||
return p
|
||||
}
|
||||
unpreviewed := func() delivery.CandidatePull { // a package new in the group: no preview carries its manifest yet
|
||||
unpreviewed := func() meshcatalog.CandidatesByGroupValuePullsItem { // a package new in the group: no preview carries its manifest yet
|
||||
p := sdkPull()
|
||||
p.Fps = map[string]delivery.CandidateModule{"mesh-sdk": {FP2: "src2:" + strings.Repeat("f", 64)}}
|
||||
p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"mesh-sdk": {Fp2: "src2:" + strings.Repeat("f", 64)}}
|
||||
return p
|
||||
}
|
||||
contractsPull := func() delivery.CandidatePull {
|
||||
contractsPull := func() meshcatalog.CandidatesByGroupValuePullsItem {
|
||||
p := pullOf("novox/mesh-catalog", 4, strings.Repeat("3", 40))
|
||||
p.Modules = []string{"mesh-contracts"}
|
||||
p.Fps = map[string]delivery.CandidateModule{"mesh-contracts": {FP2: "src2:" + strings.Repeat("e", 64), Manifest: contractsDigest}}
|
||||
p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"mesh-contracts": {Fp2: "src2:" + strings.Repeat("e", 64), Manifest: contractsDigest}}
|
||||
return p
|
||||
}
|
||||
const sdkGo, contractsGo, seatGo = SDKModulePath, "git.novox.be/novox/mesh-catalog/go", "git.novox.be/novox/mesh-catalog/go/seats/gopackageregistry"
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
pulls []delivery.CandidatePull
|
||||
pulls []meshcatalog.CandidatesByGroupValuePullsItem
|
||||
published []string // the seats that publish the members' heads
|
||||
wants int
|
||||
fails string
|
||||
@@ -107,19 +108,19 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes
|
||||
// noModule: the Go seat's published names no Go module path (ruling 1 on A2d's finding 1: never worked out).
|
||||
noModule bool
|
||||
}{
|
||||
{name: "a group", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), sdkPull()},
|
||||
{name: "a group", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), sdkPull()},
|
||||
published: []string{"go-package-registry", "npm-package-registry"}, wants: 2,
|
||||
built: map[string]string{sdkGo: "v0.1.15-feat-x.1"}, node: map[string]any{"name": "lib", "version": "0.1.15-feat-x.1"}},
|
||||
{name: "a package new in the group, read at its head", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), unpreviewed()},
|
||||
{name: "a package new in the group, read at its head", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), unpreviewed()},
|
||||
published: []string{"go-package-registry", "npm-package-registry"}, wants: 2, built: map[string]string{sdkGo: "v0.1.15-feat-x.1"}},
|
||||
{name: "one head, two Go packages (as mesh-contracts' head)", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), contractsPull()},
|
||||
{name: "one head, two Go packages (as mesh-contracts' head)", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), contractsPull()},
|
||||
published: []string{"go-package-registry"}, wants: 2,
|
||||
built: map[string]string{contractsGo: "v0.1.15-feat-x.1", seatGo: "v0.1.3-feat-x.1"}},
|
||||
{name: "a lone pull request", pulls: []delivery.CandidatePull{sdkPull()}},
|
||||
{name: "a Go published naming no module", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), sdkPull()},
|
||||
{name: "a lone pull request", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{sdkPull()}},
|
||||
{name: "a Go published naming no module", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), sdkPull()},
|
||||
published: []string{"go-package-registry", "npm-package-registry"}, wants: 2, noModule: true,
|
||||
fails: "no published of the tagged version of go of mesh-sdk (novox/mesh-sdk#12) on go-package-registry within 50ms"},
|
||||
{name: "a package never published", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), sdkPull()},
|
||||
{name: "a package never published", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), sdkPull()},
|
||||
published: []string{"go-package-registry"}, wants: 2,
|
||||
fails: "no published of the tagged version of lib of mesh-sdk (novox/mesh-sdk#12) on npm-package-registry within 50ms"},
|
||||
} {
|
||||
@@ -131,7 +132,7 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes
|
||||
b.ex.Forge = headFiles{repoSDK + " module.json " + head2: string(raw)}
|
||||
tags := &recordedTags{heard: &PublishedHeard{}, publish: row.published, noModule: row.noModule}
|
||||
b.ex.Tags, b.ex.PublishBound = tags, 50*time.Millisecond
|
||||
b.asks.Answer = func(a delivery.StepAsk) delivery.Step {
|
||||
b.asks.Answer = func(a buildstep.StepAsk) buildstep.Step {
|
||||
if len(tags.wanted) != row.wants {
|
||||
t.Errorf("%s: a member was asked before every tag-wanted was said: %d of %d", row.name, len(tags.wanted), row.wants)
|
||||
}
|
||||
@@ -147,9 +148,9 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case row.fails == "" && v.State != delivery.VerdictPass:
|
||||
case row.fails == "" && v.State != meshdeliverycheckerclient.VerdictPass:
|
||||
t.Errorf("%s: verdict = %+v", row.name, v)
|
||||
case row.fails != "" && (v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, row.fails)):
|
||||
case row.fails != "" && (v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(v.Failed.Words, row.fails)):
|
||||
t.Errorf("%s: verdict = %+v, want it to fail saying %q", row.name, v, row.fails)
|
||||
case row.fails != "" && len(b.steps.tool("fetch")) != 0:
|
||||
t.Errorf("%s: a member was built though a package was not published", row.name)
|
||||
@@ -247,7 +248,7 @@ func TestTheSiblingsAreWhereTheTestsStateThem(t *testing.T) { // review D2, ADR
|
||||
t.Errorf("the siblings named for the verdict = %v, want the node-engine at %s", out.Beside, hostRuns[:12])
|
||||
}
|
||||
got := besideOf(t, b, SDKRepository)
|
||||
if c := got[catalogue]; c.Commit != head2 || c.Ref != "refs/pull/12/head" {
|
||||
if c := got[catalogue]; c.Commit != head2 {
|
||||
t.Errorf("the catalogue beside mesh-sdk in a group = %+v, want the member's head %s", c, head2)
|
||||
}
|
||||
if h := got[host]; h.Commit != hostRuns {
|
||||
@@ -258,7 +259,7 @@ func TestTheSiblingsAreWhereTheTestsStateThem(t *testing.T) { // review D2, ADR
|
||||
b = newBench(t)
|
||||
b.ex.Trunks = trunksOf(t, map[string]string{SDKRepository: trunkS, catalogue: trunkA, host: trunkA2, networking: trunkA2})
|
||||
b.judge(t, caseOf(t, pullOf(host, 8, head1), pullOf(networking, 3, head2)))
|
||||
if n := besideOf(t, b, host)[networking]; n.Commit != head2 || n.Ref != "refs/pull/3/head" {
|
||||
if n := besideOf(t, b, host)[networking]; n.Commit != head2 {
|
||||
t.Errorf("mesh-networking beside the node-engine in a group = %+v, want the member's head %s", n, head2)
|
||||
}
|
||||
// Alone, mesh-sdk's tests read the catalogue's trunk, and the verdict names it.
|
||||
@@ -327,7 +328,7 @@ func TestACatalogueChangeHasTheCatalogueBesideItAtItsTrunk(t *testing.T) {
|
||||
func TestAMeshLabHeadGetsACheck(t *testing.T) {
|
||||
const lab = "novox/mesh-lab"
|
||||
raw := candidateOf(t, "fix/x", pullOf(lab, 3, head1))
|
||||
c, err := delivery.DecodeCandidate(raw)
|
||||
c, err := meshcatalog.DecodeCandidatesByGroupValue(raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -336,7 +337,7 @@ func TestAMeshLabHeadGetsACheck(t *testing.T) {
|
||||
t.Fatalf("the case = %+v, want mesh-lab#3 judged", cs)
|
||||
}
|
||||
b := newBench(t)
|
||||
if v := b.judge(t, cs).Verdict; v.State != delivery.VerdictPass {
|
||||
if v := b.judge(t, cs).Verdict; v.State != meshdeliverycheckerclient.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
if fetched := b.steps.tool("fetch"); len(fetched) == 0 || fetched[0].args["repository"] != lab {
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
"regexp"
|
||||
"slices"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
var digest64 = regexp.MustCompile(`^[0-9a-f]{64}$`)
|
||||
@@ -19,10 +20,10 @@ var digest64 = regexp.MustCompile(`^[0-9a-f]{64}$`)
|
||||
// indexer gives its module no fingerprint and says why. A head that does not merge has a preview of the files in conflict
|
||||
// alone, so the indexer says the candidate's modules have no fingerprint because it does not merge (issue 1445). It
|
||||
// answers false when no fetch was made.
|
||||
func PreviewOf(p Pull, fetched []Fetched) (delivery.Preview, bool) {
|
||||
func PreviewOf(p Pull, fetched []Fetched) (meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue, bool) {
|
||||
var merge string
|
||||
var conflicts []string
|
||||
mods := map[string]delivery.PreviewModule{}
|
||||
mods := map[string]meshdeliverychecker.PreviewsByRepositoryHeadTrunkValueModulesValue{}
|
||||
for _, f := range fetched {
|
||||
if len(f.Conflicts) > 0 {
|
||||
conflicts = append(conflicts, f.Conflicts...)
|
||||
@@ -30,7 +31,7 @@ func PreviewOf(p Pull, fetched []Fetched) (delivery.Preview, bool) {
|
||||
}
|
||||
if len(conflicts) > 0 {
|
||||
slices.Sort(conflicts)
|
||||
return delivery.Preview{Repository: p.Repository, Head: p.Head, Trunk: p.Trunk, Conflicts: slices.Compact(conflicts)}, true
|
||||
return meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue{Repository: p.Repository, Head: p.Head, Trunk: p.Trunk, Conflicts: slices.Compact(conflicts)}, true
|
||||
}
|
||||
for _, f := range fetched {
|
||||
if f.Merge == "" {
|
||||
@@ -43,13 +44,13 @@ func PreviewOf(p Pull, fetched []Fetched) (delivery.Preview, bool) {
|
||||
if t.Tree == "" || !digest64.MatchString(t.Manifest) {
|
||||
continue
|
||||
}
|
||||
mods[dir] = delivery.PreviewModule{Tree: t.Tree, Manifest: t.Manifest, Locks: t.Locks, Paths: t.Paths, Packages: t.Packages}
|
||||
mods[dir] = meshdeliverychecker.PreviewsByRepositoryHeadTrunkValueModulesValue{Tree: t.Tree, Manifest: t.Manifest, Locks: t.Locks, Paths: t.Paths, Packages: t.Packages}
|
||||
}
|
||||
}
|
||||
if merge == "" {
|
||||
return delivery.Preview{}, false
|
||||
return meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue{}, false
|
||||
}
|
||||
pv := delivery.Preview{Repository: p.Repository, Head: p.Head, Trunk: p.Trunk, Merge: merge}
|
||||
pv := meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue{Repository: p.Repository, Head: p.Head, Trunk: p.Trunk, Merge: merge}
|
||||
if len(mods) > 0 {
|
||||
pv.Modules = mods
|
||||
}
|
||||
@@ -67,10 +68,10 @@ func (j *Examiner) writePreview(ctx context.Context, p Pull, fetched []Fetched)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
key, err := delivery.PreviewKey(p.Repository, p.Head, p.Trunk)
|
||||
key, err := meshdeliverycheckerclient.PreviewKey(p.Repository, p.Head, p.Trunk)
|
||||
if err == nil {
|
||||
var data []byte
|
||||
if data, err = delivery.EncodePreview(pv); err == nil {
|
||||
if data, err = meshdeliverycheckerclient.EncodePreview(pv); err == nil {
|
||||
_, err = j.Previews.Create(ctx, key, data)
|
||||
if errors.Is(err, ErrConflict) {
|
||||
return
|
||||
|
||||
@@ -6,7 +6,10 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
// The checker's ranked start of checks (ADR 0536 §4, issue 1897): a freed place goes to a head never judged (or asked again
|
||||
@@ -47,7 +50,7 @@ func TestCheckerStatusNamesTheWaitingChecksByKind(t *testing.T) { // 1897/AC4
|
||||
cands := fakeState{}
|
||||
put := func(n int, head string) {
|
||||
cand := candidateOf(t, group(n), pullOf(repoA, n, head))
|
||||
ckey, _ := delivery.CandidatesKey(group(n))
|
||||
ckey, _ := meshcatalogclient.CandidatesKey(group(n))
|
||||
cands[ckey] = cand
|
||||
r.ch.Candidate(ctx, group(n), cand)
|
||||
}
|
||||
@@ -58,7 +61,7 @@ func TestCheckerStatusNamesTheWaitingChecksByKind(t *testing.T) { // 1897/AC4
|
||||
put(4, head1) // judged on the new trunk: stands
|
||||
r.ch.Wait()
|
||||
cand3 := candidateOf(t, group(3), pullOf(repoA, 3, head1)) // never judged
|
||||
ckey3, _ := delivery.CandidatesKey(group(3))
|
||||
ckey3, _ := meshcatalogclient.CandidatesKey(group(3))
|
||||
cands[ckey3] = cand3
|
||||
verdicts := fakeState{}
|
||||
for k, v := range r.verdicts.vals {
|
||||
@@ -80,10 +83,10 @@ func TestCheckerStatusNamesTheWaitingChecksByKind(t *testing.T) { // 1897/AC4
|
||||
// is still the one the mesh runs, though the node-engine's trunk has moved on (ADR 0548 §6).
|
||||
func TestCheckerStatusReadsTheNodeEngineAtItsRunningCommit(t *testing.T) { // 568/21
|
||||
cand := candidateOf(t, group(1), pullOf(repoA, 1, head1))
|
||||
ckey, _ := delivery.CandidatesKey(group(1))
|
||||
ckey, _ := meshcatalogclient.CandidatesKey(group(1))
|
||||
vkey := keyOf(t, group(1), cand)
|
||||
dc, _ := delivery.DecodeCandidate(cand)
|
||||
judged, err := delivery.EncodeVerdict(delivery.Verdict{Group: group(1), Heads: dc.HeadsHash, Trunk: map[string]string{repoA: trunkA}, Beside: map[string]string{nodeEngineRepo: "run111"}, State: delivery.VerdictPass, Levels: passing().Levels})
|
||||
dc, _ := meshcatalog.DecodeCandidatesByGroupValue(cand)
|
||||
judged, err := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: group(1), Heads: dc.Heads, Trunk: map[string]string{repoA: trunkA}, Beside: map[string]string{nodeEngineRepo: "run111"}, State: meshdeliverycheckerclient.VerdictPass, Levels: passing().Levels})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// The read tools (novox/hq ADR 0484 clauses 8 and 13, issue 1686): what the checker stored, as stored, with the line of the
|
||||
@@ -68,28 +73,28 @@ type VerdictRead struct {
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
func (r Reader) candidate(group string) (delivery.Candidate, bool) {
|
||||
key, err := delivery.CandidatesKey(group)
|
||||
func (r Reader) candidate(group string) (meshcatalog.CandidatesByGroupValue, bool) {
|
||||
key, err := meshcatalogclient.CandidatesKey(group)
|
||||
if err != nil || r.CandidateState.Get == nil {
|
||||
return delivery.Candidate{}, false
|
||||
return meshcatalog.CandidatesByGroupValue{}, false
|
||||
}
|
||||
raw, err := r.CandidateState.Get(key)
|
||||
if err != nil || len(raw) == 0 {
|
||||
return delivery.Candidate{}, false
|
||||
return meshcatalog.CandidatesByGroupValue{}, false
|
||||
}
|
||||
c, err := delivery.DecodeCandidate(raw)
|
||||
c, err := meshcatalog.DecodeCandidatesByGroupValue(raw)
|
||||
return c, err == nil
|
||||
}
|
||||
|
||||
func (r Reader) read(key string, raw json.RawMessage) (VerdictRead, error) {
|
||||
out := VerdictRead{Key: key, Verdict: raw}
|
||||
v, err := delivery.DecodeVerdict(raw)
|
||||
v, err := meshdeliverycheckerclient.DecodeVerdict(raw)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("%s holds what is not a verdict: %w", key, err)
|
||||
}
|
||||
out.Failed = failedLines(v)
|
||||
c, ok := r.candidate(v.Group)
|
||||
if !ok || (c.HeadsHash != v.Heads && GateHeads(c) != v.Heads) {
|
||||
if !ok || (c.Heads != v.Heads && GateHeads(c) != v.Heads) {
|
||||
out.Note = "the candidate no longer holds these heads"
|
||||
return out, nil
|
||||
}
|
||||
@@ -98,7 +103,7 @@ func (r Reader) read(key string, raw json.RawMessage) (VerdictRead, error) {
|
||||
if cs == nil {
|
||||
if IsUntrunked(v) {
|
||||
for _, p := range leftOf(c, v.Trunk) {
|
||||
out.Reports[pullKey(p)] = v.Failed.Words
|
||||
out.Reports[pullKey(p)] = failedOf(v).Words
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
@@ -126,7 +131,7 @@ func (r Reader) Verdicts(group string) (out []VerdictRead, cut string, err error
|
||||
var matching []string
|
||||
for _, k := range keys {
|
||||
if group != "" {
|
||||
if g, _, err := delivery.ParseCheckerVerdictKey(k); err != nil || g != group {
|
||||
if g, _, err := meshdeliverycheckerclient.ParseVerdictKey(k); err != nil || g != group {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -169,14 +174,14 @@ func (r Reader) Pull(repository string, number int) (VerdictRead, error) {
|
||||
// The candidates are read in batches, each in parallel and taken in key order, so the read stops at the batch that holds it.
|
||||
for from := 0; from < len(keys); from += ReadWorkers * 4 {
|
||||
batch := keys[from:min(from+ReadWorkers*4, len(keys))]
|
||||
cands := make([]delivery.Candidate, len(batch))
|
||||
cands := make([]meshcatalog.CandidatesByGroupValue, len(batch))
|
||||
ok := make([]bool, len(batch))
|
||||
_ = parallel(len(batch), func(i int) error {
|
||||
raw, err := r.CandidateState.Get(batch[i])
|
||||
if err != nil || len(raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
c, err := delivery.DecodeCandidate(raw)
|
||||
c, err := meshcatalog.DecodeCandidatesByGroupValue(raw)
|
||||
cands[i], ok[i] = c, err == nil
|
||||
return nil
|
||||
})
|
||||
@@ -185,10 +190,10 @@ func (r Reader) Pull(repository string, number int) (VerdictRead, error) {
|
||||
continue
|
||||
}
|
||||
for _, p := range c.Pulls {
|
||||
if p.Repository != repository || p.Number != number {
|
||||
if p.Repository != repository || int(p.Number) != number {
|
||||
continue
|
||||
}
|
||||
vkey, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash)
|
||||
vkey, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads)
|
||||
if err != nil {
|
||||
return VerdictRead{}, err
|
||||
}
|
||||
@@ -215,7 +220,7 @@ func (r Reader) Kept(level, digest string) (map[string]json.RawMessage, string,
|
||||
return nil, "", fmt.Errorf("the kept state cannot be read here")
|
||||
}
|
||||
if digest != "" {
|
||||
key, err := delivery.KeptKey(level, digest)
|
||||
key, err := meshdeliverycheckerclient.KeptKey(level, digest)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -235,7 +240,7 @@ func (r Reader) Kept(level, digest string) (map[string]json.RawMessage, string,
|
||||
sort.Strings(keys)
|
||||
var matching []string
|
||||
for _, k := range keys {
|
||||
if l, _, err := delivery.ParseKeptKey(k); err == nil && (level == "" || l == level) {
|
||||
if l, _, err := meshdeliverycheckerclient.ParseKeptKey(k); err == nil && (level == "" || l == level) {
|
||||
matching = append(matching, k)
|
||||
}
|
||||
}
|
||||
@@ -280,7 +285,7 @@ func (r Reader) Previews(repository, head string) ([]PreviewRead, []string, stri
|
||||
parts := map[string]part{}
|
||||
var matching, bad []string
|
||||
for _, k := range keys {
|
||||
repo, h, trunk, err := delivery.ParsePreviewKey(k)
|
||||
repo, h, trunk, err := meshdeliverycheckerclient.ParsePreviewKey(k)
|
||||
if err != nil {
|
||||
bad = append(bad, k)
|
||||
continue
|
||||
@@ -398,7 +403,7 @@ func (r Reader) turns(st *Status) *Turns {
|
||||
}
|
||||
|
||||
// heldBy says the turn that holds a candidate's check off, or "".
|
||||
func heldBy(t *Turns, c delivery.Candidate, heads map[string]string) string {
|
||||
func heldBy(t *Turns, c meshcatalog.CandidatesByGroupValue, heads map[string]string) string {
|
||||
cs := CaseOf(c, heads)
|
||||
if t == nil || cs == nil {
|
||||
return ""
|
||||
@@ -451,7 +456,7 @@ func (r Reader) Status() (Status, error) {
|
||||
st.Notes = append(st.Notes, fmt.Sprintf("read the first %d candidates of %d", MaxReads, total))
|
||||
}
|
||||
type judged struct {
|
||||
c delivery.Candidate
|
||||
c meshcatalog.CandidatesByGroupValue
|
||||
raw json.RawMessage
|
||||
ok bool
|
||||
}
|
||||
@@ -461,11 +466,11 @@ func (r Reader) Status() (Status, error) {
|
||||
if err != nil || len(raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
c, err := delivery.DecodeCandidate(raw)
|
||||
c, err := meshcatalog.DecodeCandidatesByGroupValue(raw)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
vkey, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash)
|
||||
vkey, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -501,10 +506,10 @@ func (r Reader) Status() (Status, error) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
v, err := delivery.DecodeVerdict(raw)
|
||||
v, err := meshdeliverycheckerclient.DecodeVerdict(raw)
|
||||
switch {
|
||||
case err != nil:
|
||||
case v.State == delivery.VerdictRunning:
|
||||
case v.State == meshdeliverycheckerclient.VerdictRunning:
|
||||
if why := read.lostWhy(c); why != "" {
|
||||
st.Lost[c.Group] = why
|
||||
}
|
||||
@@ -545,7 +550,7 @@ type stepsRead struct {
|
||||
|
||||
type moduleStep struct {
|
||||
module string
|
||||
step delivery.Step
|
||||
step buildstep.Step
|
||||
}
|
||||
|
||||
// readSteps reads the newest MaxReads keys of each build module's steps state.
|
||||
@@ -579,14 +584,14 @@ func (r Reader) readSteps() stepsRead {
|
||||
if cut != "" {
|
||||
out.notes = append(out.notes, "the steps of "+m+": read the newest "+fmt.Sprint(MaxReads)+" of its keys; a step older than those is not shown")
|
||||
}
|
||||
steps := make([]delivery.Step, len(keys))
|
||||
steps := make([]buildstep.Step, len(keys))
|
||||
decoded := make([]bool, len(keys))
|
||||
_ = parallel(len(keys), func(i int) error {
|
||||
raw, err := sr.Get(keys[i])
|
||||
if err != nil || len(raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
step, err := delivery.DecodeStep(raw)
|
||||
step, err := buildstep.DecodeStep(raw)
|
||||
steps[i], decoded[i] = step, err == nil
|
||||
return nil
|
||||
})
|
||||
@@ -596,7 +601,7 @@ func (r Reader) readSteps() stepsRead {
|
||||
}
|
||||
step := steps[i]
|
||||
out.all = append(out.all, moduleStep{module: m, step: step})
|
||||
if step.State != delivery.StepRunning {
|
||||
if step.State != buildstep.StepRunning {
|
||||
continue
|
||||
}
|
||||
rs := RunningStep{Module: m, Node: step.Node, ID: step.ID, Tool: step.Tool, What: step.What}
|
||||
@@ -615,9 +620,9 @@ func (r Reader) readSteps() stepsRead {
|
||||
|
||||
// asksOf says whether a step is an ask of one of the candidate's pull requests: its What names `<repository>@<head12>`
|
||||
// (buildstep.AskWhat).
|
||||
func asksOf(c delivery.Candidate, what string) bool {
|
||||
func asksOf(c meshcatalog.CandidatesByGroupValue, what string) bool {
|
||||
for _, p := range c.Pulls {
|
||||
if p.Repository != "" && strings.Contains(what, p.Repository+"@"+short12(p.Head.Head)) {
|
||||
if p.Repository != "" && strings.Contains(what, p.Repository+"@"+short12(p.Head)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -626,16 +631,16 @@ func asksOf(c delivery.Candidate, what string) bool {
|
||||
|
||||
// lostWhy says why a candidate whose verdict is running has a check that nothing will judge, or "": no step of its pull
|
||||
// requests can still run, and its newest one ended.
|
||||
func (s stepsRead) lostWhy(c delivery.Candidate) string {
|
||||
func (s stepsRead) lostWhy(c meshcatalog.CandidatesByGroupValue) string {
|
||||
for _, rs := range s.running {
|
||||
if asksOf(c, rs.What) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
var last *delivery.Step
|
||||
var last *buildstep.Step
|
||||
for i := range s.all {
|
||||
st := s.all[i].step
|
||||
if st.State != delivery.StepRunning && asksOf(c, st.What) && (last == nil || st.Ended.After(last.Ended)) {
|
||||
if st.State != buildstep.StepRunning && asksOf(c, st.What) && (last == nil || st.Ended.After(last.Ended)) {
|
||||
last = &s.all[i].step
|
||||
}
|
||||
}
|
||||
@@ -653,9 +658,9 @@ const DefaultStepBound = 30 * time.Minute
|
||||
// leftWhy says why a key in the running state runs no step any more, or "" when it can still be running: its build module
|
||||
// is not on its node or is not running there (the node-engine's have), or it started longer ago than the step bound, when
|
||||
// the checker stopped waiting for it.
|
||||
func (r Reader) leftWhy(module string, step delivery.Step, now time.Time, bound time.Duration) string {
|
||||
func (r Reader) leftWhy(module string, step buildstep.Step, now time.Time, bound time.Duration) string {
|
||||
if r.HaveState.Get != nil {
|
||||
key, err := delivery.HaveKey(step.Node, module)
|
||||
key, err := nodeengineclient.HaveKey(step.Node, module)
|
||||
if err == nil {
|
||||
raw, err := r.HaveState.Get(key)
|
||||
switch {
|
||||
@@ -663,7 +668,7 @@ func (r Reader) leftWhy(module string, step delivery.Step, now time.Time, bound
|
||||
case len(raw) == 0:
|
||||
return fmt.Sprintf("%s is not on %s", module, step.Node)
|
||||
default:
|
||||
if h, err := delivery.DecodeHave(raw); err == nil && h.Phase != delivery.PhaseRunning {
|
||||
if h, err := nodeengineclient.DecodeHave(raw); err == nil && h.Phase != nodeengineclient.PhaseRunning {
|
||||
return fmt.Sprintf("%s on %s is %s", module, step.Node, h.Phase)
|
||||
}
|
||||
}
|
||||
@@ -680,17 +685,17 @@ func (r Reader) leftWhy(module string, step delivery.Step, now time.Time, bound
|
||||
|
||||
// buildsNotEnded are the builder's builds of the candidate's changed modules that are neither done nor failed, as
|
||||
// Status.BuildsNotEnded says them; empty when none, or when the builder's builds are not readable here.
|
||||
func (r Reader) buildsNotEnded(c delivery.Candidate) string {
|
||||
func (r Reader) buildsNotEnded(c meshcatalog.CandidatesByGroupValue) string {
|
||||
if r.BuildState.Get == nil {
|
||||
return ""
|
||||
}
|
||||
var out []string
|
||||
for _, p := range c.Pulls {
|
||||
for module, cm := range p.Fps {
|
||||
if cm.FP2 == "" || cm.Manifest == "" {
|
||||
if cm.Fp2 == "" || cm.Manifest == "" {
|
||||
continue
|
||||
}
|
||||
key, err := delivery.BuilderBuildKey(module, cm.FP2)
|
||||
key, err := builds.BuilderBuildKey(module, cm.Fp2)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -700,8 +705,8 @@ func (r Reader) buildsNotEnded(c delivery.Candidate) string {
|
||||
}
|
||||
state := "not begun"
|
||||
if len(raw) > 0 {
|
||||
b, err := delivery.DecodeBuilderBuild(raw)
|
||||
if err != nil || b.State == delivery.BuildDone || b.State == delivery.BuildFailed {
|
||||
b, err := builds.DecodeBuilderBuild(raw)
|
||||
if err != nil || b.State == builds.BuildDone || b.State == builds.BuildFailed {
|
||||
continue
|
||||
}
|
||||
state = b.State
|
||||
|
||||
@@ -9,7 +9,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
buildsrec "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
type fakeState map[string][]byte
|
||||
@@ -40,7 +47,7 @@ func readerOf(t *testing.T) (Reader, string) {
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1), pullOf(hqRepo, 3, head2))
|
||||
r.ch.Candidate(context.Background(), "feat%2Fx", cand)
|
||||
r.ch.Wait()
|
||||
ckey, _ := delivery.CandidatesKey("feat/x")
|
||||
ckey, _ := meshcatalogclient.CandidatesKey("feat/x")
|
||||
verdicts, kept := fakeState{}, fakeState{}
|
||||
for k, v := range r.verdicts.vals {
|
||||
verdicts[k] = v
|
||||
@@ -58,7 +65,7 @@ func TestTheVerdictToolsAnswerTheStoredVerdictWithTheFullReportOfEachPullRequest
|
||||
if err != nil || len(vs) != 1 || vs[0].Key != key {
|
||||
t.Fatalf("verdicts = %+v, %v", vs, err)
|
||||
}
|
||||
if _, err := delivery.DecodeVerdict(vs[0].Verdict); err != nil {
|
||||
if _, err := meshdeliverycheckerclient.DecodeVerdict(vs[0].Verdict); err != nil {
|
||||
t.Errorf("the verdict is not as stored: %v", err)
|
||||
}
|
||||
if got := vs[0].Reports["novox/mesh-delivery#7"]; !strings.HasPrefix(got, "pass 111111111111 on aaaaaaaaaaaa") || !strings.Contains(got, "composition not yet run") {
|
||||
@@ -68,9 +75,9 @@ func TestTheVerdictToolsAnswerTheStoredVerdictWithTheFullReportOfEachPullRequest
|
||||
t.Errorf("another group's verdicts = %+v", only)
|
||||
}
|
||||
// A failed verdict is answered with every failure it names, one line each (the operator, 2026-11-02).
|
||||
raw, _ := delivery.EncodeVerdict(delivery.Verdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.VerdictFail,
|
||||
Failed: delivery.FailedFirst{Test: "unit: m/a.TestA", Words: "a_test.go:3: boom\nmore"},
|
||||
Failures: []delivery.FailedFirst{{Test: "unit: m/a.TestA", Words: "a_test.go:3: boom"}, {Machine: "g14", Words: "route-proxy is defined twice"}}})
|
||||
raw, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.VerdictFail,
|
||||
Failed: &FailedFirst{Test: "unit: m/a.TestA", Words: "a_test.go:3: boom\nmore"},
|
||||
Failures: failureItems([]FailedFirst{{Test: "unit: m/a.TestA", Words: "a_test.go:3: boom"}, {Machine: "g14", Words: "route-proxy is defined twice"}})})
|
||||
if got, _ := rd.read(key, raw); strings.Join(got.Failed, " | ") != "unit: m/a.TestA: a_test.go:3: boom | g14: route-proxy is defined twice" {
|
||||
t.Errorf("failed = %q", got.Failed)
|
||||
}
|
||||
@@ -104,7 +111,7 @@ func TestTheKeptToolAnswersTheKeysAsStoredByLevelAndByDigest(t *testing.T) { //
|
||||
t.Fatalf("one kept = %v, %v", one, err)
|
||||
}
|
||||
for _, raw := range one {
|
||||
if k, err := delivery.DecodeKept(raw); err != nil || k.Package != "m/a" {
|
||||
if k, err := meshdeliverycheckerclient.DecodeKept(raw); err != nil || k.Package != "m/a" {
|
||||
t.Errorf("kept value = %+v, %v", k, err)
|
||||
}
|
||||
}
|
||||
@@ -118,12 +125,12 @@ func TestTheKeptToolAnswersTheKeysAsStoredByLevelAndByDigest(t *testing.T) { //
|
||||
|
||||
// 1680 live acceptance: checker_previews shows the preview key of an open pull request, as stored, narrowed by repository and head.
|
||||
func TestThePreviewsToolShowsTheStoredPreviewOfAPullRequest(t *testing.T) {
|
||||
pv := delivery.Preview{Repository: repoA, Head: head1, Trunk: head2, Merge: "m1", Modules: map[string]delivery.PreviewModule{}}
|
||||
data, err := delivery.EncodePreview(pv)
|
||||
pv := meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue{Repository: repoA, Head: head1, Trunk: head2, Merge: "m1", Modules: map[string]meshdeliverychecker.PreviewsByRepositoryHeadTrunkValueModulesValue{}}
|
||||
data, err := meshdeliverycheckerclient.EncodePreview(pv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, _ := delivery.PreviewKey(repoA, head1, head2)
|
||||
key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, head2)
|
||||
rd := Reader{PreviewState: fakeState{key: data, "junk": []byte("{}")}.read()}
|
||||
got, bad, _, err := rd.Previews(repoA, head1[:8])
|
||||
if err != nil || len(got) != 1 || got[0].Key != key || got[0].Head != head1 || got[0].Trunk != head2 || string(got[0].Preview) != string(data) {
|
||||
@@ -143,29 +150,29 @@ func TestThePreviewsToolShowsTheStoredPreviewOfAPullRequest(t *testing.T) {
|
||||
// checker_status reads the steps in flight by node and the candidates without a verdict; since R4g nothing holds the checker
|
||||
// and it has no capacity of its own (ADR 0525 §4, §7; 1685 closed by the hold's deletion).
|
||||
func TestTheStatusToolReadsRunningSteps(t *testing.T) {
|
||||
running, _ := delivery.EncodeStep(delivery.Step{ID: "abc1", Tool: "build_go_test", Node: "shanks", State: delivery.StepRunning, Started: time.Date(2026, 10, 22, 15, 0, 0, 0, time.UTC)})
|
||||
passed, _ := delivery.EncodeStep(delivery.Step{ID: "abc2", Tool: "build_go_test", Node: "ace", State: delivery.StepPassed})
|
||||
running, _ := buildstep.EncodeStep(buildstep.Step{ID: "abc1", Tool: "build_go_test", Node: "shanks", State: buildstep.StepRunning, Started: time.Date(2026, 10, 22, 15, 0, 0, 0, time.UTC)})
|
||||
passed, _ := buildstep.EncodeStep(buildstep.Step{ID: "abc2", Tool: "build_go_test", Node: "ace", State: buildstep.StepPassed})
|
||||
cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1))
|
||||
ckey, _ := delivery.CandidatesKey("feat/x")
|
||||
ckey, _ := meshcatalogclient.CandidatesKey("feat/x")
|
||||
// ADR 0603 §10: feat/y's check runs and waits on the builds of its changed modules: web building, mail not begun; api
|
||||
// is done and named nowhere.
|
||||
fp, man := "src2:"+strings.Repeat("f", 64), strings.Repeat("e", 64)
|
||||
py := pullOf(repoA, 8, head2)
|
||||
py.Modules, py.Fps = []string{"api", "mail", "web"}, map[string]delivery.CandidateModule{"web": {FP2: fp, Manifest: man}, "mail": {FP2: fp, Manifest: man}, "api": {FP2: fp, Manifest: man}}
|
||||
py.Modules, py.Fps = []string{"api", "mail", "web"}, map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"web": {Fp2: fp, Manifest: man}, "mail": {Fp2: fp, Manifest: man}, "api": {Fp2: fp, Manifest: man}}
|
||||
candY := candidateOf(t, "feat/y", py)
|
||||
ykey, _ := delivery.CandidatesKey("feat/y")
|
||||
runningV, _ := delivery.EncodeVerdict(delivery.Verdict{Group: "feat/y", Heads: "0123456789ab", State: delivery.VerdictRunning})
|
||||
ykey, _ := meshcatalogclient.CandidatesKey("feat/y")
|
||||
runningV, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "feat/y", Heads: "0123456789ab", State: meshdeliverycheckerclient.VerdictRunning})
|
||||
builds := fakeState{}
|
||||
for module, state := range map[string]string{"web": delivery.BuildBuilding, "api": delivery.BuildDone} {
|
||||
k, _ := delivery.BuilderBuildKey(module, fp)
|
||||
builds[k], _ = json.Marshal(delivery.BuilderBuild{V: 1, State: state})
|
||||
for module, state := range map[string]string{"web": buildsrec.BuildBuilding, "api": buildsrec.BuildDone} {
|
||||
k, _ := buildsrec.BuilderBuildKey(module, fp)
|
||||
builds[k], _ = json.Marshal(buildsrec.BuilderBuild{V: 1, State: state})
|
||||
}
|
||||
rd := Reader{
|
||||
StepStates: map[string]StateRead{"mesh-build-go": fakeState{"shanks.abc1": running, "ace.abc2": passed}.read()},
|
||||
CandidateState: fakeState{ckey: cand, ykey: candY}.read(),
|
||||
VerdictState: fakeState{keyOf(t, "feat/y", candY): runningV}.read(),
|
||||
BuildState: builds.read(),
|
||||
HaveState: fakeState{"shanks.mesh-build-go": haveOf(t, delivery.PhaseRunning)}.read(),
|
||||
HaveState: fakeState{"shanks.mesh-build-go": haveOf(t, nodeengineclient.PhaseRunning)}.read(),
|
||||
HearingState: fakeState{"dead-letters": []byte(`{"heard":false,"streams":["SEAT_NODE_BUILD_GO"],` +
|
||||
`"why":"DEAD_LETTERS is not heard, so an ask a build seat gives up on is waited for until its bound: SEAT_NODE_BUILD_GO: permissions violation",` +
|
||||
`"at":"2026-10-26T13:00:00Z"}`)}.read(),
|
||||
@@ -195,9 +202,9 @@ func TestTheStatusToolReadsRunningSteps(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func haveOf(t *testing.T, phase delivery.Phase) []byte {
|
||||
func haveOf(t *testing.T, phase string) []byte {
|
||||
t.Helper()
|
||||
raw, err := delivery.EncodeHave(delivery.Have{Phase: phase, Health: delivery.HealthOK})
|
||||
raw, err := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Phase: phase, Health: nodeengineclient.HealthOK})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -206,7 +213,7 @@ func haveOf(t *testing.T, phase delivery.Phase) []byte {
|
||||
|
||||
func stepOf(t *testing.T, id, node string, started time.Time) []byte {
|
||||
t.Helper()
|
||||
raw, err := delivery.EncodeStep(delivery.Step{ID: id, Tool: "build_go_fetch", Node: node, State: delivery.StepRunning, Started: started})
|
||||
raw, err := buildstep.EncodeStep(buildstep.Step{ID: id, Tool: "build_go_fetch", Node: node, State: buildstep.StepRunning, Started: started})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -228,8 +235,8 @@ func TestTheStatusCountsOnlyStepsThatCanStillRunAndSaysWhyTheOthersAreLeft(t *te
|
||||
}.read(),
|
||||
},
|
||||
HaveState: fakeState{
|
||||
"g14.mesh-build-go": haveOf(t, delivery.PhaseRunning),
|
||||
"shanks.mesh-build-go": haveOf(t, delivery.PhaseFailed),
|
||||
"g14.mesh-build-go": haveOf(t, nodeengineclient.PhaseRunning),
|
||||
"shanks.mesh-build-go": haveOf(t, nodeengineclient.PhaseFailed),
|
||||
}.read(),
|
||||
StepBound: 30 * time.Minute,
|
||||
Now: func() time.Time { return now },
|
||||
@@ -258,7 +265,7 @@ func TestTheStatusCountsOnlyStepsThatCanStillRunAndSaysWhyTheOthersAreLeft(t *te
|
||||
// 1887/A10: a group of hq's pull requests alone is judged by the checker since R4g, so without a verdict it is unjudged.
|
||||
func TestTheStatusCountsAnHQOnlyGroupAsUnjudged(t *testing.T) {
|
||||
cand := candidateOf(t, "pb-x", pullOf(hqRepo, 3, head2))
|
||||
ckey, _ := delivery.CandidatesKey("pb-x")
|
||||
ckey, _ := meshcatalogclient.CandidatesKey("pb-x")
|
||||
st, err := Reader{CandidateState: fakeState{ckey: cand}.read(), VerdictState: fakeState{}.read()}.Status()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -268,7 +275,7 @@ func TestTheStatusCountsAnHQOnlyGroupAsUnjudged(t *testing.T) {
|
||||
}
|
||||
// ADR 0548 §3: a candidate whose check an approved merge's turn holds off is named with the turn.
|
||||
held := candidateOf(t, "feat/x", pullOf(repoA, 7, head1))
|
||||
hkey, _ := delivery.CandidatesKey("feat/x")
|
||||
hkey, _ := meshcatalogclient.CandidatesKey("feat/x")
|
||||
st, err = Reader{CandidateState: fakeState{hkey: held}.read(), VerdictState: fakeState{}.read(),
|
||||
Trunks: func() (map[string]string, error) { return map[string]string{repoA: trunkA}, nil },
|
||||
TurnState: fakeState{turnKey(t, repoA): turnOf(t, repoA, "fix/other", "0123456789ab")}.read()}.Status()
|
||||
@@ -311,13 +318,13 @@ func TestReadToolsReadOnlyWhatTheyNeedInParallelAndSayWhenCut(t *testing.T) { //
|
||||
kept := fakeState{}
|
||||
for i := 0; i < 1800; i++ {
|
||||
level := map[bool]string{true: "unit", false: "integration"}[i%9 != 0]
|
||||
k, _ := delivery.KeptKey(level, fmt.Sprintf("%064x", i))
|
||||
k, _ := meshdeliverycheckerclient.KeptKey(level, fmt.Sprintf("%064x", i))
|
||||
kept[k] = []byte(`{"v":1}`)
|
||||
}
|
||||
previews := fakeState{}
|
||||
for i := 0; i < 1500; i++ {
|
||||
repo := map[bool]string{true: repoA, false: repoSDK}[i < 300]
|
||||
k, _ := delivery.PreviewKey(repo, fmt.Sprintf("%08x%032x", i, 0), trunkA)
|
||||
k, _ := meshdeliverycheckerclient.PreviewKey(repo, fmt.Sprintf("%08x%032x", i, 0), trunkA)
|
||||
previews[k] = []byte(`{"v":1}`)
|
||||
}
|
||||
for _, row := range []struct {
|
||||
@@ -372,15 +379,15 @@ func TestReadToolsReadOnlyWhatTheyNeedInParallelAndSayWhenCut(t *testing.T) { //
|
||||
steps := fakeState{}
|
||||
for i := 0; i < 1500; i++ {
|
||||
id := fmt.Sprintf("mvo5%04dabcdef", i)
|
||||
step := delivery.Step{ID: id, Tool: "t", Node: "g14", State: delivery.StepPassed}
|
||||
step := buildstep.Step{ID: id, Tool: "t", Node: "g14", State: buildstep.StepPassed}
|
||||
if i == 1499 {
|
||||
step.State, step.Started = delivery.StepRunning, now.Add(-time.Minute)
|
||||
step.State, step.Started = buildstep.StepRunning, now.Add(-time.Minute)
|
||||
}
|
||||
raw, _ := delivery.EncodeStep(step)
|
||||
raw, _ := buildstep.EncodeStep(step)
|
||||
steps["g14."+id] = raw
|
||||
}
|
||||
sb := &slowBucket{vals: steps}
|
||||
st, err := Reader{StepStates: map[string]StateRead{"mesh-build-go": sb.read()}, HaveState: fakeState{"g14.mesh-build-go": haveOf(t, delivery.PhaseRunning)}.read(),
|
||||
st, err := Reader{StepStates: map[string]StateRead{"mesh-build-go": sb.read()}, HaveState: fakeState{"g14.mesh-build-go": haveOf(t, nodeengineclient.PhaseRunning)}.read(),
|
||||
Now: func() time.Time { return now }}.Status()
|
||||
if err != nil || sb.reads.Load() != MaxReads || len(st.Running) != 1 || sb.maxFlight.Load() < 2 {
|
||||
t.Errorf("status: %d reads (want %d), running %+v, in flight %d, %v", sb.reads.Load(), MaxReads, st.Running, sb.maxFlight.Load(), err)
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshconfig"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// A config value renamed (design 32 *A config value renamed*, DLV-C5): the checker asks the seat mesh-config's verb
|
||||
@@ -52,15 +53,15 @@ func renamesOf(was, now catalogue.Manifest) renameChange {
|
||||
}
|
||||
|
||||
// judgeRenames fails the check for a rename the seat refuses, a window a change would drop, or an ask that cannot be made.
|
||||
func (st *state) judgeRenames(ctx context.Context) *delivery.Verdict {
|
||||
func (st *state) judgeRenames(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
if st.c.Try != nil {
|
||||
return nil
|
||||
}
|
||||
var out *delivery.Verdict
|
||||
var out *meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
failed := func(words string) {
|
||||
f := delivery.FailedFirst{Machine: meshconfig.Seat, Words: words}
|
||||
f := FailedFirst{Machine: meshconfig.Seat, Words: words}
|
||||
if out == nil {
|
||||
out = &delivery.Verdict{State: delivery.VerdictFail, Failed: f}
|
||||
out = &meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &f}
|
||||
return
|
||||
}
|
||||
addFailure(out, f)
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// The shapes of what the build modules' steps answer, as their code writes them (mesh-catalog modules/mesh-build-go
|
||||
@@ -13,52 +9,10 @@ import (
|
||||
// cmd/mesh-build-node check.go TestResult, fetch.go FetchResult) with the fields the fetch step adds for a merge (ADR 0484
|
||||
// clause 3). Only the fields the checker reads are named; the rest is ignored.
|
||||
|
||||
// MergeTree is what a fetch that merged says of one module directory: the tree sha, the digest of its manifest, the
|
||||
// hashes of its lock files by name and the objects of its named paths. A bare string is the tree alone.
|
||||
type MergeTree struct {
|
||||
Tree string `json:"tree"`
|
||||
Manifest string `json:"manifest,omitempty"`
|
||||
Locks map[string]string `json:"locks,omitempty"`
|
||||
// Paths are, of a module whose every artifact names its paths, each named path's object; Tree is then their named
|
||||
// tree (mesh-sdk buildstep.ModuleTree, novox/hq ADR 0499 clause 2).
|
||||
Paths map[string]string `json:"paths,omitempty"`
|
||||
// Packages are the mesh packages the directory's go.sum names (mesh-sdk buildstep.ModuleTree, novox/hq ADR 0573 §2).
|
||||
Packages []string `json:"packages,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON takes a string as the tree alone, or an object.
|
||||
func (t *MergeTree) UnmarshalJSON(data []byte) error {
|
||||
if s := strings.TrimSpace(string(data)); strings.HasPrefix(s, `"`) {
|
||||
return json.Unmarshal(data, &t.Tree)
|
||||
}
|
||||
type plain MergeTree
|
||||
return json.Unmarshal(data, (*plain)(t))
|
||||
}
|
||||
|
||||
// Fetched is the answer of a build module's fetch step.
|
||||
type Fetched struct {
|
||||
Workspace string `json:"workspace"`
|
||||
Commit string `json:"commit"`
|
||||
Modules []string `json:"modules"`
|
||||
// What a fetch with a base adds: the trunk commit, the merge commit, the files in conflict (a conflict is an answer),
|
||||
// the paths the merge changes and the trees of the module directories.
|
||||
Base string `json:"base,omitempty"`
|
||||
Merge string `json:"merge,omitempty"`
|
||||
Conflicts []string `json:"conflicts,omitempty"`
|
||||
Changed []string `json:"changed,omitempty"`
|
||||
Trees map[string]MergeTree `json:"trees,omitempty"`
|
||||
// Programs are the changed files whose first bytes are an executable binary, read by the build module in its workspace
|
||||
// (issue 1577); nil for a build module that does not report it, a pointer to an empty list for one that found none.
|
||||
Programs *[]ProgramFile `json:"programs,omitempty"`
|
||||
}
|
||||
|
||||
// ProgramFile is a changed file that is a compiled program, and its format (ELF, Mach-O or PE).
|
||||
type ProgramFile struct {
|
||||
Path string `json:"path"`
|
||||
Kind string `json:"kind"`
|
||||
// Commit is set for a program a commit of the branch added and a later one removed: it stays in history once merged.
|
||||
Commit string `json:"commit,omitempty"`
|
||||
}
|
||||
// Fetched is the answer of a build module's fetch step, whole: the Delivery SDK's FetchAnswer, which a check answer keeps
|
||||
// (B2g; the build seats' part FetchAnswer): the workspace, the merge preview with its conflicts, changed paths, programs and
|
||||
// trees. A conflict is an answer.
|
||||
type Fetched = buildstep.FetchAnswer
|
||||
|
||||
// GoPackage is one package of a Go test result.
|
||||
type GoPackage struct {
|
||||
@@ -128,5 +82,5 @@ type Digested struct {
|
||||
// JudgedBy is the program that ran the digest, as Tested.JudgedBy says.
|
||||
JudgedBy string `json:"judged_by,omitempty"`
|
||||
// Needs are the packages whose tests need something of the node at this level (issue 890).
|
||||
Needs []delivery.PackageNeeds `json:"needs,omitempty"`
|
||||
Needs []buildstep.PackageNeeds `json:"needs,omitempty"`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
// A pull request whose head's seat definition does not parse fails its check, naming the seat and where it lives (design
|
||||
@@ -25,15 +26,15 @@ type SeatDefinitions interface {
|
||||
|
||||
// refuseUnparsed fails the check of each pull request with a seat definition that does not parse; a state that cannot be
|
||||
// read fails it too, saying so.
|
||||
func (st *state) refuseUnparsed(ctx context.Context) *delivery.Verdict {
|
||||
func (st *state) refuseUnparsed(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
if st.j.SeatDefinitions == nil || st.c.Try != nil {
|
||||
return nil
|
||||
}
|
||||
var out *delivery.Verdict
|
||||
var out *meshdeliverychecker.VerdictsByGroupHeadsValue
|
||||
failed := func(words string) {
|
||||
f := delivery.FailedFirst{Machine: SeatDefinitionsMachine, Words: words}
|
||||
f := FailedFirst{Machine: SeatDefinitionsMachine, Words: words}
|
||||
if out == nil {
|
||||
out = &delivery.Verdict{State: delivery.VerdictFail, Failed: f}
|
||||
out = &meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &f}
|
||||
return
|
||||
}
|
||||
addFailure(out, f)
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// A change of a package series is named in the verdict, so the review of the change sees it (design 32 *A package, its
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
func (st *state) seriesChanges(ctx context.Context) {
|
||||
for _, p := range st.c.Pulls {
|
||||
var words []string
|
||||
var changes []delivery.SeriesChange
|
||||
var changes []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem
|
||||
for _, module := range p.Modules {
|
||||
was, now, ok, err := st.manifestPair(ctx, p, module)
|
||||
if err != nil {
|
||||
@@ -72,13 +72,13 @@ func (st *state) manifestPair(ctx context.Context, p Pull, module string) (was,
|
||||
}
|
||||
|
||||
// seriesLine is a series change as the status line names it: `<module>/<artifact> series <trunk> → <head>`.
|
||||
func seriesLine(c delivery.SeriesChange) string {
|
||||
func seriesLine(c meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) string {
|
||||
return fmt.Sprintf("%s/%s series %s → %s", c.Module, c.Artifact, c.From, c.To)
|
||||
}
|
||||
|
||||
// seriesMoved are the artifacts of module whose series the head's manifest changes from the trunk's, by artifact. An
|
||||
// artifact new on the head is no series change (the lead's ruling): it has no series on the trunk.
|
||||
func seriesMoved(module string, was, now catalogue.Manifest) []delivery.SeriesChange {
|
||||
func seriesMoved(module string, was, now catalogue.Manifest) []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem {
|
||||
if was.Build == nil || now.Build == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -86,37 +86,39 @@ func seriesMoved(module string, was, now catalogue.Manifest) []delivery.SeriesCh
|
||||
for _, a := range was.Build.Artifacts {
|
||||
before[a.Name] = a.SeriesOf()
|
||||
}
|
||||
var out []delivery.SeriesChange
|
||||
var out []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem
|
||||
for _, a := range now.Build.Artifacts {
|
||||
if old, ok := before[a.Name]; ok && old != a.SeriesOf() {
|
||||
out = append(out, delivery.SeriesChange{Module: module, Artifact: a.Name, From: old, To: a.SeriesOf()})
|
||||
out = append(out, meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{Module: module, Artifact: a.Name, From: old, To: a.SeriesOf()})
|
||||
}
|
||||
}
|
||||
slices.SortFunc(out, func(a, b delivery.SeriesChange) int { return strings.Compare(a.Artifact, b.Artifact) })
|
||||
slices.SortFunc(out, func(a, b meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) int {
|
||||
return strings.Compare(a.Artifact, b.Artifact)
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
// series keeps, of a pull request (pullKey), the words of the series changes it makes and the changes themselves.
|
||||
func (c *Case) series(key string, words []string, changes []delivery.SeriesChange) {
|
||||
func (c *Case) series(key string, words []string, changes []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) {
|
||||
c.notesMu.Lock()
|
||||
defer c.notesMu.Unlock()
|
||||
if c.seriesOf == nil {
|
||||
c.seriesOf, c.seriesChanges = map[string][]string{}, map[string][]delivery.SeriesChange{}
|
||||
c.seriesOf, c.seriesChanges = map[string][]string{}, map[string][]meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{}
|
||||
}
|
||||
c.seriesOf[key], c.seriesChanges[key] = words, changes
|
||||
}
|
||||
|
||||
// SeriesChanges are the series changes a pull request (pullKey) makes, as the verdict and its event name them.
|
||||
func (c *Case) SeriesChanges(key string) []delivery.SeriesChange {
|
||||
func (c *Case) SeriesChanges(key string) []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem {
|
||||
c.notesMu.Lock()
|
||||
defer c.notesMu.Unlock()
|
||||
return slices.Clone(c.seriesChanges[key])
|
||||
}
|
||||
|
||||
// allSeries are the series changes of every pull request of the case, each module's artifact once, by module and
|
||||
// artifact: what the verdict names (delivery.Verdict.Series).
|
||||
func (c *Case) allSeries() []delivery.SeriesChange {
|
||||
var out []delivery.SeriesChange
|
||||
// artifact: what the verdict names (meshdeliverychecker.VerdictsByGroupHeadsValue.Series).
|
||||
func (c *Case) allSeries() []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem {
|
||||
var out []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem
|
||||
seen := map[string]bool{}
|
||||
for _, p := range c.Pulls {
|
||||
for _, s := range c.SeriesChanges(pullKey(p)) {
|
||||
@@ -126,7 +128,7 @@ func (c *Case) allSeries() []delivery.SeriesChange {
|
||||
}
|
||||
}
|
||||
}
|
||||
slices.SortFunc(out, func(a, b delivery.SeriesChange) int {
|
||||
slices.SortFunc(out, func(a, b meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) int {
|
||||
return strings.Compare(a.Module+"/"+a.Artifact, b.Module+"/"+b.Artifact)
|
||||
})
|
||||
return out
|
||||
|
||||
@@ -10,9 +10,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// EventTagWanted is the checker's event that asks the builder for a group member's tagged versions (design 51 *Tagged
|
||||
@@ -158,11 +159,11 @@ func (st *state) manifestAtHead(ctx context.Context, p Pull, module string) (m c
|
||||
}
|
||||
}
|
||||
if dirs == nil {
|
||||
dirs = []string{"modules/" + module, delivery.RootPath}
|
||||
dirs = []string{"modules/" + module, meshcatalogclient.RootPath}
|
||||
}
|
||||
for _, d := range dirs {
|
||||
file := "module.json"
|
||||
if t := strings.Trim(d, "/"); t != "" && t != delivery.RootPath {
|
||||
if t := strings.Trim(d, "/"); t != "" && t != meshcatalogclient.RootPath {
|
||||
file = t + "/module.json"
|
||||
}
|
||||
text, err := st.j.Forge.File(ctx, p.Repository, file, p.Head)
|
||||
@@ -248,7 +249,7 @@ func (st *state) declaredPackages(ctx context.Context) ([]declaredPackage, error
|
||||
// packages by module path in Go's form (StepAsk.Packages), and the TypeScript packages a member's Node fetch takes from the
|
||||
// registry, by the repository that publishes them.
|
||||
type groupVersions struct {
|
||||
goPackages delivery.PackageVersions
|
||||
goPackages buildstep.PackageVersions
|
||||
node map[string]NodePackage
|
||||
}
|
||||
|
||||
@@ -286,7 +287,7 @@ func (g *groupVersions) took(d declaredPackage, p TaggedPublished) {
|
||||
switch d.language {
|
||||
case "go":
|
||||
if g.goPackages == nil {
|
||||
g.goPackages = delivery.PackageVersions{}
|
||||
g.goPackages = buildstep.PackageVersions{}
|
||||
}
|
||||
g.goPackages[p.Module] = goVersion(p.Version) // Heard took no Go published without its module
|
||||
case "typescript":
|
||||
@@ -300,13 +301,13 @@ func (g *groupVersions) took(d declaredPackage, p TaggedPublished) {
|
||||
// tagged asks a group's tagged versions before any member is built (design 51 *Tagged versions*): one tag-wanted per
|
||||
// package a member declares, then each package's published awaited within the publish bound. A lone pull request and a
|
||||
// try ask nothing. A package not published within the bound fails the check naming it.
|
||||
func (st *state) tagged(ctx context.Context) *delivery.Verdict {
|
||||
func (st *state) tagged(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
||||
if st.j.Tags == nil || st.c.Try != nil || len(st.c.Pulls) < 2 {
|
||||
return nil
|
||||
}
|
||||
decl, err := st.declaredPackages(ctx)
|
||||
if err != nil {
|
||||
return fail(delivery.CheckerModule, err.Error())
|
||||
return fail(CheckerModule, err.Error())
|
||||
}
|
||||
if len(decl) == 0 {
|
||||
return nil
|
||||
@@ -316,7 +317,7 @@ func (st *state) tagged(ctx context.Context) *delivery.Verdict {
|
||||
for _, d := range decl {
|
||||
t := TagWanted{Repository: d.pull.Repository, Branch: st.c.Group, Head: d.pull.Head, Module: d.module, Package: d.name, Pull: int64(d.pull.Number)}
|
||||
if err := st.j.Tags.Want(ctx, t); err != nil {
|
||||
return fail(delivery.CheckerModule, fmt.Sprintf("the tagged version of %s of %s (%s) cannot be asked: %v", d.name, d.module, pullKey(d.pull), err))
|
||||
return fail(CheckerModule, fmt.Sprintf("the tagged version of %s of %s (%s) cannot be asked: %v", d.name, d.module, pullKey(d.pull), err))
|
||||
}
|
||||
names = append(names, d.module+"/"+d.name)
|
||||
}
|
||||
@@ -341,9 +342,9 @@ func (st *state) tagged(ctx context.Context) *delivery.Verdict {
|
||||
}
|
||||
if len(late) > 0 {
|
||||
if ctx.Err() != nil {
|
||||
return fail(delivery.CheckerModule, ctx.Err().Error())
|
||||
return fail(CheckerModule, ctx.Err().Error())
|
||||
}
|
||||
return fail(delivery.CheckerModule, fmt.Sprintf("no published of the tagged version of %s within %s, so no member is built (design 51 *Tagged versions*)", strings.Join(late, "; "), bound))
|
||||
return fail(CheckerModule, fmt.Sprintf("no published of the tagged version of %s within %s, so no member is built (design 51 *Tagged versions*)", strings.Join(late, "; "), bound))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
// A try is a pushed head judged by the checker as a pull request's head is, before its pull request is opened (ADR 0501
|
||||
@@ -29,7 +32,7 @@ const (
|
||||
TryEndTool = "checker_try_end"
|
||||
// TriesState is the checker's state of tries: the bucket mesh-delivery-checker_tries, keys try-<unix nanoseconds>.
|
||||
TriesState = "tries"
|
||||
TriesBucket = delivery.CheckerModule + "_" + TriesState
|
||||
TriesBucket = CheckerModule + "_" + TriesState
|
||||
// MaxTries and MaxTriesWaiting are checked when a try is asked, mesh-wide: a new try is refused while MaxTries tries run
|
||||
// on a build node or MaxTriesWaiting wait for one (queued, or their asks not yet taken by a node). It is no bound on
|
||||
// what runs later: tries let in while waiting may later all run at once, and each node's build capacity bounds what
|
||||
@@ -78,11 +81,11 @@ type Try struct {
|
||||
Said string `json:"said,omitempty"`
|
||||
// Took is the time from its start to its end, as Go writes a duration.
|
||||
Took string `json:"took,omitempty"`
|
||||
// Failed is every test that failed, each with its first line, at most delivery.MaxFailures and then how many more
|
||||
// Failed is every test that failed, each with its first line, at most meshdeliverycheckerclient.MaxFailures and then how many more
|
||||
// (issue 1921); or what else failed first (a conflict, a refused step).
|
||||
Failed []string `json:"failed,omitempty"`
|
||||
Levels map[string]delivery.LevelResult `json:"levels,omitempty"`
|
||||
Machines []string `json:"machines,omitempty"`
|
||||
Failed []string `json:"failed,omitempty"`
|
||||
Levels map[string]meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue `json:"levels,omitempty"`
|
||||
Machines []string `json:"machines,omitempty"`
|
||||
// Asks are the ids of the asks the try put on the build seats' streams, each ending on the step key <node>.<id> of its
|
||||
// build module: the delivery view counts those steps as a try's, not a candidate's check (build_usage).
|
||||
Asks []string `json:"asks,omitempty"`
|
||||
@@ -122,7 +125,7 @@ type tryWriter struct {
|
||||
}
|
||||
|
||||
// heard takes an ask put (step nil) or a value of its step key in, and writes the record when it changed.
|
||||
func (w *tryWriter) heard(askID string, step *delivery.Step) {
|
||||
func (w *tryWriter) heard(askID string, step *buildstep.Step) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if w.stopped {
|
||||
@@ -180,7 +183,7 @@ func (t Try) HoldsANode() bool {
|
||||
if len(t.Steps) == 0 {
|
||||
return true
|
||||
}
|
||||
return slices.ContainsFunc(t.Steps, func(s TryStep) bool { return s.State == delivery.StepRunning })
|
||||
return slices.ContainsFunc(t.Steps, func(s TryStep) bool { return s.State == buildstep.StepRunning })
|
||||
}
|
||||
|
||||
// Over says whether the try came to its end.
|
||||
@@ -199,7 +202,7 @@ func DecodeTry(raw []byte) (Try, error) {
|
||||
}
|
||||
|
||||
func encodeTry(t Try) ([]byte, error) {
|
||||
t.V = delivery.Version
|
||||
t.V = keys.Version
|
||||
return json.Marshal(t)
|
||||
}
|
||||
|
||||
@@ -232,7 +235,7 @@ func AskTry(store TryStore, trunks map[string]string, a TryAsk, now time.Time) (
|
||||
return "", fmt.Errorf("%q is not a repository, <owner>/<name>", a.Repository)
|
||||
case !tryHeadOK.MatchString(head):
|
||||
return "", fmt.Errorf("the head %q is not a full commit, 40 or 64 hex digits", a.Head)
|
||||
case a.Level != "" && a.Level != delivery.LevelUnit && a.Level != delivery.LevelIntegration:
|
||||
case a.Level != "" && a.Level != buildstep.LevelUnit && a.Level != buildstep.LevelIntegration:
|
||||
return "", fmt.Errorf("the level %q is neither unit nor integration", a.Level)
|
||||
case a.Tests != "" && !tryTestsOK.MatchString(a.Tests):
|
||||
return "", fmt.Errorf("the tests %q are not a go test -run pattern", a.Tests)
|
||||
@@ -530,9 +533,9 @@ func (tr *Tryer) run(ctx context.Context, key string) {
|
||||
// Not the change's: its ask was given up or never taken (issue 734), or a part of the mesh was not heard.
|
||||
t.State = TryError
|
||||
if IsLostAsk(v) || IsNotTaken(v) {
|
||||
t.Failed = []string{v.Failed.Words}
|
||||
t.Failed = []string{failedOf(v).Words}
|
||||
}
|
||||
case v.State == delivery.VerdictPass:
|
||||
case v.State == meshdeliverycheckerclient.VerdictPass:
|
||||
t.State = TryPass
|
||||
default:
|
||||
t.State = TryFail
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// memTries is the tries state as the tools see it, over a memKV.
|
||||
@@ -72,7 +72,7 @@ func TestATryIsAskedQueuedOncePerHeadAndAtMostFourAtOnce(t *testing.T) { // ADR
|
||||
// their asks not taken) are bounded apart, MaxTries and MaxTriesWaiting; the refusal names both counts.
|
||||
onNode := func(i int) Try {
|
||||
return Try{ID: fmt.Sprintf("try-%d", i), Repository: repoA, Head: fmt.Sprintf("%040x", i), Trunk: trunkA, State: TryRunning,
|
||||
Steps: []TryStep{{Ask: "a", State: delivery.StepRunning}}}
|
||||
Steps: []TryStep{{Ask: "a", State: buildstep.StepRunning}}}
|
||||
}
|
||||
waitingFor := func(i int) Try {
|
||||
return Try{ID: fmt.Sprintf("try-%d", i), Repository: repoA, Head: fmt.Sprintf("%040x", i), Trunk: trunkA, State: TryRunning,
|
||||
@@ -133,7 +133,7 @@ func TestATryIsJudgedAsAPullRequestsHeadAndOwesTheForgeNothing(t *testing.T) { /
|
||||
tr.Put(ctx, id, raw)
|
||||
tr.Wait()
|
||||
got := readTry(t, kv, id)
|
||||
if got.State != TryPass || got.Took == "" || got.Head != head1 || got.Trunk != trunkA || got.Said == "" || got.Levels[delivery.LevelUnit].Run != 2 {
|
||||
if got.State != TryPass || got.Took == "" || got.Head != head1 || got.Trunk != trunkA || got.Said == "" || got.Levels[buildstep.LevelUnit].Run != 2 {
|
||||
t.Fatalf("try = %+v", got)
|
||||
}
|
||||
// Issue 1890: while it runs, the try names its ask as soon as it is put, and then where its step stands; the end
|
||||
@@ -142,17 +142,17 @@ func TestATryIsJudgedAsAPullRequestsHeadAndOwesTheForgeNothing(t *testing.T) { /
|
||||
written.running[2].Steps[0].Node != "shanks" {
|
||||
t.Errorf("the running try's writes = %+v, want its ask named while it ran, then its node", written.running)
|
||||
}
|
||||
if len(got.Steps) != 1 || got.Steps[0].Node != "shanks" || got.Steps[0].State != delivery.StepPassed {
|
||||
if len(got.Steps) != 1 || got.Steps[0].Node != "shanks" || got.Steps[0].State != buildstep.StepPassed {
|
||||
t.Errorf("the ended try's steps = %+v", got.Steps)
|
||||
}
|
||||
// The try names the asks it put, so the delivery view counts their steps as a try's (build_usage).
|
||||
if len(got.Asks) != 1 {
|
||||
t.Errorf("asks = %v, want the one check ask the try put", got.Asks)
|
||||
} else if _, err := delivery.AskStepKey("shanks", got.Asks[0]); err != nil {
|
||||
} else if _, err := buildstep.AskStepKey("shanks", got.Asks[0]); err != nil {
|
||||
t.Errorf("the try names %q, which is no ask id: %v", got.Asks[0], err)
|
||||
}
|
||||
fetch := b.steps.tool("fetch")
|
||||
if len(fetch) != 1 || fetch[0].args["commit"] != head1 || fetch[0].args["base"] != trunkA || fetch[0].args["ref"] != "" {
|
||||
if len(fetch) != 1 || fetch[0].args["commit"] != head1 || fetch[0].args["base"] != trunkA || fetch[0].args["ref"] != nil {
|
||||
t.Errorf("fetch = %+v, want the head merged onto the trunk, with no pull request's ref", fetch)
|
||||
}
|
||||
if keys, _ := b.prev.Keys(ctx); len(keys) != 0 {
|
||||
@@ -170,7 +170,7 @@ func TestATryIsJudgedAsAPullRequestsHeadAndOwesTheForgeNothing(t *testing.T) { /
|
||||
func TestATryOfOneLevelAndSomeTestsRunsThemAloneAndKeepsNothing(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
kv := newMemKV()
|
||||
id, err := AskTry(memTries{kv}, tryTrunks, TryAsk{Repository: repoA, Head: head1, Level: delivery.LevelIntegration, Tests: "TestBus|TestStore"}, time.Now())
|
||||
id, err := AskTry(memTries{kv}, tryTrunks, TryAsk{Repository: repoA, Head: head1, Level: buildstep.LevelIntegration, Tests: "TestBus|TestStore"}, time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func TestATryOfOneLevelAndSomeTestsRunsThemAloneAndKeepsNothing(t *testing.T) {
|
||||
tr.Put(ctx, id, raw)
|
||||
tr.Wait()
|
||||
tests := b.steps.tool("test")
|
||||
if len(tests) != 1 || tests[0].args["level"] != delivery.LevelIntegration || tests[0].args["run"] != "TestBus|TestStore" {
|
||||
if len(tests) != 1 || tests[0].args["level"] != buildstep.LevelIntegration || tests[0].args["run"] != "TestBus|TestStore" {
|
||||
t.Fatalf("tests = %+v, want the integration level alone with the run pattern", tests)
|
||||
}
|
||||
if n := len(b.steps.tool("digest")); n != 0 {
|
||||
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
)
|
||||
|
||||
// Turns holds the gate's merge turns (novox/hq ADR 0548 §3), by repository, as a watch of the gate's turns bucket puts them
|
||||
@@ -13,26 +14,26 @@ import (
|
||||
// approved merge goes before a check not yet started. A bucket not there is read as empty.
|
||||
type Turns struct {
|
||||
mu sync.Mutex
|
||||
byRepo map[string]delivery.Turn
|
||||
byRepo map[string]meshdelivery.TurnsByRepositoryValue
|
||||
}
|
||||
|
||||
// Put takes a value of the turns bucket in, or its delete, and answers whether what the checker holds changed. A value that
|
||||
// is not a turn is dropped as a delete would be: a turn the checker cannot read holds nothing.
|
||||
func (t *Turns) Put(key string, value []byte, deleted bool) bool {
|
||||
repository, err := delivery.ParseTurnsKey(key)
|
||||
repository, err := meshdeliveryclient.ParseTurnsKey(key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.byRepo == nil {
|
||||
t.byRepo = map[string]delivery.Turn{}
|
||||
t.byRepo = map[string]meshdelivery.TurnsByRepositoryValue{}
|
||||
}
|
||||
cur, had := t.byRepo[repository]
|
||||
if !deleted {
|
||||
if turn, err := delivery.DecodeTurn(value); err == nil {
|
||||
if turn, err := meshdeliveryclient.DecodeTurn(value); err == nil {
|
||||
t.byRepo[repository] = turn
|
||||
return !had || !delivery.SameTurn(cur, turn)
|
||||
return !had || !meshdeliveryclient.SameTurn(cur, turn)
|
||||
}
|
||||
}
|
||||
delete(t.byRepo, repository)
|
||||
@@ -41,9 +42,9 @@ func (t *Turns) Put(key string, value []byte, deleted bool) bool {
|
||||
|
||||
// Holding names the first of the repositories (by name) a turn holds off the check of the heads, and the delivery that
|
||||
// waits first in it; false when none does.
|
||||
func (t *Turns) Holding(repositories []string, heads string) (string, delivery.TurnWait, bool) {
|
||||
func (t *Turns) Holding(repositories []string, heads string) (string, meshdelivery.TurnsByRepositoryValueWaitingItem, bool) {
|
||||
if t == nil {
|
||||
return "", delivery.TurnWait{}, false
|
||||
return "", meshdelivery.TurnsByRepositoryValueWaitingItem{}, false
|
||||
}
|
||||
sorted := make([]string, 0, len(repositories))
|
||||
for _, r := range repositories {
|
||||
@@ -54,7 +55,7 @@ func (t *Turns) Holding(repositories []string, heads string) (string, delivery.T
|
||||
defer t.mu.Unlock()
|
||||
for _, r := range sorted {
|
||||
turn, ok := t.byRepo[r]
|
||||
if !ok || !delivery.TurnHolds(turn, heads) {
|
||||
if !ok || !meshdeliveryclient.TurnHolds(turn, heads) {
|
||||
continue
|
||||
}
|
||||
for _, w := range turn.Waiting {
|
||||
@@ -63,14 +64,14 @@ func (t *Turns) Holding(repositories []string, heads string) (string, delivery.T
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", delivery.TurnWait{}, false
|
||||
return "", meshdelivery.TurnsByRepositoryValueWaitingItem{}, false
|
||||
}
|
||||
|
||||
// All is every turn held now, by repository (a copy).
|
||||
func (t *Turns) All() map[string]delivery.Turn {
|
||||
func (t *Turns) All() map[string]meshdelivery.TurnsByRepositoryValue {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
out := make(map[string]delivery.Turn, len(t.byRepo))
|
||||
out := make(map[string]meshdelivery.TurnsByRepositoryValue, len(t.byRepo))
|
||||
for r, turn := range t.byRepo {
|
||||
out[r] = turn
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package check
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
// EventVerdict is the checker's event that says a pull request's check ended, with the generated files its steps found out
|
||||
@@ -23,8 +23,8 @@ type VerdictSayer interface {
|
||||
// sayVerdict says, for each pull request of a check that ended in pass or fail, one verdict event naming its head and its
|
||||
// out-of-step files. A try owes no pull request anything and says none (ADR 0501 §5). A publish that fails is said in the
|
||||
// log: the verdict and the status are written already, and the next check of the heads says it again.
|
||||
func (ch *Checker) sayVerdict(ctx context.Context, cs *Case, v delivery.Verdict, out Outcome) {
|
||||
if ch.Says == nil || cs.Try != nil || (v.State != delivery.VerdictPass && v.State != delivery.VerdictFail) {
|
||||
func (ch *Checker) sayVerdict(ctx context.Context, cs *Case, v meshdeliverychecker.VerdictsByGroupHeadsValue, out Outcome) {
|
||||
if ch.Says == nil || cs.Try != nil || (v.State != meshdeliverycheckerclient.VerdictPass && v.State != meshdeliverycheckerclient.VerdictFail) {
|
||||
return
|
||||
}
|
||||
for _, p := range cs.Pulls {
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
)
|
||||
|
||||
// The controller's manifest reader is mesh-controller internal/catalogue (Manifest, manifest.go: Consumes :521,
|
||||
@@ -101,7 +103,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
||||
if m.Module != "mesh-delivery-checker" {
|
||||
t.Errorf("module = %q", m.Module)
|
||||
}
|
||||
// delivery.CheckerKeptHistory, CheckerOwedHistory, PreviewsHistory, CheckerVerdictsHistory; the names follow the SDK's buckets.
|
||||
// The histories of the checker's four seat states (checked against internal/check names below); the names follow the seat mesh-delivery-checker.
|
||||
if want := []state{{Name: "asks", History: 1}, {Name: "hearing", History: 1}, {Name: "kept", History: 1}, {Name: "owed", History: 5}, {Name: "previews", History: 5}, {Name: "tries", History: 1}, {Name: "verdicts", History: 5}, {Name: "first-views", History: 1}, {Name: "findings", History: 1}}; !reflect.DeepEqual(m.State, want) {
|
||||
t.Errorf("state = %+v, want %+v", m.State, want)
|
||||
}
|
||||
@@ -165,6 +167,28 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
||||
t.Errorf("the manifest declares %q: the checker has none", k)
|
||||
}
|
||||
}
|
||||
// D-11 (ADR 0634 §1): the holders and buckets internal/check names, no contract naming a holder, are the manifest's: a state
|
||||
// the checker reads is a `reads` (<module>.<state>) and a state of the indexer it follows a `consumes` of its views family.
|
||||
for bucket, read := range map[string]string{check.SourceBucket: "mesh-delivery-gate.source", check.TurnsBucket: "mesh-delivery-gate.turns",
|
||||
check.PresenceBucket: "mesh-delivery-watchman.presence", check.HaveBucket: "node-engine.have"} {
|
||||
if strings.Replace(bucket, "_", ".", 1) != read || !slices.Contains(m.Reads, read) {
|
||||
t.Errorf("the bucket %s is not the declared read %s", bucket, read)
|
||||
}
|
||||
}
|
||||
for bucket, view := range map[string]string{check.CandidatesBucket: "candidates", check.ModulesBucket: "modules", check.ManifestsBucket: "manifests"} {
|
||||
if bucket != check.IndexerModule+"_"+view || !slices.Contains(m.Consumes, check.IndexerModule+".views."+view) {
|
||||
t.Errorf("the bucket %s is not a followed view of the indexer's %s", bucket, view)
|
||||
}
|
||||
}
|
||||
if !slices.Contains(m.Consumes, check.IndexerModule+".views."+check.IndexerPredefinedState) {
|
||||
t.Errorf("the indexer's state %s is followed, and not declared", check.IndexerPredefinedState)
|
||||
}
|
||||
for bucket, history := range map[string]int{check.CheckerVerdictsBucket: 5, check.CheckerKeptBucket: 1, check.CheckerOwedBucket: 5, check.PreviewsBucket: 5} {
|
||||
own := strings.TrimPrefix(bucket, check.CheckerModule+"_")
|
||||
if own == bucket || !slices.ContainsFunc(m.State, func(st state) bool { return st.Name == own && st.History == history }) {
|
||||
t.Errorf("the bucket %s is not a state of the manifest at history %d", bucket, history)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestProcess(t *testing.T) { // 1678/AC1
|
||||
|
||||
Reference in New Issue
Block a user