#!/usr/bin/env python3 import os import tempfile from time import sleep from cli_tests import console from cli_tests.test_turso_cli import TestTursoShell from pydantic import BaseModel sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ") class InsertTest(BaseModel): name: str db_schema: str = "CREATE TABLE test (t1 BLOB, t2 INTEGER);" blob_size: int = 1024**2 vals: int = 100 has_blob: bool = True db_path: str = "" def run(self, limbo: TestTursoShell): zero_blob = "0" * self.blob_size * 2 big_stmt = [self.db_schema, "CREATE INDEX test_index ON test(t1);"] big_stmt = big_stmt + [ f"INSERT INTO test (t1) VALUES (zeroblob({self.blob_size}));" if i % 2 == 0 and self.has_blob else f"INSERT INTO test (t2) VALUES ({i});" for i in range(self.vals * 2) ] expected = [] for i in range(self.vals * 2): if i % 2 == 0 and self.has_blob: big_stmt.append(f"SELECT hex(t1) FROM test LIMIT 1 OFFSET {i};") expected.append(zero_blob) else: big_stmt.append(f"SELECT t2 FROM test LIMIT 1 OFFSET {i};") expected.append(f"{i}") big_stmt.append("SELECT count(*) FROM test;") expected.append(str(self.vals * 2)) big_stmt.append("DELETE FROM test;") big_stmt.append("SELECT count(*) FROM test;") expected.append(str(0)) big_stmt = "".join(big_stmt) expected = "\n".join(expected) limbo.run_test_fn(big_stmt, lambda res: validate_with_expected(res, expected), self.name) def test_compat(self): console.info("Testing in SQLite\n") with TestTursoShell( init_commands="", exec_name="sqlite3", flags=f"{self.db_path}", ) as sqlite: sqlite.run_test_fn( ".show", lambda res: f"filename: {self.db_path}" in res, "Opened db file created with Limbo in sqlite3", ) sqlite.run_test_fn( ".schema", lambda res: self.db_schema in res, "Tables created by previous Limbo test exist in db file", ) sqlite.run_test_fn( "SELECT count(*) FROM test;", lambda res: res == str(self.vals * 2), "Counting total rows inserted", ) sqlite.run_test_fn( "PRAGMA integrity_check;", lambda res: res == "ok", "Integrity Check", ) console.info() def validate_with_expected(result: str, expected: str): return (expected in result, expected) # TODO no delete tests for now def blob_tests() -> list[InsertTest]: tests: list[InsertTest] = [] for vals in range(0, 1000, 100): tests.append( InsertTest( name=f"small-insert-integer-vals-{vals}", vals=vals, has_blob=False, ) ) tests.append( InsertTest( name=f"small-insert-blob-interleaved-blob-size-{1024}", vals=10, blob_size=1024, ) ) tests.append( InsertTest( name=f"big-insert-blob-interleaved-blob-size-{1024}", vals=100, blob_size=1024, ) ) for blob_size in range(0, (1024 * 1024) + 1, 1024 * 4**4): if blob_size == 0: continue tests.append( InsertTest( name=f"small-insert-blob-interleaved-blob-size-{blob_size}", vals=10, blob_size=blob_size, ) ) tests.append( InsertTest( name=f"big-insert-blob-interleaved-blob-size-{blob_size}", vals=100, blob_size=blob_size, ) ) return tests def cleanup(db_fullpath: str): wal_path = f"{db_fullpath}-wal" shm_path = f"{db_fullpath}-shm" paths = [db_fullpath, wal_path, shm_path] for path in paths: if os.path.exists(path): os.remove(path) def main(): tests = blob_tests() for test in tests: console.info(test) with tempfile.NamedTemporaryFile(suffix=".db") as tmp: test.db_path = tmp.name try: # Use with syntax to automatically close shell on error with TestTursoShell("") as limbo: limbo.execute_dot(f".open {test.db_path}") test.run(limbo) sleep(0.3) test.test_compat() except Exception as e: console.error(f"Test FAILED: {e}") cleanup(test.db_path) exit(1) # delete db after every compat test so we we have fresh db for next test cleanup(test.db_path) console.info("All tests passed successfully.") if __name__ == "__main__": main()