TD08 : Livraison du Projet

L'objectif de ce TD est de produire une version de votre projet dans l'objectif de la livrer à un client : vos enseignants, mais aussi d'autres étudiants qui vont devoir évaluer votre travail ! Réciproquement, vous allez devoir vous-même évaluer le travail d'une autre équipe.

Commençons par lister les objectifs généraux qui définissent un excellent projet :

  • rendre un code fonctionnel, c'est-à-dire sans bugs, ni fuite mémoire ;
  • rendre un code interopérable, qui respecte rigoureusement les interfaces définies ;
  • rendre un code suffisamment testé, afin d'éviter toute regression fonctionnelle lors de son cycle de vie ;
  • rendre du code propre et suffisamment bien commenté, afin de faciliter sa prise en main par de nouveaux développeurs, sa maintenance et son évolution vers de nouvelles fonctionnalités.

Afin de réaliser au mieux les objectifs de cette évaluation croisée, nous vous proposons quelques exercices préliminaires, qui vous servirons à améliorer votre code, mais aussi à évaluer la qualité d'un autre projet. Voici le questionnaire qui va nous servir de grille d'évaluation.

Exercice 0 : débogage intégré à VS Code

Afin d'illustrer notre propos, nous allons considérer la bibliothèque queue déjà étudiée.

  • Récupérez ce projet sur GitHub et placez-vous dans la branche dans la branche bugtofix : git switch bugtofix.
  • Compilez ce projet avec CMake dans le sous-répertoire build.
  • Lancez l'exécution des tests : make test. Vous devez constater que le test test_queue_pop_tail échoue !
  • Consultez le fichier CMakeLists.txt pour trouver la commande à lancer dans un terminal pour reproduire ce bug.
  • Vérifiez que votre programme se compile bien avec le flag -g, nécessaire au bon fonctionnement du débogueur.

Vous avez fait le plus dur ou presque, c'est-à-dire isoler le bug avec un test ! Il faut maintenant comprendre plus en détail le problème pour le corriger. Dans cette situation, il convient d'utiliser un débugueur comme gdb en ligne de commande. Il est également possible de lancer une session de debug graphique & interactive dans VS Code, ce qui est plus confortable...

  • Pour ce faire, il faut préparer un fichier launch.json dans le sous-répertoire .vscode/. Récupérez ce fichier et copiez dans le sous-répertoire .vscode/.
  • Au CREMI, vous pouvez générer automatiquement le fichier launch.json pour un test donné en utilisant le script mklaunch.sh à la racine du projet (là où se trouve le fichier CMakeLists.txt) :
$ cd queue
$ /net/ens/vscode/debug/mklaunch.sh test_queue_pop_tail
  • Pour lancer la session de debug dans VS Code, il suffit de taper sur la touche F5.
  • Identifiez la fonction et la ligne de code qui provoque l'erreur... Il n'est pas demandé de la corriger.
  • Relancez une session de debug en mettant un breakpoint à l'entrée de cette fonction. Puis avancez pas à pas, en regardant l'évolution des variables en mémoire.
  • Pensez à utiliser cet outil puissant par la suite !

Exercice 1 : couverture de code

Pour qu'un jeu de tests soit efficace, il faut qu'il soit le plus exhaustif possible. On peut alors se poser la question suivante : quelle est la proportion de code source qui est exécuté lorsque je lance mon jeu de tests ? C'est précisément ce que l'on appelle en génie logiciel la couverture de code ou code coverage en anglais.

L'outil gcov intégré à gcc et cmake permet de répondre simplement à cette question. Afin d'illustrer notre propos, nous allons mettre en oeuvre cet outil sur l'exemple de la bibliothèque queue déjà étudiée.

  • A l'aide de Git, récupérez la dernière version de queue et placez-vous dans la branche coverage : git switch coverage.
  • Dans le fichier CMakeLists.txt, ajoutez l'option de compilation --coverage et vérifiez que vous avez la ligne include(CTest) en plus de enable_testing().
  • Recompilez le projet dans le sous-répertoire build. Vérifiez que l'option --coverage est bien utilisée lors de la compilation, en tapant la commande make VERBOSE=ON.
  • Afin de calculer la couverture de code, il faut obligatoirement commencer par lancer l'exécution des tests en faisant make ExperimentalTest (ou make test). Puis, dans un deuxième temps, lancez la commande make ExperimentalCoverage pour afficher la couverture de code. Cela devrait afficher le résultat ci-dessous (LOC = Lines of Code). Interprétez-le.
