Merge 'Improve extension compatibility testing' from Piotr Rżysko

Extracted from https://github.com/tursodatabase/limbo/pull/1727.

Reviewed-by: Preston Thorpe (@PThorpe92)

Closes #1741
This commit is contained in:
Jussi Saurio
2025-06-21 19:09:33 +03:00
10 changed files with 14825 additions and 18 deletions

View File

@@ -302,6 +302,7 @@ def test_crypto():
def test_series():
console.info(f"Running test_series for Limbo")
limbo = TestLimboShell()
ext_path = "./target/debug/liblimbo_series"
limbo.run_test_fn(
@@ -309,6 +310,14 @@ def test_series():
lambda res: "No such table-valued function: generate_series" in res,
)
limbo.execute_dot(f".load {ext_path}")
_test_series(limbo)
console.info(f"Running test_series for SQLite")
limbo = TestLimboShell(exec_name="sqlite3")
_test_series(limbo)
def _test_series(limbo: TestLimboShell):
limbo.run_test_fn(
"SELECT * FROM generate_series(1, 10);",
lambda res: res == "1\n2\n3\n4\n5\n6\n7\n8\n9\n10",
@@ -319,7 +328,7 @@ def test_series():
)
limbo.run_test_fn(
"SELECT * FROM generate_series(1, 10, 2, 3);",
lambda res: "Invalid Argument" in res,
lambda res: "Invalid Argument" in res or "too many arguments" in res,
)
limbo.run_test_fn(
"SELECT * FROM generate_series(10, 1, -2);",
@@ -335,14 +344,22 @@ def test_series():
def test_kv():
ext_path = "target/debug/liblimbo_ext_tests"
limbo = TestLimboShell()
_test_kv(exec_name=None, ext_path="target/debug/liblimbo_ext_tests")
_test_kv(exec_name="sqlite3", ext_path="target/debug/liblimbo_sqlite_test_ext")
def _test_kv(exec_name, ext_path):
console.info(f"Running test_kv for {ext_path}")
limbo = TestLimboShell(
exec_name=exec_name,
)
# first, create a normal table to ensure no issues
limbo.execute_dot("CREATE TABLE other (a,b,c);")
limbo.execute_dot("INSERT INTO other values (23,32,23);")
limbo.run_test_fn(
"create virtual table t using kv_store;",
lambda res: "Parse error: no such module: kv_store" in res,
lambda res: "no such module: kv_store" in res,
)
limbo.execute_dot(f".load {ext_path}")
limbo.execute_dot(
@@ -410,8 +427,11 @@ def test_kv():
lambda res: res == "100",
"can update all rows",
)
limbo.run_test_fn("delete from t limit 96;", null, "can delete 96 rows")
limbo.run_test_fn("select count(*) from t;", lambda res: "4" == res, "four rows remain")
if exec_name is None:
# Test only on Limbo, since SQLite supports the DELETE ... LIMIT syntax only when compiled
# with the SQLITE_ENABLE_UPDATE_DELETE_LIMIT option: https://www.sqlite.org/lang_delete.html
limbo.run_test_fn("delete from t limit 96;", null, "can delete 96 rows")
limbo.run_test_fn("select count(*) from t;", lambda res: "4" == res, "four rows remain")
limbo.run_test_fn("update t set key = '100' where 1;", null, "where clause evaluates properly")
limbo.run_test_fn(
"select * from t where key = '100';",

View File

@@ -54,8 +54,7 @@ class LimboShell:
return ""
self._write_to_pipe(f"SELECT '{end_marker}';")
output = ""
done = False
while not done:
while True:
ready, _, errors = select.select(
[self.pipe.stdout, self.pipe.stderr],
[],
@@ -63,12 +62,17 @@ class LimboShell:
)
ready_or_errors = set(ready + errors)
if self.pipe.stderr in ready_or_errors:
done = self._handle_error()
fragment = self.pipe.stderr.read(PIPE_BUF).decode()
if not fragment:
console.error(output, end="", _stack_offset=2)
raise RuntimeError("Error encountered in Limbo shell.")
output += fragment
if self.pipe.stdout in ready_or_errors:
fragment = self.pipe.stdout.read(PIPE_BUF).decode()
output += fragment
if output.rstrip().endswith(end_marker):
return self._clean_output(output, end_marker)
break
return self._clean_output(output, end_marker)
def _write_to_pipe(self, command: str) -> None:
if not self.pipe.stdin:
@@ -76,14 +80,6 @@ class LimboShell:
self.pipe.stdin.write((command + "\n").encode())
self.pipe.stdin.flush()
def _handle_error(self) -> None:
while True:
chunk = self.pipe.stderr.read(PIPE_BUF).decode()
if not chunk:
break # EOF
console.error(chunk, end="", _stack_offset=2)
raise RuntimeError("Error encountered in Limbo shell.")
@staticmethod
def _clean_output(output: str, marker: str) -> str:
output = output.rstrip().removesuffix(marker)