- O SQLite mantém alta confiabilidade e robustez por meio de um sistema de testes automatizados extremamente rigoroso, com 590 vezes mais código de teste do que código-fonte
- Quatro harnesses de teste independentes (TCL, TH3, SQL Logic Test, dbsqlfuzz) verificam a biblioteca principal e executam centenas de milhões de testes
- Testes de condições anormais (OOM, erros de I/O, simulação de falhas) e fuzz testing confirmam que ele opera de forma estável mesmo com entradas inválidas e falhas do sistema
- Mantém procedimentos de verificação em múltiplas camadas, como 100% de cobertura de branches e MC/DC, detecção de vazamento de recursos, Valgrind, análise estática e checklists
- Graças a esse sistema de testes metódico, o SQLite é avaliado como um banco de dados open source com confiabilidade e qualidade de nível comercial
1. Visão geral
- A confiabilidade e robustez do SQLite vêm de um processo de testes minucioso
- Na versão 3.42.0, o SQLite é composto por cerca de 155,8 KSLOC de código C e 92053,1 KSLOC de código de teste
- O sistema de testes inclui 4 harnesses independentes, 100% de cobertura de branches e milhões de casos de teste
- Inclui OOM, erros de I/O, falhas, fuzzing, valores de fronteira, regressão, arquivos de banco de dados anormais, testes com otimizações desativadas e muitos outros itens
2. Harnesses de teste
- TCL Tests
- Conjunto público de testes usado principalmente durante o desenvolvimento do SQLite
- Composto por 27,2 KSLOC de código C e 1390 arquivos de script (23,2 MB)
- Cerca de 50 mil casos de teste e, com parametrização, centenas de milhões de execuções no total
- TH3
- Conjunto comercial de testes em C que atinge 100% de cobertura de branches e MC/DC
- Também funciona em ambientes embarcados e inclui 1055,4 KSLOC e cerca de 50 mil casos
- Nos testes completos de cobertura, executa cerca de 2,4 milhões de testes, e antes de cada release são feitos 248 milhões de soak tests
- SQL Logic Test (SLT)
- Compara os resultados do SQLite com PostgreSQL, MySQL, SQL Server e Oracle 10g
- Composto por 7,2 milhões de queries e 1,12 GB de dados
- dbsqlfuzz
- Fuzzer baseado em libFuzzer que altera ao mesmo tempo SQL e arquivos de banco de dados
- Executa cerca de 1 bilhão de testes de mutação por dia, validando a robustez contra entradas maliciosas
- Ferramentas adicionais
- speedtest1.c, mptester.c, threadtest3.c, fuzzershell.c, jfuzz etc.
- Todos os testes precisam passar em múltiplas plataformas e configurações de compilação para que uma release seja possível
3. Testes de condições anormais
- Teste de OOM
- Simula falhas de
malloc() para verificar se a recuperação ocorre corretamente em situações de falta de memória
- É executado repetidamente, aumentando o contador do ponto de falha
- Teste de erro de I/O
- Usa um sistema de arquivos virtual (VFS) para simular erros de disco
- Após o erro, verifica corrupção de dados com
PRAGMA integrity_check
- Teste de falha
- Simula corte de energia e falhas do sistema operacional
- O harness TCL usa processos-filho, e o TH3 usa VFS baseado em memória
- Verifica se a transação é totalmente revertida ou totalmente concluída
- Teste de falhas combinadas
- Também valida cenários em que, após uma falha, ocorrem em sequência OOM ou erro de I/O
4. Fuzz testing
- SQL Fuzz
- Gera SQL sintaticamente válido, mas anômalo, para validar a resposta do SQLite
- American Fuzzy Lop (AFL)
- Fuzzer guiado por perfil, introduzido em 2014, que explora novos caminhos de controle
- Encontrou diversos casos de falha de
assert, crashes e resultados incorretos no SQLite
- Google OSS Fuzz
- Desde 2016, executa fuzzing automático na infraestrutura do Google
- Detecta problemas intermitentes em novos commits
- dbsqlfuzz / jfuzz
- Introduzidos como fuzzers internos a partir de 2018, alteram simultaneamente SQL e arquivos de banco
- Executam mais de 500 milhões de testes por dia, praticamente eliminando relatórios de bugs de fuzzers externos
- Desde 2024, o jfuzz adicionou validação de entradas JSONB
- Fuzzers de terceiros e fuzzcheck
- Pesquisadores externos (por exemplo, Manuel Rigger) encontraram vários casos de cálculo incorreto de resultados
- O utilitário fuzzcheck revalida milhares de casos “interessantes” entre os casos de fuzz do passado
- A relação tensa entre MC/DC e fuzz testing
- MC/DC minimiza código defensivo, enquanto fuzzing exige código defensivo
- O SQLite combina as duas abordagens para manter código robusto tanto para entradas normais quanto maliciosas
5. Testes de regressão
- Bugs reportados, depois de corrigidos, são sempre adicionados como novos casos de teste
- O objetivo é evitar a reincidência de bugs antigos
6. Detecção automática de vazamento de recursos
- Os harnesses TCL e TH3 monitoram automaticamente vazamentos de memória, arquivos, threads e mutexes
- Não pode haver vazamento de memória mesmo após OOM ou erros de I/O
7. Cobertura de testes
- O núcleo do SQLite atinge 100% de cobertura de branches com base no TH3
- Extensões como FTS3 e RTree ficam de fora
- Cobertura de instruções vs cobertura de branches
- A cobertura de branches é mais rigorosa que a cobertura de instruções e valida ambos os lados de cada condição
- Cobertura de código defensivo
- As macros ALWAYS() e NEVER() explicitam condições defensivas
- Os testes são repetidos em três formas de definição para verificar consistência
- Testes de valor de fronteira e vetores booleanos
- A macro testcase() valida tanto o resultado positivo quanto o negativo das condições
- São usados 1184
testcase()
- Alcance de MC/DC
- A macro
testcase() verifica o efeito independente de cada condição
- Medição com base em gcov
- A cobertura é medida com as opções
-fprofile-arcs -ftest-coverage
- A comparação dos resultados detecta bugs de compilador ou comportamento indefinido
- Mutation Testing
- Altera instruções de branch para verificar se os testes detectam isso
- Branches de otimização (
/*OPTIMIZATION-IF-TRUE*/) são tratados como exceção
- Experiência com cobertura completa
- Graças aos testes de todos os branches, efeitos colaterais ao alterar o código são minimizados
- O custo de manutenção é alto, mas se justifica por ser uma biblioteca de infraestrutura amplamente distribuída
8. Análise dinâmica
- Assert()
- 6754 instruções
assert verificam pré-condições, pós-condições e invariantes de loop
- Só ficam ativas em builds com
SQLITE_DEBUG
- Valgrind
- Detecta erros de memória, stack overflow e acesso a memória não inicializada
- Antes de cada release, os testes veryquick e TH3 são executados com Valgrind
- Memsys2
- Em builds com
SQLITE_MEMDEBUG, wrappers são inseridos para monitorar erros de memória
- Permite validação repetida mais rápida que o Valgrind
- Mutex Asserts
- Valida sincronização multithread com
sqlite3_mutex_held() e afins
- Journal Tests
- Verifica se o rollback journal é gravado antes do banco de dados, garantindo a atomicidade da transação
- Undefined Behavior Checks
- Detecta comportamento indefinido com
-ftrapv, -fsanitize=undefined, /RTC1 etc.
- É repetido em 32/64 bits, diferentes endiannesses e várias arquiteturas de CPU
9. Testes com otimizações desativadas
sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS) permite desativar otimizações
- O resultado deve ser o mesmo, com ou sem otimizações
- Alguns testes voltados à medição de desempenho são exceções
10. Checklist
- Antes de cada release, é validado um checklist manual de cerca de 200 itens
- Alguns levam segundos, outros levam horas
- Quando um problema é encontrado, novos itens são adicionados imediatamente, promovendo melhoria contínua
11. Análise estática
- Compila sem warnings em GCC, Clang e MSVC
- Também não há warnings válidos no Clang Static Analyzer
- A análise estática tem efeito limitado na detecção de bugs reais
12. Resumo
- Mesmo sendo open source, o SQLite mantém qualidade de nível comercial e baixa taxa de defeitos
- Testes rigorosos e design de código são os fatores centrais
- Todas as releases passam por esses procedimentos e são entregues como um motor de banco de dados confiável até mesmo em ambientes mission critical
4 comentários
Artigo relacionado: A história desconhecida do SQLite
Este é um texto que resume uma entrevista com Richard Hipp, desenvolvedor do SQLite.
Os desenvolvedores do SQLite dizem que conheceram o Do-178 quando trabalhavam na Rockwell Collins e passaram a seguir esse processo. Uma das exigências é atingir 100% de MC/DC.
O Do-178 é realmente um guia muito útil, então recomendo que todo desenvolvedor o leia.
É isto? https://alm.parasoft.com/hubfs/…
O link que você compartilhou parece ser um material de treinamento do DO-178.
Você pode ver o documento original neste link.
https://studylib.net/doc/27132454/rtca-do-178b
Comentários no Hacker News
Há mais de 10 anos, o mantenedor do SQLite fez uma apresentação na OSCON sobre práticas de teste
O que mais impressionou foi especialmente o poder das checklists. Exatamente aquela ferramenta usada por pilotos antes de cada voo
Ele também mencionou um caso dos Médicos Sem Fronteiras (Doctors Without Borders), em que a equipe médica não sabia nem os nomes uns dos outros e falava idiomas diferentes, o que levava a resultados piores nas cirurgias
A solução foi simples — criar uma checklist pré-operatória para que cada pessoa dissesse seu nome e sua função. Esse pequeno ritual aumentou a taxa de sobrevivência por meio de melhor comunicação, não por técnica
Material relacionado: exemplo de checklist do SQLite
Precisamos discutir mais a diferença entre uma checklist boa e uma ruim. Parece algo simples, como uma fórmula elegante da matemática, mas difícil de encontrar
Em especial, já li o documento FM22-100 do Exército dos EUA várias vezes, e ele é surpreendentemente moderno e inspirador
Ver documento FM22-100
Link do livro
Além de testes e CI, sigo uma checklist de deploy em Markdown. Nem guardo o resultado, mas executo passo a passo
Não sei por que os outros não fazem algo tão simples
Se houver uma página oficial sobre o caso do MSF, eu gostaria muito de ver. Não consegui encontrar no Google
Aqui está uma coleção de discussões antigas sobre testes no SQLite
Lista de threads do HN de 2009 a 2024
Parece que os reposts se repetem em intervalos de um ano
Inveja e admiração pelo processo de polir um software como o SQLite até esse nível de perfeição
É uma obra com verdadeiro espírito artesanal
Com o tempo, o padrão de qualidade sobe e você passa a receber recompensas maiores pelo mesmo esforço
Ninguém desgosta de alguém que deixa a parte em que mexeu um pouco mais limpa
O SQLite é realmente um software excelente. Também gosto do site oficial, que é centrado em informação em vez de marketing
Mas é curioso como, recentemente, as páginas do site oficial vêm aparecendo uma a uma no HN
Seria interessante juntar esses links
É interessante que o SQLite seja software aberto e ainda assim use testes privados
Só agora percebi que um projeto open source pode ter testes fechados
Parece que esse modelo pode até virar um novo modelo de negócios parecido com open-core
Exemplo: licença do railgunlabs/unicorn
A cobertura de branches de 100% do SQLite é tão impressionante quanto o próprio projeto
O mais incrível é conseguir manter isso continuamente
É interessante que os testes sejam privados. Agora que a programação com base em LLM está avançando, estamos entrando numa era em que os testes estão se tornando mais importantes do que a implementação
Ao ver recentemente o caso em que simonw converteu quase automaticamente o motor justHTML de Python para JS, lembrei da estratégia de testes do SQLite
Recentemente discuti com um LLM a compatibilidade entre SQLite e DuckDB e cheguei à conclusão de que, em termos de tratamento de concorrência, o SQLite é melhor
Fiquei surpreso com a pouca menção a testes de regressão de desempenho (performance regression) na documentação de testes do SQLite
Correção é importante, mas uma queda de desempenho em certos caminhos de consulta pode ser fatal
Fico curioso se existe algum projeto que tenha esse objetivo como missão principal
Ao ver a estabilidade do SQLite, quis saber mais sobre como foram feitos os testes de anomalias
Mas o texto quase não fala disso. Mesmo assim, o SQLite continua sendo um dos softwares mais robustos usados em todo tipo de dispositivo