Covered LOC:         211
Not covered LOC:     25
Total LOC:           236
Percentage Coverage: 89.41%
  • Dans le sous-répertoire Testing/CoverageInfo, vous trouverez les résultats détaillés sur la couverture de votre code. Par exemple dans queue.c.gcda##queue.c.gcov, vous avez l'analyse du fichier queue.c, dont voici un extrait ci-dessous. La colonne de gauche indique le nombre de fois, où chaque ligne de code est exécutée. En particulier, ##### indique une ligne de code qui n'est jamais exécutée et - signifie que cette ligne est ignorée car elle ne contient pas d'instructions.
    -:  150:
   36:  151:void queue_clear(queue *q)
    -:  152:{
  36*:  153:  assert(q);
   36:  154:  element_t *e = q->head;
   36:  155:  while (e)
    -:  156:  {
#####:  157:    element_t *tmp = e;
#####:  158:    e = e->next;
#####:  159:    free(tmp);
    -:  160:  }
   36:  161:  q->head = q->tail = NULL;
   36:  162:  q->length = 0;
   36:  163:}
    -:  164:
  • Que se passerait-il si il y avait un bug dans les lignes 157-159 ? Que faudrait-il faire pour remédier à ce problème ?
  • En analysant la couverture de code de queue.c, trouvez une fonction qui n'est pas du tout testée... Dans test_queue.c, corrigez le problème en décommentant le test correspondant. Vérifiez alors que vous arrivez à améliorer la couverture de code. Notez qu'il n'est pas vraiment possible d'atteindre une couverture de 100%, car nous ne testons pas en général les cas d'erreur, comme par exemple le passage d'arguments invalides !
  • Analysez la couverture de code dans votre projet et complétez vos tests ! Pour chaque ligne ##### qui n'est pas couverte par vos tests, posez-vous les questions suivantes. Est-ce que ce code est atteignable ? Si c'est du code mort, il faut peut-être le supprimer, sinon c'est qu'il faut certainement compléter vos tests.

Dans VSCode, vous pouvez installer l'extension Gcov Viewer qui permet de visualiser la couverture de code (Ctrl+Shift+P Gcov Viewer: Show).

vscode-gcov-viewer

Exercice 2 : détecter une fuite mémoire

L'outil valgrind (utilisé par cmake) permet de détecter simplement des fuites mémoire (ou memory leak en anglais). Afin d'illustrer notre propos, nous allons mettre en oeuvre cet outil sur l'exemple de la bibliothèque queue déjà étudiée.

  • A l'aide de Git, récupérez la dernière version de queue et placez-vous dans la branche memcheck : git switch memcheck.
  • Dans le fichier CMakeLists.txt, vérifiez que vous avez déjà la ligne include(CTest) et recompilez le projet dans le sous-répertoire build.
  • Lancez la commande make ExperimentalMemCheck, qui exécutent les tests à travers l'outil valgrind pour détecter de possibles fuites mémoire et afficher le bilan ci-dessous :
-- Processing memory checking output:
5/8 MemCheck: #5: test_queue_pop_tail ..............   Defects: 1
6/8 MemCheck: #6: test_queue_length ................   Defects: 1
MemCheck log files can be found here: ( * corresponds to test number)
Testing/Temporary/MemoryChecker.*.log
Memory checking results:
Memory Leak - 2
  • En fonction des tests qui échouent, recherchez dans le code queue.c une fuite mémoire... Afin d'obtenir une analyse plus détaillée, il est toujours possible de lancer manuellement le test avec la commande valgrind et l'option --leak-check=full. Par exemple :
$ valgrind --leak-check=full ./test_queue pop_tail
Memcheck, a memory error detector
Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
Command: ./test_queue pop_tail

HEAP SUMMARY:
    in use at exit: 2,400 bytes in 100 blocks
  total heap usage: 201 allocs, 101 frees, 2,824 bytes allocated

