mesh-delivery-contracts: the client of mesh-delivery-checker holds the rules of a verdict and a kept verdict that its holder and readers share (D-10, ADR 0634 §2, ADR 0625 §4; issue 1966)
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
// Package meshdeliverycheckerclient holds the logic the holders and callers of the seat mesh-delivery-checker share over its
|
||||
// contract (novox/hq ADR 0625 §4, ADR 0634 §2): the rules a verdict and a kept verdict must keep to be written or read. The
|
||||
// checker writes them, and the view, the gate and the indexer read them; the contract's decoding is strict by schema only, so
|
||||
// these rules are written once here and no caller copies them.
|
||||
package meshdeliverycheckerclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/kept"
|
||||
)
|
||||
|
||||
// The states of a verdict, the levels of a check and the compositions' states.
|
||||
const (
|
||||
VerdictRunning = "running"
|
||||
VerdictPass = "pass"
|
||||
VerdictFail = "fail"
|
||||
|
||||
LevelUnit = "unit"
|
||||
LevelIntegration = "integration"
|
||||
|
||||
CompositionNotRun = "not-run"
|
||||
CompositionPass = "pass"
|
||||
CompositionFail = "fail"
|
||||
)
|
||||
|
||||
// MaxFailures is the most failures a verdict names; it names the count of the rest beside them.
|
||||
const MaxFailures = 50
|
||||
|
||||
func checkLevel(level string) error {
|
||||
if level != LevelUnit && level != LevelIntegration {
|
||||
return fmt.Errorf("meshdeliverycheckerclient: the level %q is not %s or %s", level, LevelUnit, LevelIntegration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isHex12(s string) bool { return len(s) == 12 && strings.Trim(s, "0123456789abcdef") == "" }
|
||||
|
||||
// CheckRepository refuses a repository that is not <owner>/<name> with exactly one slash, each part a key part.
|
||||
func CheckRepository(repository string) error {
|
||||
owner, name, ok := strings.Cut(repository, "/")
|
||||
if !ok || strings.Contains(name, "/") {
|
||||
return fmt.Errorf("meshdeliverycheckerclient: the repository %q is not <owner>/<name>, with exactly one slash", repository)
|
||||
}
|
||||
if err := kept.CheckName("repository owner", owner); err != nil {
|
||||
return fmt.Errorf("meshdeliverycheckerclient: the repository %q: %w", repository, err)
|
||||
}
|
||||
if err := kept.CheckName("repository name", name); err != nil {
|
||||
return fmt.Errorf("meshdeliverycheckerclient: the repository %q: %w", repository, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkComposition(c *checker.VerdictsByGroupHeadsValueComposition) error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
switch c.State {
|
||||
case CompositionNotRun:
|
||||
if c.Sdk != "" || len(c.Machines) > 0 {
|
||||
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition is not run and names no mesh-sdk commit or machines")
|
||||
}
|
||||
case CompositionPass:
|
||||
if c.Sdk == "" {
|
||||
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition passed and names no mesh-sdk commit")
|
||||
}
|
||||
case CompositionFail:
|
||||
if c.Words == "" {
|
||||
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition failed and gives no words")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition state %q is not %s, %s or %s", c.State, CompositionNotRun, CompositionPass, CompositionFail)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckVerdict refuses a verdict a reader could not place: no group, heads that are not 12 lowercase hex digits, a repository
|
||||
// both merged and read beside, a carried-from that carries nothing, a series change from a series to itself or named twice, a
|
||||
// level that is neither unit nor integration or counts a negative number, a time below zero, a pass that says nothing of a
|
||||
// level, a fail that names neither a test nor a machine, and failures on a verdict that did not fail.
|
||||
func CheckVerdict(v checker.VerdictsByGroupHeadsValue) error {
|
||||
const who = "meshdeliverycheckerclient: "
|
||||
if v.Group == "" {
|
||||
return fmt.Errorf(who + "a verdict names no group")
|
||||
}
|
||||
if !isHex12(v.Heads) {
|
||||
return fmt.Errorf(who+"the verdict's heads hash %q is not 12 lowercase hex digits", v.Heads)
|
||||
}
|
||||
for repository, head := range v.Trunk {
|
||||
if head == "" {
|
||||
return fmt.Errorf(who+"the verdict's trunk head of %s is empty", repository)
|
||||
}
|
||||
}
|
||||
for repository, commit := range v.Beside {
|
||||
if commit == "" {
|
||||
return fmt.Errorf(who+"the verdict's sibling commit of %s is empty", repository)
|
||||
}
|
||||
if _, ok := v.Trunk[repository]; ok {
|
||||
return fmt.Errorf(who+"the verdict names %s as a trunk it merged and as a sibling it read", repository)
|
||||
}
|
||||
}
|
||||
if len(v.CarriedFrom) > 0 {
|
||||
if v.State == VerdictRunning {
|
||||
return fmt.Errorf(who + "a running verdict is carried from nothing: carried-from is written with its end")
|
||||
}
|
||||
moved := false
|
||||
for repository, commit := range v.CarriedFrom {
|
||||
now, inTrunk := v.Trunk[repository]
|
||||
if !inTrunk {
|
||||
now = v.Beside[repository]
|
||||
}
|
||||
if commit == "" || now == "" {
|
||||
return fmt.Errorf(who+"the verdict's carried-from names %s, which it judged on no commit of", repository)
|
||||
}
|
||||
moved = moved || commit != now
|
||||
}
|
||||
if !moved {
|
||||
return fmt.Errorf(who + "the verdict's carried-from names the commits it is judged on: nothing moved, so nothing was carried")
|
||||
}
|
||||
}
|
||||
changed := map[string]bool{}
|
||||
for _, c := range v.Series {
|
||||
if c.Module == "" || c.Artifact == "" || c.From == "" || c.To == "" {
|
||||
return fmt.Errorf(who+"the verdict's series change %+v names no module, artifact, from or to", c)
|
||||
}
|
||||
if c.From == c.To {
|
||||
return fmt.Errorf(who+"the verdict's series change of %s/%s is from %s to the same series", c.Module, c.Artifact, c.From)
|
||||
}
|
||||
if changed[c.Module+"/"+c.Artifact] {
|
||||
return fmt.Errorf(who+"the verdict names the series change of %s/%s twice", c.Module, c.Artifact)
|
||||
}
|
||||
changed[c.Module+"/"+c.Artifact] = true
|
||||
}
|
||||
for level, r := range v.Levels {
|
||||
if err := checkLevel(level); err != nil {
|
||||
return err
|
||||
}
|
||||
if r.Run < 0 || r.Kept < 0 || r.Skipped < 0 {
|
||||
return fmt.Errorf(who+"the verdict's %s level counts a negative number of tests", level)
|
||||
}
|
||||
}
|
||||
if err := checkComposition(v.Composition); err != nil {
|
||||
return err
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, level := range v.OverBound {
|
||||
if err := checkLevel(level); err != nil {
|
||||
return fmt.Errorf(who+"the verdict's over-bound names: %w", err)
|
||||
}
|
||||
if seen[level] {
|
||||
return fmt.Errorf(who+"the verdict's over-bound names the level %s twice", level)
|
||||
}
|
||||
seen[level] = true
|
||||
}
|
||||
if t := v.Took; t != nil {
|
||||
for part, ms := range map[string]int64{"queue": t.Queue, "clone": t.Clone, "unit": t.Unit, "lint": t.Lint,
|
||||
"integration-setup": t.IntegrationSetup, "integration-run": t.IntegrationRun, "composition": t.Composition} {
|
||||
if ms < 0 {
|
||||
return fmt.Errorf(who+"the verdict's time of the part %s is negative", part)
|
||||
}
|
||||
}
|
||||
}
|
||||
named := v.Failed != nil && (v.Failed.Test != "" || v.Failed.Machine != "")
|
||||
if len(v.Failures) > 0 && v.State != VerdictFail {
|
||||
return fmt.Errorf(who+"a %s verdict names no failures", v.State)
|
||||
}
|
||||
if len(v.Failures) > MaxFailures+1 {
|
||||
return fmt.Errorf(who+"the verdict names %d failures, more than %d and the count of the rest", len(v.Failures), MaxFailures)
|
||||
}
|
||||
switch v.State {
|
||||
case VerdictRunning:
|
||||
if v.Failed != nil && *v.Failed != (checker.VerdictsByGroupHeadsValueFailed{}) {
|
||||
return fmt.Errorf(who + "a running verdict names no failure")
|
||||
}
|
||||
case VerdictPass:
|
||||
if v.Failed != nil && *v.Failed != (checker.VerdictsByGroupHeadsValueFailed{}) {
|
||||
return fmt.Errorf(who + "a pass names no failure")
|
||||
}
|
||||
for _, level := range []string{LevelUnit, LevelIntegration} {
|
||||
if _, ok := v.Levels[level]; !ok {
|
||||
return fmt.Errorf(who+"a pass says nothing of the %s level", level)
|
||||
}
|
||||
}
|
||||
case VerdictFail:
|
||||
if !named {
|
||||
return fmt.Errorf(who + "a fail names neither a test nor a machine")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(who+"the verdict state %q is not running, pass or fail", v.State)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckKept refuses a kept verdict that is not a pass of a repository's package at a level, with its toolchain and its time:
|
||||
// a failure is never kept.
|
||||
func CheckKept(k checker.KeptByLevelDigestValue) error {
|
||||
const who = "meshdeliverycheckerclient: "
|
||||
if k.State != VerdictPass {
|
||||
return fmt.Errorf(who+"a kept verdict is a pass, never %q: a failure is never kept", k.State)
|
||||
}
|
||||
if err := CheckRepository(k.Repository); err != nil {
|
||||
return err
|
||||
}
|
||||
if k.Package == "" {
|
||||
return fmt.Errorf(who + "a kept verdict names no package")
|
||||
}
|
||||
if err := checkLevel(k.Level); err != nil {
|
||||
return err
|
||||
}
|
||||
if k.Tests < 0 || k.Took < 0 {
|
||||
return fmt.Errorf(who + "a kept verdict counts a negative number of tests or a negative time")
|
||||
}
|
||||
if k.Toolchain == "" {
|
||||
return fmt.Errorf(who + "a kept verdict names no toolchain version")
|
||||
}
|
||||
if k.At == "" {
|
||||
return fmt.Errorf(who + "a kept verdict says no time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package meshdeliverycheckerclient
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
)
|
||||
|
||||
type verdict = checker.VerdictsByGroupHeadsValue
|
||||
|
||||
func passing() verdict {
|
||||
return verdict{Group: "g", Heads: "0123456789ab", State: VerdictPass, Trunk: map[string]string{"novox/a": "c1"},
|
||||
Levels: map[string]checker.VerdictsByGroupHeadsValueLevelsValue{LevelUnit: {Run: 3}, LevelIntegration: {Run: 1}}}
|
||||
}
|
||||
|
||||
// The rules a verdict keeps to be written or read: each case edits a passing verdict and names what the refusal says.
|
||||
func TestAVerdictIsRefusedForWhatAReaderCouldNotPlace(t *testing.T) {
|
||||
if err := CheckVerdict(passing()); err != nil {
|
||||
t.Fatalf("a passing verdict is refused: %v", err)
|
||||
}
|
||||
for name, c := range map[string]struct {
|
||||
edit func(*verdict)
|
||||
says string
|
||||
}{
|
||||
"no group": {func(v *verdict) { v.Group = "" }, "no group"},
|
||||
"heads of 11 digits": {func(v *verdict) { v.Heads = "0123456789a" }, "12 lowercase hex"},
|
||||
"an empty trunk head": {func(v *verdict) { v.Trunk["novox/a"] = "" }, "trunk head"},
|
||||
"merged and read": {func(v *verdict) { v.Beside = map[string]string{"novox/a": "c2"} }, "trunk it merged and as a sibling"},
|
||||
"nothing carried": {func(v *verdict) { v.CarriedFrom = map[string]string{"novox/a": "c1"} }, "nothing moved"},
|
||||
"a running carry": {func(v *verdict) { v.State, v.CarriedFrom = VerdictRunning, map[string]string{"novox/a": "c0"} }, "carried from nothing"},
|
||||
"a series to itself": {func(v *verdict) {
|
||||
v.Series = []checker.VerdictsByGroupHeadsValueSeriesItem{{Module: "m", Artifact: "a", From: "0.1", To: "0.1"}}
|
||||
}, "same series"},
|
||||
"an unknown level": {func(v *verdict) { v.Levels["smoke"] = checker.VerdictsByGroupHeadsValueLevelsValue{} }, "smoke"},
|
||||
"a negative count": {func(v *verdict) { v.Levels[LevelUnit] = checker.VerdictsByGroupHeadsValueLevelsValue{Run: -1} }, "negative"},
|
||||
"a negative time": {func(v *verdict) { v.Took = &checker.VerdictsByGroupHeadsValueTook{Unit: -1} }, "negative"},
|
||||
"a pass without a level": {func(v *verdict) { delete(v.Levels, LevelIntegration) }, "integration level"},
|
||||
"a pass that failed": {func(v *verdict) { v.Failed = &checker.VerdictsByGroupHeadsValueFailed{Test: "T"} }, "names no failure"},
|
||||
"failures of a pass": {func(v *verdict) { v.Failures = []checker.VerdictsByGroupHeadsValueFailuresItem{{Test: "T"}} }, "names no failures"},
|
||||
"a fail naming nothing": {func(v *verdict) { v.State = VerdictFail }, "neither a test nor a machine"},
|
||||
"an unknown state": {func(v *verdict) { v.State = "maybe" }, "maybe"},
|
||||
"a composition passed without a commit": {func(v *verdict) {
|
||||
v.Composition = &checker.VerdictsByGroupHeadsValueComposition{State: CompositionPass}
|
||||
}, "mesh-sdk commit"},
|
||||
"the same level over bound twice": {func(v *verdict) { v.OverBound = []string{LevelUnit, LevelUnit} }, "twice"},
|
||||
} {
|
||||
v := passing()
|
||||
c.edit(&v)
|
||||
if err := CheckVerdict(v); err == nil || !strings.Contains(err.Error(), c.says) {
|
||||
t.Errorf("%s: %v, want a refusal saying %q", name, err, c.says)
|
||||
}
|
||||
}
|
||||
failed := passing()
|
||||
failed.State, failed.Failed = VerdictFail, &checker.VerdictsByGroupHeadsValueFailed{Machine: "ace"}
|
||||
if err := CheckVerdict(failed); err != nil {
|
||||
t.Errorf("a fail naming a machine is refused: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAKeptVerdictIsAPassOfAPackageAtALevel(t *testing.T) {
|
||||
ok := checker.KeptByLevelDigestValue{State: VerdictPass, Repository: "novox/mesh-sdk", Package: "./go/keys", Level: LevelUnit, Toolchain: "1.26.0", At: "2026-11-05T10:00:00Z"}
|
||||
if err := CheckKept(ok); err != nil {
|
||||
t.Fatalf("a kept pass is refused: %v", err)
|
||||
}
|
||||
for name, edit := range map[string]func(*checker.KeptByLevelDigestValue){
|
||||
"a fail": func(k *checker.KeptByLevelDigestValue) { k.State = VerdictFail },
|
||||
"no owner": func(k *checker.KeptByLevelDigestValue) { k.Repository = "mesh-sdk" },
|
||||
"no package": func(k *checker.KeptByLevelDigestValue) { k.Package = "" },
|
||||
"an unknown level": func(k *checker.KeptByLevelDigestValue) { k.Level = "smoke" },
|
||||
"negative tests": func(k *checker.KeptByLevelDigestValue) { k.Tests = -1 },
|
||||
"no toolchain": func(k *checker.KeptByLevelDigestValue) { k.Toolchain = "" },
|
||||
"no time": func(k *checker.KeptByLevelDigestValue) { k.At = "" },
|
||||
} {
|
||||
k := ok
|
||||
edit(&k)
|
||||
if CheckKept(k) == nil {
|
||||
t.Errorf("%s was taken", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker v0.0.0
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0
|
||||
)
|
||||
@@ -112,6 +112,15 @@
|
||||
],
|
||||
"series": "0.1"
|
||||
},
|
||||
{
|
||||
"name": "go-meshdeliverycheckerclient",
|
||||
"kind": "package",
|
||||
"language": "go",
|
||||
"paths": [
|
||||
"go/clients/meshdeliverycheckerclient"
|
||||
],
|
||||
"series": "0.1"
|
||||
},
|
||||
{
|
||||
"name": "go-gopackageregistryclient",
|
||||
"kind": "package",
|
||||
|
||||
Reference in New Issue
Block a user