Collect coverage from cc_binary data deps of java_test

Before this commit, if a java_test executed a cc_binary from its data
deps, coverage for this cc_binary would not be collected. This is fixed
by adding the implicit `$collect_cc_coverage` attribute to
BazelJavaTestRule, similar to how this is already done for
BazelShTestRule.

Fixes the Java part of #15098.

Closes #15096.

PiperOrigin-RevId: 438785232
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaTestRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaTestRule.java
index 6495561..6ba2788 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaTestRule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaTestRule.java
@@ -62,6 +62,13 @@
             attr(":lcov_merger", LABEL)
                 .cfg(ExecutionTransitionFactory.create())
                 .value(BaseRuleClasses.getCoverageOutputGeneratorLabel()))
+        // Add the script as an attribute in order for java_test to output code coverage results for
+        // code covered by CC binaries invocations.
+        .add(
+            attr("$collect_cc_coverage", LABEL)
+                .cfg(ExecutionTransitionFactory.create())
+                .singleArtifact()
+                .value(env.getToolsLabel("//tools/test:collect_cc_coverage")))
         /* <!-- #BLAZE_RULE(java_test).ATTRIBUTE(test_class) -->
         The Java class to be loaded by the test runner.<br/>
         <p>
diff --git a/src/test/shell/bazel/bazel_coverage_java_test.sh b/src/test/shell/bazel/bazel_coverage_java_test.sh
index d8104ce..27f86db 100755
--- a/src/test/shell/bazel/bazel_coverage_java_test.sh
+++ b/src/test/shell/bazel/bazel_coverage_java_test.sh
@@ -89,6 +89,25 @@
     cat $(rlocation io_bazel/src/test/shell/bazel/testdata/jdk_http_archives) >> WORKSPACE
 }
 
+# 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.
 #
@@ -990,4 +1009,138 @@
   assert_coverage_result "$expected_result" "$coverage_file_path"
 }
 
+function test_java_test_coverage_cc_binary() {
+  if is_gcov_missing_or_wrong_version; then
+    echo "Skipping test." && return
+  fi
+
+  ########### Setup source files and BUILD file ###########
+  cat <<EOF > BUILD
+java_test(
+    name = "NumJava",
+    srcs = ["NumJava.java"],
+    data = ["//examples/cpp:num-world"],
+    main_class = "main.NumJava",
+    use_testrunner = False,
+)
+EOF
+  cat <<EOF > NumJava.java
+package main;
+
+public class NumJava {
+  public static void main(String[] args) throws java.io.IOException {
+    Runtime.getRuntime().exec("examples/cpp/num-world");
+  }
+}
+EOF
+
+  mkdir -p examples/cpp
+
+  cat <<EOF > examples/cpp/BUILD
+package(default_visibility = ["//visibility:public"])
+
+cc_binary(
+    name = "num-world",
+    srcs = ["num-world.cc"],
+    deps = [":num-lib"],
+)
+
+cc_library(
+    name = "num-lib",
+    srcs = ["num-lib.cc"],
+    hdrs = ["num-lib.h"]
+)
+EOF
+
+  cat <<EOF > examples/cpp/num-world.cc
+#include "examples/cpp/num-lib.h"
+
+using num::NumLib;
+
+int main(int argc, char** argv) {
+  NumLib lib(30);
+  int value = 42;
+  if (argc > 1) {
+    value = 43;
+  }
+  lib.add_number(value);
+  return 0;
+}
+EOF
+
+  cat <<EOF > examples/cpp/num-lib.h
+#ifndef EXAMPLES_CPP_NUM_LIB_H_
+#define EXAMPLES_CPP_NUM_LIB_H_
+
+namespace num {
+
+class NumLib {
+ public:
+  explicit NumLib(int number);
+
+  int add_number(int value);
+
+ private:
+  int number_;
+};
+
+}  // namespace num
+
+#endif  // EXAMPLES_CPP_NUM_LIB_H_
+EOF
+
+  cat <<EOF > examples/cpp/num-lib.cc
+#include "examples/cpp/num-lib.h"
+
+namespace num {
+
+NumLib::NumLib(int number) : number_(number) {
+}
+
+int NumLib::add_number(int value) {
+  return number_ + value;
+}
+
+}  // namespace num
+EOF
+
+  ########### Run bazel coverage ###########
+  bazel coverage  --test_output=all \
+      //:NumJava &>$TEST_log || fail "Coverage for //:NumJava failed"
+
+  ########### Assert coverage results. ###########
+  local coverage_file_path="$( get_coverage_file_path_from_test_log )"
+  local expected_result_num_lib="SF:examples/cpp/num-lib.cc
+FN:8,_ZN3num6NumLib10add_numberEi
+FN:5,_ZN3num6NumLibC2Ei
+FNDA:1,_ZN3num6NumLib10add_numberEi
+FNDA:1,_ZN3num6NumLibC2Ei
+FNF:2
+FNH:2
+DA:5,1
+DA:6,1
+DA:8,1
+DA:9,1
+LH:4
+LF:4
+end_of_record"
+  assert_coverage_result "$expected_result_num_lib" "$coverage_file_path"
+  local coverage_result_num_lib_header="SF:examples/cpp/num-world.cc
+FN:5,main
+FNDA:1,main
+FNF:1
+FNH:1
+DA:5,1
+DA:6,1
+DA:7,1
+DA:8,1
+DA:9,0
+DA:11,1
+DA:12,1
+LH:6
+LF:7
+end_of_record"
+  assert_coverage_result "$coverage_result_num_lib_header" "$coverage_file_path"
+}
+
 run_suite "test tests"