24 points par GN⁺ 2025-12-19 | Aucun commentaire pour le moment. | Partager sur WhatsApp
  • SQLite maintient un haut niveau de fiabilité et de robustesse grâce à un système de tests automatisés extrêmement rigoureux, avec 590 fois plus de code de test que de code produit
  • Quatre harness de test indépendants (TCL, TH3, SQL Logic Test, dbsqlfuzz) valident la bibliothèque principale et exécutent des centaines de millions de tests
  • Des tests de situations anormales (OOM, erreurs d’E/S, simulation de crash) et des tests de fuzzing permettent de vérifier un fonctionnement stable même face à des entrées invalides et à des défaillances système
  • Des procédures de validation multicouches sont maintenues, notamment 100 % de couverture de branches et MC/DC, détection des fuites de ressources, Valgrind, analyse statique et checklist
  • Grâce à cette approche de test systématique, SQLite est considéré comme une base de données open source offrant une fiabilité et une qualité de niveau base de données commerciale

1. Vue d’ensemble

  • La fiabilité et la robustesse de SQLite proviennent d’un processus de test minutieux
    • À la version 3.42.0, SQLite se compose d’environ 155.8 KSLOC de code C et de 92053.1 KSLOC de code de test
  • Le système de test comprend 4 harness indépendants, une couverture de branches à 100 % et des millions de cas de test
    • Il inclut notamment des tests OOM, d’erreurs d’E/S, de crash, de fuzzing, de valeurs limites, de régression, de fichiers DB anormaux et de désactivation des optimisations

2. Harness de test

  • TCL Tests
    • Ensemble de tests public principalement utilisé pendant le développement de SQLite
    • Composé de 27.2 KSLOC de code C et de 1390 fichiers de scripts (23.2MB)
    • Environ 50 000 cas de test, et plusieurs millions d’exécutions lors d’un run complet grâce à la paramétrisation
  • TH3
    • Ensemble de tests commercial en C atteignant 100 % de couverture de branches et MC/DC
    • Fonctionne aussi en environnement embarqué, avec 1055.4 KSLOC et environ 50 000 cas
    • Environ 2,4 millions d’exécutions pour un test complet de couverture, et 248 millions de soak tests avant publication
  • SQL Logic Test (SLT)
    • Compare les résultats de SQLite avec PostgreSQL, MySQL, SQL Server et Oracle 10g
    • Composé de 7,2 millions de requêtes et de 1.12GB de données
  • dbsqlfuzz
    • Fuzzer basé sur libFuzzer qui fait muter simultanément le SQL et les fichiers de base de données
    • Effectue environ 1 milliard de tests de mutation par jour pour vérifier la robustesse face à des entrées malveillantes
  • Outils supplémentaires
    • speedtest1.c, mptester.c, threadtest3.c, fuzzershell.c, jfuzz, etc.
    • Tous les tests doivent passer sur plusieurs plateformes et configurations de compilation avant qu’une release soit possible

3. Tests de situations anormales

  • Tests OOM
    • Simulent des échecs de malloc() pour vérifier la capacité de récupération en cas de manque de mémoire
    • Répétés en incrémentant le compteur du point d’échec
  • Tests d’erreurs d’E/S
    • Utilisent un système de fichiers virtuel (VFS) pour simuler des erreurs disque
    • Vérifient l’intégrité des données après erreur avec PRAGMA integrity_check
  • Tests de crash
    • Simulent des coupures d’alimentation et des crashs du système d’exploitation
    • Le harness TCL s’appuie sur des processus enfants, TH3 sur un VFS en mémoire
    • Vérifient qu’une transaction est soit totalement annulée, soit totalement terminée
  • Tests de pannes combinées
    • Vérifient aussi les cas où un OOM ou une erreur d’E/S survient en chaîne après un crash

4. Tests de fuzzing

  • SQL Fuzz
    • Génère du SQL syntaxiquement valide mais anormal pour vérifier la réaction de SQLite
  • American Fuzzy Lop (AFL)
    • Fuzzer guidé par profil introduit en 2014 pour explorer de nouveaux chemins de contrôle
    • A découvert de nombreux échecs d’assert, crashs et résultats erronés dans SQLite
  • Google OSS Fuzz
    • Fuzzing automatisé exécuté sur l’infrastructure de Google depuis 2016
    • Détecte des problèmes intermittents sur les nouveaux commits
  • dbsqlfuzz / jfuzz
    • Introduits comme fuzzers internes à partir de 2018, avec mutation simultanée du SQL et des fichiers DB
    • Plus de 500 millions de tests par jour, ce qui a presque fait disparaître les rapports de bugs issus de fuzzers externes
    • Depuis 2024, jfuzz ajoute la validation des entrées JSONB
  • Fuzzers tiers et fuzzcheck
    • Des chercheurs externes (par ex. Manuel Rigger) ont découvert de nombreux cas de calculs erronés
    • L’utilitaire fuzzcheck revalide plusieurs milliers d’anciens cas de fuzz jugés « intéressants »
  • La tension entre MC/DC et fuzz testing
    • MC/DC pousse à minimiser le code défensif, tandis que le fuzzing nécessite ce code défensif
    • SQLite combine les deux approches pour conserver un code robuste face aux entrées normales comme malveillantes

