BLD2: the Go build seat keeps the answer of steps in detected-steps by fingerprint
Part of BLD2 (Go module): the state detected-steps asked by the template state, one key per artifact, answered for the same fingerprint, replaced by a newer one and by again. The drop of a key and the Node and image modules wait (drop: question to the lead; the others follow in BLD7 and BLD8). Builds: design 47 G §326, §414 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -15,6 +15,8 @@ var Kept = []announce.State{
|
||||
{Name: "keys", Readable: true},
|
||||
{Name: "steps", Readable: true},
|
||||
{Name: "pauses", Readable: true},
|
||||
// The steps detected for each artifact, kept by its fingerprint (design 47 G §326, §414).
|
||||
{Name: "detected-steps", Readable: true},
|
||||
// The condition keeper raises a first view of the indexer's manifests it could not get (ADR 0615 §6) from this.
|
||||
{Name: "findings", Readable: true},
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -147,7 +147,9 @@ func TestManifestRequiresForgeAccountKeepsKeysAndTakesFilesFromTemplates(t *test
|
||||
m := readForgeManifest(t)
|
||||
// node-tools: the module pulls its asks through the tool runner's seat pull (1911).
|
||||
// go-package-registry: the Go seat's read side, the fetch's first source (design 51, CAT-BG3, E2).
|
||||
if want := []any{"forge-account", "go-toolchain", "go-package-registry", "node-tools"}; !slices.Equal(m.Requires, want) {
|
||||
// detected-steps: the state of the verb steps (design 47 G §326, §414), asked by the template state.
|
||||
detected := map[string]any{"name": "detected-steps", "storage": "key-value", "template": "state", "options": map[string]any{"history": float64(1)}}
|
||||
if want := []any{detected, "forge-account", "go-toolchain", "go-package-registry", "node-tools"}; !reflect.DeepEqual(m.Requires, want) {
|
||||
t.Errorf("requires = %v, want %v", m.Requires, want)
|
||||
}
|
||||
keeps := false
|
||||
|
||||
@@ -41,6 +41,8 @@ func main() {
|
||||
Toolchains: stdio.State(seat.StateToolchains).Put,
|
||||
Emit: func(t string, b any) error { return stdio.Emit(t, b) },
|
||||
Steps: stdio.State(seat.StateSteps).Put,
|
||||
Detected: stdio.State(seat.StateDetectedSteps).Put,
|
||||
ReadDetected: readOf(stdio.State(seat.StateDetectedSteps)).Get,
|
||||
ReadSteps: readOf(stdio.State(seat.StateSteps)),
|
||||
Pauses: stdio.State(sdkstep.PausesState).Put,
|
||||
ReadPauses: readOf(stdio.State(sdkstep.PausesState)),
|
||||
|
||||
@@ -33,6 +33,10 @@ type Module struct {
|
||||
// Held is the puller whose running asks a step-end tool will not end (issue 1890); nil in a test.
|
||||
Held *buildstep.Puller
|
||||
ReadSteps, ReadToolchainsState StateRead
|
||||
// Detected and ReadDetected are the Put and the read of the state detected-steps (design 47 G §326, §414): the
|
||||
// answer of the verb steps, one key per artifact.
|
||||
Detected buildstep.Put
|
||||
ReadDetected func(key string) (json.RawMessage, error)
|
||||
// Pauses is the Put of the pauses state, which keeps the node's pause key without an age; ReadPauses reads it back.
|
||||
Pauses buildstep.Put
|
||||
ReadPauses StateRead
|
||||
|
||||
@@ -158,11 +158,35 @@ func (m *Module) steps(ctx context.Context, args map[string]any) (any, error) {
|
||||
if str("artifact") == "" || str("module") == "" {
|
||||
return nil, fmt.Errorf("steps: the module and the artifact are asked")
|
||||
}
|
||||
artifact, fp := str("artifact"), str("fingerprint")
|
||||
key := seat.DetectedStepsByModuleArtifactKey(str("module"), artifactName(artifact))
|
||||
// The kept answer is the artifact's newest fingerprint's: used for the same fingerprint, replaced by a newer one
|
||||
// and by again (§326).
|
||||
if again, _ := args["again"].(bool); !again && fp != "" && m.ReadDetected != nil {
|
||||
if raw, err := m.ReadDetected(key); err == nil && len(raw) > 0 {
|
||||
if kept, err := seat.DecodeDetectedStepsByModuleArtifactValue(raw); err == nil && kept.Fingerprint == fp {
|
||||
return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: kept.Steps, Kept: true}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
steps, err := m.stepsOfTree(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return StepsAnswer{Artifact: str("artifact"), Fingerprint: str("fingerprint"), Steps: steps}, nil
|
||||
if fp != "" && m.Detected != nil {
|
||||
if _, err := m.Detected(key, seat.DetectedSteps{V: 1, Fingerprint: fp, Steps: steps, At: ptr(m.now())}); err != nil {
|
||||
return nil, fmt.Errorf("steps: keeping the answer: %w", err)
|
||||
}
|
||||
}
|
||||
return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: steps}, nil
|
||||
}
|
||||
|
||||
// artifactName is the artifact of an artifact reference <module>:<artifact>.
|
||||
func artifactName(ref string) string {
|
||||
if _, name, ok := strings.Cut(ref, ":"); ok {
|
||||
return name
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
||||
// stepsOfTree fetches the commit into a workspace removed at the end and detects the steps of the module's directory.
|
||||
@@ -196,3 +220,5 @@ func (m *Module) stepsOfTree(ctx context.Context, req sdkstep.FetchRequest) ([]D
|
||||
}
|
||||
return detectSteps(dir)
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -48,3 +50,40 @@ func TestTheStepsVerbAnswersTheStepsOfAFixtureArtifact(t *testing.T) {
|
||||
t.Fatalf("a module with no test: %+v, %v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Design 47 G §326 (BLD2): steps answers from the kept key for the same fingerprint, a newer fingerprint and again
|
||||
// replace it.
|
||||
func TestTheStepsVerbKeepsItsAnswerByFingerprint(t *testing.T) {
|
||||
f := &fakeStep{files: map[string]string{"go.mod": "module example.test/mod\n", "a/a_test.go": "package a\n"}}
|
||||
m, _ := fetchModule(t, f)
|
||||
kept := map[string]json.RawMessage{}
|
||||
m.Detected = func(key string, v any) (uint64, error) {
|
||||
raw, err := json.Marshal(v)
|
||||
kept[key] = raw
|
||||
return 1, err
|
||||
}
|
||||
m.ReadDetected = func(key string) (json.RawMessage, error) { return kept[key], nil }
|
||||
call := func(fp string, again bool) StepsAnswer {
|
||||
t.Helper()
|
||||
got, err := m.steps(context.Background(), map[string]any{"module": "mod", "artifact": "mod:prog", "fingerprint": fp, "again": again,
|
||||
"fetch": map[string]any{"repository": "novox/mod", "commit": commitSHA}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return got.(StepsAnswer)
|
||||
}
|
||||
if a := call("fp1", false); a.Kept || len(kept) != 1 || kept["mod.prog"] == nil {
|
||||
t.Fatalf("first ask: %+v, keys %v", a, kept)
|
||||
}
|
||||
f.files = map[string]string{"go.mod": "module example.test/mod\n"} // the tree changed, the kept answer still answers fp1
|
||||
if a := call("fp1", false); !a.Kept || len(a.Steps) != 2 {
|
||||
t.Fatalf("same fingerprint: %+v", a)
|
||||
}
|
||||
if a := call("fp1", true); a.Kept || len(a.Steps) != 1 {
|
||||
t.Fatalf("again: %+v", a)
|
||||
}
|
||||
f.files = map[string]string{"go.mod": "module example.test/mod\n", "a/a_test.go": "package a\n"}
|
||||
if a := call("fp2", false); a.Kept || len(a.Steps) != 2 {
|
||||
t.Fatalf("newer fingerprint: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
}
|
||||
],
|
||||
"requires": [
|
||||
{"name": "detected-steps", "storage": "key-value", "template": "state", "options": {"history": 1}},
|
||||
"forge-account",
|
||||
"go-toolchain",
|
||||
"go-package-registry",
|
||||
|
||||
Reference in New Issue
Block a user