lcov, genhtml을 이용한 coverage 깔끔하게 보기

NOTE
이전의 gocv를 이용한 커버리지 정보 확인과 밀접한 관계가 있는 글 입니다.
이 글을 풍부하게 이해하기 위해서는 gcov 관련 게시글을 보고 오시길 바랍니다.
Pre-requsite

gcov의 결과를 웹 페이지를 통해 깔끔하게 보여주기 위해 lcov를 사용한다.
이를 위해 다음의 명령어를 이용해 lcov 설치를 진행하자.

sudo apt install lcov

우선 다음과 같은 프로그램이 있다고 가정하자.

// main.cpp

#include <iostream>
using namespace std;

void func(int dividend, int divisor)
{
    cout << "Incoming variable: " << dividend << " " << divisor << endl;

    int quotient = 0;
    int remainder = 0;
    try {
        if (divisor == 0)
            throw exception();
        quotient = dividend / divisor;
        remainder = dividend % divisor;
    } catch (...) {
        cerr << "Exception occured!" << endl;
        quotient = 0;
        remainder = dividend;
    }

    switch(remainder) {
    case 0:
        cout << "Dividend is multiplier of divisior" << endl;
        break;
    case 100:
        cout << "Cannot reach here under this circumstances!" << endl;
        break;
    default:
        cout << "Nothing" << endl; 
        break;
    }
}

int main()
{
    for (int i = 0; i < 100; ++i) {
        for (int j = 0; j < 100; ++j) {
            func(i, j);
        }
    }

    return 0;
}

다음의 명령어를 이용해 컴파일을 수행한다.

g++ -o main main.cpp -fprofile-arcs -ftest-coverage -g

여기서 -fprofile-arcs -ftest-coverage 옵션은 소스의 각 베이직 블록에 프로파일링 코드를 삽입하라는 옵션이다.

이 명령을 수행하면 다음과 같은 파일들이 생성된다.

jay@jay-VirtualBox:~/tutorial/gcov$ g++ -o main main.cpp -fprofile-arcs -ftest-coverage -g
jay@jay-VirtualBox:~/tutorial/gcov$ ls
main  main.cpp  main.gcno

./main을 통해 프로그램을 한 번 수행시킨다. 이는 다음의 main.gcda 파일을 생성시킨다.

jay@jay-VirtualBox:~/tutorial/gcov$ ./main
jay@jay-VirtualBox:~/tutorial/gcov$ ls
main  main.cpp  main.gcda  main.gcno

gcov main.cpp를 수행한다.

jay@jay-VirtualBox:~/tutorial/gcov$ gcov main.cpp 
File 'main.cpp'
Lines executed:89.29% of 28
Creating 'main.cpp.gcov'

File '/usr/include/c++/9/iostream'
No executable lines
Removing 'iostream.gcov'

File '/usr/include/c++/9/bits/exception.h'
Lines executed:100.00% of 1
Creating 'exception.h.gcov'

jay@jay-VirtualBox:~/tutorial/gcov$ ls
exception.h.gcov  main  main.cpp  main.cpp.gcov  main.gcda  main.gcno

lcov --rc lcov_branch_coverage=1 --capture --directory ${gcov_data_file_directory} --output-file {output_file_name} 명령을 통해 gcov 데이터 파일을 이용해 genhtml에 쓰일 파일을 생성한다.

jay@jay-VirtualBox:~/tutorial/gcov$ lcov --rc lcov_branch_coverage=1 --capture --directory ./ --output-file generated.info
Capturing coverage data from ./
Found gcov version: 9.3.0
Using intermediate gcov format
Scanning ./ for .gcda files ...
Found 1 data files in ./
Processing main.gcda
Finished .info-file creation
jay@jay-VirtualBox:~/tutorial/gcov$ ls
exception.h.gcov  main      main.cpp.gcov  main.gcno
generated.info    main.cpp  main.gcda

여기서 --rc lcov_branch_coverage=1 옵션은 분기 커버리지를 보기 위한 옵션으로 genhtml에서도 쓰인다.

마지막으로 genhtml ${info_file} --branch-coverage --output-directory ${output_directory} 명령으로 coverage 정보를 담는 html 파일을 만든다.

jay@jay-VirtualBox:~/tutorial/gcov$ genhtml generated.info --branch-coverage --output-directory ./
Reading data file generated.info
Found 2 entries.
Found common filename prefix "/usr/include/c++/9"
Writing .css and .png files.
Generating output.
Processing file /home/jay/tutorial/gcov/main.cpp
Processing file bits/exception.h
Writing directory view page.
Overall coverage rate:
  lines......: 89.7% (26 of 29 lines)
  functions..: 100.0% (3 of 3 functions)
  branches...: 76.9% (10 of 13 branches)
jay@jay-VirtualBox:~/tutorial/gcov$ ls
amber.png         generated.info     index-sort-f.html  main.gcda
bits              glass.png          index-sort-l.html  main.gcno
emerald.png       home               main               ruby.png
exception.h.gcov  index.html         main.cpp           snow.png
gcov.css          index-sort-b.html  main.cpp.gcov      updown.png

이렇게 되면 다음과 같이 coverage 정보를 시각적으로 아름답게 표현해주는 html 파일이 생성되었다.

 

genhtml을 통해 생성된 파일

gcov를 통한 커버리지 정보 알아보기

: gcov는 gcc에 포함된 유틸리티로 코드 커버리지를 파악하는 데 쓰인다. 이는 어떤 부분이 얼마만큼 수행되는지, 어느 부분이 한 번도 수행되지 않는지를 파악할 수 있고, 이런 정보를 이용해 불필요한 코드 제거에 사용되어질 수 도 있다. 앞서 설명한 gprof와 비교하자면, gprof는 함수 단위의 프로파일링 정보만을 제공하는 반면에 gcov는 베이직 블록 단위의 프로파일링을 수행해 함수 내 어떤 루프가 많이 수행되었는지, if-else 블록에서 어느 블록이 많이 수행되었는지를 알 수 있다.

// main.c
#include <stdio.h>

void func1()
{
    printf("Hello World \n");
}

void func2(int delay)
{
    printf("Delay: %d", delay);

    while (delay--);
}

int main()
{
    for (int i = 0; i < 10; ++i) {
        func1();
    }

    for (int i = 0; i < 100; ++i) {
        func2(i);
    }

    return 0;
}

위와 같은 파일이 있다고 할 때, gcov를 이용한 라인 커버리지를 확인하는 방법은 다음과 같다.

  1. gcc -o main main.c -fprofile-arcs -ftest-coverage -g 명령으로 컴파일을 수행한다.
    • -fprofile-arcs -ftest-coverage 옵션은 소스의 각 베이직 블록에 프로파일링 코드를 삽입하라는 옵션이다.
  2. ./main을 통해 프로그램을 한 번 수행시킨다.
    • 이는 main.gcda, main.gcno 파일을 생성시킨다. 이 두 파일에 소스파일의 베이직 블록에 대한 프로파일링 정보가 포함된다.
  3. gcov main.c 명령을 수행하여 main.c.gcov파일을 생성시킨다.
  4. cat main.c.gocv 명령을 통해 프로파일링 정보를 확인한다.
  1: 1: #include <stdio.h>
  -: 2: 
  -: 3: void func1()
 10: 4: {
 10: 5:     printf("Hello World \n");
 10: 6: }
  -: 7: 
  -: 8: void func2(int delay)
100: 8: {
...
  • 각 라인의 맨 앞에 있는 숫자는 해당 라인이 몇 번 호출되었는지를 나타낸다.

+ Recent posts