PluginBench
Skill
Review
Audit score 70

testing

tursodatabase/turso

Write and run SQL, TCL, and Rust tests for Turso database development.

What is testing?

This skill teaches how to write different types of tests (SQL `.sqltest`, legacy TCL, and Rust integration tests) for the Turso database project, when to use each format, and how to run them. Use it when adding features or fixing bugs that require test coverage.

  • .sqltest format for SQL compatibility testing (preferred for new tests)
  • TCL `.test` format for legacy SQL compatibility (being phased out)
  • Rust integration and unit tests for regression and complex scenarios
  • Fuzz testing for edge case discovery
  • Test conversion utilities from TCL to `.sqltest` format
  • Assertion helpers using the `asserting` crate for readable test code

How to install testing

npx skills add https://github.com/tursodatabase/turso --skill testing
Prerequisites
  • Rust toolchain installed (for running `cargo test`)
  • Make installed (for running `make test` commands)
  • Access to the Turso repository
Claude Code
Cursor
Windsurf
Cline

How to use testing

  1. 1.Choose your test type: `.sqltest` for SQL compatibility (preferred), TCL for legacy tests, or Rust for integration tests
  2. 2.For `.sqltest`: write tests in `sqlite/conformance/sqlite-sqltests/` with `test` blocks and `expect` sections
  3. 3.For TCL: write tests in `testing/*.test` using `do_execsql_test_on_specific_db` syntax
  4. 4.For Rust: create test files in `tests/integration/` using `#[test]` functions and the `asserting` crate
  5. 5.Run tests with `make test` (full suite), `make test-single TEST=filename.test` (single TCL test), `cargo test` (Rust tests), or `cargo run -p sqltest -- run <path>` (SQL tests)
  6. 6.For converting TCL to `.sqltest`: use `cargo run -- convert <TCL_path> -o <out_dir>`, then verify and adjust expected results

Use cases

Good for
  • Writing SQL compatibility tests for new database features
  • Converting legacy TCL tests to the modern `.sqltest` format
  • Creating Rust integration tests for regression prevention
  • Testing complex database scenarios with multiple rows and conditions
  • Verifying query plans and index usage in tests
Who it's for
  • Database developers working on Turso
  • Contributors adding new SQL features
  • Engineers maintaining test suites
  • QA engineers writing regression tests

testing FAQ

Which test format should I use for new tests?

.sqltest is preferred for new tests. It allows the same test cases to run against multiple backends (CLI, Rust bindings, etc.) and is replacing the legacy TCL format.

How do I convert existing TCL tests to .sqltest?

Use the conversion command `cargo run -- convert <TCL_test_path> -o <out_dir>`. The tool is not always accurate, so you may need to manually fix warnings, unroll loops, and adjust expected results to match the new database output. Avoid changing SQL statements; only update expected results.

What assertion library should I use in Rust tests?

Use the `asserting` crate for anything beyond trivial assertions. It provides extensive, readable assertions. For database-specific assertions, use `crate::assertions` which adds `row!`, `column`, and query-plan helpers.

How do I run a single test or specific test suite?

Use `make test-single TEST=filename.test` for TCL tests, `cargo test` for Rust tests, `make -C sqlite/conformance run-cli` for SQL tests, or `cargo run -p sqltest -- run <path>` for specific SQL test files.

What should I do if my test fails without my change?

Every test must fail without the change and pass with it. If a test fails before your change, either fix the test or investigate why the existing code doesn't pass it before proceeding.

Full instructions (SKILL.md)

Source of truth, from tursodatabase/turso.


name: testing description: How to write tests, when to use each type of test, and how to run them. Contains information about conversion of .test to .sqltest, and how to write .sqltest and rust tests

Testing Guide

Test Types & When to Use

TypeLocationUse Case
.sqltestsqlite/conformance/sqlite-sqltests/SQL compatibility. Preferred for new tests
TCL .testtesting/Legacy SQL compat (being phased out)
Rust integrationtests/integration/Regression tests, complex scenarios
Fuzztests/fuzz/Complex features, edge case discovery

