Trouver des lignes non exécutées de code c ++

Dans le cadre de mes tests unitaires, je veux assurer la couverture de code des tests. Le but est de placer quelque chose comme les macros REQUIRE_TEST quelque part dans le code et de vérifier si toutes ont été appelées.

 void foo(bool b) { if (b) { REQUIRE_TEST ... } else { REQUIRE_TEST ... } } void main() { foo(true); output_all_missed_REQUIRE_macros(); } 

Idéalement, la sortie devrait inclure le fichier source et la ligne de la macro.

Mon idée de départ était de faire en sorte que les macros créent des objects statiques qui s’enregistreraient dans une carte et vérifieraient ensuite si tous étaient appelés.

 #define REQUIRE_TEST \ do { \ static ___RequiredTest requiredTest(__func__, __FILE__, __LINE__);\ (void)requiredTest;\ ___RequiredTest::increaseCounter(__func__, __FILE__, __LINE__);\ } while(false) 

mais les objects statiques ne sont créés que lorsque le code est appelé pour la première fois. Ainsi, la carte ne contient que des fonctions qui sont également comptées dans la ligne suivante. Les macros REQUIRE_TEST manquantes sont introuvables. __atsortingbute__((used)) est ignoré dans ce cas.

Le gcc a un atsortingbut agréable __atsortingbute__((constructor)) , mais choisit apparemment de l’ignorer lorsqu’il est placé ici (code suivant à la place de l’object statique)

 struct teststruct { \ __atsortingbute__((constructor)) static void bla() {\ ___RequiredTest::register(__func__, __FILE__, __LINE__); \ } \ };\ 

ainsi que pour

 []() __atsortingbute__((constructor)) { \ ___RequiredTest::register(__func__, __FILE__, __LINE__); \ };\ 

La seule solution que je puisse envisager maintenant est a) d’parsingr manuellement (ou via un script) le code en dehors de la compilation habituelle (uargh) ou b) d’utiliser la macro __COUNTER__ pour compter les macros – mais je ne saurais pas quel REQUIRE_TEST spécifique les macros n’ont pas été appelées … (et tout se casse si quelqu’un d’autre décide d’utiliser également la macro __COUNTER__ …)

Existe-t-il des solutions décentes à ce problème? Qu’est-ce que je rate? Il serait bien d’avoir une macro qui ajoute la ligne et le fichier actuels, ainsi une variable de préprocesseur à chaque appel, mais ce n’est pas possible, n’est-ce pas? Existe-t-il d’autres moyens d’enregistrer quelque chose à exécuter avant main() dans un corps de fonction?

Que dis-tu de ça:

 #include  static size_t cover() { return 1; } #define COV() do { static size_t cov[2] __atsortingbute__((section("cov"))) = { __LINE__, cover() }; } while(0) static void dump_cov() { extern size_t __start_cov, __stop_cov; for (size_t* p = &__start_cov; p < &__stop_cov; p += 2) { std::cout << p[0] << ": " << p[1] << "\n"; } } int main(int argc, char* argv[]) { COV(); if (argc > 1) COV(); if (argc > 2) COV(); dump_cov(); return 0; } 

Résultats:

 $ ./cov_test 19: 1 22: 0 25: 0 

et:

 $ ./cov_test x 19: 1 22: 1 25: 0 

et:

 $ ./cov_test xy 19: 1 22: 1 25: 1 

Fondamentalement, nous configurons un tableau de couverture dans une section de mémoire nommée (nous avons évidemment utilisé des mécanismes spécifiques à GCC pour cela), que nous vidons après exécution.

Nous nous appuyons sur l’initialisation constante de la statique locale au démarrage – qui inscrit les numéros de ligne dans le tableau de couverture avec le fanion de couverture défini sur zéro – et sur l’initialisation via l’appel de fonction à cover() lors de la première exécution de l’instruction, qui définit les indicateurs de couverture sur 1 pour les lignes exécutées. Je ne suis pas sûr à 100% que tout cela n’est garanti ni par la norme, ni par ses versions (j’ai compilé avec --std=c++11 ).

Enfin, la construction avec ‘-O3’ produit également des résultats corrects (bien qu’atsortingbués dans un ordre différent):

 $ ./a 25: 0 22: 0 19: 1 

et:

 $ ./ax 25: 0 22: 1 19: 1 

et

 $ ./axy 25: 1 22: 1 19: 1 

Une approche laide mais simple consiste pour REQUIRE_TEST à utiliser __LINE__ ou __COUNTER__ pour construire le nom d’un object statique unique file-scope auquel il fait référence, et qui entraînera une erreur de compilation s’il n’a pas été déclaré. Vous devez ensuite déclarer manuellement tous ces objects au REQUIRE_TEST , un pour chaque REQUIRE_TEST – mais au moins vous obtenez une erreur de compilation si vous ne l’avez pas déjà fait.

Hey, j’ai dit que c’était moche!

Jeremy m’a donné la bonne idée d’examiner de plus près les sections. Sa réponse fonctionne – mais seulement sans fonctions inline . Après quelques recherches supplémentaires, j’ai pu trouver la solution suivante qui dépend tout autant de gcc (en raison du nom de la section) mais plus souple (et fonctionne dans des fonctions inline). La macro est maintenant la suivante:

 #define REQUIRE_TEST \ do { \ struct ___a{ static void rt() {\ ___RequiredTest::register_test(__FILE__, __LINE__);\ } };\ static auto ___rtp __atsortingbute__((section(".init_array"))) = &___a::rt; \ (void) ___rtp; \ ___RequiredTest::increase_counter(__FILE__, __LINE__); \ } while(false) 

Placer le pointeur de la fonction dans la section .init_array place en fait dans la liste des fonctions d’initialisation appelées avant main. De cette façon, on peut être sûr que la fonction définie localement est appelée avant main.