Act under a lease, keep accounts by order, one writer at composition (hq to-be 45 Phase 2)
Two controllers could both act (issue 204), a reconcile's report could overtake the apply after it and the digest decided (issue 267), and a grant could make a second writer of a machine's report. - The lease (internal/lease, ADR 0229): mesh-controller_lease key `holder`, 15 s age, renewed every 5 s by compare-and-set; the epoch is the revision it was taken at. The gate is the clock (stops 3 s before expiry); a refused renewal is a loss and the process exits; a holder that stops gives it back. serve takes it before asserting the bus. Epochs kept in the store (migration 0068 controller_epoch) as a floor: a bucket raised from nothing is compacted past it. Unleased (no epoch, S12 urgent) only when nobody holds it and the bus will not let it be written. A shell command acts under the holder's epoch, or its own lease when none. - Declarations carry `epoch` inside the signed envelope, only to a machine whose latest account carried a report_sequence (mesh-host #35); would-send is composed with the epoch last sent. Allot and the send both pass the gate. - Reports: contract in internal/link/order.go (epoch, sequence, report_sequence, older_than, refused_older). Accounts kept by epoch, then sequence, then report sequence; older refused, counted; unordered reports keep the digest rule. Plans by compare-and-set on a revision, with epoch. Conditions and calls carry the epoch and are not written off the lease. - S12 and S13 (naming the writer by epoch) watched, D5 run; reset of the bucket said. Writers table compiled in and enforced in PermissionsFor; the controller no longer publishes mesh.control.>. A contract per consumed kind, and the empty-on-error lint over the repository. - mesh-host pinned to its main with the epoch in the validator (D1 validates the envelope as sent). Needs mesh-host's genesis lock with the lease grant (mesh-host PR) for TestTheInstallersFirstUserListIsWhatTheControllerWouldCompose.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The lint, over this repository (novox/hq to-be 45 Phase 2, ADR 0227 rule 4 "how it is checked"): every
|
||||
// error branch that answers an empty collection says why that is the truth, or the build fails naming it.
|
||||
func TestNoReaderAnswersEmptyForAnError(t *testing.T) {
|
||||
found, err := EmptyOnError("../..")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
allowed := 0
|
||||
for _, f := range found {
|
||||
if f.Allowed != "" {
|
||||
allowed++
|
||||
continue
|
||||
}
|
||||
t.Errorf("%s answers an empty collection and no error when it could not read: refuse by name "+
|
||||
"(return the error), or say at the return why empty is the truth (// %s <why>)", f, Allow)
|
||||
}
|
||||
t.Logf("%d error branch(es) answer empty, each saying why", allowed)
|
||||
}
|
||||
|
||||
// The lint itself: what it finds, and what it does not.
|
||||
func TestTheLintFindsEmptyOnError(t *testing.T) {
|
||||
const src = `package x
|
||||
|
||||
func readContributions() []string {
|
||||
raw, err := read()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return parse(raw)
|
||||
}
|
||||
|
||||
func consumers() ([]string, error) {
|
||||
if err := load(); err != nil {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func byName() (map[string]int, error) {
|
||||
if err := load(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]int{}, nil
|
||||
}
|
||||
|
||||
func allowed() ([]string, error) {
|
||||
if err := load(); err != nil {
|
||||
// empty-on-error: a store not yet seeded holds no seats, and the caller keeps its defaults
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func notFound() ([]string, error) {
|
||||
if errors.Is(err, ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func inside() {
|
||||
f := func() []int {
|
||||
if readErr != nil {
|
||||
return nil
|
||||
}
|
||||
return []int{1}
|
||||
}
|
||||
_ = f
|
||||
}
|
||||
`
|
||||
fset := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fset, "x.go", src, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var failing, allowedFns []string
|
||||
for _, f := range inFile(fset, file, "x.go") {
|
||||
if f.Allowed != "" {
|
||||
allowedFns = append(allowedFns, f.Func)
|
||||
continue
|
||||
}
|
||||
failing = append(failing, f.Func)
|
||||
}
|
||||
want := "readContributions consumers inside (a function inside it)"
|
||||
if strings.Join(failing, " ") != want {
|
||||
t.Fatalf("the lint found %q, want %q", failing, want)
|
||||
}
|
||||
if strings.Join(allowedFns, " ") != "allowed" {
|
||||
t.Fatalf("the allowance was not read: %q", allowedFns)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user