mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 00:45:37 +01:00
Rename and combine testing extension crate
This commit is contained in:
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -1660,6 +1660,7 @@ dependencies = [
|
||||
"limbo_completion",
|
||||
"limbo_crypto",
|
||||
"limbo_ext",
|
||||
"limbo_ext_tests",
|
||||
"limbo_ipaddr",
|
||||
"limbo_macros",
|
||||
"limbo_percentile",
|
||||
@@ -1717,19 +1718,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "limbo_ipaddr"
|
||||
name = "limbo_ext_tests"
|
||||
version = "0.0.16"
|
||||
dependencies = [
|
||||
"ipnetwork",
|
||||
"lazy_static",
|
||||
"limbo_ext",
|
||||
"mimalloc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "limbo_kv"
|
||||
name = "limbo_ipaddr"
|
||||
version = "0.0.16"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"ipnetwork",
|
||||
"limbo_ext",
|
||||
"mimalloc",
|
||||
]
|
||||
|
||||
@@ -13,7 +13,7 @@ members = [
|
||||
"extensions/completion",
|
||||
"extensions/core",
|
||||
"extensions/crypto",
|
||||
"extensions/kvstore",
|
||||
"extensions/tests",
|
||||
"extensions/percentile",
|
||||
"extensions/regexp",
|
||||
"extensions/series",
|
||||
@@ -47,6 +47,7 @@ limbo_uuid = { path = "extensions/uuid", version = "0.0.16" }
|
||||
limbo_sqlite3_parser = { path = "vendored/sqlite3-parser", version = "0.0.16" }
|
||||
limbo_ipaddr = { path = "extensions/ipaddr", version = "0.0.16" }
|
||||
limbo_completion = { path = "extensions/completion", version = "0.0.16" }
|
||||
limbo_ext_tests = { path = "extensions/tests", version = "0.0.16" }
|
||||
|
||||
# Config for 'cargo dist'
|
||||
[workspace.metadata.dist]
|
||||
|
||||
@@ -26,6 +26,7 @@ crypto = ["limbo_crypto/static"]
|
||||
series = ["limbo_series/static"]
|
||||
ipaddr = ["limbo_ipaddr/static"]
|
||||
completion = ["limbo_completion/static"]
|
||||
testvfs = ["limbo_ext_tests/static"]
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
io-uring = { version = "0.6.1", optional = true }
|
||||
@@ -68,6 +69,7 @@ limbo_crypto = { workspace = true, optional = true, features = ["static"] }
|
||||
limbo_series = { workspace = true, optional = true, features = ["static"] }
|
||||
limbo_ipaddr = { workspace = true, optional = true, features = ["static"] }
|
||||
limbo_completion = { workspace = true, optional = true, features = ["static"] }
|
||||
limbo_ext_tests = { workspace = true, optional = true, features = ["static"] }
|
||||
miette = "7.4.0"
|
||||
strum = "0.26"
|
||||
parking_lot = "0.12.3"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "limbo_kv"
|
||||
name = "limbo_ext_tests"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@@ -17,4 +17,4 @@ lazy_static = "1.5.0"
|
||||
limbo_ext = { workspace = true, features = ["static"] }
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
mimalloc = { version = "*", default-features = false }
|
||||
mimalloc = { version = "0.1", default-features = false }
|
||||
@@ -1,19 +1,21 @@
|
||||
use lazy_static::lazy_static;
|
||||
use limbo_ext::register_extension;
|
||||
use limbo_ext::{
|
||||
register_extension, scalar, ExtResult, ResultCode, VTabCursor, VTabKind, VTabModule,
|
||||
VTabModuleDerive, Value, VfsDerive, VfsExtension, VfsFile,
|
||||
scalar, ExtResult, ResultCode, VTabCursor, VTabKind, VTabModule, VTabModuleDerive, Value,
|
||||
VfsDerive, VfsExtension, VfsFile,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref GLOBAL_STORE: Mutex<BTreeMap<i64, (String, String)>> = Mutex::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
register_extension! {
|
||||
vtabs: { KVStoreVTab },
|
||||
vfs: { TestFS },
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref GLOBAL_STORE: Mutex<BTreeMap<i64, (String, String)>> = Mutex::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
#[derive(VTabModuleDerive, Default)]
|
||||
@@ -149,12 +151,12 @@ impl VTabCursor for KVStoreCursor {
|
||||
}
|
||||
}
|
||||
|
||||
struct TestFile {
|
||||
pub struct TestFile {
|
||||
file: File,
|
||||
}
|
||||
|
||||
#[derive(VfsDerive, Default)]
|
||||
struct TestFS;
|
||||
pub struct TestFS;
|
||||
|
||||
// Test that we can have additional extension types in the same file
|
||||
// and still register the vfs at comptime if linking staticly
|
||||
@@ -1,7 +1,7 @@
|
||||
mod args;
|
||||
use args::{RegisterExtensionInput, ScalarInfo};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_macro_input, DeriveInput, ItemFn};
|
||||
use syn::{parse_macro_input, DeriveInput, Item, ItemFn, ItemMod};
|
||||
extern crate proc_macro;
|
||||
use proc_macro::{token_stream::IntoIter, Group, TokenStream, TokenTree};
|
||||
use std::collections::HashMap;
|
||||
@@ -980,3 +980,42 @@ pub fn register_extension(input: TokenStream) -> TokenStream {
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
/// Recursively search for a function in the module tree
|
||||
fn find_function_path(
|
||||
function_name: &syn::Ident,
|
||||
module_path: String,
|
||||
items: &[Item],
|
||||
) -> Option<String> {
|
||||
for item in items {
|
||||
match item {
|
||||
// if it's a function, check if its name matches
|
||||
Item::Fn(func) if func.sig.ident == *function_name => {
|
||||
return Some(module_path.clone());
|
||||
}
|
||||
// recursively search inside modules
|
||||
Item::Mod(ItemMod {
|
||||
ident,
|
||||
content: Some((_, sub_items)),
|
||||
..
|
||||
}) => {
|
||||
let new_path = format!("{}::{}", module_path, ident);
|
||||
if let Some(path) = find_function_path(function_name, new_path, sub_items) {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn locate_function(ident: syn::Ident) -> syn::Ident {
|
||||
let syntax_tree: syn::File = syn::parse_file(include_str!("lib.rs")).unwrap();
|
||||
|
||||
if let Some(full_path) = find_function_path(&ident, "crate".to_string(), &syntax_tree.items) {
|
||||
return format_ident!("{full_path}::{ident}");
|
||||
}
|
||||
|
||||
panic!("Function `{}` not found in crate!", ident);
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ def test_series():
|
||||
|
||||
|
||||
def test_kv():
|
||||
ext_path = "./target/debug/liblimbo_kv"
|
||||
ext_path = "target/debug/liblimbo_testextension"
|
||||
limbo = TestLimboShell()
|
||||
limbo.run_test_fn(
|
||||
"create virtual table t using kv_store;",
|
||||
@@ -401,17 +401,18 @@ def test_kv():
|
||||
)
|
||||
limbo.quit()
|
||||
|
||||
|
||||
def test_ipaddr():
|
||||
limbo = TestLimboShell()
|
||||
ext_path = "./target/debug/liblimbo_ipaddr"
|
||||
|
||||
|
||||
limbo.run_test_fn(
|
||||
"SELECT ipfamily('192.168.1.1');",
|
||||
lambda res: "error: no such function: " in res,
|
||||
"ipfamily function returns null when ext not loaded",
|
||||
)
|
||||
limbo.execute_dot(f".load {ext_path}")
|
||||
|
||||
|
||||
limbo.run_test_fn(
|
||||
"SELECT ipfamily('192.168.1.1');",
|
||||
lambda res: "4" == res,
|
||||
@@ -455,7 +456,7 @@ def test_ipaddr():
|
||||
lambda res: "128" == res,
|
||||
"ipmasklen function returns the mask length for IPv6",
|
||||
)
|
||||
|
||||
|
||||
limbo.run_test_fn(
|
||||
"SELECT ipnetwork('192.168.16.12/24');",
|
||||
lambda res: "192.168.16.0/24" == res,
|
||||
@@ -466,7 +467,76 @@ def test_ipaddr():
|
||||
lambda res: "2001:db8::1/128" == res,
|
||||
"ipnetwork function returns the network for IPv6",
|
||||
)
|
||||
|
||||
limbo.quit()
|
||||
|
||||
|
||||
def test_vfs():
|
||||
limbo = TestLimboShell()
|
||||
ext_path = "target/debug/liblimbo_testextension"
|
||||
limbo.run_test_fn(".vfslist", lambda x: "testvfs" not in x, "testvfs not loaded")
|
||||
limbo.execute_dot(f".load {ext_path}")
|
||||
limbo.run_test_fn(
|
||||
".vfslist", lambda res: "testvfs" in res, "testvfs extension loaded"
|
||||
)
|
||||
limbo.execute_dot(".open testing/vfs.db testvfs")
|
||||
limbo.execute_dot("create table test (id integer primary key, value float);")
|
||||
limbo.execute_dot("create table vfs (id integer primary key, value blob);")
|
||||
for i in range(50):
|
||||
limbo.execute_dot("insert into test (value) values (randomblob(32*1024));")
|
||||
limbo.execute_dot(f"insert into vfs (value) values ({i});")
|
||||
limbo.run_test_fn(
|
||||
"SELECT count(*) FROM test;",
|
||||
lambda res: res == "50",
|
||||
"Tested large write to testfs",
|
||||
)
|
||||
limbo.run_test_fn(
|
||||
"SELECT count(*) FROM vfs;",
|
||||
lambda res: res == "50",
|
||||
"Tested large write to testfs",
|
||||
)
|
||||
print("Tested large write to testfs")
|
||||
# open regular db file to ensure we don't segfault when vfs file is dropped
|
||||
limbo.execute_dot(".open testing/vfs2.db")
|
||||
limbo.execute_dot("create table test (id integer primary key, value float);")
|
||||
limbo.execute_dot("insert into test (value) values (1.0);")
|
||||
limbo.quit()
|
||||
|
||||
|
||||
def test_sqlite_vfs_compat():
|
||||
sqlite = TestLimboShell(
|
||||
init_commands="",
|
||||
exec_name="sqlite3",
|
||||
flags="testing/vfs.db",
|
||||
)
|
||||
sqlite.run_test_fn(
|
||||
".show",
|
||||
lambda res: "filename: testing/vfs.db" in res,
|
||||
"Opened db file created with vfs extension in sqlite3",
|
||||
)
|
||||
sqlite.run_test_fn(
|
||||
".schema",
|
||||
lambda res: "CREATE TABLE test (id integer PRIMARY KEY, value float);" in res,
|
||||
"Tables created by vfs extension exist in db file",
|
||||
)
|
||||
sqlite.run_test_fn(
|
||||
"SELECT count(*) FROM test;",
|
||||
lambda res: res == "50",
|
||||
"Tested large write to testfs",
|
||||
)
|
||||
sqlite.run_test_fn(
|
||||
"SELECT count(*) FROM vfs;",
|
||||
lambda res: res == "50",
|
||||
"Tested large write to testfs",
|
||||
)
|
||||
sqlite.quit()
|
||||
|
||||
|
||||
def cleanup():
|
||||
if os.path.exists("testing/vfs.db"):
|
||||
os.remove("testing/vfs.db")
|
||||
if os.path.exists("testing/vfs.db-wal"):
|
||||
os.remove("testing/vfs.db-wal")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
@@ -477,7 +547,11 @@ if __name__ == "__main__":
|
||||
test_series()
|
||||
test_kv()
|
||||
test_ipaddr()
|
||||
test_vfs()
|
||||
test_sqlite_vfs_compat()
|
||||
except Exception as e:
|
||||
print(f"Test FAILED: {e}")
|
||||
cleanup()
|
||||
exit(1)
|
||||
cleanup()
|
||||
print("All tests passed successfully.")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import select
|
||||
from time import sleep
|
||||
import subprocess
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
@@ -10,16 +10,14 @@ from typing import Callable, List, Optional
|
||||
PIPE_BUF = 4096
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShellConfig:
|
||||
sqlite_exec: str = os.getenv("LIMBO_TARGET", "./target/debug/limbo")
|
||||
sqlite_flags: List[str] = field(
|
||||
default_factory=lambda: os.getenv("SQLITE_FLAGS", "-q").split()
|
||||
)
|
||||
cwd = os.getcwd()
|
||||
test_dir: Path = field(default_factory=lambda: Path("testing"))
|
||||
py_folder: Path = field(default_factory=lambda: Path("cli_tests"))
|
||||
test_files: Path = field(default_factory=lambda: Path("test_files"))
|
||||
def __init__(self, exe_name, flags: str = "-q"):
|
||||
self.sqlite_exec: str = exe_name
|
||||
self.sqlite_flags: List[str] = flags.split()
|
||||
self.cwd = os.getcwd()
|
||||
self.test_dir: Path = Path("testing")
|
||||
self.py_folder: Path = Path("cli_tests")
|
||||
self.test_files: Path = Path("test_files")
|
||||
|
||||
|
||||
class LimboShell:
|
||||
@@ -92,14 +90,24 @@ class LimboShell:
|
||||
|
||||
def quit(self) -> None:
|
||||
self._write_to_pipe(".quit")
|
||||
sleep(0.3)
|
||||
self.pipe.terminate()
|
||||
self.pipe.kill()
|
||||
|
||||
|
||||
class TestLimboShell:
|
||||
def __init__(
|
||||
self, init_commands: Optional[str] = None, init_blobs_table: bool = False
|
||||
self,
|
||||
init_commands: Optional[str] = None,
|
||||
init_blobs_table: bool = False,
|
||||
exec_name: Optional[str] = None,
|
||||
flags="",
|
||||
):
|
||||
self.config = ShellConfig()
|
||||
if exec_name is None:
|
||||
exec_name = "./target/debug/limbo"
|
||||
if flags == "":
|
||||
flags = "-q"
|
||||
self.config = ShellConfig(exe_name=exec_name, flags=flags)
|
||||
if init_commands is None:
|
||||
# Default initialization
|
||||
init_commands = """
|
||||
@@ -132,6 +140,11 @@ INSERT INTO t VALUES (zeroblob(1024 - 1), zeroblob(1024 - 2), zeroblob(1024 - 3)
|
||||
f"Actual:\n{repr(actual)}"
|
||||
)
|
||||
|
||||
def debug_print(self, sql: str):
|
||||
print(f"debugging: {sql}")
|
||||
actual = self.shell.execute(sql)
|
||||
print(f"OUTPUT:\n{repr(actual)}")
|
||||
|
||||
def run_test_fn(
|
||||
self, sql: str, validate: Callable[[str], bool], desc: str = ""
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user