Adds capability of parsing files in parallel to CoverageOutputGenerator/LcovMerger.
- Adds capability of parsing files in parallel to CoverageOutputGenerator/LcovMerger.
- Uses immutable objects and java parallel stream to keep the code as simple as possible.
- Adds functional tests to cross-validate parallel vs sequential test output
- Makes parallel parsing the default behavior in Bazel report generator (overridable with --combined_report_opt=--parse_sequentially)
PiperOrigin-RevId: 293646734
diff --git a/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Coverage.java b/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Coverage.java
index 25c1851..aa3d87f 100644
--- a/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Coverage.java
+++ b/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Coverage.java
@@ -18,6 +18,7 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -51,6 +52,18 @@
return merged;
}
+ static Coverage create(SourceFileCoverage... sourceFilesCoverage) {
+ return create(Arrays.asList(sourceFilesCoverage));
+ }
+
+ static Coverage create(List<SourceFileCoverage> sourceFilesCoverage) {
+ Coverage coverage = new Coverage();
+ for (SourceFileCoverage sourceFileCoverage : sourceFilesCoverage) {
+ coverage.add(sourceFileCoverage);
+ }
+ return coverage;
+ }
+
/**
* Returns {@link Coverage} only for the given CC source filenames, filtering out every other CC
* sources of the given coverage. Other types of source files (e.g. Java) will not be filtered
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 9b5363c..eaaf560 100644
--- a/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/LcovMergerFlags.java
+++ b/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/LcovMergerFlags.java
@@ -55,6 +55,9 @@
@Parameter(names = "--sources_to_replace_file")
private String sourcesToReplaceFile;
+ @Parameter(names = "--parse_sequentially")
+ private boolean parseSequentially;
+
public String coverageDir() {
return coverageDir;
}
@@ -83,6 +86,10 @@
return sourceFileManifest != null;
}
+ boolean parseSequentially() {
+ return parseSequentially;
+ }
+
static LcovMergerFlags parseFlags(String[] args) {
LcovMergerFlags flags = new LcovMergerFlags();
JCommander jCommander = new JCommander(flags);
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 9b61a22..cd2ca7d 100644
--- a/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Main.java
+++ b/tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator/Main.java
@@ -35,6 +35,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -45,7 +46,7 @@
public class Main {
private static final Logger logger = Logger.getLogger(Main.class.getName());
- public static void main(String[] args) {
+ public static void main(String... args) {
LcovMergerFlags flags = null;
try {
flags = LcovMergerFlags.parseFlags(args);
@@ -62,8 +63,14 @@
: Collections.emptyList();
Coverage coverage =
Coverage.merge(
- parseFiles(getTracefiles(flags, filesInCoverageDir), LcovParser::parse),
- parseFiles(getGcovInfoFiles(filesInCoverageDir), GcovParser::parse));
+ parseFiles(
+ getTracefiles(flags, filesInCoverageDir),
+ LcovParser::parse,
+ flags.parseSequentially()),
+ parseFiles(
+ getGcovInfoFiles(filesInCoverageDir),
+ GcovParser::parse,
+ flags.parseSequentially()));
if (flags.sourcesToReplaceFile() != null) {
coverage.maybeReplaceSourceFileNames(getMapFromFile(flags.sourcesToReplaceFile()));
@@ -246,7 +253,15 @@
return mapBuilder.build();
}
- private static Coverage parseFiles(List<File> files, Parser parser) {
+ static Coverage parseFiles(List<File> files, Parser parser, boolean parseSequentially) {
+ if (parseSequentially) {
+ return parseFilesSequentially(files, parser);
+ } else {
+ return parseFilesInParallel(files, parser);
+ }
+ }
+
+ static Coverage parseFilesSequentially(List<File> files, Parser parser) {
Coverage coverage = new Coverage();
for (File file : files) {
try {
@@ -265,6 +280,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());
+ }
+
/**
* Returns a list of all the files with the given extension found recursively under the given dir.
*/
diff --git a/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/BUILD b/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/BUILD
index 4f597de..7acaa7e 100644
--- a/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/BUILD
+++ b/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/BUILD
@@ -122,6 +122,9 @@
"//third_party:junit4",
"//third_party:truth",
"//tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator:Constants",
+ "//tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator:Coverage",
+ "//tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator:LcovParser",
+ "//tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator:LcovPrinter",
"//tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator:MainLibrary",
],
)
diff --git a/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/LcovMergerTestUtils.java b/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/LcovMergerTestUtils.java
index cfb6486..10f0dda 100644
--- a/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/LcovMergerTestUtils.java
+++ b/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/LcovMergerTestUtils.java
@@ -17,6 +17,12 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -400,4 +406,41 @@
assertThat(merged.nrOfLinesWithNonZeroExecution()).isEqualTo(14);
assertThat(merged.nrOfInstrumentedLines()).isEqualTo(14);
}
+
+ static List<String> generateLcovContents(
+ String srcPrefix, int numSourceFiles, int numLinesPerSourceFile) {
+ ArrayList<String> lines = new ArrayList<>();
+ for (int i = 0; i < numSourceFiles; i++) {
+ lines.add(String.format("SF:%s%s.cc", srcPrefix, i));
+ lines.add("FNF:0");
+ lines.add("FNH:0");
+ for (int srcLineNum = 1; srcLineNum <= numLinesPerSourceFile; srcLineNum += 4) {
+ lines.add(String.format("BA:%s,2", srcLineNum));
+ }
+ lines.add("BRF:" + numLinesPerSourceFile / 4);
+ lines.add("BRH:" + numLinesPerSourceFile / 4);
+ for (int srcLineNum = 1; srcLineNum <= numLinesPerSourceFile; srcLineNum++) {
+ lines.add(String.format("DA:%s,%s", srcLineNum, srcLineNum % 2));
+ }
+ lines.add("LH:" + numLinesPerSourceFile / 2);
+ lines.add("LF:" + numLinesPerSourceFile);
+ lines.add("end_of_record");
+ }
+ return lines;
+ }
+
+ static List<Path> generateLcovFiles(
+ String srcPrefix, int numLcovFiles, int numSrcFiles, int numLinesPerSrcFile, Path coverageDir)
+ throws IOException {
+ Path lcovFile = Files.createFile(Paths.get(coverageDir.toString(), "coverage0.dat"));
+ List<Path> lcovFiles = new ArrayList<>();
+ Files.write(lcovFile, generateLcovContents(srcPrefix, numSrcFiles, numLinesPerSrcFile));
+ lcovFiles.add(lcovFile);
+ for (int i = 1; i < numLcovFiles; i++) {
+ lcovFiles.add(
+ Files.createSymbolicLink(
+ Paths.get(coverageDir.toString(), String.format("coverage%s.dat", i)), lcovFile));
+ }
+ return lcovFiles;
+ }
}
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 52e90bb..84b650a 100644
--- a/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/MainTest.java
+++ b/tools/test/CoverageOutputGenerator/javatests/com/google/devtools/coverageoutputgenerator/MainTest.java
@@ -17,13 +17,16 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.coverageoutputgenerator.Constants.TRACEFILE_EXTENSION;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -31,11 +34,12 @@
@RunWith(JUnit4.class)
public class MainTest {
+ @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
private Path coverageDir;
@Before
public void createCoverageDirectory() throws IOException {
- coverageDir = Files.createTempDirectory("coverage-dir");
+ coverageDir = temporaryFolder.newFolder("coverage-dir").toPath();
}
@Test
@@ -55,4 +59,34 @@
List<File> tracefiles = Main.getFilesWithExtension(coverageFiles, TRACEFILE_EXTENSION);
assertThat(tracefiles).hasSize(2);
}
+
+ @Test
+ public void testParallelParse_1KLoC_1KLcovFiles() throws IOException {
+ assertParallelParse(1024, 4, 256);
+ }
+
+ @Test
+ public void testParallelParse_1MLoC_4LcovFiles() throws IOException {
+ assertParallelParse(4, 1024, 1024);
+ }
+
+ private void assertParallelParse(int numLcovFiles, int numSourceFiles, int numLinesPerSourceFile)
+ throws IOException {
+
+ ByteArrayOutputStream sequentialOutput = new ByteArrayOutputStream();
+ ByteArrayOutputStream parallelOutput = new ByteArrayOutputStream();
+
+ LcovMergerTestUtils.generateLcovFiles(
+ "test_data/simple_test", numLcovFiles, numSourceFiles, numLinesPerSourceFile, coverageDir);
+
+ List<File> coverageFiles = Main.getCoverageFilesInDir(coverageDir.toAbsolutePath().toString());
+
+ Coverage sequentialCoverage = Main.parseFilesSequentially(coverageFiles, LcovParser::parse);
+ LcovPrinter.print(sequentialOutput, sequentialCoverage);
+
+ Coverage parallelCoverage = Main.parseFilesInParallel(coverageFiles, LcovParser::parse);
+ LcovPrinter.print(parallelOutput, parallelCoverage);
+
+ assertThat(parallelOutput.toString()).isEqualTo(sequentialOutput.toString());
+ }
}