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" ;;