fix(clients): repair Python & Rust tests, add container test orchestration
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled

- Python: fix wire protocol test method names (bool_val -> bool, etc.)
- Python: make integration tests and example fully async with pytest-asyncio
- Rust: add tokio dev-dependency and convert integration tests to async/await
- Rust: update ping_test example to async
- Nim: remove committed ELF build artifact
- Docker: add BARADB_HOST/BARADB_PORT env vars to test containers
- Docker: fix docker-compose.test.yml usage (remove --abort-on-container-exit)
- Add scripts/test-clients.sh for sequential client test runs
- Remove docker-compose.test.yml from .gitignore so it is tracked
- Fix repository URLs in Python and Rust READMEs
This commit is contained in:
2026-05-14 23:35:45 +03:00
parent c55d3080cf
commit 359f945170
14 changed files with 311 additions and 129 deletions
+2 -2
View File
@@ -8,8 +8,8 @@ const assert = require('node:assert');
const net = require('net');
const { Client, WireValue, QueryBuilder } = require('../baradb');
const HOST = 'localhost';
const PORT = 9472;
const HOST = process.env.BARADB_HOST || 'localhost';
const PORT = parseInt(process.env.BARADB_PORT || '9472', 10);
function serverAvailable() {
return new Promise((resolve) => {
+5 -3
View File
@@ -1,15 +1,17 @@
## BaraDB Nim Client — Integration Tests
## Requires a running BaraDB server on localhost:9472.
## Requires a running BaraDB server.
## Set BARADB_HOST / BARADB_PORT env vars to override defaults.
import std/unittest
import std/asyncdispatch
import std/asyncnet
import std/strutils
import std/os
import baradb/client
const
TestHost = "127.0.0.1"
TestPort = 9472
TestHost = getEnv("BARADB_HOST", "127.0.0.1")
TestPort = parseInt(getEnv("BARADB_PORT", "9472"))
proc serverAvailable(): bool =
try:
+1 -1
View File
@@ -21,7 +21,7 @@ pip install baradb
Or from source:
```bash
git clone https://github.com/katehonz/barabaDB.git
git clone https://github.com/barabadb/baradadb.git
cd clients/python
pip install -e ".[dev]"
```
+30 -29
View File
@@ -6,35 +6,36 @@ This script demonstrates common operations with the BaraDB Python client.
Run it while a BaraDB server is listening on localhost:9472.
"""
import asyncio
import sys
from baradb import Client, QueryBuilder, WireValue
def example_connection():
async def example_connection():
print("=== Connection ===")
client = Client("localhost", 9472)
client.connect()
await client.connect()
print(f"Connected: {client.is_connected()}")
print(f"Ping: {client.ping()}")
client.close()
print(f"Ping: {await client.ping()}")
await client.close()
print(f"Connected after close: {client.is_connected()}")
print()
def example_context_manager():
async def example_context_manager():
print("=== Context Manager ===")
with Client("localhost", 9472) as client:
async with Client("localhost", 9472) as client:
print(f"Inside context: {client.is_connected()}")
print(f"Ping: {client.ping()}")
print(f"Ping: {await client.ping()}")
print("Outside context: closed automatically")
print()
def example_simple_query():
async def example_simple_query():
print("=== Simple Query ===")
with Client("localhost", 9472) as client:
result = client.query("SELECT 42 as answer, 'BaraDB' as db")
async with Client("localhost", 9472) as client:
result = await client.query("SELECT 42 as answer, 'BaraDB' as db")
print(f"Columns: {result.columns}")
print(f"Rows: {result.rows}")
print(f"Row count: {result.row_count}")
@@ -43,15 +44,15 @@ def example_simple_query():
print()
def example_parameterized_query():
async def example_parameterized_query():
print("=== Parameterized Query ===")
with Client("localhost", 9472) as client:
result = client.query_params(
async with Client("localhost", 9472) as client:
result = await client.query_params(
"SELECT $1 as num, $2 as txt, $3 as flag",
[
WireValue.int64(123),
WireValue.string("hello world"),
WireValue.bool_val(True),
WireValue.bool(True),
],
)
for row in result:
@@ -59,9 +60,9 @@ def example_parameterized_query():
print()
def example_query_builder():
async def example_query_builder():
print("=== Query Builder ===")
with Client("localhost", 9472) as client:
async with Client("localhost", 9472) as client:
sql = (
QueryBuilder(client)
.select("id", "name")
@@ -75,10 +76,10 @@ def example_query_builder():
print()
def example_vector():
async def example_vector():
print("=== Vector Value ===")
with Client("localhost", 9472) as client:
result = client.query_params(
async with Client("localhost", 9472) as client:
result = await client.query_params(
"SELECT $1 as embedding",
[WireValue.vector([0.1, 0.2, 0.3, 0.4])],
)
@@ -87,34 +88,34 @@ def example_vector():
print()
def example_ddl_dml():
async def example_ddl_dml():
print("=== DDL & DML ===")
with Client("localhost", 9472) as client:
async with Client("localhost", 9472) as client:
# Clean up
try:
client.execute("DROP TABLE IF EXISTS demo_products")
await client.execute("DROP TABLE IF EXISTS demo_products")
except Exception as exc:
print(f"Cleanup warning: {exc}")
client.execute(
await client.execute(
"CREATE TABLE demo_products (id INT PRIMARY KEY, name STRING, price FLOAT)"
)
affected = client.execute(
affected = await client.execute(
"INSERT INTO demo_products (id, name, price) VALUES (1, 'Widget', 9.99)"
)
print(f"Insert affected rows: {affected}")
result = client.query("SELECT * FROM demo_products")
result = await client.query("SELECT * FROM demo_products")
print(f"Select returned {result.row_count} row(s)")
for row in result:
print(f" {row}")
client.execute("DROP TABLE demo_products")
await client.execute("DROP TABLE demo_products")
print("Table dropped")
print()
def main():
async def main():
print("BaraDB Python Client Examples")
print("Make sure BaraDB is running on localhost:9472")
print()
@@ -131,10 +132,10 @@ def main():
for fn in examples:
try:
fn()
await fn()
except Exception as exc:
print(f"ERROR in {fn.__name__}: {exc}", file=sys.stderr)
if __name__ == "__main__":
main()
asyncio.run(main())
+1
View File
@@ -48,6 +48,7 @@ packages = ["baradb"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
asyncio_mode = "auto"
[tool.mypy]
strict = true
+42 -39
View File
@@ -5,13 +5,16 @@ Requires a running BaraDB server on localhost:9472.
These tests are skipped automatically if the server is unreachable.
"""
import asyncio
import os
import socket
import pytest
import pytest_asyncio
from baradb import Client, WireValue, QueryBuilder
BARADB_HOST = "localhost"
BARADB_PORT = 9472
BARADB_HOST = os.environ.get("BARADB_HOST", "localhost")
BARADB_PORT = int(os.environ.get("BARADB_PORT", "9472"))
def _server_available() -> bool:
@@ -29,41 +32,41 @@ pytestmark = pytest.mark.skipif(
)
@pytest.fixture
def client():
@pytest_asyncio.fixture
async def client():
c = Client(BARADB_HOST, BARADB_PORT)
c.connect()
await c.connect()
yield c
c.close()
await c.close()
class TestConnection:
def test_connect_and_close(self):
async def test_connect_and_close(self):
c = Client(BARADB_HOST, BARADB_PORT)
assert not c.is_connected()
c.connect()
await c.connect()
assert c.is_connected()
c.close()
await c.close()
assert not c.is_connected()
def test_context_manager(self):
with Client(BARADB_HOST, BARADB_PORT) as c:
async def test_context_manager(self):
async with Client(BARADB_HOST, BARADB_PORT) as c:
assert c.is_connected()
assert not c.is_connected()
class TestPing:
def test_ping(self, client):
assert client.ping() is True
async def test_ping(self, client):
assert await client.ping() is True
class TestQuery:
def test_simple_select(self, client):
result = client.query("SELECT 1 as one")
async def test_simple_select(self, client):
result = await client.query("SELECT 1 as one")
assert result.row_count >= 0 # server may return rows or empty
def test_query_with_params(self, client):
result = client.query_params(
async def test_query_with_params(self, client):
result = await client.query_params(
"SELECT $1 as num, $2 as txt",
[WireValue.int64(42), WireValue.string("hello")],
)
@@ -71,22 +74,22 @@ class TestQuery:
class TestExecute:
def test_create_table_and_insert(self, client):
async def test_create_table_and_insert(self, client):
# Clean up first (best-effort)
try:
client.execute("DROP TABLE IF EXISTS test_users")
await client.execute("DROP TABLE IF EXISTS test_users")
except Exception:
pass
client.execute(
await client.execute(
"CREATE TABLE test_users (id INT PRIMARY KEY, name STRING, age INT)"
)
affected = client.execute(
affected = await client.execute(
"INSERT INTO test_users (id, name, age) VALUES (1, 'Alice', 30)"
)
assert affected >= 0
result = client.query("SELECT name, age FROM test_users WHERE id = 1")
result = await client.query("SELECT name, age FROM test_users WHERE id = 1")
assert result.row_count == 1
row = result.rows[0]
# Server returns all columns; map by name via dict iteration
@@ -94,20 +97,20 @@ class TestExecute:
assert row_dict["name"] == "Alice"
assert row_dict["age"] == 30
client.execute("DROP TABLE test_users")
await client.execute("DROP TABLE test_users")
class TestQueryBuilder:
def test_builder_exec(self, client):
async def test_builder_exec(self, client):
try:
client.execute("DROP TABLE IF EXISTS test_products")
await client.execute("DROP TABLE IF EXISTS test_products")
except Exception:
pass
client.execute(
await client.execute(
"CREATE TABLE test_products (id INT PRIMARY KEY, name STRING, price FLOAT)"
)
client.execute(
await client.execute(
"INSERT INTO test_products (id, name, price) VALUES (1, 'Widget', 9.99)"
)
@@ -117,36 +120,36 @@ class TestQueryBuilder:
.from_("test_products")
.where("id = 1")
)
result = qb.exec()
result = await qb.exec()
assert result.row_count == 1
client.execute("DROP TABLE test_products")
await client.execute("DROP TABLE test_products")
class TestAuth:
def test_auth_with_dummy_token(self, client):
async def test_auth_with_dummy_token(self, client):
# Server uses default JWT secret in dev mode, any token format is accepted
# depending on server config. We just verify the method does not crash.
try:
client.auth("dummy-token-for-testing")
await client.auth("dummy-token-for-testing")
except Exception as exc:
# Auth may fail with invalid token — that's acceptable for this test
assert "Auth" in str(exc) or "error" in str(exc).lower()
class TestTransactions:
def test_transaction_begin_commit(self, client):
async def test_transaction_begin_commit(self, client):
try:
client.execute("DROP TABLE IF EXISTS test_txn")
await client.execute("DROP TABLE IF EXISTS test_txn")
except Exception:
pass
client.execute("CREATE TABLE test_txn (id INT PRIMARY KEY)")
await client.execute("CREATE TABLE test_txn (id INT PRIMARY KEY)")
client.execute("BEGIN")
client.execute("INSERT INTO test_txn (id) VALUES (1)")
client.execute("COMMIT")
await client.execute("BEGIN")
await client.execute("INSERT INTO test_txn (id) VALUES (1)")
await client.execute("COMMIT")
result = client.query("SELECT COUNT(*) FROM test_txn")
result = await client.query("SELECT COUNT(*) FROM test_txn")
assert result.row_count >= 0
client.execute("DROP TABLE test_txn")
await client.execute("DROP TABLE test_txn")
+6 -6
View File
@@ -18,11 +18,11 @@ class TestWireValue:
assert data == b"\x00"
def test_bool_true(self):
wv = WireValue.bool_val(True)
wv = WireValue.bool(True)
assert wv.serialize() == b"\x01\x01"
def test_bool_false(self):
wv = WireValue.bool_val(False)
wv = WireValue.bool(False)
assert wv.serialize() == b"\x01\x00"
def test_int8(self):
@@ -70,7 +70,7 @@ class TestWireValue:
assert data[5:] == b"hello"
def test_bytes(self):
wv = WireValue.bytes_val(b"\xde\xad\xbe\xef")
wv = WireValue.bytes(b"\xde\xad\xbe\xef")
data = wv.serialize()
assert data[0] == FieldKind.BYTES
length = struct.unpack(">I", data[1:5])[0]
@@ -87,7 +87,7 @@ class TestWireValue:
assert floats == [1.0, 2.0, 3.0]
def test_json(self):
wv = WireValue.json_val('{"key": "value"}')
wv = WireValue.json('{"key": "value"}')
data = wv.serialize()
assert data[0] == FieldKind.JSON
length = struct.unpack(">I", data[1:5])[0]
@@ -95,7 +95,7 @@ class TestWireValue:
def test_array(self):
inner = [WireValue.string("a"), WireValue.string("b")]
wv = WireValue.array_val(inner)
wv = WireValue.array(inner)
data = wv.serialize()
assert data[0] == FieldKind.ARRAY
count = struct.unpack(">I", data[1:5])[0]
@@ -103,7 +103,7 @@ class TestWireValue:
def test_object(self):
inner = {"name": WireValue.string("Bara"), "age": WireValue.int32(42)}
wv = WireValue.object_val(inner)
wv = WireValue.object(inner)
data = wv.serialize()
assert data[0] == FieldKind.OBJECT
count = struct.unpack(">I", data[1:5])[0]
+2 -1
View File
@@ -5,7 +5,7 @@ edition = "2021"
authors = ["BaraDB Team <team@baradb.dev>"]
description = "Official async Rust client for BaraDB — binary protocol client"
license = "Apache-2.0"
repository = "https://github.com/katehonz/barabaDB"
repository = "https://github.com/barabadb/baradadb.git"
documentation = "https://docs.baradb.dev"
readme = "README.md"
keywords = ["database", "baradb", "multimodal", "client", "wire-protocol"]
@@ -16,6 +16,7 @@ rust-version = "1.70"
tokio = { version = "1.35", features = ["full"] }
[dev-dependencies]
tokio = { version = "1.35", features = ["full"] }
[[example]]
name = "basic"
+1 -1
View File
@@ -23,7 +23,7 @@ tokio = { version = "1.35", features = ["full"] }
Or from source:
```bash
git clone https://github.com/katehonz/barabaDB.git
git clone https://github.com/barabadb/baradadb.git
cd clients/rust
cargo build
```
+5 -4
View File
@@ -1,11 +1,12 @@
use baradb::Client;
fn main() {
let mut client = Client::connect("localhost", 9472).unwrap();
#[tokio::main]
async fn main() {
let mut client = Client::connect("localhost", 9472).await.unwrap();
println!("Connected: {}", client.is_connected());
match client.ping() {
match client.ping().await {
Ok(v) => println!("Ping: {}", v),
Err(e) => println!("Ping error: {}", e),
}
client.close();
client.close().await;
}
+57 -42
View File
@@ -1,104 +1,118 @@
// BaraDB Rust Client — Integration Tests
// Requires a running BaraDB server on localhost:9472.
// Requires a running BaraDB server.
// Set BARADB_HOST / BARADB_PORT env vars to override defaults.
use std::net::TcpStream;
use std::time::Duration;
use baradb::{Client, QueryBuilder, WireValue};
const HOST: &str = "127.0.0.1";
const PORT: u16 = 9472;
fn server_available() -> bool {
TcpStream::connect((HOST, PORT)).is_ok()
fn host() -> String {
std::env::var("BARADB_HOST").unwrap_or_else(|_| "127.0.0.1".to_string())
}
#[test]
fn test_connect_and_close() {
fn port() -> u16 {
std::env::var("BARADB_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(9472)
}
fn server_available() -> bool {
TcpStream::connect((host().as_str(), port())).is_ok()
}
#[tokio::test]
async fn test_connect_and_close() {
if !server_available() {
return;
}
let mut client = Client::connect(HOST, PORT).unwrap();
let mut client = Client::connect(&host(), port()).await.unwrap();
assert!(client.is_connected());
client.close();
client.close().await;
assert!(!client.is_connected());
}
#[test]
fn test_ping() {
#[tokio::test]
async fn test_ping() {
if !server_available() {
return;
}
let mut client = Client::connect(HOST, PORT).unwrap();
assert!(client.ping().unwrap());
client.close();
let mut client = Client::connect(&host(), port()).await.unwrap();
assert!(client.ping().await.unwrap());
client.close().await;
}
#[test]
fn test_simple_select() {
#[tokio::test]
async fn test_simple_select() {
if !server_available() {
return;
}
let mut client = Client::connect(HOST, PORT).unwrap();
let result = client.query("SELECT 1 as one").unwrap();
let mut client = Client::connect(&host(), port()).await.unwrap();
let result = client.query("SELECT 1 as one").await.unwrap();
assert!(result.row_count() >= 0);
client.close();
client.close().await;
}
#[test]
fn test_parameterized_query() {
#[tokio::test]
async fn test_parameterized_query() {
if !server_available() {
return;
}
let mut client = Client::connect(HOST, PORT).unwrap();
let mut client = Client::connect(&host(), port()).await.unwrap();
let result = client
.query_params(
"SELECT $1 as num, $2 as txt",
&[WireValue::Int64(42), WireValue::String("hello".to_string())],
)
.await
.unwrap();
assert!(result.row_count() >= 0);
client.close();
client.close().await;
}
#[test]
fn test_create_table_insert_select_drop() {
#[tokio::test]
async fn test_create_table_insert_select_drop() {
if !server_available() {
return;
}
let mut client = Client::connect(HOST, PORT).unwrap();
let mut client = Client::connect(&host(), port()).await.unwrap();
let _ = client.execute("DROP TABLE IF EXISTS rust_test_users");
let _ = client.execute("DROP TABLE IF EXISTS rust_test_users").await;
client
.execute("CREATE TABLE rust_test_users (id INT PRIMARY KEY, name STRING, age INT)")
.await
.unwrap();
let affected = client
.execute("INSERT INTO rust_test_users (id, name, age) VALUES (1, 'Alice', 30)")
.await
.unwrap();
assert!(affected >= 0);
let result = client
.query("SELECT name, age FROM rust_test_users WHERE id = 1")
.await
.unwrap();
assert_eq!(result.row_count(), 1);
client.execute("DROP TABLE rust_test_users").unwrap();
client.close();
client.execute("DROP TABLE rust_test_users").await.unwrap();
client.close().await;
}
#[test]
fn test_query_builder_exec() {
#[tokio::test]
async fn test_query_builder_exec() {
if !server_available() {
return;
}
let mut client = Client::connect(HOST, PORT).unwrap();
let mut client = Client::connect(&host(), port()).await.unwrap();
let _ = client.execute("DROP TABLE IF EXISTS rust_test_products");
let _ = client.execute("DROP TABLE IF EXISTS rust_test_products").await;
client
.execute("CREATE TABLE rust_test_products (id INT PRIMARY KEY, name STRING, price FLOAT)")
.await
.unwrap();
client
.execute("INSERT INTO rust_test_products (id, name, price) VALUES (1, 'Widget', 9.99)")
.await
.unwrap();
let result = QueryBuilder::new(&mut client)
@@ -106,21 +120,22 @@ fn test_query_builder_exec() {
.from("rust_test_products")
.where_clause("id = 1")
.exec()
.await
.unwrap();
assert_eq!(result.row_count(), 1);
client.execute("DROP TABLE rust_test_products").unwrap();
client.close();
client.execute("DROP TABLE rust_test_products").await.unwrap();
client.close().await;
}
#[test]
fn test_auth_dummy_token() {
#[tokio::test]
async fn test_auth_dummy_token() {
if !server_available() {
return;
}
let mut client = Client::connect(HOST, PORT).unwrap();
let res = client.auth("dummy-token-for-testing");
let mut client = Client::connect(&host(), port()).await.unwrap();
let res = client.auth("dummy-token-for-testing").await;
// Dev server may accept or reject — both are fine
match res {
Ok(()) => {}
@@ -129,5 +144,5 @@ fn test_auth_dummy_token() {
assert!(msg.contains("Auth") || msg.to_lowercase().contains("error"));
}
}
client.close();
client.close().await;
}