G §57, §345: a replace leaves the object it replaced, and a value over a bundle's object limit is refused, never split
Builds: design 47 G §57, §345 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -3,6 +3,7 @@ package stateobjects
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -13,11 +14,11 @@ import (
|
||||
func big(n int) map[string]string { return map[string]string{"text": strings.Repeat("x", n)} }
|
||||
|
||||
// valueRows is what every backend of Values holds: a value of 1x, 10x and 100x the limit is read back whole, a replace
|
||||
// leaves one object, a delete leaves one listed key-gone (ADR 0666 §16, C-Q1).
|
||||
func valueRows(t *testing.T, v Values) {
|
||||
// leaves the old object and a delete leaves its object, both listed key-gone until the drop (ADR 0666 §16, ADR 0595 §4).
|
||||
func valueRows(t *testing.T, v Values, multiples ...int) {
|
||||
t.Helper()
|
||||
limit := kvlimit.ModuleStateValueBytes
|
||||
for _, times := range []int{1, 10, 100} {
|
||||
for _, times := range multiples {
|
||||
key := map[int]string{1: "one", 10: "ten", 100: "hundred"}[times]
|
||||
want := big(times * limit)
|
||||
if err := v.Put(key, want); err != nil {
|
||||
@@ -43,9 +44,9 @@ func valueRows(t *testing.T, v Values) {
|
||||
t.Errorf("a small value is kept in its key, got %s", e.Value)
|
||||
return
|
||||
}
|
||||
// a replace removes the object it replaced; the same value again leaves its object
|
||||
// a replace leaves the object it replaced, listed key-gone (ADR 0595 §4); the same value again adds none
|
||||
before, _ := v.Objects.List()
|
||||
if err := v.Put("one", big(2*limit)); err != nil { // 10x key
|
||||
if err := v.Put("one", big(2*limit)); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -54,17 +55,16 @@ func valueRows(t *testing.T, v Values) {
|
||||
return
|
||||
}
|
||||
after, _ := v.Objects.List()
|
||||
if len(after) != len(before) {
|
||||
t.Errorf("a replace left %d objects, %d before it", len(after), len(before))
|
||||
if len(after) != len(before)+1 {
|
||||
t.Errorf("a replace left %d objects, %d before it: the old one must stay", len(after), len(before))
|
||||
return
|
||||
}
|
||||
// a replace by a small value removes the object too
|
||||
if err := v.Put("one", "small now"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if now, _ := v.Objects.List(); len(now) != len(before)-1 {
|
||||
t.Errorf("replacing a large value by a small one left %d objects, want %d", len(now), len(before)-1)
|
||||
if now, _ := v.Objects.List(); len(now) != len(after) {
|
||||
t.Errorf("replacing by a small value changed the objects: %d, want %d", len(now), len(after))
|
||||
return
|
||||
}
|
||||
// a delete leaves the object, listed key-gone, and the drop removes it alone
|
||||
@@ -83,16 +83,17 @@ func valueRows(t *testing.T, v Values) {
|
||||
gone = append(gone, k.Key)
|
||||
}
|
||||
}
|
||||
if len(gone) != 1 || gone[0] != "ten" {
|
||||
t.Errorf("the objects whose key is gone: %v, want ten's", gone)
|
||||
slices.Sort(gone)
|
||||
if !slices.Equal(gone, []string{"one", "one", "ten"}) {
|
||||
t.Errorf("the objects whose key is gone: %v, want the two replaced of one and ten's", gone)
|
||||
return
|
||||
}
|
||||
dropped, err := v.DropKeyGone()
|
||||
if err != nil || len(dropped) != 1 {
|
||||
if err != nil || len(dropped) != 3 {
|
||||
t.Errorf("dropped %v, %v", dropped, err)
|
||||
return
|
||||
}
|
||||
if left, _ := v.Objects.List(); len(left) != len(kept)-1 {
|
||||
if left, _ := v.Objects.List(); len(left) != len(kept)-3 {
|
||||
t.Errorf("the drop left %d objects of %d", len(left), len(kept))
|
||||
return
|
||||
}
|
||||
@@ -107,7 +108,7 @@ func TestValuesOfAnySizeThroughTheRuntime(t *testing.T) {
|
||||
m := newMemory()
|
||||
out := runtimeOf(t, m, func() (any, error) {
|
||||
v := ForState("views")
|
||||
valueRows(t, v)
|
||||
valueRows(t, v, 1, 10) // a bundle puts at most 8 MiB in an object: 100x is a process's
|
||||
return "done", nil
|
||||
})
|
||||
if !strings.Contains(out.(string), "done") {
|
||||
@@ -156,33 +157,16 @@ func (r *raceKV) Get(key string) (*Entry, error) {
|
||||
return e, err
|
||||
}
|
||||
|
||||
// A store that takes no object as large as the value gets it split over objects, read back whole, replaced and dropped as
|
||||
// one (a bundle's runtime carries a message of 16 MiB).
|
||||
func TestAValueLargerThanTheStoreTakesIsSplitOverObjects(t *testing.T) {
|
||||
// A store that takes no object as large as the value refuses it, loudly, and writes nothing.
|
||||
func TestAValueLargerThanTheStoreTakesIsRefused(t *testing.T) {
|
||||
m := newMemory()
|
||||
m.most = 100_000
|
||||
v := m.values()
|
||||
want := big(5*kvlimit.ModuleStateValueBytes + 10)
|
||||
if err := v.Put("k", want); err != nil {
|
||||
t.Fatal(err)
|
||||
err := v.Put("k", big(5*kvlimit.ModuleStateValueBytes))
|
||||
if err == nil || !strings.Contains(err.Error(), "refused") {
|
||||
t.Fatalf("the put: %v", err)
|
||||
}
|
||||
if l, _ := (memObjects{m}).List(); len(l) < 13 {
|
||||
t.Fatalf("%d objects, want the value split over at least 13", len(l))
|
||||
}
|
||||
var got map[string]string
|
||||
if ok, err := v.Get("k", &got); err != nil || !ok || got["text"] != want["text"] {
|
||||
t.Fatalf("read back %d, %v, %v", len(got["text"]), ok, err)
|
||||
}
|
||||
if err := v.Delete("k"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
kept, _ := v.Kept()
|
||||
for _, k := range kept {
|
||||
if !k.KeyGone || k.Key != "k" {
|
||||
t.Fatalf("a part %+v is not listed as key-gone for k", k)
|
||||
}
|
||||
}
|
||||
if dropped, err := v.DropKeyGone(); err != nil || len(dropped) != len(kept) {
|
||||
t.Fatalf("dropped %v of %d, %v", dropped, len(kept), err)
|
||||
if e, _ := m.Get("k"); e != nil || len(m.objects) != 0 {
|
||||
t.Fatal("a refused value left a key or an object")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user