Second review of #231: when neither the stream nor its condition can be read, nothing is known of its fill, so none is said; the unread warning covers it. Only a condition read back as open is kept, with its own words.
166 lines
7.2 KiB
Go
166 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/conditions"
|
|
)
|
|
|
|
// **A filling log or bucket is said at three quarters of its cap and cleared below seven tenths**
|
|
// (novox/hq ADR 0297 §6): raised at 75% naming the module, the bucket or log and its fill; not at 74.9%;
|
|
// kept between 70% and 75% while it is open, and not raised there when it is not; and cleared — said no
|
|
// more — once it reads below 70%, open or not.
|
|
func TestAFillingBucketOrLogIsRaisedAtThreeQuartersAndClearedBelowSevenTenths(t *testing.T) {
|
|
const mib = 1024 * 1024
|
|
log := bucketOrLog{Module: "mesh-issues", Kind: "log", Name: "changes", Stream: "LOG_mesh-issues_changes"}
|
|
bucket := bucketOrLog{Module: "mesh-issues", Kind: "bucket", Name: "issues", Stream: "KV_mesh-issues_issues"}
|
|
for _, c := range []struct {
|
|
name string
|
|
of bucketOrLog
|
|
bytes uint64
|
|
max uint64
|
|
open bool
|
|
said bool
|
|
}{
|
|
{"a log at exactly 75%", log, 768 * mib, 1024 * mib, false, true},
|
|
{"a bucket at exactly 75%", bucket, 48 * mib, 64 * mib, false, true},
|
|
{"a bucket full", bucket, 64 * mib, 64 * mib, false, true},
|
|
{"a log just under 75%", log, 768*mib - 1, 1024 * mib, false, false},
|
|
{"a bucket at 72%, not open", bucket, 64 * mib * 72 / 100, 64 * mib, false, false},
|
|
{"a bucket at 72%, open: kept", bucket, 64 * mib * 72 / 100, 64 * mib, true, true},
|
|
{"a log at exactly 70%, open: kept", log, 700 * mib, 1000 * mib, true, true},
|
|
{"a log just under 70%, open: cleared", log, 700*mib - 1, 1000 * mib, true, false},
|
|
{"a bucket at 10%, open: cleared", bucket, 64 * mib / 10, 64 * mib, true, false},
|
|
{"a bucket with no cap", bucket, 100, 0, true, false},
|
|
} {
|
|
o, said := fillObservation(c.of, filled{Bytes: c.bytes, Max: c.max}, c.open)
|
|
if said != c.said {
|
|
t.Errorf("%s: said %v, want %v", c.name, said, c.said)
|
|
continue
|
|
}
|
|
if !said {
|
|
continue
|
|
}
|
|
if o.Key() != fillKey(c.of) || o.Kind != kindBucketOrLogFilling || o.Severity != conditions.Warning {
|
|
t.Errorf("%s: raised as %s (%s, %s)", c.name, o.Key(), o.Kind, o.Severity)
|
|
}
|
|
for _, part := range []string{c.of.Module, c.of.Kind + " " + c.of.Name, "%", " of "} {
|
|
if !strings.Contains(o.Summary, part) {
|
|
t.Errorf("%s: does not name %q: %q", c.name, part, o.Summary)
|
|
}
|
|
}
|
|
if !strings.Contains(o.Said, "bytes") {
|
|
t.Errorf("%s: the evidence does not say the fill in bytes: %q", c.name, o.Said)
|
|
}
|
|
}
|
|
o, _ := fillObservation(log, filled{Bytes: 800 * mib, Max: 1024 * mib}, false)
|
|
if !strings.Contains(o.Summary, "800.0 MiB of 1024.0 MiB, 78%") {
|
|
t.Errorf("the fill is said as %q", o.Summary)
|
|
}
|
|
}
|
|
|
|
// **One bucket or log that cannot be read does not stop the others being judged** (review of #231): it is
|
|
// said as unread with its reason, a fill condition open for it is kept, and every other bucket and log
|
|
// is judged by its own fill.
|
|
func TestAnUnreadableBucketOrLogIsSaidAndTheRestAreStillJudged(t *testing.T) {
|
|
const mib = 1024 * 1024
|
|
broken := bucketOrLog{Module: "a", Kind: "bucket", Name: "broken", Stream: "KV_a_broken"}
|
|
brokenOpen := bucketOrLog{Module: "a", Kind: "log", Name: "was-filling", Stream: "LOG_a_was-filling"}
|
|
full := bucketOrLog{Module: "b", Kind: "log", Name: "changes", Stream: "LOG_b_changes"}
|
|
empty := bucketOrLog{Module: "c", Kind: "bucket", Name: "quiet", Stream: "KV_c_quiet"}
|
|
absent := bucketOrLog{Module: "d", Kind: "bucket", Name: "not-yet", Stream: "KV_d_not-yet"}
|
|
refusal := errors.New("the bus did not answer")
|
|
read := func(_ context.Context, s bucketOrLog) (filled, bool, error) {
|
|
switch s {
|
|
case broken, brokenOpen:
|
|
return filled{}, false, refusal
|
|
case full:
|
|
return filled{Bytes: 900 * mib, Max: 1000 * mib}, true, nil
|
|
case empty:
|
|
return filled{Bytes: 1, Max: 64 * mib}, true, nil
|
|
}
|
|
return filled{}, false, nil
|
|
}
|
|
open := func(_ context.Context, key string) (conditions.Condition, bool, error) {
|
|
if key == fillKey(brokenOpen) {
|
|
return conditions.Condition{Key: key, Severity: conditions.Warning,
|
|
Summary: "a's log was-filling holds 800.0 MiB of 1024.0 MiB, 78%"}, true, nil
|
|
}
|
|
return conditions.Condition{}, false, nil
|
|
}
|
|
got := map[string]conditions.Observation{}
|
|
for _, o := range judgeFills(context.Background(), []bucketOrLog{broken, brokenOpen, full, empty, absent}, read, open) {
|
|
got[o.Key()] = o
|
|
}
|
|
for _, s := range []bucketOrLog{broken, brokenOpen} {
|
|
o, said := got[conditions.Key(conditions.ScopeBus, s.Stream, "unread")]
|
|
if !said || o.Kind != kindBucketOrLogUnread || !o.Confirm || !strings.Contains(o.Said, refusal.Error()) {
|
|
t.Errorf("%s could not be read and was said as %+v", s.Stream, o)
|
|
}
|
|
}
|
|
if o, kept := got[fillKey(brokenOpen)]; !kept || !strings.Contains(o.Said, "kept open") {
|
|
t.Errorf("an open fill condition of a log not read this time was not kept: %+v", o)
|
|
}
|
|
if _, said := got[fillKey(broken)]; said {
|
|
t.Error("a bucket not read and not filling was said filling")
|
|
}
|
|
if o, said := got[fillKey(full)]; !said || o.Kind != kindBucketOrLogFilling {
|
|
t.Errorf("a log at 90%% beside an unreadable one was not judged: %+v", got)
|
|
}
|
|
if len(got) != 4 {
|
|
t.Errorf("said %d findings, want 4 (two unread, one kept, one filling): %v", len(got), got)
|
|
}
|
|
}
|
|
|
|
// Both kinds have plain words of their own, which hold to the plain rule (novox/hq ADR 0253).
|
|
func TestAFillingOrUnreadBucketOrLogIsSaidInPlainWords(t *testing.T) {
|
|
for _, kind := range []string{kindBucketOrLogFilling, kindBucketOrLogUnread} {
|
|
wording, has := plainWordings[kind]
|
|
if !has {
|
|
t.Errorf("%s has no plain words", kind)
|
|
continue
|
|
}
|
|
if why, ok := conditions.PlainWords(wording(conditions.Observation{Kind: kind})); !ok {
|
|
t.Errorf("%s: %s", kind, why)
|
|
}
|
|
}
|
|
}
|
|
|
|
// **When neither the bucket or log nor its condition can be read, nothing is said of its fill** (second
|
|
// review of #231): the unread finding covers it, and no fill is invented for it. For one that is read and
|
|
// measured between seven tenths and three quarters, a condition that cannot be read is taken as open, so
|
|
// an unknown never clears it.
|
|
func TestNothingIsSaidOfAFillWhenNeitherItNorItsConditionCanBeRead(t *testing.T) {
|
|
const mib = 1024 * 1024
|
|
broken := bucketOrLog{Module: "a", Kind: "log", Name: "changes", Stream: "LOG_a_changes"}
|
|
between := bucketOrLog{Module: "b", Kind: "bucket", Name: "issues", Stream: "KV_b_issues"}
|
|
read := func(_ context.Context, s bucketOrLog) (filled, bool, error) {
|
|
if s == broken {
|
|
return filled{}, false, errors.New("the bus did not answer")
|
|
}
|
|
return filled{Bytes: 72 * mib, Max: 100 * mib}, true, nil
|
|
}
|
|
open := func(context.Context, string) (conditions.Condition, bool, error) {
|
|
return conditions.Condition{}, false, errors.New("the conditions bucket did not answer")
|
|
}
|
|
got := map[string]conditions.Observation{}
|
|
for _, o := range judgeFills(context.Background(), []bucketOrLog{broken, between}, read, open) {
|
|
got[o.Key()] = o
|
|
}
|
|
if o, said := got[fillKey(broken)]; said {
|
|
t.Fatalf("a fill was said for a log whose fill and condition could not be read: %+v", o)
|
|
}
|
|
if _, said := got[conditions.Key(conditions.ScopeBus, broken.Stream, "unread")]; !said {
|
|
t.Error("the log that could not be read was not said as unread")
|
|
}
|
|
if _, kept := got[fillKey(between)]; !kept {
|
|
t.Error("a bucket at 72% whose condition could not be read was cleared")
|
|
}
|
|
if len(got) != 2 {
|
|
t.Errorf("said %d findings, want 2: %v", len(got), got)
|
|
}
|
|
}
|