Keep every strict read of a manifest in the indexer's judge, and add no test to the budget
Issue 1585, S5-S8: the indexer's fingerprint reads go through judge.ForInputs, so judge/ is the one place in mesh-delivery that calls mesh-sdk's rules; the command's request judging moves into judge.Request, tested in the judge's table; two predefined tests become one. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -12,8 +12,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
|
||||
@@ -23,9 +21,6 @@ import (
|
||||
// Package is this command's import path in the indexer's Go module (the directory delivery.ComposeJob's Dir names).
|
||||
const Package = "mesh-delivery-indexer/cmd/mesh-judge"
|
||||
|
||||
// Between names, in a case's verdict, the problems between manifests that name no module of the case.
|
||||
const Between = "(between manifests)"
|
||||
|
||||
func main() { os.Exit(run(os.Args[1:], os.Stdin, os.Stdout, os.Stderr)) }
|
||||
|
||||
func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
@@ -51,7 +46,7 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
fmt.Fprintln(stderr, "mesh-judge:", err)
|
||||
return 1
|
||||
}
|
||||
v, err := Judge(r)
|
||||
v, err := judge.Request(r)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, "mesh-judge:", err)
|
||||
return 1
|
||||
@@ -64,39 +59,3 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
fmt.Fprintln(stdout, string(out))
|
||||
return 0
|
||||
}
|
||||
|
||||
// Judge judges every case of r: each case's manifests as one catalogue (judge.Shelf), every manifest accepted or
|
||||
// refused with its problems; problems between manifests that name none of them under Between. The verdict's SDK
|
||||
// names this judge (judge.Identity). A manifest left as a digest is refused as a request it cannot run.
|
||||
func Judge(r composer.Request) (composer.Verdict, error) {
|
||||
v := composer.Verdict{V: composer.Version, SDK: judge.Identity()}
|
||||
seen := map[string]bool{}
|
||||
for _, c := range r.Cases {
|
||||
if seen[c.Name] {
|
||||
return composer.Verdict{}, fmt.Errorf("the case %q is named twice", c.Name)
|
||||
}
|
||||
seen[c.Name] = true
|
||||
raws := map[string][]byte{}
|
||||
for name, m := range c.Manifests {
|
||||
if len(m.Raw) == 0 {
|
||||
return composer.Verdict{}, fmt.Errorf("%w: %s (digest %s)", composer.ErrUnfilled, name, m.Digest)
|
||||
}
|
||||
raws[name] = m.Raw
|
||||
}
|
||||
refused, unnamed := judge.Shelf(raws, r.Predefined, 0)
|
||||
cv := composer.CaseVerdict{Name: c.Name, Manifests: map[string]composer.ManifestRead{}}
|
||||
for name := range raws {
|
||||
if problems := refused[name]; len(problems) > 0 {
|
||||
cv.Manifests[name] = composer.ManifestRead{Refused: strings.Join(problems, "\n")}
|
||||
continue
|
||||
}
|
||||
cv.Manifests[name] = composer.ManifestRead{Accepted: true}
|
||||
}
|
||||
if len(unnamed) > 0 {
|
||||
sort.Strings(unnamed)
|
||||
cv.Manifests[Between] = composer.ManifestRead{Refused: strings.Join(unnamed, "\n")}
|
||||
}
|
||||
v.Cases = append(v.Cases, cv)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
)
|
||||
|
||||
// The command the checker runs (novox/hq ADR 0578 §2): a composer request in, one line of a composer verdict out, each
|
||||
// manifest accepted or refused with its problems; a request it cannot run exits non-zero.
|
||||
func TestTheCommandJudgesARequest(t *testing.T) {
|
||||
r := composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "change", Manifests: map[string]composer.Manifest{
|
||||
"a": {Raw: json.RawMessage(`{"module":"a","version":"1"}`)},
|
||||
"b": {Raw: json.RawMessage(`{"module":"b","version":"1","requires":["amqp"]}`)},
|
||||
}}}}
|
||||
raw, _ := json.Marshal(r)
|
||||
var out, errs bytes.Buffer
|
||||
if code := run(nil, bytes.NewReader(raw), &out, &errs); code != 0 {
|
||||
t.Fatalf("exit %d: %s", code, errs.String())
|
||||
}
|
||||
var v composer.Verdict
|
||||
if err := json.Unmarshal(out.Bytes(), &v); err != nil || v.V != composer.Version || len(v.Cases) != 1 || v.SDK == "" {
|
||||
t.Fatalf("verdict %s: %v", out.String(), err)
|
||||
}
|
||||
c := v.Cases[0]
|
||||
if !c.Manifests["a"].Accepted || c.Manifests["b"].Accepted || !strings.Contains(c.Manifests["b"].Refused, `requires "amqp"`) {
|
||||
t.Fatalf("case: %+v", c)
|
||||
}
|
||||
r.Cases[0].Manifests["c"] = composer.Manifest{Digest: strings.Repeat("a", 64)}
|
||||
raw, _ = json.Marshal(r)
|
||||
if code := run(nil, bytes.NewReader(raw), &out, &errs); code == 0 {
|
||||
t.Fatal("a manifest left as a digest was judged")
|
||||
}
|
||||
if code := run(nil, strings.NewReader("{"), &out, &errs); code == 0 {
|
||||
t.Fatal("a request that is no JSON was judged")
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"mesh-delivery-indexer/judge"
|
||||
)
|
||||
|
||||
// The reasons a module has no fingerprint of the new form while the indexer writes none for any module (novox/hq
|
||||
@@ -67,7 +69,7 @@ func inputsOf(module, raw, tree string, locks map[string]string, base baseFP) mo
|
||||
// gives the merge's value (novox/hq ADR 0499 clause 2); a message that does not carry the object of each such path gives it
|
||||
// none, naming the paths (clause 3). Every other module keeps its directory's tree, whatever objects are given.
|
||||
func inputsOfNamed(module, raw, tree string, locks, objects map[string]string, base baseFP) moduleInputs {
|
||||
m, err := catalogue.ParseManifestForInputs([]byte(raw))
|
||||
m, err := judge.ForInputs([]byte(raw))
|
||||
if err != nil {
|
||||
return moduleInputs{None2: None2Unreadable + err.Error()}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"mesh-delivery-indexer/judge"
|
||||
)
|
||||
|
||||
// applyMerged writes, for each module whose manifest the merged message carries (each directory of the merge commit that
|
||||
@@ -153,7 +154,7 @@ func (ix *Indexer) namedObjects(ctx context.Context, f delivery.Forge) (map[stri
|
||||
objects := map[string]map[string]string{}
|
||||
need := map[string][]string{}
|
||||
for dir, raw := range f.Manifests {
|
||||
m, err := catalogue.ParseManifestForInputs([]byte(raw))
|
||||
m, err := judge.ForInputs([]byte(raw))
|
||||
if err != nil {
|
||||
continue // inputsOfNamed says why
|
||||
}
|
||||
|
||||
@@ -19,9 +19,7 @@ func TestAManifestFallingBackToAPredefinedKeyIsRead(t *testing.T) { // 1751
|
||||
t.Errorf("%s: inputs = %+v, want the manifest read and its inputs kept", module, in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPredefinedKeyOfAnotherTypeThanItsValueIsStillRefused(t *testing.T) { // 1751: the read judges what it can
|
||||
// A predefined key of another type than its value is still refused: the read judges what it can.
|
||||
raw := strings.Replace(fixture(t, "catalogue-835bf22b-xterm"), `"predefined": "font-size"`, `"predefined": "font-size", "trusted": "yes"`, 1)
|
||||
if in := inputsOf("xterm", raw, "newtree", nil, func(string) (string, string) { return "", "none" }); in.Kept != nil {
|
||||
t.Errorf("inputs = %+v, want a manifest the catalogue refuses still refused", in)
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"mesh-delivery-indexer/judge"
|
||||
)
|
||||
|
||||
// The new-form fingerprint of a module is written, and written again with no merge, when what it is of changes
|
||||
@@ -385,7 +387,7 @@ func (ix *Indexer) manifestRaw(ctx context.Context, digest string) (string, cata
|
||||
if err != nil {
|
||||
return "", catalogue.Manifest{}, err
|
||||
}
|
||||
m, err := catalogue.ParseManifestForInputs([]byte(kept.JSON))
|
||||
m, err := judge.ForInputs([]byte(kept.JSON))
|
||||
return kept.JSON, m, err
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
)
|
||||
|
||||
// Manifest judges one manifest alone: decoded strictly, and every rule a manifest is held to by itself. The list of
|
||||
@@ -43,6 +44,12 @@ func Manifest(raw []byte, predefined catalogue.PredefinedList) (m catalogue.Mani
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ForInputs is Manifest worded as the strict parser words a refusal: the indexer's read of a manifest for the inputs of its
|
||||
// fingerprint, which keeps a refused manifest's reason in its value (None2) as it always has.
|
||||
func ForInputs(raw []byte) (catalogue.Manifest, error) {
|
||||
return catalogue.ParseManifestForInputs(raw)
|
||||
}
|
||||
|
||||
// listed is how the strict parser words a refusal of several problems, one per line after its header.
|
||||
const listed = "this manifest cannot be used:\n - "
|
||||
|
||||
@@ -160,6 +167,45 @@ func Named(problem string, shelf catalogue.Shelf) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// Unnamed names, in a request's case verdict, the problems between manifests that no one module's removal ends.
|
||||
const Unnamed = "(between manifests)"
|
||||
|
||||
// Request judges every case of a composer request (mesh-sdk composer.Request), what cmd/mesh-judge answers the checker:
|
||||
// each case's manifests as one catalogue (Shelf), every manifest accepted or refused with its problems, one per line; the
|
||||
// problems no module's removal ends under Between. The verdict's SDK names this judge (Identity). A manifest left as a
|
||||
// digest, or a case named twice, is a request it cannot run.
|
||||
func Request(r composer.Request) (composer.Verdict, error) {
|
||||
v := composer.Verdict{V: composer.Version, SDK: Identity()}
|
||||
seen := map[string]bool{}
|
||||
for _, c := range r.Cases {
|
||||
if seen[c.Name] {
|
||||
return composer.Verdict{}, fmt.Errorf("the case %q is named twice", c.Name)
|
||||
}
|
||||
seen[c.Name] = true
|
||||
raws := map[string][]byte{}
|
||||
for name, m := range c.Manifests {
|
||||
if len(m.Raw) == 0 {
|
||||
return composer.Verdict{}, fmt.Errorf("%w: %s (digest %s)", composer.ErrUnfilled, name, m.Digest)
|
||||
}
|
||||
raws[name] = m.Raw
|
||||
}
|
||||
refused, unnamed := Shelf(raws, r.Predefined, 0)
|
||||
cv := composer.CaseVerdict{Name: c.Name, Manifests: map[string]composer.ManifestRead{}}
|
||||
for name := range raws {
|
||||
if problems := refused[name]; len(problems) > 0 {
|
||||
cv.Manifests[name] = composer.ManifestRead{Refused: strings.Join(problems, "\n")}
|
||||
continue
|
||||
}
|
||||
cv.Manifests[name] = composer.ManifestRead{Accepted: true}
|
||||
}
|
||||
if len(unnamed) > 0 {
|
||||
cv.Manifests[Unnamed] = composer.ManifestRead{Refused: strings.Join(unnamed, "\n")}
|
||||
}
|
||||
v.Cases = append(v.Cases, cv)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
var (
|
||||
identityOnce sync.Once
|
||||
identity string
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package judge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
)
|
||||
|
||||
// The judge (novox/hq ADR 0578 §1): a manifest alone is accepted, or refused with each problem; an unknown field is
|
||||
@@ -64,4 +66,19 @@ func TestTheJudge(t *testing.T) {
|
||||
if id := Identity(); !strings.HasPrefix(id, "exe:") || id != Identity() {
|
||||
t.Errorf("identity %q", id)
|
||||
}
|
||||
// What cmd/mesh-judge answers the checker (ADR 0578 §2): a composer request in, a verdict out, each manifest accepted
|
||||
// or refused; a manifest left as a digest is a request it cannot run.
|
||||
r := composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "change", Manifests: map[string]composer.Manifest{
|
||||
"a": {Raw: json.RawMessage(`{"module":"a","version":"1"}`)},
|
||||
"b": {Raw: json.RawMessage(`{"module":"b","version":"1","requires":["amqp"]}`)},
|
||||
}}}}
|
||||
v, err := Request(r)
|
||||
if err != nil || v.V != composer.Version || len(v.Cases) != 1 || v.SDK != Identity() || !v.Cases[0].Manifests["a"].Accepted ||
|
||||
v.Cases[0].Manifests["b"].Accepted || !strings.Contains(v.Cases[0].Manifests["b"].Refused, `requires "amqp"`) {
|
||||
t.Fatalf("request: %+v %v", v, err)
|
||||
}
|
||||
r.Cases[0].Manifests["c"] = composer.Manifest{Digest: strings.Repeat("a", 64)}
|
||||
if _, err := Request(r); err == nil {
|
||||
t.Fatal("a manifest left as a digest was judged")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user