Use all intermediate .gcov files that are generated.
Previously we would only use one `.gcov` file per `gcov` invocation for the final coverage report. This is incorrect because `gcov` generates multiple `.gcov` files for all the sources and headers used for creating the corresponding `.gcda`/`.gcno` files.
One example affected by this is when a `cc_library` has multiple headers. We need coverage information for all the headers used.
This PR adds several tests to make sure coverage collection works correctly when a `cc_library` has multiple header files and when a header file is used by multiple `cc_library` targets.
Unnecessary coverage information will be filtered out by `CoverageOutputGenerator`.
Fixes #6129
Fixes #5880
Closes #6232.
PiperOrigin-RevId: 215374625
diff --git a/src/test/shell/bazel/bazel_cc_code_coverage_test.sh b/src/test/shell/bazel/bazel_cc_code_coverage_test.sh
index ab7f640..fa1ded3 100755
--- a/src/test/shell/bazel/bazel_cc_code_coverage_test.sh
+++ b/src/test/shell/bazel/bazel_cc_code_coverage_test.sh
@@ -82,9 +82,20 @@
cat << EOF > "$ROOT_VAR/coverage_srcs/a.cc"
#include "a.h"
+#include "b.h"
int a(bool what) {
if (what) {
+ return b(1);
+ } else {
+ return b(-1);
+ }
+}
+EOF
+
+ cat << EOF > "$ROOT_VAR/coverage_srcs/b.h"
+int b(int what) {
+ if (what > 0) {
return 1;
} else {
return 2;
@@ -103,7 +114,9 @@
generate_and_execute_instrumented_binary coverage_srcs/test \
"$COVERAGE_DIR_VAR/coverage_srcs" \
- coverage_srcs/a.h coverage_srcs/a.cc coverage_srcs/t.cc
+ coverage_srcs/a.h coverage_srcs/a.cc \
+ coverage_srcs/b.h \
+ coverage_srcs/t.cc
# g++ generates the notes files in the current directory. The documentation
# (https://gcc.gnu.org/onlinedocs/gcc/Gcov-Data-Files.html#Gcov-Data-Files)
@@ -203,20 +216,43 @@
# The expected coverage result for coverage_srcs/a.cc in lcov format.
local expected_lcov_result_a_cc="TN:
SF:coverage_srcs/a.cc
-FN:3,_Z1ab
+FN:4,_Z1ab
FNDA:1,_Z1ab
FNF:1
FNH:1
-DA:3,1
DA:4,1
DA:5,1
-DA:7,0
+DA:6,1
+DA:8,0
LF:4
LH:3
end_of_record"
assert_coverage_entry_in_file "$expected_lcov_result_a_cc" "$output_file"
}
+# Asserts if coverage result in lcov format for coverage_srcs/a.cc is included
+# in the given output file.
+#
+# - output_file The location of the coverage output file.
+function assert_lcov_coverage_srcs_b_h() {
+ local output_file="${1}"; shift
+
+ # The expected coverage result for coverage_srcs/a.cc in lcov format.
+ local expected_lcov_result="SF:coverage_srcs/b.h
+FN:1,_Z1bi
+FNDA:1,_Z1bi
+FNF:1
+FNH:1
+DA:1,1
+DA:2,1
+DA:3,1
+DA:5,0
+LF:4
+LH:3
+end_of_record"
+ assert_coverage_entry_in_file "$expected_lcov_result" "$output_file"
+}
+
# Asserts if coverage result in lcov format for coverage_srcs/t.cc is included
# in the given output file.
#
@@ -249,13 +285,13 @@
# The expected coverage result for coverage_srcs/a.cc in gcov format.
local expected_gcov_result_a_cc="file:coverage_srcs/a.cc
-function:3,1,_Z1ab
-lcount:3,1
+function:4,1,_Z1ab
lcount:4,1
-branch:4,taken
-branch:4,nottaken
lcount:5,1
-lcount:7,0"
+branch:5,taken
+branch:5,nottaken
+lcount:6,1
+lcount:8,0"
assert_coverage_entry_in_file "$expected_gcov_result_a_cc" "$output_file"
}
@@ -276,6 +312,21 @@
assert_coverage_entry_in_file "$expected_gcov_result_t_cc" "$output_file"
}
+function assert_gcov_coverage_srcs_b_h() {
+ local output_file="${1}"; shift
+
+ # The expected coverage result for coverage_srcs/t.cc in gcov format.
+ local expected_gcov_result="file:coverage_srcs/b.h
+function:1,1,_Z1bi
+lcount:1,1
+lcount:2,1
+branch:2,taken
+branch:2,nottaken
+lcount:3,1
+lcount:5,0"
+ assert_coverage_entry_in_file "$expected_gcov_result" "$output_file"
+}
+
function test_cc_test_coverage_lcov() {
# Run the C++ coverage script with the environment setup accordingly.
# This will get coverage results for coverage_srcs/a.cc and
@@ -293,14 +344,14 @@
# order in the coverage report is not relevant.
assert_lcov_coverage_srcs_a_cc "$output_file"
assert_lcov_coverage_srcs_t_cc "$output_file"
+ assert_lcov_coverage_srcs_b_h "$output_file"
- # The expected total number of lines of output file is 25. This assertion
- # is needed to make sure no other source files are included in the output
- # file.
+ # This assertion is needed to make sure no other source files are included
+ # in the output file.
local nr_lines="$(wc -l < "$output_file")"
- [[ "$nr_lines" == 25 ]] || \
+ [[ "$nr_lines" == 37 ]] || \
fail "Number of lines in C++ lcov coverage output file is "\
- "$nr_lines and different than 25"
+ "$nr_lines and different than 37"
}
function test_cc_test_coverage_gcov() {
@@ -318,14 +369,6 @@
# Location of the output file of the C++ coverage script when gcov is used.
local output_file="$COVERAGE_DIR_VAR/_cc_coverage.gcov"
- # The expected total number of lines of output file is 13. This assertion
- # is needed to make sure no other source files are included in the output
- # file.
- local nr_lines="$(wc -l < "$output_file")"
- [[ "$nr_lines" == 13 ]] || \
- fail "Number of lines in C++ gcov coverage output file is "\
- "$nr_lines and different than 13"
-
# Assert that the coverage output file contains the coverage data for the
# two cc files: coverage_srcs/a.cc and coverage_srcs/t.cc.
# The result for each source file must be asserted separately because the
@@ -334,6 +377,14 @@
# order in the coverage report is not relevant.
assert_gcov_coverage_srcs_a_cc "$output_file"
assert_gcov_coverage_srcs_t_cc "$output_file"
+ assert_gcov_coverage_srcs_b_h "$output_file"
+
+ # This assertion is needed to make sure no other source files are included
+ # in the output file.
+ local nr_lines="$(wc -l < "$output_file")"
+ [[ "$nr_lines" == 21 ]] || \
+ fail "Number of lines in C++ gcov coverage output file is "\
+ "$nr_lines and different than 21"
}
run_suite "Testing tools/test/collect_cc_coverage.sh"
diff --git a/src/test/shell/bazel/bazel_coverage_test.sh b/src/test/shell/bazel/bazel_coverage_test.sh
index 4f57752..834626b 100755
--- a/src/test/shell/bazel/bazel_coverage_test.sh
+++ b/src/test/shell/bazel/bazel_coverage_test.sh
@@ -64,6 +64,25 @@
EOF
}
+# Returns 0 if gcov is not installed or if a version before 7.0 was found.
+# Returns 1 otherwise.
+function is_gcov_missing_or_wrong_version() {
+ local -r gcov_location=$(which gcov)
+ if [[ ! -x ${gcov_location:-/usr/bin/gcov} ]]; then
+ echo "gcov not installed."
+ return 0
+ fi
+
+ "$gcov_location" -version | grep "LLVM" && \
+ echo "gcov LLVM version not supported." && return 0
+ # gcov -v | grep "gcov" outputs a line that looks like this:
+ # gcov (Debian 7.3.0-5) 7.3.0
+ local gcov_version="$(gcov -v | grep "gcov" | cut -d " " -f 4 | cut -d "." -f 1)"
+ [ "$gcov_version" -lt 7 ] \
+ && echo "gcov versions before 7.0 is not supported." && return 0
+ return 1
+}
+
# Asserts if the given expected coverage result is included in the given output
# file.
#
@@ -110,8 +129,6 @@
local coverage_output_file="$( get_coverage_file_path_from_test_log )"
# Check the expected coverage for a.cc in the coverage file.
- # Note that t.cc is not included in the coverage report because it is
- # coming from a cc_test whose code is not included in the report by default.
local expected_result_a_cc="SF:a.cc
FN:3,_Z1ab
FNDA:1,_Z1ab
@@ -125,6 +142,9 @@
LF:4
end_of_record"
assert_coverage_result "$expected_result_a_cc" "$coverage_output_file"
+ # t.cc is not included in the coverage report because test targets are not
+ # instrumented by default.
+ assert_not_contains "SF:t\.cc" "$coverage_output_file"
# Verify that this is also true for cached coverage actions.
bazel coverage --test_output=all --build_event_text_file=bep.txt //:t \
@@ -136,21 +156,10 @@
}
function test_cc_test_coverage_gcov() {
- local -r gcov_location=$(which gcov)
- if [[ ! -x ${gcov_location:-/usr/bin/gcov} ]]; then
- echo "gcov not installed. Skipping test."
- return
+ if is_gcov_missing_or_wrong_version; then
+ echo "Skipping test." && return
fi
- "$gcov_location" -version | grep "LLVM" && \
- echo "gcov LLVM version not supported. Skipping test." && return
- # gcov -v | grep "gcov" outputs a line that looks like this:
- # gcov (Debian 7.3.0-5) 7.3.0
- local gcov_version="$(gcov -v | grep "gcov" | cut -d " " -f 4 | cut -d "." -f 1)"
- [ "$gcov_version" -lt 7 ] \
- && echo "gcov version before 7.0 is not supported. Skipping test." \
- && return
-
setup_a_cc_lib_and_t_cc_test
bazel coverage --experimental_cc_coverage --test_output=all \
@@ -160,8 +169,6 @@
local coverage_file_path="$( get_coverage_file_path_from_test_log )"
# Check the expected coverage for a.cc in the coverage file.
- # Note that t.cc is not included in the coverage report because it is
- # coming from a cc_test whose code is not included in the report by default.
local expected_result_a_cc="SF:a.cc
FN:3,_Z1ab
FNDA:1,_Z1ab
@@ -178,6 +185,9 @@
LF:4
end_of_record"
assert_coverage_result "$expected_result_a_cc" "$coverage_file_path"
+ # t.cc is not included in the coverage report because test targets are not
+ # instrumented by default.
+ assert_not_contains "SF:t\.cc" "$coverage_file_path"
# Verify that this is also true for cached coverage actions.
bazel coverage --experimental_cc_coverage --test_output=all \
@@ -365,6 +375,600 @@
assert_coverage_result "$coverage_result_hello_lib_header" "$coverage_file_path"
}
+function test_cc_test_gcov_multiple_headers() {
+ if is_gcov_missing_or_wrong_version; then
+ echo "Skipping test." && return
+ fi
+
+ ############## Setting up the test sources and BUILD file ##############
+ mkdir -p "coverage_srcs/"
+ cat << EOF > BUILD
+cc_library(
+ name = "a",
+ srcs = ["coverage_srcs/a.cc"],
+ hdrs = ["coverage_srcs/a.h", "coverage_srcs/b.h"]
+)
+
+cc_test(
+ name = "t",
+ srcs = ["coverage_srcs/t.cc"],
+ deps = [":a"]
+)
+EOF
+ cat << EOF > "coverage_srcs/a.h"
+int a(bool what);
+EOF
+
+ cat << EOF > "coverage_srcs/a.cc"
+#include "a.h"
+#include "b.h"
+#include <iostream>
+
+int a(bool what) {
+ if (what) {
+ std::cout << "Calling b(1)";
+ return b(1);
+ } else {
+ std::cout << "Calling b(-1)";
+ return b(-1);
+ }
+}
+EOF
+
+ cat << EOF > "coverage_srcs/b.h"
+int b(int what) {
+ if (what > 0) {
+ return 1;
+ } else {
+ return 2;
+ }
+}
+EOF
+
+ cat << EOF > "coverage_srcs/t.cc"
+#include <stdio.h>
+#include "a.h"
+
+int main(void) {
+ a(true);
+}
+EOF
+
+ ############## Running bazel coverage ##############
+ bazel coverage --experimental_cc_coverage --test_output=all //:t \
+ &>"$TEST_log" || fail "Coverage for //:t failed"
+
+ ##### Putting together the expected coverage results #####
+ local coverage_file_path="$( get_coverage_file_path_from_test_log )"
+ local expected_result_a_cc="SF:coverage_srcs/a.cc
+FN:13,_GLOBAL__sub_I_a.cc
+FN:5,_Z1ab
+FN:13,_Z41__static_initialization_and_destruction_0ii
+FNDA:1,_GLOBAL__sub_I_a.cc
+FNDA:1,_Z1ab
+FNDA:1,_Z41__static_initialization_and_destruction_0ii
+FNF:3
+FNH:3
+BA:6,2
+BA:13,2
+BRF:2
+BRH:2
+DA:5,1
+DA:6,1
+DA:7,1
+DA:8,1
+DA:10,0
+DA:11,0
+DA:13,3
+LH:5
+LF:7
+end_of_record"
+ local expected_result_b_h="SF:coverage_srcs/b.h
+FN:1,_Z1bi
+FNDA:1,_Z1bi
+FNF:1
+FNH:1
+BA:2,2
+BRF:1
+BRH:1
+DA:1,1
+DA:2,1
+DA:3,1
+DA:5,0
+LH:3
+LF:4
+end_of_record"
+ local expected_result_t_cc="SF:coverage_srcs/t.cc
+FN:4,main
+FNDA:1,main
+FNF:1
+FNH:1
+DA:4,1
+DA:5,1
+DA:6,1
+LH:3
+LF:3
+end_of_record"
+
+ ############## Asserting the coverage results ##############
+ assert_coverage_result "$expected_result_a_cc" "$coverage_file_path"
+ assert_coverage_result "$expected_result_b_h" "$coverage_file_path"
+ # coverage_srcs/t.cc is not included in the coverage report because the test
+ # targets are not instrumented by default.
+ assert_not_contains "SF:coverage_srcs/t\.cc" "$coverage_file_path"
+ # iostream should not be in the final coverage report because it is a syslib
+ assert_not_contains "iostream" "$coverage_file_path"
+}
+
+function test_cc_test_gcov_multiple_headers_instrument_test_target() {
+ if is_gcov_missing_or_wrong_version; then
+ echo "Skipping test." && return
+ fi
+
+ ############## Setting up the test sources and BUILD file ##############
+ mkdir -p "coverage_srcs/"
+ cat << EOF > BUILD
+cc_library(
+ name = "a",
+ srcs = ["coverage_srcs/a.cc"],
+ hdrs = ["coverage_srcs/a.h", "coverage_srcs/b.h"]
+)
+
+cc_test(
+ name = "t",
+ srcs = ["coverage_srcs/t.cc"],
+ deps = [":a"]
+)
+EOF
+ cat << EOF > "coverage_srcs/a.h"
+int a(bool what);
+EOF
+
+ cat << EOF > "coverage_srcs/a.cc"
+#include "a.h"
+#include "b.h"
+#include <iostream>
+
+int a(bool what) {
+ if (what) {
+ std::cout << "Calling b(1)";
+ return b(1);
+ } else {
+ std::cout << "Calling b(-1)";
+ return b(-1);
+ }
+}
+EOF
+
+ cat << EOF > "coverage_srcs/b.h"
+int b(int what) {
+ if (what > 0) {
+ return 1;
+ } else {
+ return 2;
+ }
+}
+EOF
+
+ cat << EOF > "coverage_srcs/t.cc"
+#include <stdio.h>
+#include "a.h"
+
+int main(void) {
+ a(true);
+}
+EOF
+
+ ############## Running bazel coverage ##############
+ bazel coverage --experimental_cc_coverage --instrument_test_targets \
+ --test_output=all //:t &>"$TEST_log" || fail "Coverage for //:t failed"
+
+ ##### Putting together the expected coverage results #####
+ local coverage_file_path="$( get_coverage_file_path_from_test_log )"
+ local expected_result_a_cc="SF:coverage_srcs/a.cc
+FN:13,_GLOBAL__sub_I_a.cc
+FN:5,_Z1ab
+FN:13,_Z41__static_initialization_and_destruction_0ii
+FNDA:1,_GLOBAL__sub_I_a.cc
+FNDA:1,_Z1ab
+FNDA:1,_Z41__static_initialization_and_destruction_0ii
+FNF:3
+FNH:3
+BA:6,2
+BA:13,2
+BRF:2
+BRH:2
+DA:5,1
+DA:6,1
+DA:7,1
+DA:8,1
+DA:10,0
+DA:11,0
+DA:13,3
+LH:5
+LF:7
+end_of_record"
+ local expected_result_b_h="SF:coverage_srcs/b.h
+FN:1,_Z1bi
+FNDA:1,_Z1bi
+FNF:1
+FNH:1
+BA:2,2
+BRF:1
+BRH:1
+DA:1,1
+DA:2,1
+DA:3,1
+DA:5,0
+LH:3
+LF:4
+end_of_record"
+ local expected_result_t_cc="SF:coverage_srcs/t.cc
+FN:4,main
+FNDA:1,main
+FNF:1
+FNH:1
+DA:4,1
+DA:5,1
+DA:6,1
+LH:3
+LF:3
+end_of_record"
+
+ ############## Asserting the coverage results ##############
+ assert_coverage_result "$expected_result_a_cc" "$coverage_file_path"
+ assert_coverage_result "$expected_result_b_h" "$coverage_file_path"
+ # coverage_srcs/t.cc should be included in the coverage report
+ assert_coverage_result "$expected_result_t_cc" "$coverage_file_path"
+ # iostream should not be in the final coverage report because it is a syslib
+ assert_not_contains "iostream" "$coverage_file_path"
+}
+
+function test_cc_test_gcov_same_header_different_libs() {
+ if is_gcov_missing_or_wrong_version; then
+ echo "Skipping test." && return
+ fi
+
+ ############## Setting up the test sources and BUILD file ##############
+ mkdir -p "coverage_srcs/"
+ cat << EOF > BUILD
+cc_library(
+ name = "a",
+ srcs = ["coverage_srcs/a.cc"],
+ hdrs = ["coverage_srcs/a.h", "coverage_srcs/b.h"]
+)
+
+cc_library(
+ name = "c",
+ srcs = ["coverage_srcs/c.cc"],
+ hdrs = ["coverage_srcs/c.h", "coverage_srcs/b.h"]
+)
+
+cc_test(
+ name = "t",
+ srcs = ["coverage_srcs/t.cc"],
+ deps = [":a", ":c"]
+)
+EOF
+ cat << EOF > "coverage_srcs/a.h"
+int a(bool what);
+EOF
+
+ cat << EOF > "coverage_srcs/a.cc"
+#include "a.h"
+#include "b.h"
+
+int a(bool what) {
+ if (what) {
+ return b_for_a(1);
+ } else {
+ return b_for_a(-1);
+ }
+}
+EOF
+
+ cat << EOF > "coverage_srcs/b.h"
+// Lines 2-8 are covered by calling b_for_a from a.cc.
+int b_for_a(int what) { // Line 2: executed once
+ if (what > 0) { // Line 3: executed once
+ return 1; // Line 4: executed once
+ } else {
+ return 2; // Line 6: not executed
+ }
+}
+
+// Lines 11-17 are covered by calling b_for_a from a.cc.
+int b_for_c(int what) { // Line 11: executed once
+ if (what > 0) { // Line 12: executed once
+ return 1; // Line 13: not executed
+ } else {
+ return 2; // Line 15: executed once
+ }
+}
+EOF
+
+ cat << EOF > "coverage_srcs/c.h"
+int c(bool what);
+EOF
+
+ cat << EOF > "coverage_srcs/c.cc"
+#include "c.h"
+#include "b.h"
+
+int c(bool what) {
+ if (what) {
+ return b_for_c(1);
+ } else {
+ return b_for_c(-1);
+ }
+}
+EOF
+
+ cat << EOF > "coverage_srcs/t.cc"
+#include "a.h"
+#include "c.h"
+
+int main(void) {
+ a(true);
+ c(false);
+}
+EOF
+
+ ############## Running bazel coverage ##############
+ bazel coverage --experimental_cc_coverage --test_output=all //:t \
+ &>"$TEST_log" || fail "Coverage for //:t failed"
+
+ ##### Putting together the expected coverage results #####
+ local coverage_file_path="$( get_coverage_file_path_from_test_log )"
+ local expected_result_a_cc="SF:coverage_srcs/a.cc
+FN:4,_Z1ab
+FNDA:1,_Z1ab
+FNF:1
+FNH:1
+BA:5,2
+BRF:1
+BRH:1
+DA:4,1
+DA:5,1
+DA:6,1
+DA:8,0
+LH:3
+LF:4
+end_of_record"
+ local expected_result_b_h="SF:coverage_srcs/b.h
+FN:2,_Z7b_for_ai
+FN:11,_Z7b_for_ci
+FNDA:1,_Z7b_for_ai
+FNDA:1,_Z7b_for_ci
+FNF:2
+FNH:2
+BA:3,2
+BA:12,2
+BRF:2
+BRH:2
+DA:2,1
+DA:3,1
+DA:4,1
+DA:6,0
+DA:11,1
+DA:12,1
+DA:13,0
+DA:15,1
+LH:6
+LF:8
+end_of_record"
+ local expected_result_c_cc="SF:coverage_srcs/c.cc
+FN:4,_Z1cb
+FNDA:1,_Z1cb
+FNF:1
+FNH:1
+BA:5,2
+BRF:1
+BRH:1
+DA:4,1
+DA:5,1
+DA:6,0
+DA:8,1
+LH:3
+LF:4
+end_of_record"
+
+ ############## Asserting the coverage results ##############
+ assert_coverage_result "$expected_result_a_cc" "$coverage_file_path"
+ assert_coverage_result "$expected_result_b_h" "$coverage_file_path"
+ assert_coverage_result "$expected_result_c_cc" "$coverage_file_path"
+ # coverage_srcs/t.cc is not included in the coverage report because the test
+ # targets are not instrumented by default.
+ assert_not_contains "SF:coverage_srcs/t\.cc" "$coverage_file_path"
+}
+
+function test_cc_test_gcov_same_header_different_libs_multiple_exec() {
+ if is_gcov_missing_or_wrong_version; then
+ echo "Skipping test." && return
+ fi
+
+ ############## Setting up the test sources and BUILD file ##############
+ mkdir -p "coverage_srcs/"
+ cat << EOF > BUILD
+cc_library(
+ name = "a",
+ srcs = ["coverage_srcs/a.cc"],
+ hdrs = ["coverage_srcs/a.h", "coverage_srcs/b.h"]
+)
+
+cc_library(
+ name = "c",
+ srcs = ["coverage_srcs/c.cc"],
+ hdrs = ["coverage_srcs/c.h", "coverage_srcs/b.h"]
+)
+
+cc_test(
+ name = "t",
+ srcs = ["coverage_srcs/t.cc"],
+ deps = [":a", ":c"]
+)
+EOF
+ cat << EOF > "coverage_srcs/a.h"
+int a(bool what);
+int a_redirect();
+EOF
+
+ cat << EOF > "coverage_srcs/a.cc"
+#include "a.h"
+#include "b.h"
+
+int a(bool what) {
+ if (what) {
+ return b_for_a(1);
+ } else {
+ return b_for_a(-1);
+ }
+}
+
+int a_redirect() {
+ return b_for_all();
+}
+EOF
+
+ cat << EOF > "coverage_srcs/b.h"
+// Lines 2-8 are covered by calling b_for_a from a.cc.
+int b_for_a(int what) { // Line 2: executed once
+ if (what > 0) { // Line 3: executed once
+ return 1; // Line 4: executed once
+ } else {
+ return 2; // Line 6: not executed
+ }
+}
+
+// Lines 11-17 are covered by calling b_for_a from a.cc.
+int b_for_c(int what) { // Line 11: executed once
+ if (what > 0) { // Line 12: executed once
+ return 1; // Line 13: not executed
+ } else {
+ return 2; // Line 15: executed once
+ }
+}
+
+int b_for_all() { // Line 21: executed 3 times (2x from a.cc and 1x from c.cc)
+ return 10; // Line 21: executed 3 times (2x from a.cc and 1x from c.cc)
+}
+EOF
+
+ cat << EOF > "coverage_srcs/c.h"
+int c(bool what);
+int c_redirect();
+EOF
+
+ cat << EOF > "coverage_srcs/c.cc"
+#include "c.h"
+#include "b.h"
+
+int c(bool what) {
+ if (what) {
+ return b_for_c(1);
+ } else {
+ return b_for_c(-1);
+ }
+}
+
+int c_redirect() {
+ return b_for_all();
+}
+EOF
+
+ cat << EOF > "coverage_srcs/t.cc"
+#include "a.h"
+#include "c.h"
+
+int main(void) {
+ a(true);
+ c(false);
+ a_redirect();
+ a_redirect();
+ c_redirect();
+}
+EOF
+
+ ############## Running bazel coverage ##############
+ bazel coverage --experimental_cc_coverage --test_output=all //:t \
+ &>"$TEST_log" || fail "Coverage for //:t failed"
+
+ ##### Putting together the expected coverage results #####
+ local coverage_file_path="$( get_coverage_file_path_from_test_log )"
+ local expected_result_a_cc="SF:coverage_srcs/a.cc
+FN:12,_Z10a_redirectv
+FN:4,_Z1ab
+FNDA:2,_Z10a_redirectv
+FNDA:1,_Z1ab
+FNF:2
+FNH:2
+BA:5,2
+BRF:1
+BRH:1
+DA:4,1
+DA:5,1
+DA:6,1
+DA:8,0
+DA:12,2
+DA:13,2
+LH:5
+LF:6
+end_of_record"
+ local expected_result_b_h="SF:coverage_srcs/b.h
+FN:2,_Z7b_for_ai
+FN:11,_Z7b_for_ci
+FN:19,_Z9b_for_allv
+FNDA:1,_Z7b_for_ai
+FNDA:1,_Z7b_for_ci
+FNDA:3,_Z9b_for_allv
+FNF:3
+FNH:3
+BA:3,2
+BA:12,2
+BRF:2
+BRH:2
+DA:2,1
+DA:3,1
+DA:4,1
+DA:6,0
+DA:11,1
+DA:12,1
+DA:13,0
+DA:15,1
+DA:19,3
+DA:20,3
+LH:8
+LF:10
+end_of_record"
+ local expected_result_c_cc="SF:coverage_srcs/c.cc
+FN:12,_Z10c_redirectv
+FN:4,_Z1cb
+FNDA:1,_Z10c_redirectv
+FNDA:1,_Z1cb
+FNF:2
+FNH:2
+BA:5,2
+BRF:1
+BRH:1
+DA:4,1
+DA:5,1
+DA:6,0
+DA:8,1
+DA:12,1
+DA:13,1
+LH:5
+LF:6
+end_of_record"
+
+ ############## Asserting the coverage results ##############
+ assert_coverage_result "$expected_result_a_cc" "$coverage_file_path"
+ assert_coverage_result "$expected_result_b_h" "$coverage_file_path"
+ assert_coverage_result "$expected_result_c_cc" "$coverage_file_path"
+ # coverage_srcs/t.cc is not included in the coverage report because the test
+ # targets are not instrumented by default.
+ assert_not_contains "SF:coverage_srcs/t\.cc" "$coverage_file_path"
+}
+
function test_cc_test_llvm_coverage_doesnt_fail() {
local -r llvmprofdata=$(which llvm-profdata)
if [[ ! -x ${llvmprofdata:-/usr/bin/llvm-profdata} ]]; then
diff --git a/tools/test/collect_cc_coverage.sh b/tools/test/collect_cc_coverage.sh
index d81d857..af59eda 100755
--- a/tools/test/collect_cc_coverage.sh
+++ b/tools/test/collect_cc_coverage.sh
@@ -113,14 +113,15 @@
function gcov_coverage() {
local output_file="${1}"; shift
- touch "$output_file"
+ # We'll save the standard output of each the gcov command in this log.
+ local gcov_log="$output_file.gcov.log"
- # Move .gcno files in $COVERAGE_DIR as the gcda files, because gcov
- # expects them to be under the same directory.
+ # Copy .gcno files next to their corresponding .gcda files in $COVERAGE_DIR
+ # because gcov expects them to be in the same directory.
cat "${COVERAGE_MANIFEST}" | grep ".gcno$" | while read gcno_path; do
local gcda="${COVERAGE_DIR}/$(dirname ${gcno_path})/$(basename ${gcno_path} .gcno).gcda"
- # If the gcda file was not found we generate empty coverage from the gcno
+ # If the gcda file was not found we skip generating coverage from the gcno
# file.
if [[ -f "$gcda" ]]; then
# gcov expects both gcno and gcda files to be in the same directory.
@@ -140,92 +141,53 @@
# .gcda data files.
# "${gcda"} The input file name. gcov is looking for data files
# named after the input filename without its extension.
- "${GCOV}" -i -b -o "$(dirname ${gcda})" "${gcda}"
-
# gcov produces files called <source file name>.gcov in the current
# directory. These contain the coverage information of the source file
# they correspond to. One .gcov file is produced for each source
- # (and/or header) file containing code which was compiled to produce
- # the .gcda files.
- # We try to find the correct source and header files that were generated
- # for the current gcno.
- # Retrieving every .gcov file that was generated in the current
- # directory is not correct because it can contain coverage information
- # for sources that are not included by the command line flag
- # --instrumentation_filter.
+ # (or header) file containing code which was compiled to produce the
+ # .gcda files.
+ "${GCOV}" -i -b -o "$(dirname ${gcda})" "${gcda}" &> "$gcov_log"
- local gcov_file="$(get_source_or_header_file source $gcno_path)"
- if [ -f "$gcov_file" ]; then
- cat "$gcov_file" >> "${output_file}"
- # We don't need this file anymore.
- rm -f "$gcov_file"
- fi
-
- gcov_file="$(get_source_or_header_file header $gcno_path)"
- if [ -f "$gcov_file" ]; then
- cat "$gcov_file" >> "${output_file}"
- # We don't need this file anymore.
- rm -f "$gcov_file"
- fi
+ # Go through all the files that were created by the gcov command above
+ # and append their content to the output .gcov file.
+ #
+ # For each source file gcov outputs to stdout something like this:
+ #
+ # File 'examples/cpp/hello-lib.cc'
+ # Lines executed:100.00% of 8
+ # Creating 'hello-lib.cc.gcov'
+ #
+ # We grep the names of the files that were created from that output.
+ cat "$gcov_log" | grep "Creating" | cut -d " " -f 2 | cut -d"'" -f2 | \
+ while read gcov_file; do
+ echo "Processing $gcov_file"
+ cat "$gcov_file" >> "$output_file"
+ # Remove the intermediate gcov file because it is not useful anymore.
+ rm -f "$gcov_file"
+ done
fi
done
- echo "Coverage output file contains:"
- cat "${output_file}"
-}
-
-# Returns a .gcov corresponding to either a C++ source file or a C++ header
-# file depending on the given file type, that could have been generated by gcov
-# for the given gcno file.
-#
-# - filetype Can be either "source" or "header".
-# - gcno_file The .gcno filename.
-function get_source_or_header_file() {
- local filetype="${1}"; shift
- local gcno_file="${1}"; shift
-
- # gcov places results in the current working dir. The gcov documentation
- # doesn't provide much details about how the name of the output file is
- # generated, other than hinting at it being named <source file name>.gcov.
- # Since we only know the gcno filename, we try and see which of the
- # following extensions the source file had.
- declare -a source_extensions
-
- case "$filetype" in
- ("source") source_extensions=("" ".cc" ".cpp" ".c") ;;
- ("header") source_extensions=(".h" ".hh") ;;
- esac
-
- declare -a is_pic_extensions=("" ".pic")
-
- local gcov_file=""
- for ext in "${source_extensions[@]}"
- do
- for pic_ext in "${is_pic_extensions[@]}"
- do
- gcov_file="$(basename ${gcno_file} "$pic_ext.gcno")$ext.gcov"
- if [ -f "$gcov_file" ]; then
- echo "$gcov_file" && return
- fi
- done
- done
}
function main() {
init_gcov
+ # If llvm code coverage is used, we output the raw code coverage report in
+ # the $COVERAGE_OUTPUT_FILE. This report will not be converted to any other
+ # format by LcovMerger.
+ # TODO(#5881): Convert profdata reports to lcov.
if uses_llvm; then
BAZEL_CC_COVERAGE_TOOL="PROFDATA"
fi
- # All the output files must be generated under COVERAGE_DIR.
- #
- # When this script is invoked by tools/test/collect_coverage.sh the
- # .dat and .gcov files will be picked up by CoverageOutputGenerator and their
+ # When using either gcov or lcov, have an output file specific to the test
+ # and format used. For lcov we generate a ".dat" output file and for gcov
+ # a ".gcov" output file. It is important that these files are generated under
+ # COVERAGE_DIR.
+ # When this script is invoked by tools/test/collect_coverage.sh either of
+ # these two coverage reports will be picked up by LcovMerger and their
# content will be converted and/or merged with other reports to an lcov
# format, generating the final code coverage report.
- # The .profdata file will also be picked up by CoverageOutputGenerator but it
- # won't be merged or converted to lcov, but its content will be copied to the
- # final code coverage report.
case "$BAZEL_CC_COVERAGE_TOOL" in
("GCOV") gcov_coverage "$COVERAGE_DIR/_cc_coverage.gcov" ;;
("LCOV") lcov_coverage "$COVERAGE_DIR/_cc_coverage.dat" ;;