Note: TCL tests are being phased out in favor of the .sqltest suites in sqlite/conformance/. The .sqltest format allows the same test cases to run against multiple backends (CLI, Rust bindings, etc.).

Running Tests

# Main test suite (TCL compat, sqlite3 compat, Python wrappers)
make test

# Single TCL test
make test-single TEST=select.test

# SQL test runner
make -C sqlite/conformance run-cli

# OR
cargo run -p sqltest -- run <test-file or directory>

# Rust unit/integration tests (full workspace)
cargo test

Writing Tests

.sqltest (Preferred)

@database :default:

test example-addition {
    SELECT 1 + 1;
}
expect {
    2
}

test example-multiple-rows {
    SELECT id, name FROM users WHERE id < 3;
}
expect {
    1|alice
    2|bob
}

Location: sqlite/conformance/sqlite-sqltests/*.sqltest

You must start converting TCL tests with the convert command from the test runner (e.g cargo run -- convert <TCL_test_path> -o <out_dir>). It is not always accurate, but it will convert most of the tests. If some conversion emits a warning you will have to write by hand whatever is missing from it (e.g unroll a for each loop by hand). Then you need to verify the tests work by running them with make -C sqlite/conformance run-rust, and adjust their output if something was wrong with the conversion. Also, we use harcoded databases in TCL, but with .sqltest we generate the database with a different seed, so you will probably need to change the expected test result to match the new database query output. Avoid changing the SQL statements from the test, just change the expected result

TCL

do_execsql_test_on_specific_db {:memory:} test-name {
  SELECT 1 + 1;
} {2}

Location: testing/*.test

Rust Integration

// tests/integration/test_foo.rs
#[test]
fn test_something() {
    let conn = Connection::open_in_memory().unwrap();
    // ...
}

Assertions

Prefer the asserting crate for anything more than trivial assertions. The assertions it offers are extensive and make tests more readable. Some simple examples:

use asserting::prelude::*;

assert_that!(conn.execute("SELECT * FROM t1;")).is_err();
assert_that_code!(|| parse(bad_input)).panics_with_message("unexpected token");

assert_that!(&rows)
    .has_length(3)
    .any_satisfies(|r| r
    .name == "bob")
    .first_element_ref()
    .is_equal_to(&Row { id: 1, name: "alice".into() });

assert_that!(&header)
    .named("page 2 header")
    .satisfies_with_message("be a leaf table page", |h| h[0] == 0x0d);

// soft assertions: mark the test as failed but don't stop
verify_that!(&plan)
    .starts_with("SEARCH")
    .contains("USING INDEX")
    .soft_panic();

crate::assertions adds row! for result rows, plus column and query-plan assertions:

use crate::assertions::{AssertColumn, AssertQueryPlan, Cell, NULL};

assert_that!(limbo_exec_rows(&conn, "SELECT id, name FROM t ORDER BY id"))
    .is_equal_to(vec![row![1, "alice"], row![2, NULL]]);

assert_that!(limbo_exec_rows(&conn, "SELECT id FROM t WHERE id = 1"))
    .single_element()
    .is_equal_to(row![1]);

assert_that!(limbo_exec_rows(&conn, "SELECT id, name FROM t"))
    .column(1)
    .contains(Cell::from("alice"));

assert_that!(limbo_exec_rows(&conn, "EXPLAIN QUERY PLAN SELECT id FROM t WHERE name = 'a'"))
    .uses_index("idx_name")
    .searches_table("t")
    .has_table_access_order(["t"]);

Key Rules

  • Every functional change needs a test
  • Test must fail without change, pass with it
  • Prefer in-memory DBs: :memory: (sqltest) or {:memory:} (TCL)
  • Don't invent new test formats. Follow existing patterns
  • Write tests first when possible

Test Database Schema

testing/system/testing.db has users and products tables. See docs/testing.md for schema.

Logging During Tests

RUST_LOG=none,turso_core=trace make test

Output: testing/system/test.log. Warning: very verbose.