I am building a dynamically-linked library, and am setting up a simple test suite. I want to use gcov to generate a static code analysis coverage report.
My library is a C file containing function implementations and a header file containing function prototypes. My test suite is simply an application that calls the functions in various ways and confirms the validity of the output.
I am compiling both the library and the test suite with the -fprofile-arcs
and -ftest-coverage
flags, as described on GNU's guide to GCOV. I am also including the -O0
flag to disable compiler optimization and the -g
flag to enable debug symbols. The executable generated from the test suite is linked dynamically to the library.
All files compile cleanly and without warning, but it fails to link the test suite to the library -- citing a "hidden symbol __gcov_merge_add
". If I compile without the -fprofile-arcs
and -ftest-coverage
flags, the linking succeeds and I am able to run the test suite executable.
So I have a few questions which still aren't resolved after reading the GNU guide to GCOV.
Here is my inc/mylib.h
file:
#ifndef __MYLIB_H__
#define __MYLIB_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
int
foo (int a);
int
bar (int a);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __MYLIB_H__ */
Here is my src/mylib.c
file:
#include <stdio.h>
#include "mylib.h"
int foo(int a) {
if (a > 5) {
return 5;
}
return a;
}
int bar(int a) {
if (a < 0) {
return 0;
}
return a;
}
Here is my test/unittests.c
file:
#include <stdio.h>
#include "mylib.h"
void run_foo_tests() {
int inputs[] = {3, 6};
int expected_results[] = {3, 5};
int i, actual_result;
for ( i = 0; i < sizeof(inputs) / sizeof(int); i++ ) {
actual_result = foo(inputs[i]);
if (actual_result == expected_results[i]) {
printf("Test %d passed!\n", i + 1);
} else {
printf("Test %d failed!\n", i + 1);
printf(" Expected result: %d\n", expected_results[i]);
printf(" Actual result: %d\n", actual_result);
}
}
}
void run_bar_tests() {
int inputs[] = {3, -1};
int expected_results[] = {3, 0};
int i, actual_result;
for ( i = 0; i < sizeof(inputs) / sizeof(int); i++ ) {
actual_result = bar(inputs[i]);
if (actual_result == expected_results[i]) {
printf("Test %d passed!\n", i + 1);
} else {
printf("Test %d failed!\n", i + 1);
printf(" Expected result: %d\n", expected_results[i]);
printf(" Actual result: %d\n", actual_result);
}
}
}
int main(int argc, char *argv[]) {
run_foo_tests();
run_bar_tests();
return 0;
}
Here is my Makefile
:
CC=gcc
CFLAGS=-Wall -std=c89 -g -O0 -Iinc -fprofile-arcs -ftest-coverage
all: clean build run_tests
build:
$(CC) $(CFLAGS) -fPIC -c src/*.c -o lib/mylib.o
$(CC) -shared lib/mylib.o -o lib/libmylib.so
$(CC) $(CFLAGS) test/*.c -o bin/unittests -Llib -lmylib
run_tests:
LD_LIBRARY_PATH=lib bin/unittests
gcov src/*.c
clean:
rm -f *.gcda *.gcno *.gcov
rm -rf bin lib ; mkdir bin lib
When I run make
, I am presented with this output:
rm -f *.gcda *.gcno *.gcov
rm -rf bin lib ; mkdir bin lib
gcc -Wall -std=c89 -g -O0 -Iinc -fprofile-arcs -ftest-coverage -fPIC -c src/*.c -o lib/mylib.o
gcc -shared lib/mylib.o -o lib/libmylib.so
gcc -Wall -std=c89 -g -O0 -Iinc -fprofile-arcs -ftest-coverage test/*.c -o bin/unittests -Llib -lmylib
/usr/bin/ld: bin/unittests: hidden symbol `__gcov_merge_add' in /usr/lib/gcc/x86_64-redhat-linux/4.8.5/libgcov.a(_gcov_merge_add.o) is referenced by DSO
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status
make: *** [build] Error 1
You need to give the command line argument -lgcov
for the linker (normally -ftest-coverage
implies -lgcov
, but you have a separate linking step where -ftest-coverage
is not given as a command line argument). Alternatively, you can just use the --coverage
command line argument, which is a shortcut also for -fprofile-arcs
and -ftest-coverage
, as explained here: http://www.univ-orleans.fr/sciences/info/ressources/webada/doc/gnat/gcc_3.html:
--coverage
This option is used to compile and link code instrumented for coverage analysis. The option is a synonym for `-fprofile-arcs' `-ftest-coverage' (when compiling) and `-lgcov' (when linking). See the documentation for those options for more details.
At the same place, btw., it is also explained that you don't have to compile all files with these options, which hopefully answers your question 2.