2,400 (24 direct, 2,376 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
   at 0x483C7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
   by 0x10A1C6: queue_push_head (queue.c:46)
   by 0x109793: test_pop_tail (test_queue.c:112)
   by 0x109FF5: main (test_queue.c:223)

LEAK SUMMARY:
   definitely lost: 24 bytes in 1 blocks
   indirectly lost: 2,376 bytes in 99 blocks
     possibly lost: 0 bytes in 0 blocks
   still reachable: 0 bytes in 0 blocks
        suppressed: 0 bytes in 0 blocks

ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
  • En résumé, on détecte une fuite mémoire correspondant à une allocation dynamique avec malloc() à la ligne queue.c:46, qui n'est a priori pas libérée par un free(). Après une enquête rapide, trouvez l'origine du problème et corrigez la ligne problématique dans queue.c. Vérifiez qu'il n'y a plus de fuite mémoire avec valgrind.
  • Détectez les éventuelles fuites mémoire dans votre projet et corrigez-les !

Exercice 3 : interopérabilité

En génie logiciel, l’interopérabilité est la capacité d'un système, dont les interfaces sont clairement spécifiées, à fonctionner correctement avec d’autres systèmes existants ou futurs.

Dans le contexte de notre projet, cela consiste principalement à respecter rigoureusement la séparation imposée par les interfaces de la bibliothèque game : game.h, game_aux.h et game_ext.h. En pratique, les exécutables du projet (tests ou game_text) doivent se limiter strictement aux fonctions spécifiées par ces interfaces et ne pas avoir d'autres dépendances. Cela garantit que votre bibliothèque est réutilisable dans d'autres projets, et à l'inverse que vos exécutables peuvent utiliser d'autres bibliothèques.

  • Vérifiez que vos exécutables (game_text et tests) compilent et fonctionnent correctement avec la bibliothèque libgame.a fournies par vos enseignants. Si votre exécutable est correctement liée à cette bibliothèque, une mention Copyright doit s'afficher dans votre terminal au lancement du jeu. Sinon corrigez le problème.
  • A l'inverse, vérifiez que l'exécutable game_text.o peut se lier à votre bibliothèque game sans problème. De même, une mention Copyright doit s'afficher dans votre terminal au lancement du jeu. Sinon corrigez le problème.
  • Dans le contexte de l'évaluation croisée, vous allez devoir vérifier que le projet d'une autre équipe est interopérable avec votre propre projet, comme illustré sur la figure ci-dessous.

interop

Nota Bene : Pour tester l'interopérabilité de libgame.a, il suffit de compiler le projet cible et de remplacer directement dans le répertoire build le fichier libgame.a par une autre version avant de recompiler les exécutables avec la commande make. Attention, de ne pas faire un make clean avant, ce qui aurait pour effet de supprimer la nouvelle version de libgame.a !

Exercice 4 : du code propre

  • En guise d'introduction, regardez la vidéo Commenter et nettoyer son code.
  • Appliquez au mieux ces principes à votre projet, afin de livrer un code qui soit le plus propre possible : commentaires, indentation, nommage cohérent des variables et des fonctions, pas de constantes magiques, fonctions de longueur raisonnable, pas de code mort ou de duplication inutile, ...

Exercice 5 : livraison du projet et évaluation croisée

Afin d'évaluer chaque projet, nous nous appuyons sur ce questionnaire. Lisez-le attentivement afin d'améliorer votre propre projet avant sa livraison. Appuyez-vous notamment sur les exercices préliminaires de ce TD.

Ce travail se déroule en deux étapes, bien séparées :

  • Livraison (travail en équipe) : Dans un premier temps, vous utiliserez l'activité Moodle VPL Livraison pour effectuer le rendu final de votre projet, incluant la V1, les extensions de la V2, les tests et le programme game_text. Votre dépôt doit contenir à sa racine un fichier CMakeLists.txt prêt à compiler votre code source sans erreurs, un fichier README.md bien documenté. En outre, il ne doit pas contenir de fichiers superflux ou temporaires.
  • Evaluation Croisée (travail individuel) : Dans un deuxième temps, chaque étudiant va se voir affecté l'évaluation d'un autre projet, sous forme d'une archive .zip générée automatiquement à partir du rendu effectué à l'étape précédente. Afin d'évaluer ce projet, il vous faudra télécharger son archive .zip sur Moodle, le compiler, le tester et rendre dans Moodle vos réponses au questionnaire fourni. Notez que vous serez vous-même évalué sur la pertinence de vos réponses à ce questionnaire.

Les modalités de rendu de ces activités sont précisés sur Moodle.