commit cd9478f3b3893090aab15305e948ea875f8e9649
parent 2108b77e5392058c82acf98871aaf951a6bdc65e
Author: lash <dev@holbrook.no>
Date:   Sun,  1 Sep 2024 17:02:04 +0100
WIP db test vectors
Diffstat:
7 files changed, 148 insertions(+), 56 deletions(-)
diff --git a/db/db.go b/db/db.go
@@ -8,6 +8,10 @@ import (
 )
 
 const (
+	safeLock =DATATYPE_BIN | DATATYPE_MENU | DATATYPE_TEMPLATE | DATATYPE_STATICLOAD
+)
+
+const (
 	// Invalid datatype, must raise error if attempted used.
 	DATATYPE_UNKNOWN = 0
 	// Bytecode
@@ -21,11 +25,11 @@ const (
 	// State and cache from persister
 	DATATYPE_STATE = 16
 	// Application data
-	DATATYPE_USERSTART = 32
+	DATATYPE_USERDATA = 32
 )
 
 const (
-	datatype_sessioned_threshold = DATATYPE_TEMPLATE
+	datatype_sessioned_threshold = DATATYPE_STATICLOAD
 )
 
 // Db abstracts all data storage and retrieval as a key-value store
@@ -45,7 +49,11 @@ type Db interface {
 	// * DATATYPE_STATE
 	// * DATATYPE_USERSTART
 	SetSession(sessionId string)
-	SetLock(typ uint8, locked bool)
+	// SetLock disables modification of data that is readonly in the vm context.
+	// If called with typ value 0, it will permanently lock all readonly members.
+	SetLock(typ uint8, locked bool) error
+	// Safe returns true if db is safe for use with a vm.
+	Safe() bool
 }
 
 type lookupKey struct {
@@ -73,30 +81,44 @@ type baseDb struct {
 	sid []byte
 	lock uint8
 	lang *lang.Language
+	seal bool
 }
 
 // ensures default locking of read-only entries
 func(db *baseDb) defaultLock() {
-	db.lock = DATATYPE_BIN | DATATYPE_MENU | DATATYPE_TEMPLATE | DATATYPE_STATICLOAD
+	db.lock |= safeLock
+}
+
+func(db *baseDb) Safe() bool {
+	return db.lock & safeLock == safeLock
 }
 
-// SetPrefix implements Db.
+// SetPrefix implements the Db interface.
 func(db *baseDb) SetPrefix(pfx uint8) {
 	db.pfx = pfx
 }
 
-// SetSession implements Db.
+// SetSession implements the Db interface.
 func(db *baseDb) SetSession(sessionId string) {
 	db.sid = append([]byte(sessionId), 0x2E)
 }
 
-// SetSafety disables modification of data that 
-func(db *baseDb) SetLock(pfx uint8, lock bool) {
+// SetLock implements the Db interface.
+func(db *baseDb) SetLock(pfx uint8, lock bool) error {
+	if db.seal {
+		return errors.New("SetLock on sealed db")
+	}
+	if pfx == 0 {
+		db.seal = true
+		db.defaultLock()
+		return nil
+	}
 	if lock {
 		db.lock	|= pfx
 	} else {
 		db.lock &= ^pfx
 	}
+	return nil
 }
 
 func(db *baseDb) checkPut() bool {
@@ -125,6 +147,6 @@ func(db *baseDb) ToKey(ctx context.Context, key []byte) (lookupKey, error) {
 		if ok {
 			lk.Translation = ToDbKey(db.pfx, b, &ln)
 		}
-	}	
+	}
 	return lk, nil
 }
diff --git a/db/db_test.go b/db/db_test.go
@@ -1,9 +1,16 @@
 package db
 
 import (
+	"bytes"
 	"context"
 	"encoding/hex"
+	"errors"
+	"fmt"
 	"path"
+	"strconv"
+	"testing"
+
+	"git.defalsify.org/vise.git/lang"
 )
 
 type testCase struct {
@@ -11,12 +18,15 @@ type testCase struct {
 	s string
 	k []byte
 	v []byte
+	x []byte
+	l *lang.Language
 }
 
 type testVector struct {
 	c map[string]*testCase
 	v []string
 	i int
+	s string
 }
 
 func(tc *testCase) Key() []byte {
@@ -35,17 +45,36 @@ func(tc *testCase) Session() string {
 	return tc.s
 }
 
-func(tv *testVector) add(typ uint8, k string, v string, session string) {
+func(tc *testCase) Expect() []byte {
+	return tc.x
+}
+
+func(tv *testVector) add(typ uint8, k string, v string, session string, expect string, language string)  {
 	var b []byte
+	var x []byte
 	var err error
+	var ln *lang.Language
 
 	if typ == DATATYPE_BIN {
 		b, err = hex.DecodeString(v)
 		if err != nil {
 			panic(err)
 		}
+		x, err = hex.DecodeString(expect)
+		if err != nil {
+			panic(err)
+		}
 	} else {
 		b = []byte(v)
+		x = []byte(expect)
+	}
+	
+	if language != "" {
+		lo, err := lang.LanguageFromCode(language)
+		if err != nil {
+			panic(err)
+		}
+		ln = &lo
 	}
 
 	o := &testCase {
@@ -53,10 +82,15 @@ func(tv *testVector) add(typ uint8, k string, v string, session string) {
 		k: []byte(k),
 		v: b,
 		s: session,
+		x: x,
+		l: ln,
 	}
-	s := path.Join(session, k)
+	s := path.Join(strconv.Itoa(int(typ)), session)
+	s = path.Join(s, k)
 	tv.c[s] = o
+	i := len(tv.v)
 	tv.v = append(tv.v, s)
+	logg.Tracef("add testcase", "i", i, "s", s, "k", o.k)
 }
 
 func(tv *testVector) next() (int, *testCase) {
@@ -94,16 +128,78 @@ func(tv *testVector) put(ctx context.Context, db Db) error {
 	return nil
 }
 
-func generateTestVectors() testVector {
-	tv := testVector{c: make(map[string]*testCase)}
-	tv.add(DATATYPE_BIN, "foo", "deadbeef", "")
-	tv.add(DATATYPE_BIN, "foo", "beeffeed", "tinkywinky")
-	tv.add(DATATYPE_TEMPLATE, "foo", "inky", "")
-	tv.add(DATATYPE_TEMPLATE, "foo", "pinky", "dipsy")
-	tv.add(DATATYPE_MENU, "foo", "blinky", "")
-	tv.add(DATATYPE_MENU, "foo", "clyde", "lala")
-	tv.add(DATATYPE_STATICLOAD, "foo", "bar", "")
-	tv.add(DATATYPE_STATICLOAD, "foo", "baz", "po")
+func(tv *testVector) label() string {
+	return tv.s
+}
+
+func generateSessionTestVectors() testVector {
+	tv := testVector{c: make(map[string]*testCase), s: "session"}
+	tv.add(DATATYPE_BIN, "foo", "deadbeef", "", "beeffeed", "")
+	tv.add(DATATYPE_BIN, "foo", "beeffeed", "inky", "beeffeed", "")
+	tv.add(DATATYPE_TEMPLATE, "foo", "tinkywinky", "", "dipsy", "")
+	tv.add(DATATYPE_TEMPLATE, "foo", "dipsy", "pinky", "dipsy", "")
+	tv.add(DATATYPE_MENU, "foo", "lala", "", "pu", "")
+	tv.add(DATATYPE_MENU, "foo", "pu", "blinky", "pu", "")
+	tv.add(DATATYPE_STATICLOAD, "foo", "bar", "", "baz", "")
+	tv.add(DATATYPE_STATICLOAD, "foo", "baz", "clyde", "baz", "")
+	tv.add(DATATYPE_STATE, "foo", "xyzzy", "", "xyzzy", "")
+	tv.add(DATATYPE_STATE, "foo", "plugh", "sue", "plugh", "")
+	tv.add(DATATYPE_USERDATA, "foo", "itchy", "", "itchy", "")
+	tv.add(DATATYPE_USERDATA, "foo", "scratchy", "poochie", "scratchy", "")
+	return tv
+}
+
+func generateLanguageTestVectors() testVector {
+	tv := testVector{c: make(map[string]*testCase), s: "language"}
+	tv.add(DATATYPE_BIN, "foo", "deadbeef", "", "beeffeed", "")
+	tv.add(DATATYPE_BIN, "foo", "deadbeef", "", "deadbeef", "nor")
 	return tv
 }
 
+func runTest(t *testing.T, ctx context.Context, db Db, vs testVector) error {
+	err := vs.put(ctx, db)
+	if err != nil {
+		return err
+	}
+	for true {
+		i, tc := vs.next()
+		if i == -1 {
+			break
+		}
+		s := fmt.Sprintf("Test%sTyp%dKey%s", vs.label(), tc.Typ(), tc.Key())
+		if tc.Session() != "" {
+			s += "Session" + tc.Session()
+		} else {
+			s += "NoSession"
+		}
+		r := t.Run(s, func(t *testing.T) {
+			db.SetPrefix(tc.Typ())
+			db.SetSession(tc.Session())
+			v, err := db.Get(ctx, tc.Key())
+			if err != nil {
+				t.Fatal(err)
+			}
+			if !bytes.Equal(tc.Expect(), v) {
+				t.Fatalf("expected %s, got %s", tc.Expect(), v)
+			}
+		})
+		if !r {
+			return errors.New("subtest fail")
+		}
+	}
+	return nil
+
+}
+func runTests(t *testing.T, ctx context.Context, db Db) error {
+	err := runTest(t, ctx, db, generateSessionTestVectors())
+	if err != nil {
+		return err
+	}
+
+	err = runTest(t, ctx, db, generateLanguageTestVectors())
+	if err != nil {
+		return err
+	}
+	
+	return nil
+}
diff --git a/db/fs_test.go b/db/fs_test.go
@@ -18,7 +18,7 @@ func TestPutGetFs(t *testing.T) {
 		t.Fatal(err)
 	}
 	db := NewFsDb()
-	db.SetPrefix(DATATYPE_USERSTART)
+	db.SetPrefix(DATATYPE_USERDATA)
 	db.SetSession(sid)
 
 	dbi = db
diff --git a/db/gdbm_test.go b/db/gdbm_test.go
@@ -16,7 +16,7 @@ func TestPutGetGdbm(t *testing.T) {
 		t.Fatal(err)
 	}
 	db := NewGdbmDb()
-	db.SetPrefix(DATATYPE_USERSTART)
+	db.SetPrefix(DATATYPE_USERDATA)
 	db.SetSession(sid)
 
 	dbi = db
diff --git a/db/mem.go b/db/mem.go
@@ -42,6 +42,7 @@ func(mdb *memDb) toHexKey(ctx context.Context, key []byte) (memLookupKey, error)
 	if lk.Translation != nil {
 		mk.Translation = hex.EncodeToString(lk.Translation)
 	}
+	logg.TraceCtxf(ctx, "converted key", "orig", key, "b", lk, "s", mk)
 	return mk, err
 }
 
diff --git a/db/mem_test.go b/db/mem_test.go
@@ -3,48 +3,21 @@ package db
 import (
 	"bytes"
 	"context"
-	"fmt"
 	"testing"
 )
 
 func TestCasesMem(t *testing.T) {
-	var i int
-	var tc *testCase
-
 	ctx := context.Background()
-	vs := generateTestVectors()
+
 	db := NewMemDb()
 	err := db.Connect(ctx, "")
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = vs.put(ctx, db)
 
-	for true {
-		i, tc = vs.next()
-		if i == -1 {
-			break
-		}
-		s := fmt.Sprintf("TestTyp%dKey%s", tc.Typ(), tc.Key())
-		if tc.Session() != "" {
-			s += "Session" + tc.Session()
-		} else {
-			s += "NoSession"
-		}
-		r := t.Run(s, func(t *testing.T) {
-			db.SetPrefix(tc.Typ())
-			db.SetSession(tc.Session())
-			v, err := db.Get(ctx, tc.Key())
-			if err != nil {
-				t.Fatal(err)
-			}
-			if !bytes.Equal(tc.Val(), v) {
-				t.Fatalf("expected %x, got %x", tc.Val(), v)
-			}
-		})
-		if !r {
-			t.Fatalf("subtest fail")
-		}
+	err = runTests(t, ctx, db)
+	if err != nil {
+		t.Fatal(err)
 	}
 }
 
@@ -53,7 +26,7 @@ func TestPutGetMem(t *testing.T) {
 	ctx := context.Background()
 	sid := "ses"
 	db := NewMemDb()
-	db.SetPrefix(DATATYPE_USERSTART)
+	db.SetPrefix(DATATYPE_USERDATA)
 	db.SetSession(sid)
 
 	dbi = db
diff --git a/db/pg_test.go b/db/pg_test.go
@@ -10,7 +10,7 @@ func TestPutGetPg(t *testing.T) {
 	var dbi Db
 	ses := "xyzzy"
 	db := NewPgDb().WithSchema("vvise")
-	db.SetPrefix(DATATYPE_USERSTART)
+	db.SetPrefix(DATATYPE_USERDATA)
 	db.SetSession(ses)
 	ctx := context.Background()