mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-03 16:34:19 +01:00
Merge 'Test: write tests for file backed db' from Pedro Muniz
First attempt at closing #1212. Also with this PR, I added the option of using `with` syntax for `TestLimboShell`. It automatically closes the shell on error, and facilitates error handling overall. If this is merged, I can update the other python tests to use `with` as well. Reviewed-by: Preston Thorpe (@PThorpe92) Closes #1230
This commit is contained in:
12
.github/workflows/rust.yml
vendored
12
.github/workflows/rust.yml
vendored
@@ -75,6 +75,18 @@ jobs:
|
||||
curl -L $LINK/$CARGO_C_FILE | tar xz -C ~/.cargo/bin
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install
|
||||
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev --all-packages
|
||||
|
||||
- uses: "./.github/shared/install_sqlite"
|
||||
- name: Test
|
||||
run: make test
|
||||
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
26
Makefile
26
Makefile
@@ -62,16 +62,20 @@ limbo-wasm:
|
||||
cargo build --package limbo-wasm --target wasm32-wasi
|
||||
.PHONY: limbo-wasm
|
||||
|
||||
test: limbo test-compat test-vector test-sqlite3 test-shell test-extensions test-memory
|
||||
uv-sync:
|
||||
uv sync --all-packages
|
||||
.PHONE: uv-sync
|
||||
|
||||
test: limbo uv-sync test-compat test-vector test-sqlite3 test-shell test-extensions test-memory test-write test-update
|
||||
.PHONY: test
|
||||
|
||||
test-extensions: limbo
|
||||
test-extensions: limbo uv-sync
|
||||
cargo build --package limbo_regexp
|
||||
./testing/cli_tests/extensions.py
|
||||
uv run --project limbo_test test-extensions
|
||||
.PHONY: test-extensions
|
||||
|
||||
test-shell: limbo
|
||||
SQLITE_EXEC=$(SQLITE_EXEC) ./testing/cli_tests/cli_test_cases.py
|
||||
test-shell: limbo uv-sync
|
||||
SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-shell
|
||||
.PHONY: test-shell
|
||||
|
||||
test-compat:
|
||||
@@ -94,10 +98,18 @@ test-json:
|
||||
SQLITE_EXEC=$(SQLITE_EXEC) ./testing/json.test
|
||||
.PHONY: test-json
|
||||
|
||||
test-memory:
|
||||
SQLITE_EXEC=$(SQLITE_EXEC) ./testing/cli_tests/memory.py
|
||||
test-memory: limbo uv-sync
|
||||
SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-memory
|
||||
.PHONY: test-memory
|
||||
|
||||
test-write: limbo uv-sync
|
||||
SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-write
|
||||
.PHONY: test-write
|
||||
|
||||
test-update: limbo uv-sync
|
||||
SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-update
|
||||
.PHONY: test-update
|
||||
|
||||
clickbench:
|
||||
./perf/clickbench/benchmark.sh
|
||||
.PHONY: clickbench
|
||||
|
||||
17
pyproject.toml
Normal file
17
pyproject.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[project]
|
||||
dependencies = [
|
||||
"rich>=14.0.0",
|
||||
]
|
||||
name = "limbo"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
version = "0.1.0"
|
||||
|
||||
[tool.uv]
|
||||
package = false
|
||||
|
||||
[tool.uv.sources]
|
||||
limbo_test = { workspace = true }
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["testing"]
|
||||
1
testing/README.md
Normal file
1
testing/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Limbo Testing
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
from test_limbo_cli import TestLimboShell
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from pathlib import Path
|
||||
import time
|
||||
import os
|
||||
from cli_tests import console
|
||||
|
||||
|
||||
def test_basic_queries():
|
||||
@@ -300,8 +301,8 @@ def test_insert_default_values():
|
||||
limbo.quit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Running all Limbo CLI tests...")
|
||||
def main():
|
||||
console.info("Running all Limbo CLI tests...")
|
||||
test_basic_queries()
|
||||
test_schema_operations()
|
||||
test_file_operations()
|
||||
@@ -319,4 +320,8 @@ if __name__ == "__main__":
|
||||
test_table_patterns()
|
||||
test_update_with_limit()
|
||||
test_update_with_limit_and_offset()
|
||||
print("All tests have passed")
|
||||
console.info("All tests have passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
122
testing/cli_tests/console.py
Normal file
122
testing/cli_tests/console.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from typing import Any, Optional, Union
|
||||
from rich.console import Console, JustifyMethod
|
||||
from rich.theme import Theme
|
||||
from rich.style import Style
|
||||
|
||||
|
||||
custom_theme = Theme(
|
||||
{
|
||||
"info": "bold blue",
|
||||
"error": "bold red",
|
||||
"debug": "bold blue",
|
||||
"test": "bold green",
|
||||
}
|
||||
)
|
||||
console = Console(theme=custom_theme, force_terminal=True)
|
||||
|
||||
|
||||
def info(
|
||||
*objects: Any,
|
||||
sep: str = " ",
|
||||
end: str = "\n",
|
||||
style: Optional[Union[str, Style]] = None,
|
||||
justify: Optional[JustifyMethod] = None,
|
||||
emoji: Optional[bool] = None,
|
||||
markup: Optional[bool] = None,
|
||||
highlight: Optional[bool] = None,
|
||||
log_locals: bool = False,
|
||||
_stack_offset: int = 1,
|
||||
):
|
||||
console.log(
|
||||
"[info]INFO[/info]",
|
||||
*objects,
|
||||
sep=sep,
|
||||
end=end,
|
||||
style=style,
|
||||
justify=justify,
|
||||
emoji=emoji,
|
||||
markup=markup,
|
||||
highlight=highlight,
|
||||
log_locals=log_locals,
|
||||
_stack_offset=_stack_offset + 1,
|
||||
)
|
||||
|
||||
|
||||
def error(
|
||||
*objects: Any,
|
||||
sep: str = " ",
|
||||
end: str = "\n",
|
||||
style: Optional[Union[str, Style]] = None,
|
||||
justify: Optional[JustifyMethod] = None,
|
||||
emoji: Optional[bool] = None,
|
||||
markup: Optional[bool] = None,
|
||||
highlight: Optional[bool] = None,
|
||||
log_locals: bool = False,
|
||||
_stack_offset: int = 1,
|
||||
):
|
||||
console.log(
|
||||
"[error]ERROR[/error]",
|
||||
*objects,
|
||||
sep=sep,
|
||||
end=end,
|
||||
style=style,
|
||||
justify=justify,
|
||||
emoji=emoji,
|
||||
markup=markup,
|
||||
highlight=highlight,
|
||||
log_locals=log_locals,
|
||||
_stack_offset=_stack_offset + 1,
|
||||
)
|
||||
|
||||
|
||||
def debug(
|
||||
*objects: Any,
|
||||
sep: str = " ",
|
||||
end: str = "\n",
|
||||
style: Optional[Union[str, Style]] = None,
|
||||
justify: Optional[JustifyMethod] = None,
|
||||
emoji: Optional[bool] = None,
|
||||
markup: Optional[bool] = None,
|
||||
highlight: Optional[bool] = None,
|
||||
log_locals: bool = False,
|
||||
_stack_offset: int = 1,
|
||||
):
|
||||
console.log(
|
||||
"[debug]DEBUG[/debug]",
|
||||
*objects,
|
||||
sep=sep,
|
||||
end=end,
|
||||
style=style,
|
||||
justify=justify,
|
||||
emoji=emoji,
|
||||
markup=markup,
|
||||
highlight=highlight,
|
||||
log_locals=log_locals,
|
||||
_stack_offset=_stack_offset + 1,
|
||||
)
|
||||
|
||||
def test(
|
||||
*objects: Any,
|
||||
sep: str = " ",
|
||||
end: str = "\n",
|
||||
style: Optional[Union[str, Style]] = None,
|
||||
justify: Optional[JustifyMethod] = None,
|
||||
emoji: Optional[bool] = None,
|
||||
markup: Optional[bool] = None,
|
||||
highlight: Optional[bool] = None,
|
||||
log_locals: bool = False,
|
||||
_stack_offset: int = 1,
|
||||
):
|
||||
console.log(
|
||||
"[test]TEST[/test]",
|
||||
*objects,
|
||||
sep=sep,
|
||||
end=end,
|
||||
style=style,
|
||||
justify=justify,
|
||||
emoji=emoji,
|
||||
markup=markup,
|
||||
highlight=highlight,
|
||||
log_locals=log_locals,
|
||||
_stack_offset=_stack_offset + 1,
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from test_limbo_cli import TestLimboShell
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from cli_tests import console
|
||||
|
||||
sqlite_exec = "./scripts/limbo-sqlite3"
|
||||
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
|
||||
@@ -81,7 +82,7 @@ def test_regexp():
|
||||
lambda res: "Parse error: no such function" in res,
|
||||
)
|
||||
limbo.run_test_fn(f".load {extension_path}", null)
|
||||
print(f"Extension {extension_path} loaded successfully.")
|
||||
console.info(f"Extension {extension_path} loaded successfully.")
|
||||
limbo.run_test_fn("SELECT regexp('a.c', 'abc');", true)
|
||||
limbo.run_test_fn("SELECT regexp('a.c', 'ac');", false)
|
||||
limbo.run_test_fn("SELECT regexp('[0-9]+', 'the year is 2021');", true)
|
||||
@@ -522,7 +523,7 @@ def test_vfs():
|
||||
lambda res: res == "50",
|
||||
"Tested large write to testfs",
|
||||
)
|
||||
print("Tested large write to testfs")
|
||||
console.info("Tested large write to testfs")
|
||||
limbo.quit()
|
||||
|
||||
|
||||
@@ -588,7 +589,7 @@ def cleanup():
|
||||
os.remove("testing/vfs.db-wal")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main():
|
||||
try:
|
||||
test_regexp()
|
||||
test_uuid()
|
||||
@@ -601,8 +602,12 @@ if __name__ == "__main__":
|
||||
test_kv()
|
||||
test_drop_virtual_table()
|
||||
except Exception as e:
|
||||
print(f"Test FAILED: {e}")
|
||||
console.error(f"Test FAILED: {e}")
|
||||
cleanup()
|
||||
exit(1)
|
||||
cleanup()
|
||||
print("All tests passed successfully.")
|
||||
console.info("All tests passed successfully.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from test_limbo_cli import TestLimboShell
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from cli_tests import console
|
||||
|
||||
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
|
||||
|
||||
@@ -96,15 +97,13 @@ def main():
|
||||
tests = memory_tests()
|
||||
# TODO see how to parallelize this loop with different subprocesses
|
||||
for test in tests:
|
||||
limbo = TestLimboShell()
|
||||
try:
|
||||
stub_memory_test(limbo, **test)
|
||||
with TestLimboShell("") as limbo:
|
||||
stub_memory_test(limbo, **test)
|
||||
except Exception as e:
|
||||
print(f"Test FAILED: {e}")
|
||||
limbo.quit()
|
||||
console.error(f"Test FAILED: {e}")
|
||||
exit(1)
|
||||
limbo.quit() # remove this line when `with` statement is supported for TestLimboShell
|
||||
print("All tests passed successfully.")
|
||||
console.info("All tests passed successfully.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,6 +5,7 @@ from time import sleep
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Callable, List, Optional
|
||||
from cli_tests import console
|
||||
|
||||
|
||||
PIPE_BUF = 4096
|
||||
@@ -74,12 +75,15 @@ class LimboShell:
|
||||
|
||||
def _handle_error(self) -> bool:
|
||||
while True:
|
||||
error_output = self.pipe.stderr.read(PIPE_BUF)
|
||||
if error_output == b"":
|
||||
return True
|
||||
print(error_output.decode(), end="")
|
||||
return False
|
||||
|
||||
ready, _, errors = select.select(
|
||||
[self.pipe.stderr], [], [self.pipe.stderr], 0
|
||||
)
|
||||
if not (ready + errors):
|
||||
break
|
||||
error_output = self.pipe.stderr.read(PIPE_BUF).decode()
|
||||
console.error(error_output, 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)
|
||||
@@ -128,7 +132,7 @@ INSERT INTO t VALUES (zeroblob(1024 - 1), zeroblob(1024 - 2), zeroblob(1024 - 3)
|
||||
self.shell.quit()
|
||||
|
||||
def run_test(self, name: str, sql: str, expected: str) -> None:
|
||||
print(f"Running test: {name}")
|
||||
console.test(f"Running test: {name}", _stack_offset=2)
|
||||
actual = self.shell.execute(sql)
|
||||
assert actual == expected, (
|
||||
f"Test failed: {name}\n"
|
||||
@@ -138,17 +142,26 @@ INSERT INTO t VALUES (zeroblob(1024 - 1), zeroblob(1024 - 2), zeroblob(1024 - 3)
|
||||
)
|
||||
|
||||
def debug_print(self, sql: str):
|
||||
print(f"debugging: {sql}")
|
||||
console.debug(f"debugging: {sql}", _stack_offset=2)
|
||||
actual = self.shell.execute(sql)
|
||||
print(f"OUTPUT:\n{repr(actual)}")
|
||||
console.debug(f"OUTPUT:\n{repr(actual)}", _stack_offset=2)
|
||||
|
||||
def run_test_fn(
|
||||
self, sql: str, validate: Callable[[str], bool], desc: str = ""
|
||||
) -> None:
|
||||
actual = self.shell.execute(sql)
|
||||
# Print the test that is executing before executing the sql command
|
||||
# Printing later confuses the user of the code what test has actually failed
|
||||
if desc:
|
||||
print(f"Testing: {desc}")
|
||||
console.test(f"Testing: {desc}", _stack_offset=2)
|
||||
actual = self.shell.execute(sql)
|
||||
assert validate(actual), f"Test failed\nSQL: {sql}\nActual:\n{repr(actual)}"
|
||||
|
||||
def execute_dot(self, dot_command: str) -> None:
|
||||
self.shell._write_to_pipe(dot_command)
|
||||
|
||||
# Enables the use of `with` syntax
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exception_type, exception_value, exception_traceback):
|
||||
self.quit()
|
||||
|
||||
138
testing/cli_tests/update.py
Normal file
138
testing/cli_tests/update.py
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from pydantic import BaseModel
|
||||
from cli_tests import console
|
||||
|
||||
|
||||
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
|
||||
|
||||
|
||||
class UpdateTest(BaseModel):
|
||||
name: str
|
||||
db_schema: str = "CREATE TABLE test (key INTEGER, t1 BLOB, t2 INTEGER, t3 TEXT);"
|
||||
blob_size: int = 1024
|
||||
vals: int = 1000
|
||||
updates: int = 1
|
||||
db_path: str = "testing/update.db"
|
||||
|
||||
def init_db(self):
|
||||
with TestLimboShell(
|
||||
init_commands="",
|
||||
exec_name="sqlite3",
|
||||
flags=f"{self.db_path}",
|
||||
) as sqlite:
|
||||
sqlite.execute_dot(f".open {self.db_path}")
|
||||
zero_blob = "0" * self.blob_size * 2
|
||||
t2_val = "1"
|
||||
t3_val = "2"
|
||||
stmt = [self.db_schema]
|
||||
stmt = stmt + [
|
||||
f"INSERT INTO test (key, t1, t2, t3) VALUES ({i} ,zeroblob({self.blob_size}), {t2_val}, {t3_val});"
|
||||
for i in range(self.vals)
|
||||
]
|
||||
stmt.append("SELECT count(*) FROM test;")
|
||||
|
||||
sqlite.run_test(
|
||||
"Init Update Db in Sqlite",
|
||||
"".join(stmt),
|
||||
f"{self.vals}",
|
||||
)
|
||||
|
||||
stmt = [
|
||||
f"SELECT hex(t1), t2, t3 FROM test LIMIT 1 OFFSET {i};"
|
||||
for i in range(self.vals)
|
||||
]
|
||||
|
||||
expected = [f"{zero_blob}|{t2_val}|{t3_val}" for _ in range(self.vals)]
|
||||
sqlite.run_test(
|
||||
"Check Values correctly inserted in Sqlite",
|
||||
"".join(stmt),
|
||||
"\n".join(expected),
|
||||
)
|
||||
|
||||
def run(self, limbo: TestLimboShell):
|
||||
limbo.execute_dot(f".open {self.db_path}")
|
||||
# TODO blobs are hard. Forget about blob updates for now
|
||||
# one_blob = ("0" * ((self.blob_size * 2) - 1)) + "1"
|
||||
# TODO For now update just on one row. To expand the tests in the future
|
||||
# use self.updates and do more than 1 update
|
||||
t2_update_val = "123"
|
||||
stmt = f"UPDATE test SET t2 = {t2_update_val} WHERE key = {0};"
|
||||
limbo.run_test(self.name, stmt, "")
|
||||
|
||||
def test_compat(self):
|
||||
console.info("Testing in SQLite\n")
|
||||
|
||||
with TestLimboShell(
|
||||
init_commands="",
|
||||
exec_name="sqlite3",
|
||||
flags=f"{self.db_path}",
|
||||
) as sqlite:
|
||||
sqlite.execute_dot(f".open {self.db_path}")
|
||||
zero_blob = "0" * self.blob_size * 2
|
||||
|
||||
t2_val = "1"
|
||||
t2_update_val = "123"
|
||||
t3_val = "2"
|
||||
stmt = []
|
||||
stmt.append("SELECT count(*) FROM test;")
|
||||
|
||||
sqlite.run_test(
|
||||
"Check all rows present in Sqlite",
|
||||
"".join(stmt),
|
||||
f"{self.vals}",
|
||||
)
|
||||
|
||||
stmt = [
|
||||
f"SELECT hex(t1), t2, t3 FROM test LIMIT 1 OFFSET {i};"
|
||||
for i in range(self.vals)
|
||||
]
|
||||
|
||||
expected = [
|
||||
f"{zero_blob}|{t2_val}|{t3_val}"
|
||||
if i != 0
|
||||
else f"{zero_blob}|{t2_update_val}|{t3_val}"
|
||||
for i in range(self.vals)
|
||||
]
|
||||
sqlite.run_test(
|
||||
"Check Values correctly updated in Sqlite",
|
||||
"".join(stmt),
|
||||
"\n".join(expected),
|
||||
)
|
||||
console.info()
|
||||
|
||||
|
||||
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():
|
||||
test = UpdateTest(name="Update 1 column", vals=1)
|
||||
console.info(test)
|
||||
|
||||
db_path = test.db_path
|
||||
try:
|
||||
test.init_db()
|
||||
# Use with syntax to automatically close shell on error
|
||||
with TestLimboShell("") as limbo:
|
||||
test.run(limbo)
|
||||
|
||||
test.test_compat()
|
||||
|
||||
except Exception as e:
|
||||
console.error(f"Test FAILED: {e}")
|
||||
cleanup(db_path)
|
||||
exit(1)
|
||||
# delete db after every compat test so we we have fresh db for next test
|
||||
cleanup(db_path)
|
||||
console.info("All tests passed successfully.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
157
testing/cli_tests/write.py
Executable file
157
testing/cli_tests/write.py
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from cli_tests.test_limbo_cli import TestLimboShell
|
||||
from pydantic import BaseModel
|
||||
from cli_tests import console
|
||||
|
||||
|
||||
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 = "testing/writes.db"
|
||||
|
||||
def run(self, limbo: TestLimboShell):
|
||||
zero_blob = "0" * self.blob_size * 2
|
||||
big_stmt = [self.db_schema]
|
||||
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 = "".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 TestLimboShell(
|
||||
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",
|
||||
)
|
||||
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)
|
||||
db_path = test.db_path
|
||||
try:
|
||||
# Use with syntax to automatically close shell on error
|
||||
with TestLimboShell("") as limbo:
|
||||
limbo.execute_dot(f".open {db_path}")
|
||||
test.run(limbo)
|
||||
|
||||
test.test_compat()
|
||||
|
||||
except Exception as e:
|
||||
console.error(f"Test FAILED: {e}")
|
||||
cleanup(db_path)
|
||||
exit(1)
|
||||
# delete db after every compat test so we we have fresh db for next test
|
||||
cleanup(db_path)
|
||||
console.info("All tests passed successfully.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
30
testing/pyproject.toml
Normal file
30
testing/pyproject.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[project]
|
||||
description = "Limbo Python Testing Project"
|
||||
name = "limbo_test"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"faker>=37.1.0",
|
||||
"pydantic>=2.11.1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
test-write = "cli_tests.write:main"
|
||||
test-shell = "cli_tests.cli_test_cases:main"
|
||||
test-extensions = "cli_tests.extensions:main"
|
||||
test-update = "cli_tests.update:main"
|
||||
test-memory = "cli_tests.memory:main"
|
||||
|
||||
[tool.uv]
|
||||
package = true
|
||||
|
||||
[build-system]
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["hatchling", "hatch-vcs"]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["cli_tests"]
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
172
uv.lock
generated
Normal file
172
uv.lock
generated
Normal file
@@ -0,0 +1,172 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[manifest]
|
||||
members = [
|
||||
"limbo",
|
||||
"limbo-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "faker"
|
||||
version = "37.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "tzdata" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/a6/b77f42021308ec8b134502343da882c0905d725a4d661c7adeaf7acaf515/faker-37.1.0.tar.gz", hash = "sha256:ad9dc66a3b84888b837ca729e85299a96b58fdaef0323ed0baace93c9614af06", size = 1875707 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/a1/8936bc8e79af80ca38288dd93ed44ed1f9d63beb25447a4c59e746e01f8d/faker-37.1.0-py3-none-any.whl", hash = "sha256:dc2f730be71cb770e9c715b13374d80dbcee879675121ab51f9683d262ae9a1c", size = 1918783 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "limbo"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "rich" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "rich", specifier = ">=14.0.0" }]
|
||||
|
||||
[[package]]
|
||||
name = "limbo-test"
|
||||
version = "0.1.0"
|
||||
source = { editable = "testing" }
|
||||
dependencies = [
|
||||
{ name = "faker" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "faker", specifier = ">=37.1.0" },
|
||||
{ name = "pydantic", specifier = ">=2.11.1" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/93/a3/698b87a4d4d303d7c5f62ea5fbf7a79cab236ccfbd0a17847b7f77f8163e/pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968", size = 782817 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/12/f9221a949f2419e2e23847303c002476c26fbcfd62dc7f3d25d0bec5ca99/pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8", size = 442648 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.33.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/05/91ce14dfd5a3a99555fce436318cc0fd1f08c4daa32b3248ad63669ea8b4/pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3", size = 434080 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/20/de2ad03ce8f5b3accf2196ea9b44f31b0cd16ac6e8cfc6b21976ed45ec35/pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555", size = 2032214 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/af/6817dfda9aac4958d8b516cbb94af507eb171c997ea66453d4d162ae8948/pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d", size = 1852338 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/f3/49193a312d9c49314f2b953fb55740b7c530710977cabe7183b8ef111b7f/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365", size = 1896913 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/e0/c746677825b2e29a2fa02122a8991c83cdd5b4c5f638f0664d4e35edd4b2/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da", size = 1986046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/ec/44914e7ff78cef16afb5e5273d480c136725acd73d894affdbe2a1bbaad5/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0", size = 2128097 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/f5/c6247d424d01f605ed2e3802f338691cae17137cee6484dce9f1ac0b872b/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885", size = 2681062 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/85/114a2113b126fdd7cf9a9443b1b1fe1b572e5bd259d50ba9d5d3e1927fa9/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9", size = 2007487 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/40/3c05ed28d225c7a9acd2b34c5c8010c279683a870219b97e9f164a5a8af0/pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181", size = 2121382 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/22/e70c086f41eebd323e6baa92cc906c3f38ddce7486007eb2bdb3b11c8f64/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d", size = 2072473 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/84/d1614dedd8fe5114f6a0e348bcd1535f97d76c038d6102f271433cd1361d/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3", size = 2249468 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/c0/787061eef44135e00fddb4b56b387a06c303bfd3884a6df9bea5cb730230/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b", size = 2254716 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/e2/27262eb04963201e89f9c280f1e10c493a7a37bc877e023f31aa72d2f911/pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585", size = 1916450 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/8d/25ff96f1e89b19e0b70b3cd607c9ea7ca27e1dcb810a9cd4255ed6abf869/pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606", size = 1956092 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/64/66a2efeff657b04323ffcd7b898cb0354d36dae3a561049e092134a83e9c/pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225", size = 1908367 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/54/295e38769133363d7ec4a5863a4d579f331728c71a6644ff1024ee529315/pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87", size = 1813331 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/9c/0c8ea02db8d682aa1ef48938abae833c1d69bdfa6e5ec13b21734b01ae70/pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b", size = 1986653 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/4f/3fb47d6cbc08c7e00f92300e64ba655428c05c56b8ab6723bd290bae6458/pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7", size = 1931234 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/3e/b00a62db91a83fff600de219b6ea9908e6918664899a2d85db222f4fbf19/typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b", size = 106520 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 },
|
||||
]
|
||||
Reference in New Issue
Block a user