2015-04-27 10 views
6

Come parte del test dell'unità, voglio garantire la copertura del codice dei test. L'obiettivo è di posizionare qualcosa come i macro REQUIRE_TEST da qualche parte nel codice e controllare se tutti questi sono stati chiamati.Trova righe non eseguite del codice C++

void foo(bool b) { 
    if (b) { 
    REQUIRE_TEST 
    ... 
    } else { 
    REQUIRE_TEST 
    ... 
    } 
} 

void main() { 
    foo(true); 

    output_all_missed_REQUIRE_macros(); 
} 

Idealmente l'uscita dovrebbe includere sorgente del file e la linea della macro.

La mia idea iniziale era di avere le macro creano oggetti statici che si registrerebbe in qualche mappa e poi verificare se tutti questi sono stati chiamati

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

ma l'oggetto statico vengono creati solo quando il codice viene chiamato la prima volta. Quindi la mappa contiene solo funzioni che vengono conteggiate anche nella riga successiva - mancano i macro REQUIRE_TEST mancanti. lo __attribute__((used)) viene ignorato in questo caso.

Il gcc ha una bella attributo __attribute__((constructor)), ma apparentemente sceglie di ignorarlo quando posto qui (codice seguente invece dell'oggetto statico)

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

nonché per

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

L'unica way out posso ora pensare a) manualmente (o tramite script) analizzare il codice al di fuori della normale compilation (uargh) o b) usare la macro __COUNTER__ per contare le macro - ma poi non saprei quali specifiche macro REQUIRE_TEST sono state non calle d ... (e si rompe tutto, se qualcun altro decide di utilizzare la macro __COUNTER__ così ...)

Esistono soluzioni decenti a questo problema? Cosa mi manca? E ' sarebbe bello avere una macro che aggiunge la riga corrente e il file quindi qualche variabile del preprocessore ogni volta che viene chiamata - ma non è possibile , giusto? Esistono altri modi per registrare qualcosa come eseguito prima dello main() che può essere eseguito all'interno di un corpo di una funzione?

+0

Ricerca gli avvisi e le opzioni della riga di comando per il compilatore. Molti compilatori hanno la capacità di identificare "codice morto". –

+8

TL; DR; Perché non usi qualche codice di profilazione del codice di copertura come ['gcov'] (https://gcc.gnu.org/onlinedocs/gcc/Gcov.html) e analizzi i risultati, ad es. usando ['lcov'] (http://ltp.sourceforge.net/coverage/lcov.php)? Questo potrebbe non necessariamente approvare veramente il codice _dead, se non viene eseguito dagli scenari di test. Uno strumento di analisi del codice statico potrebbe servire meglio a trovare cose del genere. –

+0

@ πάνταῥεῖ 'gcov' è molto dettagliato - ma anche in parti di codice in cui non mi interessa davvero. Posizionare marcatori come "REQUIRE_TEST" mi permetterebbe di specificare quali parti mi interessano. Sarebbe anche un'altra dipendenza per tutti quelli a cui spedisco la mia libreria - e sembra che C++ sia _almost_ in grado di fare ciò che voglio ... Se uno qualsiasi dei miei tentativi funzionasse avrei tutto ciò che ho sempre desiderato. Forse qualcosa come 'gcov' sarà l'unica soluzione alla fine - ma mi piacerebbe sapere che ciò che sto cercando non è possibile prima di" rinunciare "e aggiungere un'altra dipendenza ... – example

risposta

1

Un approccio brutto, ma semplice è per REQUIRE_TEST da usare __LINE__ o __COUNTER__ per costruire il nome di un oggetto statico univoco di file-scope, che si riferisce, e che causerà un errore di compilazione se non è stato dichiarato . È quindi necessario dichiarare manualmente tutti questi oggetti in primo piano, uno per ogni REQUIRE_TEST, ma almeno si ottiene un errore di compilazione se non lo si è fatto.

Ehi, ho detto che era brutto!

2

ne dite di questo:

#include <iostream> 

static size_t cover() { return 1; } 

#define COV() do { static size_t cov[2] __attribute__((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; 
} 

Risultati:

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

e:

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

e:

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

Fondamentalmente, abbiamo impostato un array di copertura in una sezione di memoria denominata (ovviamente abbiamo usato meccanismi specifici di GCC per questo), che dump dopo l'esecuzione.

Facciamo affidamento sull'inizializzazione costante delle statistiche locali eseguite all'avvio - che porta i numeri di riga nell'array di copertura con flag di copertura impostato su zero - e sull'inizializzazione tramite la chiamata di funzione a cover() eseguita alla prima esecuzione dell'istruzione , che imposta i flag di copertura su 1 per le linee eseguite. Non sono sicuro al 100% che tutto ciò sia garantito dallo standard, né da quale versione dello standard (ho compilato con --std=c++11).

Infine, costruendo con '-O3' produce risultati corretti (seppur allocato in un ordine diverso):

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

e:

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

e

$ ./a x y 
25: 1 
22: 1 
19: 1 
+0

Secondo lo standard, il compilatore è permesso di inizializzare la statica locale prima di main - se lo desidera. Ma inizializzarlo con 0 e aggiungere un'altra riga di codice che aumenta questo numero nella macro dovrebbe funzionare bene.Grazie per l'idea di ripetere questa sezione personalizzata. Aggiungerò anche il nome del file corrente, ma dovrebbe essere praticamente quello che volevo =) – example

+0

hai qualche idea del perché questo dà un conflitto di tipo di sezione quando la macro 'COV' è usata in una funzione inline? – example

1

Jeremy mi ha dato l'idea giusta per dare un'occhiata più da vicino alle sezioni. La sua risposta funziona, ma solo senza le funzioni inline. Con un po 'più di ricerca sono stato in grado di trovare la seguente soluzione che è altrettanto dipendente da gcc (a causa del nome della sezione) ma più flessibile (e funziona in funzioni inline). La macro è la seguente:

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

Posizionando il puntatore a funzione nella sezione .init_array effettivamente colloca nella lista delle funzioni di inizializzazione che vengono chiamati prima principale. In questo modo si può essere certi che la funzione definita localmente viene chiamata prima di main.

Problemi correlati