Files
Baradb/tests/test_all.nim
T
dimgigov 5c84aeccf8 feat: MVCC, wire protocol, HTTP API, schema system, CLI shell — 39 tests
- MVCC: multi-version concurrency control with snapshot isolation
- Deadlock detection: wait-for graph with cycle detection
- Wire Protocol: binary message format, 16 types, big-endian serialization
- HTTP/REST API: router with CORS, path params, JSON
- Schema system: types, links, properties, migration diffing
- CLI: interactive query shell (bara shell)
- 18 new tests (39 total, all passing)
2026-05-06 00:32:02 +03:00

468 lines
14 KiB
Nim

## BaraDB — Test Suite
import std/unittest
import std/tables
import std/strutils
import barabadb/core/types
import barabadb/core/mvcc
import barabadb/core/deadlock
import barabadb/storage/bloom
import barabadb/storage/wal
import barabadb/storage/lsm
import barabadb/query/lexer as lex
import barabadb/query/ast
import barabadb/query/parser
import barabadb/vector/engine as vengine
import barabadb/graph/engine as gengine
import barabadb/fts/engine as fts
import barabadb/protocol/wire
import barabadb/schema/schema as schema
suite "Core Types":
test "Value creation":
let v = Value(kind: vkInt64, int64Val: 42)
check v.kind == vkInt64
check v.int64Val == 42
test "String value":
let v = Value(kind: vkString, strVal: "hello")
check v.strVal == "hello"
test "RecordId creation":
let id = newRecordId()
check uint64(id) > 0
suite "Bloom Filter":
test "Basic bloom filter operations":
var bf = newBloomFilter(1000)
let data1 = cast[seq[byte]]("hello")
let data2 = cast[seq[byte]]("world")
bf.add(data1)
bf.add(data2)
check bf.contains(data1)
check bf.contains(data2)
suite "Write-Ahead Log":
test "WAL creation":
var wal = newWriteAheadLog("/tmp/baradb_test_wal")
check wal.entryCount == 0
wal.close()
suite "LSM-Tree Storage":
test "Put and Get":
var db = newLSMTree("/tmp/baradb_test_lsm")
let key = "testkey"
let value = cast[seq[byte]]("testvalue")
db.put(key, value)
let (found, val) = db.get(key)
check found
check val == value
db.close()
test "Delete":
var db = newLSMTree("/tmp/baradb_test_lsm2")
let key = "delkey"
let value = cast[seq[byte]]("delval")
db.put(key, value)
db.delete(key)
let (found, _) = db.get(key)
check not found
db.close()
test "Contains":
var db = newLSMTree("/tmp/baradb_test_lsm3")
let key = "exists"
check not db.contains(key)
db.put(key, cast[seq[byte]]("val"))
check db.contains(key)
db.close()
suite "BaraQL Lexer":
test "Tokenize simple SELECT":
let tokens = lex.tokenize("SELECT name FROM users WHERE age > 18")
check tokens.len > 0
check tokens[0].kind == tkSelect
check tokens[1].kind == tkIdent
check tokens[1].value == "name"
check tokens[2].kind == tkFrom
check tokens[3].kind == tkIdent
check tokens[3].value == "users"
test "Tokenize string literals":
let tokens = lex.tokenize("'hello world'")
check tokens[0].kind == tkStringLit
check tokens[0].value == "hello world"
test "Tokenize operators":
let tokens = lex.tokenize("a + b * c")
check tokens[0].kind == tkIdent
check tokens[1].kind == tkPlus
check tokens[2].kind == tkIdent
check tokens[3].kind == tkStar
suite "BaraQL Parser":
test "Parse simple SELECT":
let ast = parse("SELECT name FROM users WHERE age > 18")
check ast.kind == nkStatementList
check ast.stmts.len == 1
check ast.stmts[0].kind == nkSelect
test "Parse SELECT with LIMIT":
let ast = parse("SELECT * FROM items LIMIT 10")
check ast.stmts[0].selLimit != nil
suite "Vector Engine":
test "Distance metrics":
let a = @[1.0'f32, 0.0'f32, 0.0'f32]
let b = @[0.0'f32, 1.0'f32, 0.0'f32]
let c = @[1.0'f32, 0.0'f32, 0.0'f32]
check vengine.cosineDistance(a, b) > 0.9
check vengine.cosineDistance(a, c) < 0.1
check vengine.euclideanDistance(a, b) > 1.0
check vengine.euclideanDistance(a, c) < 0.1
test "HNSW index insert and search":
var idx = vengine.newHNSWIndex(3)
vengine.insert(idx, 1, @[1.0'f32, 0.0'f32, 0.0'f32])
vengine.insert(idx, 2, @[0.0'f32, 1.0'f32, 0.0'f32])
vengine.insert(idx, 3, @[0.0'f32, 0.0'f32, 1.0'f32])
let results = vengine.search(idx, @[1.0'f32, 0.1'f32, 0.0'f32], 2)
check results.len == 2
suite "Graph Engine":
test "Add nodes and edges":
var g = gengine.newGraph()
let n1 = gengine.addNode(g, "Person", {"name": "Alice"}.toTable)
let n2 = gengine.addNode(g, "Person", {"name": "Bob"}.toTable)
let e1 = gengine.addEdge(g, n1, n2, "knows")
check gengine.nodeCount(g) == 2
check gengine.edgeCount(g) == 1
test "BFS traversal":
var g = gengine.newGraph()
let n1 = gengine.addNode(g, "A")
let n2 = gengine.addNode(g, "B")
let n3 = gengine.addNode(g, "C")
let n4 = gengine.addNode(g, "D")
discard gengine.addEdge(g, n1, n2)
discard gengine.addEdge(g, n1, n3)
discard gengine.addEdge(g, n2, n4)
let traversal = gengine.bfs(g, n1)
check traversal.len == 4
check traversal[0] == n1
test "DFS traversal":
var g = gengine.newGraph()
let n1 = gengine.addNode(g, "A")
let n2 = gengine.addNode(g, "B")
let n3 = gengine.addNode(g, "C")
discard gengine.addEdge(g, n1, n2)
discard gengine.addEdge(g, n1, n3)
let traversal = gengine.dfs(g, n1)
check traversal.len == 3
test "Shortest path":
var g = gengine.newGraph()
let n1 = gengine.addNode(g, "A")
let n2 = gengine.addNode(g, "B")
let n3 = gengine.addNode(g, "C")
discard gengine.addEdge(g, n1, n2)
discard gengine.addEdge(g, n2, n3)
let path = gengine.shortestPath(g, n1, n3)
check path.len == 3
test "PageRank":
var g = gengine.newGraph()
let n1 = gengine.addNode(g, "A")
let n2 = gengine.addNode(g, "B")
let n3 = gengine.addNode(g, "C")
discard gengine.addEdge(g, n1, n2)
discard gengine.addEdge(g, n2, n3)
discard gengine.addEdge(g, n3, n1)
let ranks = gengine.pageRank(g)
check ranks.len == 3
for nodeId, rank in ranks:
check rank > 0.0
test "Dijkstra":
var g = gengine.newGraph()
let n1 = gengine.addNode(g, "A")
let n2 = gengine.addNode(g, "B")
let n3 = gengine.addNode(g, "C")
discard gengine.addEdge(g, n1, n2, weight = 1.0)
discard gengine.addEdge(g, n2, n3, weight = 2.0)
discard gengine.addEdge(g, n1, n3, weight = 10.0)
let dists = gengine.dijkstra(g, n1)
check dists[n1] == 0.0
check dists[n2] == 1.0
check dists[n3] == 3.0
suite "Full-Text Search":
test "Tokenization":
let tokens = fts.tokenize("The quick brown fox jumps over the lazy dog")
check tokens.len > 0
check "the" notin tokens
test "Inverted index operations":
var idx = fts.newInvertedIndex()
fts.addDocument(idx, 1, "The quick brown fox")
fts.addDocument(idx, 2, "The lazy brown dog")
fts.addDocument(idx, 3, "The quick red car")
check fts.documentCount(idx) == 3
check fts.termCount(idx) > 0
test "Search results":
var idx = fts.newInvertedIndex()
fts.addDocument(idx, 1, "Nim programming language is fast")
fts.addDocument(idx, 2, "Python is popular for data science")
fts.addDocument(idx, 3, "Rust is a systems programming language")
let results = fts.search(idx, "programming language")
check results.len > 0
check results[0].score > 0
test "Document removal":
var idx = fts.newInvertedIndex()
fts.addDocument(idx, 1, "test document")
fts.addDocument(idx, 2, "another document")
check fts.documentCount(idx) == 2
fts.removeDocument(idx, 1)
check fts.documentCount(idx) == 1
suite "MVCC Transactions":
test "Begin and commit transaction":
var tm = newTxnManager()
let txn = tm.beginTxn()
check txn.state == tsActive
check tm.write(txn, "key1", cast[seq[byte]]("value1"))
check tm.commit(txn)
check txn.state == tsCommitted
test "Read own writes":
var tm = newTxnManager()
let txn = tm.beginTxn()
discard tm.write(txn, "key1", cast[seq[byte]]("value1"))
let (found, val) = tm.read(txn, "key1")
check found
check val == cast[seq[byte]]("value1")
discard tm.commit(txn)
test "Abort transaction":
var tm = newTxnManager()
let txn = tm.beginTxn()
discard tm.write(txn, "key1", cast[seq[byte]]("value1"))
discard tm.abortTxn(txn)
check txn.state == tsAborted
test "Snapshot isolation — no dirty reads":
var tm = newTxnManager()
let txn1 = tm.beginTxn()
discard tm.write(txn1, "key1", cast[seq[byte]]("value1"))
let txn2 = tm.beginTxn()
let (found, _) = tm.read(txn2, "key1")
check not found # txn2 can't see txn1's uncommitted write
discard tm.commit(txn1)
# txn2 still can't see it (snapshot taken before commit)
let (found2, _) = tm.read(txn2, "key1")
check not found2
discard tm.abortTxn(txn2)
test "Committed writes visible to new transactions":
var tm = newTxnManager()
let txn1 = tm.beginTxn()
discard tm.write(txn1, "key1", cast[seq[byte]]("value1"))
discard tm.commit(txn1)
let txn2 = tm.beginTxn()
let (found, val) = tm.read(txn2, "key1")
check found
check val == cast[seq[byte]]("value1")
discard tm.commit(txn2)
test "Savepoint and rollback":
var tm = newTxnManager()
let txn = tm.beginTxn()
discard tm.write(txn, "key1", cast[seq[byte]]("value1"))
tm.savepoint(txn)
discard tm.write(txn, "key2", cast[seq[byte]]("value2"))
check tm.rollbackToSavepoint(txn)
let (found1, _) = tm.read(txn, "key1")
check found1
let (found2, _) = tm.read(txn, "key2")
check not found2
discard tm.commit(txn)
test "Delete via xmax":
var tm = newTxnManager()
let txn1 = tm.beginTxn()
discard tm.write(txn1, "key1", cast[seq[byte]]("value1"))
discard tm.commit(txn1)
let txn2 = tm.beginTxn()
discard tm.delete(txn2, "key1")
discard tm.commit(txn2)
let txn3 = tm.beginTxn()
let (found, _) = tm.read(txn3, "key1")
check not found
discard tm.commit(txn3)
suite "Deadlock Detection":
test "No deadlock without cycles":
var dd = newDeadlockDetector()
dd.addWait(1, 2)
dd.addWait(2, 3)
check not dd.hasDeadlock()
test "Detect simple deadlock":
var dd = newDeadlockDetector()
dd.addWait(1, 2)
dd.addWait(2, 1)
check dd.hasDeadlock()
test "Find deadlock victim":
var dd = newDeadlockDetector()
dd.addWait(1, 2)
dd.addWait(2, 3)
dd.addWait(3, 1)
let victim = dd.findDeadlockVictim()
check victim == 3 # youngest txn
test "Remove transaction clears edges":
var dd = newDeadlockDetector()
dd.addWait(1, 2)
dd.addWait(2, 1)
dd.removeTxn(2)
check not dd.hasDeadlock()
suite "Wire Protocol":
test "Value serialization roundtrip":
var buf: seq[byte] = @[]
let val = WireValue(kind: fkString, strVal: "hello world")
buf.serializeValue(val)
var pos = 0
let decoded = buf.deserializeValue(pos)
check decoded.kind == fkString
check decoded.strVal == "hello world"
test "Int64 serialization":
var buf: seq[byte] = @[]
let val = WireValue(kind: fkInt64, int64Val: 42)
buf.serializeValue(val)
var pos = 0
let decoded = buf.deserializeValue(pos)
check decoded.kind == fkInt64
check decoded.int64Val == 42
test "Array serialization":
var buf: seq[byte] = @[]
let val = WireValue(kind: fkArray, arrayVal: @[
WireValue(kind: fkInt32, int32Val: 1),
WireValue(kind: fkInt32, int32Val: 2),
WireValue(kind: fkInt32, int32Val: 3),
])
buf.serializeValue(val)
var pos = 0
let decoded = buf.deserializeValue(pos)
check decoded.kind == fkArray
check decoded.arrayVal.len == 3
check decoded.arrayVal[1].int32Val == 2
test "Vector serialization":
var buf: seq[byte] = @[]
let val = WireValue(kind: fkVector, vecVal: @[1.0'f32, 2.0'f32, 3.0'f32])
buf.serializeValue(val)
var pos = 0
let decoded = buf.deserializeValue(pos)
check decoded.kind == fkVector
check decoded.vecVal.len == 3
test "Query message creation":
let msg = makeQueryMessage(1, "SELECT * FROM users")
check msg.len > 0
check msg[3] == byte(mkQuery) # big-endian uint32, last byte
suite "Schema System":
test "Create type with properties":
var s = newSchema()
let person = newType("Person")
person.addProperty("name", "str", required = true)
person.addProperty("age", "int32")
s.addType("default", person)
check s.getType("Person") != nil
check s.getType("Person").properties.len == 2
test "Create type with links":
var s = newSchema()
let person = newType("Person")
person.addProperty("name", "str", required = true)
s.addType("default", person)
let movie = newType("Movie")
movie.addProperty("title", "str", required = true)
movie.addLink("actors", "Person", multi = true)
s.addType("default", movie)
check movie.links.len == 1
check movie.links["actors"].target == "Person"
test "Schema diff":
let s1 = newSchema()
let t1 = newType("Person")
t1.addProperty("name", "str")
s1.addType("default", t1)
let s2 = newSchema()
let t2 = newType("Person")
t2.addProperty("name", "str")
t2.addProperty("age", "int32")
s2.addType("default", t2)
let movieT = newType("Movie")
movieT.addProperty("title", "str")
s2.addType("default", movieT)
let d = diff(s1, s2)
check d.addedTypes.len == 1
check d.addedTypes[0] == "Movie"
check d.modifiedTypes.len == 1
check d.modifiedTypes[0].addedProperties.len == 1
test "Type validation":
let t = newType("Person")
t.addProperty("name", "str", required = true)
t.addLink("friend", "") # empty target
let errors = t.validateType()
check errors.len == 1 # empty link target
test "Migration creation":
var s = newSchema()
let m1 = s.createMigration("initial", "CREATE TYPE Person { name: str }")
check m1.id == 1
let m2 = s.createMigration("add age", "ALTER TYPE Person { ADD age: int32 }")
check m2.id == 2
check m2.parentId == 1
test "Type to string":
let t = newType("Person")
t.addProperty("name", "str", required = true)
t.addProperty("age", "int32")
t.addLink("friend", "Person")
let s = $t
check s.find("Person") >= 0
check s.find("name") >= 0
check s.find("str") >= 0