SDK
Go Developer Testing SDK (pkg/chaostest)
Como integrar o ChaosSQL diretamente em suítes de testes unitários do Go (*testing.T): referência completa da API fluente do builder chaostest.New(), asserções de invariantes e síntese automática de repro_test.go.
Introdução ao SDK (pkg/chaostest)
O ChaosSQL Go Developer Testing SDK (pkg/chaostest) foi desenvolvido para engenheiros de software que desejam incorporar testes de concorrência e invariantes diretamente nas suas suítes de testes unitários em Go (*testing.T). Ele permite testar camadas de persistência, repositórios e serviços de domínio diretamente via comando nativo go test ./..., com suporte completo a go test -race e sem a necessidade de instalar ou gerenciar binários externos.
API Fluente do Builder: chaostest.New(t)
O pacote expõe um padrão builder fluente com encadeamento de métodos para configurar o cenário de concorrência de forma intuitiva:
| Método | Parâmetros | Descrição Técnica |
|---|---|---|
New(t testing.TB) |
testing.TB |
Inicializa uma nova instância de teste atrelada ao ciclo de vida do teste atual. |
WithDriver(driver) |
drivers.DatabaseDriver |
Define a instância do driver. Se omitido, provisiona automaticamente um SQLite in-memory isolado. |
WithSchema(sql) |
string |
Define os comandos SQL DDL para criação de tabelas e índices. |
WithSeed(sql) |
string |
Define os comandos SQL DML para carga inicial de dados antes do teste. |
WithInvariant(name, query, expr) |
string, string, string |
Registra uma regra de negócio que deve ser preservada ao final do teste. |
WithJitter(minMs, maxMs) |
int, int |
Configura a faixa de jitter em milissegundos injetada entre os passos de cada transação (padrão: 1ms a 5ms). |
AddOperation(name, steps...) |
string, ...string |
Registra uma transação concorrente. Suporta sintaxe de captura -> var e expressões {var - 100}. |
AddOperationWithParams(name, params, steps...) |
string, map[string]string, ...string |
Registra uma operação com parâmetros gerados dinamicamente por transação. |
Asserções de Teste: AssertNoAnomalies vs AssertAnomalyDetected
1. AssertNoAnomalies(ctx, workers, iterations, seed)
Asserção positiva: valida que a implementação do serviço ou repositório não apresenta violação de invariante nem anomalia sob a semente e o agendamento configurados. É evidência para esse agendamento, não prova para todos. Se uma anomalia for detectada ou uma invariante for violada, o método invoca t.Fatalf reportando:
- Nome e valor da invariante que falhou.
- Classificação formal da anomalia no catálogo Adya (ex:
P4_LOST_UPDATE). - Métricas do Delta-Debugging (ddmin): redução de N operações originais para o caso minimal.
- Diagrama de Sequência Mermaid formatado pronto para copiar.
2. AssertAnomalyDetected(ctx, workers, iterations, seed, expectedAnomaly...)
Asserção negativa: utilizada em testes de regressão para provar empiricamente que um código vulnerável ou um nível de isolamento insuficiente realmente dispara a anomalia prevista. Use para manter o teste de regressão honesto: se um refactor deixar de reproduzir o bug, o teste falha.
Exemplo Completo: Testando e Mitigando Lost Update
O exemplo abaixo demonstra como escrever um teste unitário em Go que comprova a eficácia de uma mitigação concorrente (migração de Read-Modify-Write vulnerável para um UPDATE atômico com guarda):
package bank_test
import (
"context"
"testing"
"github.com/bregaldahq/chaossql/internal/drivers"
"github.com/bregaldahq/chaossql/pkg/chaostest"
)
// TestAccountWithdrawal_AtomicMitigation valida que saques concorrentes
// usando update atômico impedem a ocorrência de Lost Update (P4).
func TestAccountWithdrawal_AtomicMitigation(t *testing.T) {
ctx := context.Background()
// 1. Instanciar driver de banco de dados (SQLite puro em memória)
driver := drivers.NewSQLiteDriver("file:bank_test?mode=memory&cache=shared")
defer driver.Close()
// 2. Construir o fuzzer de concorrência com o SDK fluente
tester := chaostest.New(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).
// Transação mitigada: UPDATE atômico com verificação de saldo suficiente
AddOperation("atomic_withdrawal",
"UPDATE accounts SET balance = balance - 100 WHERE id = 1 AND balance >= 100;",
"INSERT INTO ledger (amount) VALUES (100);",
)
// 3. Executar o fuzzer com 8 workers simultâneos e 40 transações
// Deve passar com 100% de sucesso sem violação de invariantes
tester.AssertNoAnomalies(ctx, 8, 40, 42)
}
Síntese Automática de repro_test.go pelo ddmin
Quando um teste executado via SDK falha com AssertNoAnomalies, o algoritmo Delta-Debugging (ddmin) não apenas isola o subconjunto de 2 operações mínimas, mas também sintetiza no log do teste o código fonte completo em Go de um arquivo repro_test.go independente.
Esse código gerado pode ser colado em um arquivo local para depuração com breakpoints no Delve, permitindo que a equipe reproduza o defeito sem executar o fuzzer.
seed: 42). A mesma semente produz o mesmo agendamento em todo runner de CI, então uma falha se repete em vez de oscilar (flakiness).