Lcov merger no longer fails on empty coverage

 - Lcov merger now produces an output file upon empty inputs
 - Prevents build failure on empty coverage invocations

PiperOrigin-RevId: 296019600
diff --git a/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Main.java b/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Main.java
index cd2ca7d..4e50e29 100644
--- a/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Main.java
+++ b/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Main.java
@@ -47,12 +47,22 @@
   private static final Logger logger = Logger.getLogger(Main.class.getName());
 
   public static void main(String... args) {
+    try {
+      int exitCode = runWithArgs(args);
+      System.exit(exitCode);
+    } catch (Exception e) {
+      logger.log(Level.SEVERE, "Unhandled exception on lcov tool: " + e.getMessage());
+      System.exit(1);
+    }
+  }
+
+  static int runWithArgs(String... args) {
     LcovMergerFlags flags = null;
     try {
       flags = LcovMergerFlags.parseFlags(args);
     } catch (IllegalArgumentException e) {
       logger.log(Level.SEVERE, e.getMessage());
-      System.exit(1);
+      return 1;
     }
 
     File outputFile = new File(flags.outputFile());
@@ -80,8 +90,19 @@
     if (coverage.isEmpty()) {
       int exitStatus = 0;
       if (profdataFile == null) {
-        logger.log(Level.WARNING, "There was no coverage found.");
-        exitStatus = 0;
+        try {
+          logger.log(Level.WARNING, "There was no coverage found.");
+          Files.createFile(outputFile.toPath()); // Generate empty declared output
+          exitStatus = 0;
+        } catch (IOException e) {
+          logger.log(
+              Level.SEVERE,
+              "Could not create empty output file "
+                  + outputFile.getName()
+                  + " due to: "
+                  + e.getMessage());
+          exitStatus = 1;
+        }
       } else {
         // Bazel doesn't support yet converting profdata files to lcov. We still want to output a
         // coverage report so we copy the content of the profdata file to the output file. This is
@@ -102,7 +123,7 @@
           exitStatus = 1;
         }
       }
-      System.exit(exitStatus);
+      return exitStatus;
     }
 
     if (!coverage.isEmpty() && profdataFile != null) {
@@ -112,7 +133,7 @@
       logger.log(
           Level.WARNING,
           "Bazel doesn't support LLVM profdata coverage amongst other coverage formats.");
-      System.exit(0);
+      return 0;
     }
 
     if (!flags.filterSources().isEmpty()) {
@@ -129,8 +150,19 @@
     }
 
     if (coverage.isEmpty()) {
-      logger.log(Level.WARNING, "There was no coverage found.");
-      System.exit(0);
+      try {
+        logger.log(Level.WARNING, "There was no coverage found.");
+        Files.createFile(outputFile.toPath()); // Generate empty declared output
+        return 0;
+      } catch (IOException e) {
+        logger.log(
+            Level.SEVERE,
+            "Could not create empty output file "
+                + outputFile.getName()
+                + " due to: "
+                + e.getMessage());
+        return 1;
+      }
     }
 
     int exitStatus = 0;
@@ -143,7 +175,7 @@
           "Could not write to output file " + outputFile + " due to " + e.getMessage());
       exitStatus = 1;
     }
-    System.exit(exitStatus);
+    return exitStatus;
   }
 
   /**
diff --git a/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/MainTest.java b/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/MainTest.java
index 84b650a..370458f 100644
--- a/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/MainTest.java
+++ b/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/MainTest.java
@@ -22,11 +22,13 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.List;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
@@ -35,6 +37,7 @@
 public class MainTest {
 
   @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+  @Rule public TestName testName = new TestName();
   private Path coverageDir;
 
   @Before
@@ -70,6 +73,37 @@
     assertParallelParse(4, 1024, 1024);
   }
 
+  @Test
+  public void testEmptyInputProducesEmptyOutput() throws IOException {
+    Path output =
+        Paths.get(
+            temporaryFolder.getRoot().getAbsolutePath(),
+            testName.getMethodName() + ".coverage.dat");
+    int exitCode =
+        Main.runWithArgs(
+            "--coverage_dir", coverageDir.toAbsolutePath().toString(),
+            "--output_file", output.toAbsolutePath().toString());
+    assertThat(exitCode).isEqualTo(0);
+    assertThat(output.toFile().exists()).isTrue();
+    assertThat(output.toFile().length()).isEqualTo(0L);
+  }
+
+  @Test
+  public void testNonEmptyInputProducesNonEmptyOutput() throws IOException {
+    LcovMergerTestUtils.generateLcovFiles("test_data/simple_test", 8, 8, 8, coverageDir);
+    Path output =
+        Paths.get(
+            temporaryFolder.getRoot().getAbsolutePath(),
+            testName.getMethodName() + ".coverage.dat");
+    int exitCode =
+        Main.runWithArgs(
+            "--coverage_dir", coverageDir.toAbsolutePath().toString(),
+            "--output_file", output.toAbsolutePath().toString());
+    assertThat(exitCode).isEqualTo(0);
+    assertThat(output.toFile().exists()).isTrue();
+    assertThat(output.toFile().length()).isGreaterThan(0L);
+  }
+
   private void assertParallelParse(int numLcovFiles, int numSourceFiles, int numLinesPerSourceFile)
       throws IOException {