Limit the number of threads used to parallel parse and merge lcov files

PiperOrigin-RevId: 300786351
diff --git a/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/LcovMergerFlags.java b/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/LcovMergerFlags.java
index eaaf560..15e2dd8 100644
--- a/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/LcovMergerFlags.java
+++ b/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/LcovMergerFlags.java
@@ -26,6 +26,7 @@
 @Parameters(separators = "= ", optionPrefixes = "--")
 class LcovMergerFlags {
   private static final Logger logger = Logger.getLogger(LcovMergerFlags.class.getName());
+  private static final int DEFAULT_PARSE_FILE_PARALLELISM = 8;
 
   @Parameter(names = "--coverage_dir")
   private String coverageDir;
@@ -55,8 +56,8 @@
   @Parameter(names = "--sources_to_replace_file")
   private String sourcesToReplaceFile;
 
-  @Parameter(names = "--parse_sequentially")
-  private boolean parseSequentially;
+  @Parameter(names = "--parse_parallelism")
+  private Integer parseParallelism;
 
   public String coverageDir() {
     return coverageDir;
@@ -86,8 +87,8 @@
     return sourceFileManifest != null;
   }
 
-  boolean parseSequentially() {
-    return parseSequentially;
+  int parseParallelism() {
+    return parseParallelism == null ? DEFAULT_PARSE_FILE_PARALLELISM : parseParallelism;
   }
 
   static LcovMergerFlags parseFlags(String[] args) {
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 4e50e29..21f3565 100644
--- a/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Main.java
+++ b/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Main.java
@@ -35,8 +35,9 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.Collectors;
@@ -56,7 +57,7 @@
     }
   }
 
-  static int runWithArgs(String... args) {
+  static int runWithArgs(String... args) throws ExecutionException, InterruptedException {
     LcovMergerFlags flags = null;
     try {
       flags = LcovMergerFlags.parseFlags(args);
@@ -76,11 +77,9 @@
             parseFiles(
                 getTracefiles(flags, filesInCoverageDir),
                 LcovParser::parse,
-                flags.parseSequentially()),
+                flags.parseParallelism()),
             parseFiles(
-                getGcovInfoFiles(filesInCoverageDir),
-                GcovParser::parse,
-                flags.parseSequentially()));
+                getGcovInfoFiles(filesInCoverageDir), GcovParser::parse, flags.parseParallelism()));
 
     if (flags.sourcesToReplaceFile() != null) {
       coverage.maybeReplaceSourceFileNames(getMapFromFile(flags.sourcesToReplaceFile()));
@@ -285,11 +284,12 @@
     return mapBuilder.build();
   }
 
-  static Coverage parseFiles(List<File> files, Parser parser, boolean parseSequentially) {
-    if (parseSequentially) {
+  static Coverage parseFiles(List<File> files, Parser parser, int parallelism)
+      throws ExecutionException, InterruptedException {
+    if (parallelism == 1) {
       return parseFilesSequentially(files, parser);
     } else {
-      return parseFilesInParallel(files, parser);
+      return parseFilesInParallel(files, parser, parallelism);
     }
   }
 
@@ -312,29 +312,31 @@
     return coverage;
   }
 
-  static Coverage parseFilesInParallel(List<File> files, Parser parser) {
-    return files.stream()
-        .parallel()
-        .map(
-            file -> {
-              try (FileInputStream inputStream = new FileInputStream(file)) {
-                logger.log(Level.INFO, "Parsing file " + file);
-                return parser.parse(inputStream);
-              } catch (IOException e) {
-                logger.log(
-                    Level.SEVERE,
-                    "File "
-                        + file.getAbsolutePath()
-                        + " could not be parsed due to: "
-                        + e.getMessage());
-                System.exit(1);
-              }
-              return null;
-            })
-        .filter(Objects::nonNull)
-        .map(Coverage::create)
-        .reduce(Coverage::merge)
-        .orElse(Coverage.create());
+  static Coverage parseFilesInParallel(List<File> files, Parser parser, int parallelism)
+      throws ExecutionException, InterruptedException {
+    ForkJoinPool pool = new ForkJoinPool(parallelism);
+    return pool.submit(
+            () ->
+                files.parallelStream()
+                    .map(
+                        file -> {
+                          try (FileInputStream inputStream = new FileInputStream(file)) {
+                            logger.log(Level.INFO, "Parsing file " + file);
+                            return Coverage.create(parser.parse(inputStream));
+                          } catch (IOException e) {
+                            logger.log(
+                                Level.SEVERE,
+                                "File "
+                                    + file.getAbsolutePath()
+                                    + " could not be parsed due to: "
+                                    + e.getMessage());
+                            System.exit(1);
+                          }
+                          return null;
+                        })
+                    .reduce(Coverage::merge)
+                    .orElse(Coverage.create()))
+        .get();
   }
 
   /**
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 370458f..e39d190 100644
--- a/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/MainTest.java
+++ b/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/MainTest.java
@@ -36,6 +36,7 @@
 @RunWith(JUnit4.class)
 public class MainTest {
 
+  private static final int TEST_PARSE_PARALLELISM = 8;
   @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
   @Rule public TestName testName = new TestName();
   private Path coverageDir;
@@ -64,17 +65,17 @@
   }
 
   @Test
-  public void testParallelParse_1KLoC_1KLcovFiles() throws IOException {
+  public void testParallelParse_1KLoC_1KLcovFiles() throws Exception {
     assertParallelParse(1024, 4, 256);
   }
 
   @Test
-  public void testParallelParse_1MLoC_4LcovFiles() throws IOException {
+  public void testParallelParse_1MLoC_4LcovFiles() throws Exception {
     assertParallelParse(4, 1024, 1024);
   }
 
   @Test
-  public void testEmptyInputProducesEmptyOutput() throws IOException {
+  public void testEmptyInputProducesEmptyOutput() throws Exception {
     Path output =
         Paths.get(
             temporaryFolder.getRoot().getAbsolutePath(),
@@ -89,7 +90,7 @@
   }
 
   @Test
-  public void testNonEmptyInputProducesNonEmptyOutput() throws IOException {
+  public void testNonEmptyInputProducesNonEmptyOutput() throws Exception {
     LcovMergerTestUtils.generateLcovFiles("test_data/simple_test", 8, 8, 8, coverageDir);
     Path output =
         Paths.get(
@@ -105,7 +106,7 @@
   }
 
   private void assertParallelParse(int numLcovFiles, int numSourceFiles, int numLinesPerSourceFile)
-      throws IOException {
+      throws Exception {
 
     ByteArrayOutputStream sequentialOutput = new ByteArrayOutputStream();
     ByteArrayOutputStream parallelOutput = new ByteArrayOutputStream();
@@ -118,7 +119,8 @@
     Coverage sequentialCoverage = Main.parseFilesSequentially(coverageFiles, LcovParser::parse);
     LcovPrinter.print(sequentialOutput, sequentialCoverage);
 
-    Coverage parallelCoverage = Main.parseFilesInParallel(coverageFiles, LcovParser::parse);
+    Coverage parallelCoverage =
+        Main.parseFilesInParallel(coverageFiles, LcovParser::parse, TEST_PARSE_PARALLELISM);
     LcovPrinter.print(parallelOutput, parallelCoverage);
 
     assertThat(parallelOutput.toString()).isEqualTo(sequentialOutput.toString());