5. Tests de régression

  • Tout bug signalé est obligatoirement ajouté sous forme de nouveau cas de test après correction
    • L’objectif est d’éviter la réapparition d’anciens bugs

6. Détection automatique des fuites de ressources

  • Les harness TCL et TH3 surveillent automatiquement les fuites de mémoire, de fichiers, de threads et de mutex
    • Même après un OOM ou une erreur d’E/S, aucune fuite mémoire ne doit subsister

7. Couverture de test

  • Le cœur de SQLite atteint 100 % de couverture de branches selon TH3
    • Hors extensions comme FTS3 et RTree
  • Statement vs Branch Coverage
    • La couverture de branches est plus stricte que la couverture d’instructions, car elle valide chaque embranchement dans les deux sens
  • Couverture du code défensif
    • Les macros ALWAYS() et NEVER() explicitent les conditions défensives
    • Les tests sont répétés avec trois formes de définition pour vérifier la cohérence
  • Tests de valeurs limites et de vecteurs booléens
    • La macro testcase() vérifie à la fois les résultats positifs et négatifs des conditions
    • 1184 appels à testcase() sont utilisés
  • Atteinte de MC/DC
    • La macro testcase() permet de vérifier l’impact indépendant de chaque condition
  • Mesure basée sur gcov
    • La couverture est mesurée avec les options -fprofile-arcs -ftest-coverage
    • La comparaison des résultats permet de détecter des bugs compilateur ou des comportements indéfinis
  • Mutation Testing
    • Modifie les instructions de branchement pour vérifier que les tests détectent ces changements
    • Les branches d’optimisation (/*OPTIMIZATION-IF-TRUE*/) sont traitées comme exception
  • Retour d’expérience sur la couverture complète
    • Le test de toutes les branches minimise les effets de bord lors des modifications du code
    • Le coût de maintenance est élevé, mais il est justifié pour une bibliothèque d’infrastructure largement déployée

8. Analyse dynamique

  • Assert()
    • 6754 assertions vérifient les préconditions, postconditions et invariants de boucle
    • Actives uniquement dans les builds SQLITE_DEBUG
  • Valgrind
    • Détecte les erreurs mémoire, les débordements de pile et les accès à de la mémoire non initialisée
    • Avant chaque release, les tests veryquick et TH3 sont exécutés sous Valgrind
  • Memsys2
    • Dans les builds SQLITE_MEMDEBUG, un wrapper est inséré pour surveiller les erreurs mémoire
    • Permet des vérifications répétées plus rapides qu’avec Valgrind
  • Mutex Asserts
    • sqlite3_mutex_held() et autres vérifient la synchronisation multithread
  • Journal Tests
    • Vérifient que le journal de rollback est écrit avant la DB, garantissant l’atomicité des transactions
  • Undefined Behavior Checks
    • Détectent les comportements indéfinis avec -ftrapv, -fsanitize=undefined, /RTC1, etc.
    • Répétés en 32/64 bits, selon l’endianness et sur diverses architectures CPU

9. Tests avec optimisations désactivées

  • sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS) permet de désactiver les optimisations
    • Les résultats doivent être identiques avec ou sans optimisations
    • Certains tests de mesure de performance font exception

10. Checklist

  • Avant chaque release, une checklist manuelle d’environ 200 éléments est vérifiée
    • Certains points prennent quelques secondes, d’autres plusieurs heures
    • En cas de problème détecté, un nouvel élément est ajouté immédiatement pour améliorer le processus en continu

11. Analyse statique

  • Compilation sans avertissement avec GCC, Clang et MSVC
    • Aucun avertissement valide non plus avec Clang Static Analyzer
    • L’analyse statique a une efficacité limitée pour détecter les bugs réels

12. Résumé

  • Bien qu’open source, SQLite maintient une qualité de niveau commercial et un faible taux de défauts
    • Des tests rigoureux et la conception du code en sont les facteurs clés
    • Chaque release passe par les procédures ci-dessus pour fournir un moteur de base de données fiable même dans des environnements critiques

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.