Nous utilisons LCOV/GCOV pour produire une couverture de test de nos projets. Récemment, nous avons essayé d'activer la couverture de branche en plus. Mais il semble que cela ne produise tout simplement pas les résultats escomptés d'un point de vue de développeur de haut niveau.
L'utilisation de la couverture de branches avec C++ augmente le rapport avec des branches partout. Nous soupçonnons (comme l'indique la recherche des problèmes) que la plupart du temps, le code de traitement des exceptions crée ces "branches cachées". Et GCOV/LCOV ne semble pas les ignorer.
J'ai créé un petit projet test pour montrer le problème: https://github.com/ghandmann/lcov-branch-coverage-weirdness
Nous utilisons actuellement Ubuntu 16.04. avec:
Notre code de production est construit avec c ++ 11 activé. L'exemple minimal n'est pas construit avec c ++ 11 activé, mais comme nous avons expérimenté un peu toutes les différentes options (c ++ standard, optimisation, -fno-exceptions
), nous n'avons pas réussi à obtenir un résultat acceptable.
Quelqu'un a des idées? Tipps? Utilisons-nous quelque chose dans le mauvais sens? Est-ce - comme dit ailleurs - un comportement vraiment attendu?
Mettre à jour:
Comme indiqué également dans la liste de diffusion gcc-help , ces "branches cachées" sont dues à la gestion des exceptions. Ainsi, l'ajout du commutateur -fno-exceptions
à gcc produit une couverture de branche à 100% pour les programmes "simples". Mais lorsque les exceptions sont désactivées, gcc refuse de compiler du code qui utilise réellement des exceptions (par exemple, try-catch, throw). Par conséquent, ce n'est pas une option pour le code de production réel. On dirait que vous devez simplement déclarer une couverture d'environ 50% comme étant le nouveau 100% dans ce cas. ;)
Le fait est que GCC enregistre également des informations sur les branches pour chaque ligne où une sortie de portée due à une exception levée est possible (par exemple, sur Fedora 25 avec GCC 6.3.1 et lcov 1.12).
La valeur de cette information est limitée. Le principal cas d'utilisation des données de couverture de branche est une instruction if complexe comportant une expression logique multi-clausale comme celle-ci:
if (foo < 1 && (bar > x || y == 0))
Supposons que vous souhaitiez vérifier si votre suite de tests couvre également le cas bar > x
ou si vous avez simplement des cas de test où y == 0
.
Pour cela, la collecte de données sur la couverture des branches et la visualisation par genhtml de lcov sont utiles. Pour des déclarations simples telles que
if (p == nullptr) {
return false;
}
return true;
vous n'avez pas besoin de données de couverture de branche car vous voyez si la branche a été prise ou non en regardant la couverture des lignes suivantes.
L'entrée de genhtml
générée par lcov
est dans un format texte relativement simple (cf. geninfo(1)
). Ainsi, vous pouvez le post-traiter de sorte que toutes les lignes commençant par BRDA:
et n'appartenant pas à une instruction if soient supprimées. Voir par exemple filterbr.py
qui implémente cette approche. Voir aussi gen-coverage.py
pour les autres étapes de traitement de lcov/genhtml et un projet example dans lequel le fichier de trace résultant est chargé sur codecov (codecov n'utilise pas genhtml
mais peut importer des fichiers de trace lcov et affiche les données de couverture de branche ).
-O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls
réduit quelque peu le nombre de données de couverture de branche enregistrées, mais pas beaucoup-fprofile-instr-generate -fcoverage-mapping
et post-traitement avec llvm-profdata
et llvm-cov
). Cette chaîne d'outils ne prend toutefois pas en charge les données de couverture de branche (à partir du 2017-05-01).--rc lcov_branch_coverage=0
et --no-branch-coverage
)GCC va ajouter un tas de choses sur la gestion des exceptions. Surtout quand vous faites des appels de fonction.
Vous pouvez résoudre ce problème en ajoutant -fno-exceptions -fno-inline
to à votre version.
Je devrais ajouter que vous ne voulez probablement que ces drapeaux à des fins de test ... donc quelque chose comme ça:
g++ -O0 --coverage -fno-exceptions -fno-inline main.cpp -o test-coverage
Vous pouvez essayer g++ -O3 --coverage main.cpp -o testcov
. J'ai essayé ceci avec g ++ - 5.4 sur votre fichier et cela fonctionne très bien, ce qui signifie que les exceptions sont supprimées avec les appels standard printf et string.
En fait, tout indicateur d'optimisation autre que O0
amènera gcov à ignorer les exceptions générées pour les appels de bibliothèque standard simples dans un fichier CPP. Je ne sais pas si les exceptions normales seront également optimisées (je ne pense pas, mais je ne l'ai pas encore essayé).
Mais, je ne suis pas sûr si votre projet exige que seul O0 soit utilisé avec votre code et non O1
, O2
, O3
ou même Os
.
Je viens de rencontrer le même problème et je veux me débarrasser de ces branches non couvertes à cause d'exceptions. J'ai trouvé une solution adaptée pour moi:
J'évite juste d'utiliser "throw exception" dans mon code, que je veux couvrir, directement. J'ai conçu une classe, qui propose des méthodes qui jettent les exceptions à la place. Comme la classe des exceptions n’est pas si complexe, la couverture ne m’intéresse pas vraiment. J’exclue donc tout avec LCOV_EXCL_START et LCOV_EXCL_STOP. Sinon, je pourrais également désactiver la couverture de branche uniquement pour cette classe d'exception.
Je reconnais que ce n’est pas une solution simple, mais pour les besoins de notre projet, c’est parfait Pour d’autres raisons également (j’ai besoin que cette classe d’exception soit flexible pour pouvoir en offrir une implémentation différente: une fois lancée une exception, une autre le temps de faire autre chose).