2017-04-19 5 views
1

J'utilise gcov/lcov pour l'analyse de couverture des tests unitaires googletest.L'analyse de couverture de certaines macros googletest montre une couverture incomplète lorsqu'il est réparti sur plusieurs lignes - pourquoi?

Un problème récurrent est que le rapport de couverture montre des lignes non couvertes dans le code de test pour certaines macros googletest lorsque la macro est répartie sur plusieurs lignes.

Je sais gov/lcov ne peut pas être plus précis qu'une seule ligne, mais je suis intrigué par le comportement que je vois. Quelqu'un peut-il expliquer cela? exemple minimal:

#include <gtest/gtest.h> 

TEST(coverage,incomplete) 
{ 
    // Every second line in every invocation here will show up as uncovered: 
    EXPECT_NO_THROW(40 + 
        2); 
    EXPECT_NO_THROW(40 + 2 
       ); 
    EXPECT_NO_THROW(40 + 2) 
    ; 
} 

TEST(coverage,complete) 
{ 
    // This test does not show uncovered lines 
    EXPECT_NO_THROW(40 + 2); 
    EXPECT_EQ(40 
      + 
      2 
      , // even though this is spread over several lines 
      42 
      ) 
    ; 
} 

Comment l'analyse de couverture a été réalisée:

g++-4.8 -Igtest/googletest/include/ --coverage -o coverage_macropp coverage_macropp.cpp gtest/googletest/make/gtest_main.a -pthread 
./coverage_macropp 
lcov --capture --directory . --output-file coverage.info 
genhtml --demangle-cpp coverage.info --output-directory coverage 

L'analyse de la couverture dans le navigateur Web affichera alors les lignes 7, 9 et 11 non couverte:

 Line data Source code 

    1    : #include <gtest/gtest.h> 
    2    : 
    3   5 : TEST(coverage,incomplete) 
    4    : { 
    5    : // Every second line in every invocation here will show up as uncovered: 
    6   1 : EXPECT_NO_THROW(40 + 
    7   0 :     2); 
    8   1 : EXPECT_NO_THROW(40 + 2 
    9   0 :     ); 
    10   1 : EXPECT_NO_THROW(40 + 2) 
    11   0 :  ; 
    12   1 : } 
    13    : 
    14   5 : TEST(coverage,complete) 
    15    : { 
    16    : // This test does not show uncovered lines 
    17   1 : EXPECT_NO_THROW(40 + 2); 
    18   1 : EXPECT_EQ(40 
    19    :    + 
    20    :    2 
    21    :    , // even though this is spread over several lines 
    22    :    42 
    23    :   ) 
    24   1 :  ; 
    25   4 : } 

Pourquoi? Et pourquoi la macro EXPECT_EQ n'est-elle pas affectée?

+0

Cela dépend de la manière dont gcov analyse réellement ses entrées, et éventuellement de la façon dont les macros sont définies (par exemple, elles collent ou non des jetons). Donc, ce sont deux endroits où chercher une explication. – Peter

Répondre

1

C'était une enquête fascinante. En regardant ces macros, j'ai appris des choses sur switch/case et goto, que je ne savais pas étaient possibles. Mais la raison des différences de comportement vient des constructions if/else, qui ont en commun que les autres branches ne sont jamais exécutées, mais qui diffèrent dans le fait que le compilateur sait déjà, que ces autres branches ne seront jamais exécutées .

D'abord, la diffusion de la macro sur plusieurs lignes dans le code source a produit préprocesseur (dans la mesure où il importe pour cette question) le code suivant pour le compilateur et son analyseur de couverture:

if (condition) statement1; else statement2 


    ; 

Et apparemment, , l'analyseur de couverture gcov, compte la ligne avec le point-virgule solitaire comme une ligne de code, et considère qu'elle est exécutée lorsque l'instruction 2 de la branche else est exécutée.

Maintenant, pour reproduire la différence d'analyse de couverture, comme on l'observe dans la question, considérez cet exemple programme:

#include <stdio.h> 
#include <time.h> 

int main(int, char*[]) { 
    const bool true_1 = true; 
    const bool true_2 = time(NULL) != 0; 

    if (true_1) 42; else printf("hello\n") 
        ; 
    if (true_2) 42; else printf("hello\n") 
        ; 
    return 0; 
} 

true_1 et true_2 sont toujours vrai (dans ma vie, et si je ne plaisante pas avec les ordinateurs 'horloges), mais dans le cas de true_1, le compilateur le sait, alors que pour true_2, il ne peut pas le savoir. (Oui, je pourrais probablement trouver un initialiseur plus sûr pour true_2 Cela fonctionnera pour l'instant.)

Notez également que les instructions dans les if-branches ne font rien, mais les instructions dans les autres branches auraient des effets secondaires.

Le gcov/lcov analyse de couverture pour ce programme ressemble à ceci:

 Line data Source code 

    1    : #include <stdio.h> 
    2    : #include <time.h> 
    3    : 
    4   1 : int main(int, char*[]) { 
    5   1 : const bool true_1 = true; 
    6   1 : const bool true_2 = time(NULL) != 0; 
    7    : 
    8    : if (true_1) 42; else printf("hello\n") 
    9    :      ; 
    10   1 : if (true_2) 42; else printf("hello\n") 
    11   0 :      ; 
    12   1 : return 0; 
    13    : } 

Parce que le compilateur sait déjà que true_1 est vrai, la branche else, et par conséquent, la ligne 9, ne sont pas pris en considération pour l'analyse de couverture . C'est même vrai lors de la compilation avec -O0.

La ligne 11 de ce programme est toutefois prise en compte pour l'analyse de couverture, car true_2 n'est connu qu'à l'exécution.En note: si j'avais utilisé une autre instruction fictive dans la branche else, ou même un appel de fonction connu pour n'avoir aucun effet secondaire, comme sin (7) au lieu de printf ("hello") , alors cette ligne ne compterait pas non plus dans l'analyse de couverture de gcov. Jusqu'à présent, il semble que, pour une couverture formelle complète, je dois contraindre les macros googletest à une seule ligne source, si le résultat du test n'est pas déjà connu au moment de la compilation.

+0

Bonne prise! Et très bien formulé. Je pense que vous devriez accepter votre propre réponse. – Jonas