ChaosSQL

SDK

Go Developer Testing SDK (chaostest)

Programmatic API in pkg/chaostest for embedding deterministic isolation fuzzing directly into standard go test suites.

Introduction to the SDK (pkg/chaostest)

The ChaosSQL Go Developer Testing SDK (pkg/chaostest) is built for software engineers who want to embed deterministic concurrency and invariant fuzzing directly into standard Go test suites (*testing.T). It allows developers to validate database repositories, persistence layers, and domain services natively via go test ./..., offering full compatibility with go test -race without requiring external binaries or CLI orchestration.

Fluent Builder API: chaostest.New(t) & NewHarness(t)

The package provides a fluent builder pattern (chaostest.New(t) or chaostest.NewHarness(t)) with method chaining to configure concurrency test harnesses intuitively:

Method Parameters Technical Description
New(t testing.TB)
or NewHarness(t)
testing.TB Initializes a new test harness bound to the lifecycle of the current testing context.
WithDriver(driver) drivers.DatabaseDriver Sets the database driver instance. If omitted, automatically provisions an isolated in-memory SQLite database.
WithSchema(sql) string Defines DDL SQL statements for table and index creation.
WithSeed(sql) string Defines DML SQL statements for initial dataset seeding prior to fuzzing.
WithInvariant(name, query, expr) string, string, string Registers a global consistency rule that must hold true upon completion.
WithJitter(minMs, maxMs) int, int Sets the random millisecond jitter injected between transactional steps (default: 1ms to 5ms).
AddOperation(name, steps...) string, ...string Registers a concurrent transaction. Supports variable capture (-> var) and expressions ({var - 100}).
AddOperationWithParams(name, params, steps...) string, map[string]string, ...string Registers an operation with dynamically randomized per-transaction parameters.

Test Assertions: AssertNoAnomalies vs AssertAnomalyDetected

1. AssertNoAnomalies(ctx, workers, iterations, seed)

Positive assertion: verifies that the service or repository implementation shows no invariant violation or anomaly under the configured seed and schedule. It is evidence for that schedule, not a proof for every schedule. If an invariant is violated or an anomaly is detected, the method invokes t.Fatalf, logging:

  • Violated invariant name, expected condition, and actual query result.
  • Formal Adya anomaly classification (e.g., P4_LOST_UPDATE).
  • Delta-Debugging (ddmin) compression statistics: shrinking N operations down to the minimal counterexample.
  • Copy-pasteable Mermaid sequence diagram illustrating the conflicting schedule.

2. AssertAnomalyDetected(ctx, workers, iterations, seed, expectedAnomaly...)

Negative assertion: used in regression test suites to empirically prove that unmitigated code or insufficient transaction isolation levels trigger the expected anomaly. Use it to keep a regression test honest: if a refactor stops reproducing the bug, the test fails.

Complete Example: Testing and Mitigating Lost Update

The example below demonstrates how to write a native Go unit test proving the effectiveness of an atomic update mitigation against Lost Update (P4):

package bank_test

import (
    "context"
    "testing"

    "github.com/bregaldahq/chaossql/internal/drivers"
    "github.com/bregaldahq/chaossql/pkg/chaostest"
)

// TestAccountWithdrawal_AtomicMitigation verifies that concurrent withdrawals
// utilizing an atomic update prevent the Lost Update anomaly (P4).
func TestAccountWithdrawal_AtomicMitigation(t *testing.T) {
    ctx := context.Background()

    // 1. Instantiate database driver (pure-Go in-memory SQLite)
    driver := drivers.NewSQLiteDriver("file:bank_test?mode=memory&cache=shared")
    defer driver.Close()

    // 2. Build concurrency fuzzer harness using the fluent SDK
    tester := chaostest.NewHarness(t).
        WithDriver(driver).
        WithSchema(`
            CREATE TABLE accounts (
                id INT PRIMARY KEY,
                balance INT NOT NULL
            );
            CREATE TABLE ledger (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                amount INT NOT NULL
            );
        `).
        WithSeed("INSERT INTO accounts (id, balance) VALUES (1, 1000);").
        WithInvariant(
            "ledger_balance_consistency",
            "SELECT (SELECT balance FROM accounts WHERE id = 1) AS actual, " +
            "(1000 - COALESCE(SUM(amount), 0)) AS expected FROM ledger;",
            "actual == expected",
        ).
        WithJitter(1, 4).
        // Mitigated transaction: atomic UPDATE with balance guard predicate
        AddOperation("atomic_withdrawal",
            "UPDATE accounts SET balance = balance - 100 WHERE id = 1 AND balance >= 100;",
            "INSERT INTO ledger (amount) VALUES (100);",
        )

    // 3. Execute fuzzer across 8 concurrent workers and 40 transactions
    // Must pass with 100% success and zero invariant violations
    tester.AssertNoAnomalies(ctx, 8, 40, 42)
}

Automated Synthesis of repro_test.go via ddmin

When a test executed via the SDK fails under AssertNoAnomalies, the Delta-Debugging (ddmin) engine isolates the 2-operation minimal counterexample and synthesizes ready-to-run Go source code for an independent repro_test.go directly into the test logs.

Engineers can copy this generated test into a local file and attach Delve debugger breakpoints, reproducing the race condition without running the fuzzer.

Strict Determinism in CI: When configuring regression tests in Go, always supply a constant static seed (e.g., seed: 42). The same seed produces the same schedule on every CI runner, so a failure reproduces instead of flaking.