go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

commit 9d4e1346d9a91161e0f52694436b2efecf121f9c
parent 8d77f50a3abb094631ec23915a164e9cc37147bb
Author: lash <dev@holbrook.no>
Date:   Sun,  1 Sep 2024 20:09:53 +0100

Move postgres to separate package

Diffstat:
Mdb/db.go | 32++++++++++++++++++++++----------
Mdb/db_test.go | 301-------------------------------------------------------------------------------
Mdb/fs.go | 7++++---
Mdb/gdbm.go | 7++++---
Mdb/mem.go | 8+++++---
Ddb/pg.go | 142-------------------------------------------------------------------------------
Ddb/pg_test.go | 65-----------------------------------------------------------------
Adb/postgres/pg.go | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adb/postgres/pg_test.go | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adb/testutil.go | 308+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 554 insertions(+), 527 deletions(-)

diff --git a/db/db.go b/db/db.go @@ -83,8 +83,8 @@ func ToDbKey(typ uint8, b []byte, l *lang.Language) []byte { return append(k, b...) } -// baseDb is a base class for all Db implementations. -type baseDb struct { +// BaseDb is a base class for all Db implementations. +type BaseDb struct { pfx uint8 sid []byte lock uint8 @@ -92,31 +92,37 @@ type baseDb struct { seal bool } +func NewBaseDb() *BaseDb { + db := &BaseDb{} + db.defaultLock() + return db +} + // ensures default locking of read-only entries -func(db *baseDb) defaultLock() { +func(db *BaseDb) defaultLock() { db.lock |= safeLock } -func(db *baseDb) Safe() bool { +func(db *BaseDb) Safe() bool { return db.lock & safeLock == safeLock } // SetPrefix implements the Db interface. -func(db *baseDb) SetPrefix(pfx uint8) { +func(db *BaseDb) SetPrefix(pfx uint8) { db.pfx = pfx } // SetLanguage implements the Db interface. -func(db *baseDb) SetLanguage(ln *lang.Language) { +func(db *BaseDb) SetLanguage(ln *lang.Language) { db.lang = ln } // SetSession implements the Db interface. -func(db *baseDb) SetSession(sessionId string) { +func(db *BaseDb) SetSession(sessionId string) { db.sid = append([]byte(sessionId), 0x2E) } // SetLock implements the Db interface. -func(db *baseDb) SetLock(pfx uint8, lock bool) error { +func(db *BaseDb) SetLock(pfx uint8, lock bool) error { if db.seal { return errors.New("SetLock on sealed db") } @@ -133,14 +139,20 @@ func(db *baseDb) SetLock(pfx uint8, lock bool) error { return nil } -func(db *baseDb) checkPut() bool { +func(db *BaseDb) checkPut() bool { return db.pfx & db.lock == 0 } +// CheckPut returns true if the current selected data type can be written to. +func(db *BaseDb) CheckPut() bool { + return db.checkPut() +} + + // ToKey creates a DbKey within the current session context. // // TODO: hard to read, clean up -func(db *baseDb) ToKey(ctx context.Context, key []byte) (lookupKey, error) { +func(db *BaseDb) ToKey(ctx context.Context, key []byte) (lookupKey, error) { var ln *lang.Language var lk lookupKey var b []byte diff --git a/db/db_test.go b/db/db_test.go @@ -1,302 +1 @@ package db - -import ( - "bytes" - "context" - "encoding/hex" - "errors" - "fmt" - "path" - "testing" - - "git.defalsify.org/vise.git/lang" -) - - -type testCase struct { - typ uint8 - s string - k []byte - v []byte - x []byte - l *lang.Language - t string -} - -type testVector struct { - c map[string]*testCase - v []string - i int - s string -} - -type testFunc func() testVector - -var ( - tests = []testFunc{ - generateSessionTestVectors, - generateMultiSessionTestVectors, - generateLanguageTestVectors, - generateMultiLanguageTestVectors, - generateSessionLanguageTestVectors, - } - dataTypeDebug = map[uint8]string{ - DATATYPE_BIN: "bytecode", - DATATYPE_TEMPLATE: "template", - DATATYPE_MENU: "menu", - DATATYPE_STATICLOAD: "staticload", - DATATYPE_STATE: "state", - DATATYPE_USERDATA: "udata", - } -) - -func(tc *testCase) Key() []byte { - return tc.k -} - -func(tc *testCase) Val() []byte { - return tc.v -} - -func(tc *testCase) Typ() uint8 { - return tc.typ -} - -func(tc *testCase) Session() string { - return tc.s -} - -func(tc *testCase) Lang() string { - if tc.l == nil { - return "" - } - return tc.l.Code -} - -func(tc *testCase) Expect() []byte { - return tc.x -} - -func(tc *testCase) Label() string { - return tc.t -} - -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 - } - s := dataTypeDebug[typ] - s = path.Join(s, session) - s = path.Join(s, k) - if ln != nil { - s = path.Join(s, language) - } - o := &testCase { - typ: typ, - k: []byte(k), - v: b, - s: session, - x: x, - l: ln, - t: s, - } - 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) { - i := tv.i - if i == len(tv.v) { - return -1, nil - } - tv.i++ - return i, tv.c[tv.v[i]] -} - -func(tv *testVector) rewind() { - tv.i = 0 -} - -func(tv *testVector) put(ctx context.Context, db Db) error { - var i int - var tc *testCase - defer tv.rewind() - - for true { - i, tc = tv.next() - if i == -1 { - break - } - logg.TraceCtxf(ctx, "running put for test", "vector", tv.label(), "case", tc.Label()) - db.SetPrefix(tc.Typ()) - db.SetSession(tc.Session()) - db.SetLock(tc.Typ(), false) - db.SetLanguage(nil) - if tc.Lang() != "" { - ln, err := lang.LanguageFromCode(tc.Lang()) - if err != nil { - return err - } - db.SetLanguage(&ln) - } - err := db.Put(ctx, tc.Key(), tc.Val()) - if err != nil { - return err - } - db.SetLock(tc.Typ(), true) - } - return nil -} - -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", "beeffeed", "", "beeffeed", "nor") - tv.add(DATATYPE_TEMPLATE, "foo", "tinkywinky", "", "tinkywinky", "") - tv.add(DATATYPE_TEMPLATE, "foo", "dipsy", "", "dipsy", "nor") - tv.add(DATATYPE_MENU, "foo", "lala", "", "lala", "") - tv.add(DATATYPE_MENU, "foo", "pu", "", "pu", "nor") - tv.add(DATATYPE_STATICLOAD, "foo", "bar", "", "bar", "") - tv.add(DATATYPE_STATICLOAD, "foo", "baz", "", "baz", "nor") - tv.add(DATATYPE_STATE, "foo", "xyzzy", "", "plugh", "") - tv.add(DATATYPE_STATE, "foo", "plugh", "", "plugh", "nor") - tv.add(DATATYPE_USERDATA, "foo", "itchy", "", "scratchy", "") - tv.add(DATATYPE_USERDATA, "foo", "scratchy", "", "scratchy", "nor") - return tv -} - -func generateMultiLanguageTestVectors() testVector { - tv := testVector{c: make(map[string]*testCase), s: "multilanguage"} - tv.add(DATATYPE_TEMPLATE, "foo", "tinkywinky", "", "pu", "") - tv.add(DATATYPE_TEMPLATE, "foo", "dipsy", "", "dipsy", "nor") - tv.add(DATATYPE_TEMPLATE, "foo", "lala", "", "lala", "swa") - tv.add(DATATYPE_TEMPLATE, "foo", "pu", "", "pu", "") - return tv -} - -func generateSessionLanguageTestVectors() testVector { - tv := testVector{c: make(map[string]*testCase), s: "sessionlanguage"} - tv.add(DATATYPE_TEMPLATE, "foo", "tinkywinky", "", "pu", "") - tv.add(DATATYPE_TEMPLATE, "foo", "dipsy", "", "lala", "nor") - tv.add(DATATYPE_TEMPLATE, "foo", "lala", "bar", "lala", "nor") - tv.add(DATATYPE_TEMPLATE, "foo", "pu", "bar", "pu", "") - tv.add(DATATYPE_STATE, "foo", "inky", "", "pinky", "") - tv.add(DATATYPE_STATE, "foo", "pinky", "", "pinky", "nor") - tv.add(DATATYPE_STATE, "foo", "blinky", "bar", "clyde", "nor") - tv.add(DATATYPE_STATE, "foo", "clyde", "bar", "clyde", "") - tv.add(DATATYPE_BIN, "foo", "deadbeef", "", "feebdaed", "") - tv.add(DATATYPE_BIN, "foo", "beeffeed", "", "feebdaed", "nor") - tv.add(DATATYPE_BIN, "foo", "deeffeeb", "baz", "feebdaed", "nor") - tv.add(DATATYPE_BIN, "foo", "feebdaed", "baz", "feebdaed", "") - return tv -} - -func generateMultiSessionTestVectors() testVector { - tv := testVector{c: make(map[string]*testCase), s: "multisession"} - tv.add(DATATYPE_TEMPLATE, "foo", "red", "", "blue", "") - tv.add(DATATYPE_TEMPLATE, "foo", "green", "bar", "blue", "") - tv.add(DATATYPE_TEMPLATE, "foo", "blue", "baz", "blue", "") - tv.add(DATATYPE_STATE, "foo", "inky", "", "inky", "") - tv.add(DATATYPE_STATE, "foo", "pinky", "clyde", "pinky", "") - tv.add(DATATYPE_STATE, "foo", "blinky", "sue", "blinky", "") - tv.add(DATATYPE_BIN, "foo", "deadbeef", "", "feebdeef", "") - tv.add(DATATYPE_BIN, "foo", "feedbeef", "bar", "feebdeef", "") - tv.add(DATATYPE_BIN, "foo", "feebdeef", "baz", "feebdeef", "") - 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%s[%d]%s", vs.label(), i, tc.Label()) - r := t.Run(s, func(t *testing.T) { - db.SetPrefix(tc.Typ()) - db.SetSession(tc.Session()) - if tc.Lang() != "" { - ln, err := lang.LanguageFromCode(tc.Lang()) - if err != nil { - t.Fatal(err) - } - db.SetLanguage(&ln) - } else { - db.SetLanguage(nil) - } - 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 { - for _, fn := range tests { - err := runTest(t, ctx, db, fn()) - if err != nil { - return err - } - } - - return nil -} diff --git a/db/fs.go b/db/fs.go @@ -17,14 +17,15 @@ type fsLookupKey struct { // pure filesystem backend implementation if the Db interface. type fsDb struct { - baseDb + *BaseDb dir string } // NewFsDb creates a filesystem backed Db implementation. func NewFsDb() *fsDb { - db := &fsDb{} - db.baseDb.defaultLock() + db := &fsDb{ + BaseDb: NewBaseDb(), + } return db } diff --git a/db/gdbm.go b/db/gdbm.go @@ -10,15 +10,16 @@ import ( // gdbmDb is a gdbm backend implementation of the Db interface. type gdbmDb struct { - baseDb + *BaseDb conn *gdbm.Database prefix uint8 } // Creates a new gdbm backed Db implementation. func NewGdbmDb() *gdbmDb { - db := &gdbmDb{} - db.baseDb.defaultLock() + db := &gdbmDb{ + BaseDb: NewBaseDb(), + } return db } diff --git a/db/mem.go b/db/mem.go @@ -14,14 +14,16 @@ type memLookupKey struct { // memDb is a memory backend implementation of the Db interface. type memDb struct { - baseDb + *BaseDb store map[string][]byte } // NewmemDb returns an in-process volatile Db implementation. func NewMemDb() *memDb { - db := &memDb{} - db.baseDb.defaultLock() + db := &memDb{ + BaseDb: NewBaseDb(), + } + db.BaseDb.defaultLock() return db } diff --git a/db/pg.go b/db/pg.go @@ -1,142 +0,0 @@ -package db - -import ( - "context" - "errors" - "fmt" - - "github.com/jackc/pgx/v5/pgxpool" -) - -// pgDb is a Postgres backend implementation of the Db interface. -type pgDb struct { - baseDb - conn *pgxpool.Pool - schema string - prefix uint8 -} - -// NewpgDb creates a new Postgres backed Db implementation. -func NewPgDb() *pgDb { - db := &pgDb{ - schema: "public", - } - db.baseDb.defaultLock() - return db -} - -// WithSchema sets the Postgres schema to use for the storage table. -func(pdb *pgDb) WithSchema(schema string) *pgDb { - pdb.schema = schema - return pdb -} - -// Connect implements Db. -func(pdb *pgDb) Connect(ctx context.Context, connStr string) error { - if pdb.conn != nil { - panic("already connected") - } - var err error - conn, err := pgxpool.New(ctx, connStr) - if err != nil { - return err - } - pdb.conn = conn - return pdb.prepare(ctx) -} - -// Put implements Db. -func(pdb *pgDb) Put(ctx context.Context, key []byte, val []byte) error { - if !pdb.checkPut() { - return errors.New("unsafe put and safety set") - } - k, err := pdb.ToKey(ctx, key) - if err != nil { - return err - } - tx, err := pdb.conn.Begin(ctx) - if err != nil { - return err - } - query := fmt.Sprintf("INSERT INTO %s.kv_vise (key, value) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET value = $2;", pdb.schema) - _, err = tx.Exec(ctx, query, k, val) - if err != nil { - tx.Rollback(ctx) - return err - } - tx.Commit(ctx) - return nil -} - -// Get implements Db. -func(pdb *pgDb) Get(ctx context.Context, key []byte) ([]byte, error) { - lk, err := pdb.ToKey(ctx, key) - if err != nil { - return nil, err - } - if lk.Translation != nil { - tx, err := pdb.conn.Begin(ctx) - if err != nil { - return nil, err - } - query := fmt.Sprintf("SELECT value FROM %s.kv_vise WHERE key = $1", pdb.schema) - rs, err := tx.Query(ctx, query, lk.Translation) - if err != nil { - return nil, err - } - defer rs.Close() - if rs.Next() { - r := rs.RawValues() - return r[0], nil - } - } - - tx, err := pdb.conn.Begin(ctx) - if err != nil { - return nil, err - } - query := fmt.Sprintf("SELECT value FROM %s.kv_vise WHERE key = $1", pdb.schema) - rs, err := tx.Query(ctx, query, lk.Translation) - if err != nil { - return nil, err - } - defer rs.Close() - if !rs.Next() { - return nil, NewErrNotFound(key) - } - r := rs.RawValues() - return r[0], nil -} - -// Close implements Db. -func(pdb *pgDb) Close() error { - pdb.Close() - return nil -} - -// set up table -func(pdb *pgDb) prepare(ctx context.Context) error { - tx, err := pdb.conn.Begin(ctx) - if err != nil { - tx.Rollback(ctx) - return err - } - query := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s.kv_vise ( - id SERIAL NOT NULL, - key BYTEA NOT NULL UNIQUE, - value BYTEA NOT NULL - ); -`, pdb.schema) - _, err = tx.Exec(ctx, query) - if err != nil { - tx.Rollback(ctx) - return err - } - - err = tx.Commit(ctx) - if err != nil { - tx.Rollback(ctx) - return err - } - return nil -} diff --git a/db/pg_test.go b/db/pg_test.go @@ -1,65 +0,0 @@ -package db - -import ( - "bytes" - "context" - "testing" -) - -func TestCasesPg(t *testing.T) { - ctx := context.Background() - - db := NewPgDb().WithSchema("vvise") - t.Skip("need postgresql mock") - - err := db.Connect(ctx, "postgres://vise:esiv@localhost:5432/visedb") - if err != nil { - t.Fatal(err) - } - - err = runTests(t, ctx, db) - if err != nil { - t.Fatal(err) - } -} - -func TestPutGetPg(t *testing.T) { - var dbi Db - ses := "xyzzy" - db := NewPgDb().WithSchema("vvise") - db.SetPrefix(DATATYPE_USERDATA) - db.SetSession(ses) - ctx := context.Background() - - dbi = db - _ = dbi - - t.Skip("need postgresql mock") - err := db.Connect(ctx, "postgres://vise:esiv@localhost:5432/visedb") - if err != nil { - t.Fatal(err) - } - err = db.Put(ctx, []byte("foo"), []byte("bar")) - if err != nil { - t.Fatal(err) - } - b, err := db.Get(ctx, []byte("foo")) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(b, []byte("bar")) { - t.Fatalf("expected 'bar', got %x", b) - } - err = db.Put(ctx, []byte("foo"), []byte("plugh")) - if err != nil { - t.Fatal(err) - } - b, err = db.Get(ctx, []byte("foo")) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(b, []byte("plugh")) { - t.Fatalf("expected 'plugh', got %x", b) - } - -} diff --git a/db/postgres/pg.go b/db/postgres/pg.go @@ -0,0 +1,144 @@ +package db + +import ( + "context" + "errors" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" + + "git.defalsify.org/vise.git/db" +) + +// pgDb is a Postgres backend implementation of the Db interface. +type pgDb struct { + *db.BaseDb + conn *pgxpool.Pool + schema string + prefix uint8 +} + +// NewpgDb creates a new Postgres backed Db implementation. +func NewPgDb() *pgDb { + db := &pgDb{ + BaseDb: db.NewBaseDb(), + schema: "public", + } + return db +} + +// WithSchema sets the Postgres schema to use for the storage table. +func(pdb *pgDb) WithSchema(schema string) *pgDb { + pdb.schema = schema + return pdb +} + +// Connect implements Db. +func(pdb *pgDb) Connect(ctx context.Context, connStr string) error { + if pdb.conn != nil { + panic("already connected") + } + var err error + conn, err := pgxpool.New(ctx, connStr) + if err != nil { + return err + } + pdb.conn = conn + return pdb.prepare(ctx) +} + +// Put implements Db. +func(pdb *pgDb) Put(ctx context.Context, key []byte, val []byte) error { + if !pdb.CheckPut() { + return errors.New("unsafe put and safety set") + } + k, err := pdb.ToKey(ctx, key) + if err != nil { + return err + } + tx, err := pdb.conn.Begin(ctx) + if err != nil { + return err + } + query := fmt.Sprintf("INSERT INTO %s.kv_vise (key, value) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET value = $2;", pdb.schema) + _, err = tx.Exec(ctx, query, k, val) + if err != nil { + tx.Rollback(ctx) + return err + } + tx.Commit(ctx) + return nil +} + +// Get implements Db. +func(pdb *pgDb) Get(ctx context.Context, key []byte) ([]byte, error) { + lk, err := pdb.ToKey(ctx, key) + if err != nil { + return nil, err + } + if lk.Translation != nil { + tx, err := pdb.conn.Begin(ctx) + if err != nil { + return nil, err + } + query := fmt.Sprintf("SELECT value FROM %s.kv_vise WHERE key = $1", pdb.schema) + rs, err := tx.Query(ctx, query, lk.Translation) + if err != nil { + return nil, err + } + defer rs.Close() + if rs.Next() { + r := rs.RawValues() + return r[0], nil + } + } + + tx, err := pdb.conn.Begin(ctx) + if err != nil { + return nil, err + } + query := fmt.Sprintf("SELECT value FROM %s.kv_vise WHERE key = $1", pdb.schema) + rs, err := tx.Query(ctx, query, lk.Translation) + if err != nil { + return nil, err + } + defer rs.Close() + if !rs.Next() { + return nil, db.NewErrNotFound(key) + } + r := rs.RawValues() + return r[0], nil +} + +// Close implements Db. +func(pdb *pgDb) Close() error { + pdb.Close() + return nil +} + +// set up table +func(pdb *pgDb) prepare(ctx context.Context) error { + tx, err := pdb.conn.Begin(ctx) + if err != nil { + tx.Rollback(ctx) + return err + } + query := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s.kv_vise ( + id SERIAL NOT NULL, + key BYTEA NOT NULL UNIQUE, + value BYTEA NOT NULL + ); +`, pdb.schema) + _, err = tx.Exec(ctx, query) + if err != nil { + tx.Rollback(ctx) + return err + } + + err = tx.Commit(ctx) + if err != nil { + tx.Rollback(ctx) + return err + } + return nil +} diff --git a/db/postgres/pg_test.go b/db/postgres/pg_test.go @@ -0,0 +1,67 @@ +package db + +import ( + "bytes" + "context" + "testing" + + "git.defalsify.org/vise.git/db" +) + +func TestCasesPg(t *testing.T) { + ctx := context.Background() + + store := NewPgDb().WithSchema("vvise") + t.Skip("need postgresql mock") + + err := store.Connect(ctx, "postgres://vise:esiv@localhost:5432/visedb") + if err != nil { + t.Fatal(err) + } + + err = db.RunTests(t, ctx, store) + if err != nil { + t.Fatal(err) + } +} + +func TestPutGetPg(t *testing.T) { + var dbi db.Db + ses := "xyzzy" + store := NewPgDb().WithSchema("vvise") + store.SetPrefix(db.DATATYPE_USERDATA) + store.SetSession(ses) + ctx := context.Background() + + dbi = store + _ = dbi + + t.Skip("need postgresql mock") + err := store.Connect(ctx, "postgres://vise:esiv@localhost:5432/visedb") + if err != nil { + t.Fatal(err) + } + err = store.Put(ctx, []byte("foo"), []byte("bar")) + if err != nil { + t.Fatal(err) + } + b, err := store.Get(ctx, []byte("foo")) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(b, []byte("bar")) { + t.Fatalf("expected 'bar', got %x", b) + } + err = store.Put(ctx, []byte("foo"), []byte("plugh")) + if err != nil { + t.Fatal(err) + } + b, err = store.Get(ctx, []byte("foo")) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(b, []byte("plugh")) { + t.Fatalf("expected 'plugh', got %x", b) + } + +} diff --git a/db/testutil.go b/db/testutil.go @@ -0,0 +1,308 @@ +package db + +import ( + "bytes" + "context" + "encoding/hex" + "errors" + "fmt" + "path" + "testing" + + "git.defalsify.org/vise.git/lang" +) + + +type testCase struct { + typ uint8 + s string + k []byte + v []byte + x []byte + l *lang.Language + t string +} + +type testVector struct { + c map[string]*testCase + v []string + i int + s string +} + +type testFunc func() testVector + +var ( + tests = []testFunc{ + generateSessionTestVectors, + generateMultiSessionTestVectors, + generateLanguageTestVectors, + generateMultiLanguageTestVectors, + generateSessionLanguageTestVectors, + } + dataTypeDebug = map[uint8]string{ + DATATYPE_BIN: "bytecode", + DATATYPE_TEMPLATE: "template", + DATATYPE_MENU: "menu", + DATATYPE_STATICLOAD: "staticload", + DATATYPE_STATE: "state", + DATATYPE_USERDATA: "udata", + } +) + +func(tc *testCase) Key() []byte { + return tc.k +} + +func(tc *testCase) Val() []byte { + return tc.v +} + +func(tc *testCase) Typ() uint8 { + return tc.typ +} + +func(tc *testCase) Session() string { + return tc.s +} + +func(tc *testCase) Lang() string { + if tc.l == nil { + return "" + } + return tc.l.Code +} + +func(tc *testCase) Expect() []byte { + return tc.x +} + +func(tc *testCase) Label() string { + return tc.t +} + +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 + } + s := dataTypeDebug[typ] + s = path.Join(s, session) + s = path.Join(s, k) + if ln != nil { + s = path.Join(s, language) + } + o := &testCase { + typ: typ, + k: []byte(k), + v: b, + s: session, + x: x, + l: ln, + t: s, + } + 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) { + i := tv.i + if i == len(tv.v) { + return -1, nil + } + tv.i++ + return i, tv.c[tv.v[i]] +} + +func(tv *testVector) rewind() { + tv.i = 0 +} + +func(tv *testVector) put(ctx context.Context, db Db) error { + var i int + var tc *testCase + defer tv.rewind() + + for true { + i, tc = tv.next() + if i == -1 { + break + } + logg.TraceCtxf(ctx, "running put for test", "vector", tv.label(), "case", tc.Label()) + db.SetPrefix(tc.Typ()) + db.SetSession(tc.Session()) + db.SetLock(tc.Typ(), false) + db.SetLanguage(nil) + if tc.Lang() != "" { + ln, err := lang.LanguageFromCode(tc.Lang()) + if err != nil { + return err + } + db.SetLanguage(&ln) + } + err := db.Put(ctx, tc.Key(), tc.Val()) + if err != nil { + return err + } + db.SetLock(tc.Typ(), true) + } + return nil +} + +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", "beeffeed", "", "beeffeed", "nor") + tv.add(DATATYPE_TEMPLATE, "foo", "tinkywinky", "", "tinkywinky", "") + tv.add(DATATYPE_TEMPLATE, "foo", "dipsy", "", "dipsy", "nor") + tv.add(DATATYPE_MENU, "foo", "lala", "", "lala", "") + tv.add(DATATYPE_MENU, "foo", "pu", "", "pu", "nor") + tv.add(DATATYPE_STATICLOAD, "foo", "bar", "", "bar", "") + tv.add(DATATYPE_STATICLOAD, "foo", "baz", "", "baz", "nor") + tv.add(DATATYPE_STATE, "foo", "xyzzy", "", "plugh", "") + tv.add(DATATYPE_STATE, "foo", "plugh", "", "plugh", "nor") + tv.add(DATATYPE_USERDATA, "foo", "itchy", "", "scratchy", "") + tv.add(DATATYPE_USERDATA, "foo", "scratchy", "", "scratchy", "nor") + return tv +} + +func generateMultiLanguageTestVectors() testVector { + tv := testVector{c: make(map[string]*testCase), s: "multilanguage"} + tv.add(DATATYPE_TEMPLATE, "foo", "tinkywinky", "", "pu", "") + tv.add(DATATYPE_TEMPLATE, "foo", "dipsy", "", "dipsy", "nor") + tv.add(DATATYPE_TEMPLATE, "foo", "lala", "", "lala", "swa") + tv.add(DATATYPE_TEMPLATE, "foo", "pu", "", "pu", "") + return tv +} + +func generateSessionLanguageTestVectors() testVector { + tv := testVector{c: make(map[string]*testCase), s: "sessionlanguage"} + tv.add(DATATYPE_TEMPLATE, "foo", "tinkywinky", "", "pu", "") + tv.add(DATATYPE_TEMPLATE, "foo", "dipsy", "", "lala", "nor") + tv.add(DATATYPE_TEMPLATE, "foo", "lala", "bar", "lala", "nor") + tv.add(DATATYPE_TEMPLATE, "foo", "pu", "bar", "pu", "") + tv.add(DATATYPE_STATE, "foo", "inky", "", "pinky", "") + tv.add(DATATYPE_STATE, "foo", "pinky", "", "pinky", "nor") + tv.add(DATATYPE_STATE, "foo", "blinky", "bar", "clyde", "nor") + tv.add(DATATYPE_STATE, "foo", "clyde", "bar", "clyde", "") + tv.add(DATATYPE_BIN, "foo", "deadbeef", "", "feebdaed", "") + tv.add(DATATYPE_BIN, "foo", "beeffeed", "", "feebdaed", "nor") + tv.add(DATATYPE_BIN, "foo", "deeffeeb", "baz", "feebdaed", "nor") + tv.add(DATATYPE_BIN, "foo", "feebdaed", "baz", "feebdaed", "") + return tv +} + +func generateMultiSessionTestVectors() testVector { + tv := testVector{c: make(map[string]*testCase), s: "multisession"} + tv.add(DATATYPE_TEMPLATE, "foo", "red", "", "blue", "") + tv.add(DATATYPE_TEMPLATE, "foo", "green", "bar", "blue", "") + tv.add(DATATYPE_TEMPLATE, "foo", "blue", "baz", "blue", "") + tv.add(DATATYPE_STATE, "foo", "inky", "", "inky", "") + tv.add(DATATYPE_STATE, "foo", "pinky", "clyde", "pinky", "") + tv.add(DATATYPE_STATE, "foo", "blinky", "sue", "blinky", "") + tv.add(DATATYPE_BIN, "foo", "deadbeef", "", "feebdeef", "") + tv.add(DATATYPE_BIN, "foo", "feedbeef", "bar", "feebdeef", "") + tv.add(DATATYPE_BIN, "foo", "feebdeef", "baz", "feebdeef", "") + 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%s[%d]%s", vs.label(), i, tc.Label()) + r := t.Run(s, func(t *testing.T) { + db.SetPrefix(tc.Typ()) + db.SetSession(tc.Session()) + if tc.Lang() != "" { + ln, err := lang.LanguageFromCode(tc.Lang()) + if err != nil { + t.Fatal(err) + } + db.SetLanguage(&ln) + } else { + db.SetLanguage(nil) + } + 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 { + for _, fn := range tests { + err := runTest(t, ctx, db, fn()) + if err != nil { + return err + } + } + + return nil +} + +func RunTests(t *testing.T, ctx context.Context, db Db) error { + return runTests(t, ctx, db) +}