Don't skip filtering when manifest of source-file paths is empty

When a coverage entry doesn't correspond to a file listed in execPathsOfUninstrumentedFiles, it's discarded. However, since this class has a constructor which takes no arguments and sets execPathsOfUninstrumentedFiles to an empty-set. But this leads to those same no-filtering behavior when the constructor is passed an empty list of valid source files.

Instead, the "leave as is" behavior should be conditioned on `execPathsOfUninstrumentedFiles == null`, and an empty set should be treated consistently with other sets with no matching items.

RELNOTES: None.
PiperOrigin-RevId: 445497757
diff --git a/src/java_tools/junitrunner/java/com/google/testing/coverage/JacocoLCOVFormatter.java b/src/java_tools/junitrunner/java/com/google/testing/coverage/JacocoLCOVFormatter.java
index ae9f058..b2c2bc5 100644
--- a/src/java_tools/junitrunner/java/com/google/testing/coverage/JacocoLCOVFormatter.java
+++ b/src/java_tools/junitrunner/java/com/google/testing/coverage/JacocoLCOVFormatter.java
@@ -20,6 +20,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.TreeMap;
 import org.jacoco.core.analysis.IBundleCoverage;
 import org.jacoco.core.analysis.IClassCoverage;
@@ -44,17 +45,21 @@
   // jars passed through java_import or some custom rule where blaze doesn't have enough context to
   // compute the right paths, but relies on these pre-computed exec paths.
   // Exec paths can be provided in two formats, either as a plain string or as a delimited
-  // string mapping source file paths to class paths.
-  private final ImmutableSet<String> execPathsOfUninstrumentedFiles;
+  // string mapping source file paths to class paths. Coverage entries whose class-paths are not the
+  // suffix of any file in this list are discarded.  If not provided (as is
+  // the case when class is initialized with the zero-argument constructor), the entries are
+  // returned unchanged (but note this may result in LCOV output which do not reference actual
+  // file-paths).
+  private final Optional<ImmutableSet<String>> execPathsOfUninstrumentedFiles;
 
   private static final String EXEC_PATH_DELIMITER = "///";
 
   public JacocoLCOVFormatter(ImmutableSet<String> execPathsOfUninstrumentedFiles) {
-    this.execPathsOfUninstrumentedFiles = execPathsOfUninstrumentedFiles;
+    this.execPathsOfUninstrumentedFiles = Optional.of(execPathsOfUninstrumentedFiles);
   }
 
   public JacocoLCOVFormatter() {
-    this.execPathsOfUninstrumentedFiles = ImmutableSet.of();
+    this.execPathsOfUninstrumentedFiles = Optional.empty();
   }
 
   public IReportVisitor createVisitor(
@@ -64,13 +69,13 @@
       private Map<String, Map<String, IClassCoverage>> sourceToClassCoverage = new TreeMap<>();
       private Map<String, ISourceFileCoverage> sourceToFileCoverage = new TreeMap<>();
 
-      private String getExecPathForEntryName(String fileName) {
+      private String getExecPathForEntryName(String classPath) {
         if (execPathsOfUninstrumentedFiles.isEmpty()) {
-          return fileName;
+          return classPath;
         }
 
-        String matchingFileName = fileName.startsWith("/") ? fileName : "/" + fileName;
-        for (String execPath : execPathsOfUninstrumentedFiles) {
+        String matchingFileName = classPath.startsWith("/") ? classPath : "/" + classPath;
+        for (String execPath : execPathsOfUninstrumentedFiles.get()) {
           if (execPath.contains(EXEC_PATH_DELIMITER)) {
             String[] parts = execPath.split(EXEC_PATH_DELIMITER, 2);
             if (parts.length != 2) {
diff --git a/src/java_tools/junitrunner/javatests/com/google/testing/coverage/JacocoLCOVFormatterUninstrumentedTest.java b/src/java_tools/junitrunner/javatests/com/google/testing/coverage/JacocoLCOVFormatterUninstrumentedTest.java
index 0e6aab1..d56d33e 100644
--- a/src/java_tools/junitrunner/javatests/com/google/testing/coverage/JacocoLCOVFormatterUninstrumentedTest.java
+++ b/src/java_tools/junitrunner/javatests/com/google/testing/coverage/JacocoLCOVFormatterUninstrumentedTest.java
@@ -146,7 +146,7 @@
 
   @Test
   public void testVisitBundleWithNoMatchHasEmptyOutput() throws IOException {
-    // Paths
+    // Non-matching path
     ImmutableSet<String> execPaths = ImmutableSet.of("/path/does/not/match/anything.txt");
     JacocoLCOVFormatter formatter = new JacocoLCOVFormatter(execPaths);
     IReportVisitor visitor =
@@ -159,4 +159,36 @@
     String coverageOutput = writer.toString();
     assertThat(coverageOutput).isEmpty();
   }
+
+  @Test
+  public void testVisitBundleWithNoExecPathsHasEmptyOutput() throws IOException {
+    // Empty list of exec paths
+    ImmutableSet<String> execPaths = ImmutableSet.of();
+    JacocoLCOVFormatter formatter = new JacocoLCOVFormatter(execPaths);
+    IReportVisitor visitor =
+        formatter.createVisitor(
+            new PrintWriter(writer), new TreeMap<String, BranchCoverageDetail>());
+
+    visitor.visitBundle(mockBundle, mock(ISourceFileLocator.class));
+    visitor.visitEnd();
+
+    String coverageOutput = writer.toString();
+    assertThat(coverageOutput).isEmpty();
+  }
+
+  @Test
+  public void testVisitBundleWithoutExecPathsDoesNotPruneOutput() throws IOException {
+    // No paths, don't attempt to demangle paths and prune the output, just output with
+    // class-paths as is.
+    JacocoLCOVFormatter formatter = new JacocoLCOVFormatter();
+    IReportVisitor visitor =
+        formatter.createVisitor(
+            new PrintWriter(writer), new TreeMap<String, BranchCoverageDetail>());
+
+    visitor.visitBundle(mockBundle, mock(ISourceFileLocator.class));
+    visitor.visitEnd();
+
+    String coverageOutput = writer.toString();
+    assertThat(coverageOutput).isNotEmpty();
+  }
 }