testing/runner/docs/architecture.md
This document describes the architecture of the test runner, including the backend trait system and result comparison.
The test runner is designed with extensibility in mind. The core components are:
.sqltest files into an AST (covered in dsl-spec.md)┌─────────────────────────────────────────────────────────────┐
│ Test Runner │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌─────────────┐ │
│ │ Parser │──│ Executor │──│ Comparison│──│ Output │ │
│ └─────────┘ └────┬─────┘ └───────────┘ └─────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ Backend │ │
│ └─────┬─────┘ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────┐ ┌────────┐ ┌────────┐ │
│ │ CLI │ │ Python │ │ Go │ ... │
│ └───────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────────┘
SqlBackend TraitThe core abstraction for any SQL execution target:
#[async_trait]
pub trait SqlBackend: Send + Sync {
/// Name of this backend (for filtering and display)
fn name(&self) -> &str;
/// Create a new isolated database instance
async fn create_database(&self, config: &DatabaseConfig) -> Result<Box<dyn DatabaseInstance>>;
}
DatabaseInstance TraitRepresents a single database connection:
#[async_trait]
pub trait DatabaseInstance: Send + Sync {
/// Execute SQL and return results
async fn execute(&mut self, sql: &str) -> Result<QueryResult>;
/// Close and cleanup the database
async fn close(self: Box<Self>) -> Result<()>;
}
QueryResult StructStandardized result format:
pub struct QueryResult {
/// Rows returned, each row is a vector of string-formatted columns
pub rows: Vec<Vec<String>>,
/// Error message if the query failed
pub error: Option<String>,
}
The runner supports multiple ways to compare expected vs actual results:
Row-by-row, column-by-column comparison:
pub fn compare_exact(actual: &[Vec<String>], expected: &[String]) -> ComparisonResult
Regex matching against the output:
pub fn compare_pattern(actual: &[Vec<String>], pattern: &str) -> ComparisonResult
Set comparison (order-independent):
pub fn compare_unordered(actual: &[Vec<String>], expected: &[String]) -> ComparisonResult
Verify an error occurred:
pub fn compare_error(actual_error: Option<&str>, expected_pattern: Option<&str>) -> ComparisonResult
None, any error is acceptedSome, the error message must contain the patternComparisonResultpub enum ComparisonResult {
/// Results match
Match,
/// Results don't match
Mismatch { reason: String },
}
TestResultpub struct TestResult {
/// Name of the test
pub name: String,
/// Outcome of the test
pub outcome: TestOutcome,
/// Duration of the test
pub duration: Duration,
}
pub enum TestOutcome {
Passed,
Failed { reason: String },
Skipped { reason: String },
Error { message: String },
}
For each test case:
Setup Phase
Execution Phase
Comparison Phase
Cleanup Phase
┌─────────────────────────────────────────────┐
│ For each test: │
│ ┌─────────┐ │
│ │ Create │ │
│ │ DB │ │
│ └────┬────┘ │
│ ▼ │
│ ┌─────────┐ │
│ │ Run │ (for each @setup decorator) │
│ │ Setups │ │
│ └────┬────┘ │
│ ▼ │
│ ┌─────────┐ │
│ │Execute │ │
│ │ Test │ │
│ └────┬────┘ │
│ ▼ │
│ ┌─────────┐ │
│ │Compare │ │
│ │ Result │ │
│ └────┬────┘ │
│ ▼ │
│ ┌─────────┐ │
│ │ Close │ │
│ │ DB │ │
│ └─────────┘ │
└─────────────────────────────────────────────┘
Errors are categorized as:
All errors include context (file, test name, line number) for debugging.
src/backends/mod.rs)The backend module defines the core traits:
#[async_trait]
pub trait SqlBackend: Send + Sync {
fn name(&self) -> &str;
async fn create_database(&self, config: &DatabaseConfig)
-> Result<Box<dyn DatabaseInstance>, BackendError>;
}
#[async_trait]
pub trait DatabaseInstance: Send + Sync {
/// Execute setup SQL (may buffer for memory databases)
async fn execute_setup(&mut self, sql: &str) -> Result<(), BackendError>;
/// Execute SQL and return results (includes buffered setup SQL)
async fn execute(&mut self, sql: &str) -> Result<QueryResult, BackendError>;
async fn close(self: Box<Self>) -> Result<(), BackendError>;
}
QueryResult provides a uniform result format:
rows: Vector of rows, each row is a vector of string-formatted column valueserror: Optional error message if the query failedBackendError covers all backend failure modes:
CreateDatabase: Failed to create/open databaseExecute: Failed to execute SQLClose: Failed to close databaseNotAvailable: Backend not installed/configuredTimeout: Query execution timed outsrc/comparison/)The comparison module uses the similar crate for generating diffs:
Exact Comparison (exact.rs):
Pattern Comparison (pattern.rs):
regex crateUnordered Comparison (unordered.rs):
Error Comparison (in mod.rs):
None, any error is acceptedSome, error message must contain the patternOn mismatch, exact comparison produces output like:
--- expected
+++ actual
1|Alice
-2|Bob
+2|Charlie
3|Dave
This makes it easy to identify exactly what changed.