Florent Jardin   À propos  Archives

Halte aux régressions

Pour garantir la qualité du code d’un logiciel, rien de mieux que la validation par les tests. Ces derniers peuvent être de différentes natures (fonctionnels, intégration, unitaires, performance, etc.) et permettent de respecter une série d’exigences que s’imposent les développeurs pour maintenir et faire évoluer ledit logiciel dans la bonne direction.

Dans cet article, je souhaite explorer le système de tests tel qu’il est (et a été) implémenté dans PostgreSQL et comment le réemployer dans la rédaction d’une extension communautaire. Si vous ne connaissiez pas l’outil pg_regress, il n’aura plus de secret pour vous !


Aux origines des tests de régression

Avant même l’émergence du plus avancé des systèmes de bases de données open-source du monde que l’on connait, le projet Berkeley POSTGRES disposait déjà d’un répertoire src/regress/regress dans sa dernière version connue. Celui-ci fut définitivement adopté sous la forme de src/test/regress lors de la reprise du projet Postgre95 par les deux étudiants Andrew Yu et Jolly Chen.

Ce n’est pas anodin, car ce répertoire existe encore dans les versions modernes et porte toujours les mêmes responsabilités, à savoir : s’assurer que les fonctionnalités de PostgreSQL ne présentent aucune régression à chaque patch ou nouvelle version majeure. Ce système de test est relativement simple à appréhender et très répandu dans le milieu du développement des logiciels libres.

Il repose sur les fichiers de tests au format SQL et des fichiers de sorties au format OUT. L’astuce consiste à exécuter le code SQL sur une instance en cours d’exécution et de capter la sortie standard dans un fichier de résultat. Pour chaque test (sql), le fichier de résultat (result) est ensuite comparé au résultat attendu du test (expected) à l’aide de la méthode diff.

Fonctionnement du système de tests

Ce traitement est réalisé depuis la version 7.1 par l’utilitaire pg_regress.sh. À l’origine, ce dernier était un simple script shell responsable de monter une instance PostgreSQL temporaire au besoin, de rapprocher les fichiers SQL de leurs résultats OUT et de fournir un résumé des tests. Le script fut intégralement remplacé par son équivalent pg_regress réécrit en C à la sortie de la version 8.2, pour faciliter notamment :

  • La validation des tests sur un environnement Windows sans émulateur de shell tel que mingw
  • La mise à disposition d’un outil prêt à l’emploi avec l’installation de PostgreSQL, sans les dépendances systèmes requises par l’ancien script
  • L’implémentation de nouvelles améliorations comme l’exécution concurrente des tests ou un résultat plus conviviale

Lors d’une compilation des binaires d’une version quelconque de PostgreSQL, il est possible de valider tout ou partie des fonctionnalités à l’aide de la commande make check ou make installcheck. La première des deux commandes créée une instance temporaire alors que la seconde va exécuter les tests sur une instance en cours d’exécution.

make check

PATH="tmp_install/var/lib/pgsql-14.2/bin:src/test/regress:$PATH" \
LD_LIBRARY_PATH="tmp_install/var/lib/pgsql-14.2/lib:$LD_LIBRARY_PATH" \
../../../src/test/regress/pg_regress \
  --temp-instance=./tmp_check \
  --inputdir=. --bindir= --dlpath=. \
  --max-concurrent-tests=20 \
  --make-testtablespace-dir \
  --schedule=./parallel_schedule
============== removing existing temp instance        ==============
============== creating temporary instance            ==============
============== initializing database system           ==============
============== starting postmaster                    ==============
running on port 58082 with PID 24013
============== creating database "regression"         ==============
CREATE DATABASE
ALTER DATABASE
============== running regression test queries        ==============
test tablespace                   ... ok          165 ms
parallel group (20 tests):
     boolean                      ... ok           47 ms
     char                         ... ok           40 ms
     name                         ... ok           41 ms
     varchar                      ... ok           38 ms
     text                         ... ok           34 ms
     int2                         ... ok           24 ms
     int4                         ... ok           23 ms
     int8                         ... ok           55 ms
     oid                          ... ok           43 ms
     float4                       ... ok           60 ms
...
parallel group (2 tests):
     event_trigger                ... ok           59 ms
     oidjoins                     ... ok          119 ms
test fast_default                 ... ok           71 ms
test stats                        ... ok          620 ms

============== shutting down postmaster               ==============
============== removing temporary instance            ==============
...
=======================
 All 210 tests passed. 
=======================

Tester son extension avec PGXS

En plusieurs années, l’outil pg_regress s’est étendu aux fonctionnalités annexes du projet PostgreSQL, comme les langages embarqués (plpgsql, plperl, etc.) ou les contributions communautaires.

Le système PGXS propose aux membres de la communauté d’enrichir leurs Makefile avec des règles de compilation, d’installation et de validation par pg_regress. Dans un projet, il est ainsi recommandé d’inclure les directives du PGXS pour bénéficier de la règle installcheck responsable des tests.

Prenons l’exemple de la contribution pgstattuple avec la définition de son Makefile. Ce dernier contient les quelques variables nécessaires pour la compilation et l’installation, puis inclut les règles pgxs.mk si le système est utilisé.

# contrib/pgstattuple/Makefile
REGRESS = pgstattuple

ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/pgstattuple
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif

Avec cette configuration par défaut, l’outil pg_regress part à la recherche des fichiers sql/ dans le répertoire courant, et les exécute sur l’instance pour ensuite comparer ses résultats avec les fichiers expected/. Le contenu du fichier expected/pgstattuple.out peut être consulté directement dans le code source de PostgreSQL.

pgstattuple
├── expected
│   └── pgstattuple.out
├── results
│   └── pgstattuple.out
└── sql
    └── pgstattuple.sql

Lançons les tests de l’extension avec le système PGXS :

cd contrib/pgstattuple
make USE_PGXS=1 install installcheck
./lib/pgxs/src/makefiles/../../src/test/regress/pg_regress \
  --inputdir=./ --bindir='./bin' \
  --dbname=contrib_regression pgstattuple

(using postmaster on Unix socket, default port)
============== dropping database "contrib_regression" ==============
DROP DATABASE
============== creating database "contrib_regression" ==============
CREATE DATABASE
ALTER DATABASE
============== running regression test queries        ==============
test pgstattuple                  ... ok          167 ms

=====================
 All 1 tests passed. 
=====================

Pensez-y !

Vous souhaitez développer votre propre extension pour révolutionner PostgreSQL ? Pensez à écrire vos tests avec le framework PGXS ! La documentation est très fournie à ce sujet pour prendre en main les variables d’environnement. De plus, depuis la version PostgreSQL 9.4, il est possible de bénéficier de standard TAP pour rédiger vos tests.

Les extensions sont nombreuses et ce n’est jamais une mauvaise idée de s’inspirer des contributions maintenues dans le projet PostgreSQL. Je recommande également la série d’articles rédigée par Manuel Kniep pour comprendre le processus complet de l’écriture d’une extension ou la conférence de Lætitia Avrot sur l’extension pgwaffles créée à l’occasion du FOSDEM 2021.