aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar elenairina <elenairina@google.com>2018-06-11 08:25:41 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-06-11 08:27:22 -0700
commite28c0ab84c7967e10cc3cd5333560c54653084bd (patch)
tree9a66b0192d9049d16956218ca3bafc65af1de63f
parentbbf3e421ed8b2b431a72cd3ab4ba591dc8833634 (diff)
Implement LcovMerger.
LcovMerger is a tool that merges all the intermediate lcov tracefiles (with .dat extension) found under a coverage directory and prints the merged tracefile to a given output file. A custom implementation for merging lcov tracefiles is needed because the merging functionality of lcov itself is very slow. LcovMerger is required to get a single coverage report (lcov tracefile) from a bazel coverage command that executes multiple tests. ATM LcovMerger is only invoked by tools/test/collect_coverage.sh that collects and merges the tracefiles from a single test invocation. It will also be used from a CoverageReportAction. Progress on #5246. PiperOrigin-RevId: 200054506
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaTestRule.java4
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java7
-rwxr-xr-xsrc/test/shell/bazel/bazel_coverage_test.sh16
-rw-r--r--tools/BUILD4
-rw-r--r--tools/test/BUILD24
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD106
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BranchCoverage.java57
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Coverage.java40
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovConstants.java38
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovMerger.java81
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovParser.java410
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovPrinter.java229
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LineCoverage.java46
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java63
-rw-r--r--tools/test/LcovMerger/java/com/google/devtools/lcovmerger/SourceFileCoverage.java298
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/BUILD108
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/BranchCoverageTest.java156
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/CoverageTest.java81
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovMergerTest.java80
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovMergerTestUtils.java424
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovParserTest.java73
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovPrinterTest.java94
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LineCoverageTest.java126
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/MainTest.java58
-rw-r--r--tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/SourceFileCoverageTest.java80
25 files changed, 2507 insertions, 196 deletions
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 ebb64f29ca..dbda63a19f 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
@@ -66,7 +66,9 @@ public final class BazelJavaTestRule implements RuleDefinition {
// Input files for test actions collecting code coverage
.add(
attr("$lcov_merger", LABEL)
- .value(env.getLabel("@bazel_tools//tools/test:LcovMerger")))
+ .value(env.getLabel(
+ "@bazel_tools//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:Main"
+ )))
.add(
attr("$jacocorunner", LABEL)
.value(
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
index 3b7579909b..d64c9b16a3 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -131,10 +131,15 @@ public final class BazelAnalysisMock extends AnalysisMock {
"filegroup(name = 'runtime', srcs = ['test-setup.sh'])",
"filegroup(name = 'test_setup', srcs = ['test-setup.sh'])",
"filegroup(name = 'collect_coverage', srcs = ['collect_coverage.sh'])",
- "filegroup(name='coverage_support', srcs=['collect_coverage.sh','LcovMerger'])",
+ "filegroup(name='coverage_support', srcs=['collect_coverage.sh'])",
"filegroup(name = 'coverage_report_generator', srcs = ['coverage_report_generator.sh'])");
config.create(
+ "/bazel_tools_workspace/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD",
+ "filegroup(name='srcs', srcs = glob(['**']))",
+ "filegroup(name='Main', srcs = ['Main.java'])");
+
+ config.create(
"/bazel_tools_workspace/tools/python/BUILD",
"package(default_visibility=['//visibility:public'])",
"exports_files(['precompile.py'])",
diff --git a/src/test/shell/bazel/bazel_coverage_test.sh b/src/test/shell/bazel/bazel_coverage_test.sh
index 54e4b30603..17f1f7f77d 100755
--- a/src/test/shell/bazel/bazel_coverage_test.sh
+++ b/src/test/shell/bazel/bazel_coverage_test.sh
@@ -206,12 +206,12 @@ EOF
cat <<EOF > result.dat
SF:com/example/Collatz.java
FN:3,com/example/Collatz::<init> ()V
-FNDA:0,com/example/Collatz::<init> ()V
FN:6,com/example/Collatz::getCollatzFinal (I)I
+FNDA:0,com/example/Collatz::<init> ()V
FNDA:1,com/example/Collatz::getCollatzFinal (I)I
+FNF:0
+FNH:0
BA:6,2
-BA:6,2
-BA:9,2
BA:9,2
DA:3,0
DA:6,3
@@ -219,6 +219,8 @@ DA:7,2
DA:9,4
DA:10,5
DA:12,7
+LH:0
+LF:0
end_of_record
EOF
@@ -297,12 +299,12 @@ EOF
cat <<EOF > result.dat
SF:src/main/com/example/Collatz.java
FN:3,com/example/Collatz::<init> ()V
-FNDA:0,com/example/Collatz::<init> ()V
FN:6,com/example/Collatz::getCollatzFinal (I)I
+FNDA:0,com/example/Collatz::<init> ()V
FNDA:1,com/example/Collatz::getCollatzFinal (I)I
+FNF:0
+FNH:0
BA:6,2
-BA:6,2
-BA:9,2
BA:9,2
DA:3,0
DA:6,3
@@ -310,6 +312,8 @@ DA:7,2
DA:9,4
DA:10,5
DA:12,7
+LH:0
+LF:0
end_of_record
EOF
diff --git a/tools/BUILD b/tools/BUILD
index 0f28085e75..84e96b1605 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -31,6 +31,8 @@ filegroup(
"//tools/osx:srcs",
"//tools/osx/crosstool:srcs",
"//tools/test:srcs",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:srcs",
+ "//tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger:srcs",
"//tools/python:srcs",
"//tools/runfiles:srcs",
"//tools/sh:srcs",
@@ -65,10 +67,12 @@ filegroup(
"//tools/python:embedded_tools",
"//tools/runfiles:embedded_tools",
"//tools/test:srcs",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:srcs",
"//tools/osx/crosstool:srcs",
"//tools/osx:srcs",
"//tools/sh:embedded_tools",
"//tools/whitelists:srcs",
"//tools/zip:srcs",
+ "//third_party:srcs",
],
)
diff --git a/tools/test/BUILD b/tools/test/BUILD
index bc70912a07..3f7d28b72a 100644
--- a/tools/test/BUILD
+++ b/tools/test/BUILD
@@ -17,30 +17,6 @@ filegroup(
srcs = ["collect_coverage.sh"],
)
-java_binary(
- name = "LcovMerger",
- srcs = glob(["LcovMerger/java/**/*.java"]),
- main_class = "com.google.devtools.lcovmerger.Main",
-)
-
-java_library(
- name = "LcovMergerTestUtils",
- srcs = glob(["LcovMerger/java/**/*.java"]),
-)
-
-java_test(
- name = "LcovMergerTest",
- srcs = glob(["LcovMerger/javatests/**/*.java"]),
- deps = [
- ":LcovMergerTestUtils",
- "//src/main/java/com/google/devtools/build/lib/vfs",
- "//src/test/java/com/google/devtools/build/lib:foundations_testutil",
- "//src/test/java/com/google/devtools/build/lib:testutil",
- "//third_party:junit4",
- "//third_party:truth",
- ],
-)
-
filegroup(
name = "coverage_support",
srcs = ["collect_coverage.sh"],
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD
new file mode 100644
index 0000000000..b7d4d7b08c
--- /dev/null
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BUILD
@@ -0,0 +1,106 @@
+package(
+ default_visibility = [
+ "//tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger:__pkg__",
+ ],
+)
+
+licenses(["notice"]) # Apache 2.0
+
+java_library(
+ name = "BranchCoverage",
+ srcs = ["BranchCoverage.java"],
+ deps = [
+ "//third_party:auto_value",
+ ],
+)
+
+java_library(
+ name = "LineCoverage",
+ srcs = ["LineCoverage.java"],
+ deps = [
+ "//third_party:auto_value",
+ "//third_party:jsr305",
+ ],
+)
+
+java_library(
+ name = "SourceFileCoverage",
+ srcs = ["SourceFileCoverage.java"],
+ deps = [
+ ":BranchCoverage",
+ ":LineCoverage",
+ "//third_party:auto_value",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ ],
+)
+
+java_library(
+ name = "LcovPrinter",
+ srcs = ["LcovPrinter.java"],
+ deps = [
+ ":BranchCoverage",
+ ":Coverage",
+ ":LcovConstants",
+ ":LineCoverage",
+ ":SourceFileCoverage",
+ "//third_party:guava",
+ ],
+)
+
+java_library(
+ name = "LcovConstants",
+ srcs = ["LcovConstants.java"],
+)
+
+java_library(
+ name = "LcovParser",
+ srcs = ["LcovParser.java"],
+ deps = [
+ ":BranchCoverage",
+ ":LcovConstants",
+ ":LineCoverage",
+ ":SourceFileCoverage",
+ ],
+)
+
+java_library(
+ name = "Coverage",
+ srcs = ["Coverage.java"],
+ deps = [":SourceFileCoverage"],
+)
+
+java_library(
+ name = "MainLibrary",
+ srcs = ["Main.java"],
+ deps = [
+ ":Coverage",
+ ":LcovConstants",
+ ":LcovParser",
+ ":LcovPrinter",
+ ":SourceFileCoverage",
+ "//third_party:guava",
+ ],
+)
+
+java_binary(
+ name = "Main",
+ srcs = ["Main.java"],
+ main_class = "com.google.devtools.lcovmerger.Main",
+ visibility = ["//visibility:public"],
+ deps = [
+ ":Coverage",
+ ":LcovConstants",
+ ":LcovParser",
+ ":LcovPrinter",
+ ":MainLibrary",
+ ":SourceFileCoverage",
+ "//third_party:guava",
+ ],
+)
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = ["//visibility:public"],
+)
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BranchCoverage.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BranchCoverage.java
new file mode 100644
index 0000000000..3b469ffb25
--- /dev/null
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/BranchCoverage.java
@@ -0,0 +1,57 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Stores branch coverage information.
+ */
+@AutoValue
+abstract class BranchCoverage {
+
+ static BranchCoverage create(
+ int lineNumber, int blockNumber, int branchNumber, boolean wasExecuted, int nrOfExecutions) {
+ assert (wasExecuted && nrOfExecutions > 0) || (!wasExecuted && nrOfExecutions == 0);
+ return new AutoValue_BranchCoverage(
+ lineNumber, blockNumber, branchNumber, wasExecuted, nrOfExecutions);
+ }
+
+ /**
+ * Merges two given instances of {@link BranchCoverage}.
+ *
+ * Calling {@code lineNumber()}, {@code blockNumber()} and {@code branchNumber()} must return the
+ * same values for {@code first} and {@code second}.
+ */
+ static BranchCoverage merge(BranchCoverage first, BranchCoverage second) {
+ assert first.lineNumber() == second.lineNumber();
+ assert first.blockNumber() == second.blockNumber();
+ assert first.branchNumber() == second.branchNumber();
+
+ return create(
+ first.lineNumber(),
+ first.blockNumber(),
+ first.branchNumber(),
+ first.wasExecuted() || second.wasExecuted(),
+ first.nrOfExecutions() + second.nrOfExecutions());
+ }
+
+ abstract int lineNumber();
+ // The two numbers below should be -1 for non-gcc emitted coverage (e.g. Java).
+ abstract int blockNumber(); // internal gcc internal ID for the branch
+ abstract int branchNumber(); // internal gcc internal ID for the branch
+ abstract boolean wasExecuted();
+ abstract int nrOfExecutions();
+}
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Coverage.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Coverage.java
new file mode 100644
index 0000000000..82733aa84a
--- /dev/null
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Coverage.java
@@ -0,0 +1,40 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import java.util.Collection;
+import java.util.TreeMap;
+
+class Coverage {
+ private final TreeMap<String, SourceFileCoverage> sourceFiles;
+
+ Coverage() {
+ sourceFiles = new TreeMap<>();
+ }
+
+ void add(SourceFileCoverage input) {
+ String sourceFilename = input.sourceFileName();
+ if (sourceFiles.containsKey(sourceFilename)) {
+ SourceFileCoverage old = sourceFiles.get(sourceFilename);
+ sourceFiles.put(sourceFilename, SourceFileCoverage.merge(old, input));
+ } else {
+ sourceFiles.put(sourceFilename, input);
+ }
+ }
+
+ Collection<SourceFileCoverage> getAllSourceFiles() {
+ return sourceFiles.values();
+ }
+}
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovConstants.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovConstants.java
new file mode 100644
index 0000000000..83c6ce5f85
--- /dev/null
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovConstants.java
@@ -0,0 +1,38 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+/**
+ * Stores markers used by the lcov tracefile. See
+ * <a href="http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php"> lcov documentation</a>
+ */
+class LcovConstants {
+ static final String SF_MARKER = "SF:";
+ static final String FN_MARKER = "FN:";
+ static final String FNDA_MARKER = "FNDA:";
+ static final String FNF_MARKER = "FNF:";
+ static final String FNH_MARKER = "FNH:";
+ static final String BRDA_MARKER = "BRDA:";
+ static final String BA_MARKER = "BA:";
+ static final String BRF_MARKER = "BRF:";
+ static final String BRH_MARKER = "BRH:";
+ static final String DA_MARKER = "DA:";
+ static final String LH_MARKER = "LH:";
+ static final String LF_MARKER = "LF:";
+ static final String END_OF_RECORD_MARKER = "end_of_record";
+ static final String LCOV_DELIMITER = ",";
+ static final String TAKEN = "-";
+ static final String TRACEFILE_EXTENSION = ".dat";
+}
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovMerger.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovMerger.java
deleted file mode 100644
index ebad7eb579..0000000000
--- a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovMerger.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2016 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.lcovmerger;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * A component that converts language specific raw coverage data to pseudo lcov format.
- */
-class LcovMerger {
- private static final Logger logger = Logger.getLogger(LcovMerger.class.getName());
-
- private final String outputFile;
- private final List<File> fileList;
-
- /**
- * Constructs an {@link LcovMerger} and collects the raw coverage files.
- */
- LcovMerger(String originalCoverageFilesDirectory, String generatedCoverageDataOutputPath) {
- this.fileList = getDatFiles(originalCoverageFilesDirectory);
- this.outputFile = generatedCoverageDataOutputPath;
- }
-
- /**
- * Merge all files in {@link fileList} and write to {@link outputFile}.
- *
- * @return successful or not
- */
- boolean merge() {
- if (fileList.isEmpty()) {
- logger.log(Level.SEVERE, "No lcov file found.");
- return false;
- }
- if (fileList.size() > 1) {
- logger.log(Level.SEVERE, "Only one lcov file supported now, but found " + fileList.size());
- return false;
- }
- try {
- Files.copy(
- fileList.get(0).toPath(), Paths.get(outputFile), StandardCopyOption.REPLACE_EXISTING);
- } catch (IOException e) {
- logger.log(Level.SEVERE, "Failed to copy file: " + e.getMessage());
- return false;
- }
- return true;
- }
-
- private List<File> getDatFiles(String coverageDir) {
- List<File> datFiles = new ArrayList<>();
- try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(coverageDir), "*.dat")) {
- for (Path entry : stream) {
- datFiles.add(entry.toFile());
- }
- } catch (IOException x) {
- logger.log(Level.SEVERE, "error reading folder " + coverageDir + ": " + x.getMessage());
- }
- return datFiles;
- }
-}
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovParser.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovParser.java
new file mode 100644
index 0000000000..ec6a3ee247
--- /dev/null
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovParser.java
@@ -0,0 +1,410 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static com.google.devtools.lcovmerger.LcovConstants.BA_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.BRDA_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.BRF_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.BRH_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.DA_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.END_OF_RECORD_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.FNDA_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.FNF_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.FNH_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.FN_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.LCOV_DELIMITER;
+import static com.google.devtools.lcovmerger.LcovConstants.LF_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.LH_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.SF_MARKER;
+import static com.google.devtools.lcovmerger.LcovConstants.TAKEN;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A parser for the lcov tracefile format used by geninfo. See
+ * <a href="http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php"> lcov documentation</a>
+ */
+class LcovParser {
+
+ private static final Logger logger = Logger.getLogger(LcovParser.class.getName());
+ private SourceFileCoverage currentSourceFileCoverage;
+ private final InputStream tracefileStream;
+
+ private LcovParser(InputStream tracefileStream) {
+ this.tracefileStream = tracefileStream;
+ }
+
+ /**
+ * Returns a list of the source files found in the given tracefile.
+ */
+ public static List<SourceFileCoverage> parse(InputStream tracefileStream) throws IOException {
+ LcovParser lcovParser = new LcovParser(tracefileStream);
+ return lcovParser.parse();
+ }
+
+ /**
+ * Reads the tracefile line by line and creates a SourceFileCoverage object
+ * for each section of the file between a SF:<source file> line and an
+ * end_of_record line.
+ *
+ * @return a list of each source file path found in the tracefile
+ */
+ private List<SourceFileCoverage> parse() throws IOException {
+ List<SourceFileCoverage> allSourceFiles = new ArrayList<>();
+ try (BufferedReader bufferedReader =
+ new BufferedReader(new InputStreamReader(tracefileStream, UTF_8))) {
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ parseLine(line, allSourceFiles);
+ }
+ bufferedReader.close();
+ }
+ return allSourceFiles;
+ }
+
+ /**
+ * Merges {@code currentSourceFileCoverage} into {@code allSourceFilesCoverageData} and resets
+ * {@code currentSourceFileCoverage} to null.
+ */
+ private void reset(List<SourceFileCoverage> allSourceFiles) {
+ allSourceFiles.add(currentSourceFileCoverage);
+ currentSourceFileCoverage = null;
+ }
+
+ /*
+ * Reads the line and redirects the parsing to the corresponding {@code parseXLine} method. Every
+ * {@code parseXLine} methods fills in data to {@code currentSourceFileCoverage} accordingly.
+ */
+ private boolean parseLine(String line, List<SourceFileCoverage> allSourceFiles) {
+ if (line.startsWith(SF_MARKER)) {
+ return parseSFLine(line);
+ }
+ // currentSourceFileCoverage should be null only before calling an SF line, otherwise
+ // the object should have been created in parseSFLine. If currentSourceFileCoverage is null
+ // here it means the parser arrived in an invalid state.
+ if (currentSourceFileCoverage == null) {
+ return false;
+ }
+ if (line.startsWith(FN_MARKER)) {
+ return parseFNLine(line);
+ }
+ if (line.startsWith(FNDA_MARKER)) {
+ return parseFNDALine(line);
+ }
+ if (line.startsWith(FNF_MARKER)) {
+ return parseFNFLine(line);
+ }
+ if (line.startsWith(FNH_MARKER)) {
+ return parseFNHLine(line);
+ }
+ if (line.startsWith(BRDA_MARKER)) {
+ return parseBRDALine(line);
+ }
+ if (line.startsWith(BA_MARKER)) {
+ return parseBALine(line);
+ }
+ if (line.startsWith(BRF_MARKER)) {
+ return parseBRFLine(line);
+ }
+ if (line.startsWith(BRH_MARKER)) {
+ return parseBRHLine(line);
+ }
+ if (line.startsWith(DA_MARKER)) {
+ return parseDALine(line);
+ }
+ if (line.startsWith(LH_MARKER)) {
+ return parseLHLine(line);
+ }
+ if (line.startsWith(LF_MARKER)) {
+ return parseLFLine(line);
+ }
+ if (line.equals(END_OF_RECORD_MARKER)) {
+ reset(allSourceFiles);
+ return true;
+ }
+ logger.log(Level.WARNING, "Tracefile includes invalid line: " + line);
+ return false;
+ }
+
+ // SF:<path to source file name>
+ private boolean parseSFLine(String line) {
+ if (currentSourceFileCoverage != null) {
+ logger.log(Level.WARNING, "Tracefile doesn't have SF:<source file> line before" + line);
+ return false;
+ }
+ String sourcefile = line.substring(SF_MARKER.length());
+ if (sourcefile.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile doesn't contain source file name on line: " + line);
+ return false;
+ }
+ currentSourceFileCoverage = new SourceFileCoverage(sourcefile);
+ return true;
+ }
+
+ // FN:<line number of function start>,<function name>
+ private boolean parseFNLine(String line) {
+ String lineContent = line.substring(FN_MARKER.length());
+ String[] funcData = lineContent.split(LCOV_DELIMITER, -1);
+ if (funcData.length != 2 || funcData[0].isEmpty() || funcData[1].isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid FN line " + line);
+ return false;
+ }
+ try {
+ int lineNrFunctionStart = Integer.parseInt(funcData[0]);
+ String functionName = funcData[1];
+ currentSourceFileCoverage.addLineNumber(functionName, lineNrFunctionStart);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING, "Tracefile contains invalid line number on FN line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // FNDA:<execution count>,<function name>
+ private boolean parseFNDALine(String line) {
+ String lineContent = line.substring(FNDA_MARKER.length());
+ String[] funcData = lineContent.split(LCOV_DELIMITER, -1);
+ if (funcData.length != 2 || funcData[0].isEmpty() || funcData[1].isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid FNDA line " + line);
+ return false;
+ }
+ try {
+ int executionCount = Integer.parseInt(funcData[0]);
+ String functionName = funcData[1];
+ currentSourceFileCoverage.addFunctionExecution(functionName, executionCount);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING, "Tracefile contains invalid execution count on FN line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // FNF:<number of functions found>
+ private boolean parseFNFLine(String line) {
+ String lineContent = line.substring(FNF_MARKER.length());
+ if (lineContent.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid FNF line " + line);
+ return false;
+ }
+ try {
+ int nrFunctionsFound = Integer.parseInt(lineContent);
+ currentSourceFileCoverage.nrFunctionsFound(nrFunctionsFound);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING,
+ "Tracefile contains invalid number of functions on FNF line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // FNH:<number of function hit>
+ private boolean parseFNHLine(String line) {
+ String lineContent = line.substring(FNH_MARKER.length());
+ if (lineContent.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid FNH line " + line);
+ return false;
+ }
+ try {
+ int nrFunctionsHit = Integer.parseInt(lineContent);
+ currentSourceFileCoverage.nrFunctionsHit(nrFunctionsHit);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING,
+ "Tracefile contains invalid number of functions hit on FNH line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // BA:<line number>,<taken>
+ private boolean parseBALine(String line) {
+ String lineContent = line.substring(BA_MARKER.length());
+ String[] lineData = lineContent.split(LCOV_DELIMITER, -1);
+ if (lineData.length != 2) {
+ logger.log(Level.WARNING, "Tracefile contains invalid BRDA line " + line);
+ return false;
+ }
+ for (String data : lineData) {
+ if (data.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid BRDA line " + line);
+ return false;
+ }
+ }
+ try {
+ int lineNumber = Integer.parseInt(lineData[0]);
+ int taken = Integer.parseInt(lineData[1]);
+
+ boolean wasExecuted = false;
+ if (taken == 1 || taken == 2) {
+ wasExecuted = true;
+ }
+ BranchCoverage branchCoverage =
+ BranchCoverage.create(lineNumber, -1, -1, wasExecuted, taken);
+
+ currentSourceFileCoverage.addBranch(lineNumber, branchCoverage);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING, "Tracefile contains an invalid number BA line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // BRDA:<line number>,<block number>,<branch number>,<taken>
+ private boolean parseBRDALine(String line) {
+ String lineContent = line.substring(BRDA_MARKER.length());
+ String[] lineData = lineContent.split(LCOV_DELIMITER, -1);
+ if (lineData.length != 4) {
+ logger.log(Level.WARNING, "Tracefile contains invalid BRDA line " + line);
+ return false;
+ }
+ for (String data : lineData) {
+ if (data.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid BRDA line " + line);
+ return false;
+ }
+ }
+ try {
+ int lineNumber = Integer.parseInt(lineData[0]);
+ int blockNumber = Integer.parseInt(lineData[1]);
+ int branchNumber = Integer.parseInt(lineData[2]);
+ String taken = lineData[3];
+
+ boolean wasExecuted = false;
+ int executionCount = 0;
+ if (taken.equals(TAKEN)) {
+ executionCount = Integer.parseInt(taken);
+ wasExecuted = true;
+ }
+ BranchCoverage branchCoverage =
+ BranchCoverage.create(lineNumber, blockNumber, branchNumber, wasExecuted, executionCount);
+
+ currentSourceFileCoverage.addBranch(lineNumber, branchCoverage);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING, "Tracefile contains an invalid number BRDA line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // BRF:<number of branches found>
+ private boolean parseBRFLine(String line) {
+ String lineContent = line.substring(BRF_MARKER.length());
+ if (lineContent.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid BRF line " + line);
+ return false;
+ }
+ try {
+ int nrBranchesFound = Integer.parseInt(lineContent);
+ currentSourceFileCoverage.nrBranchesFound(nrBranchesFound);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING,
+ "Tracefile contains invalid number of branches in BRDA line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // BRH:<number of branches hit>
+ private boolean parseBRHLine(String line) {
+ String lineContent = line.substring(BRH_MARKER.length());
+ if (lineContent.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid BRH line " + line);
+ return false;
+ }
+ try {
+ int nrBranchesHit = Integer.parseInt(lineContent);
+ currentSourceFileCoverage.nrBranchesHit(nrBranchesHit);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING,
+ "Tracefile contains invalid number of branches hit in BRH line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // DA:<line number>,<execution count>,[,<checksum>]
+ private boolean parseDALine(String line) {
+ String lineContent = line.substring(DA_MARKER.length());
+ String[] lineData = lineContent.split(LCOV_DELIMITER, -1);
+ if (lineData.length != 2 && lineData.length != 3) {
+ logger.log(Level.WARNING, "Tracefile contains invalid DA line " + line);
+ return false;
+ }
+ for (String data : lineData) {
+ if (data.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid DA line " + line);
+ return false;
+ }
+ }
+ try {
+ int lineNumber = Integer.parseInt(lineData[0]);
+ int executionCount = Integer.parseInt(lineData[1]);
+ String checkSum = null;
+ if (lineData.length == 3) {
+ checkSum = lineData[2];
+ }
+ LineCoverage lineCoverage =
+ LineCoverage.create(lineNumber, executionCount, checkSum);
+ currentSourceFileCoverage.addLine(
+ lineNumber, lineCoverage);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING, "Tracefile contains an invalid number on DA line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // LH:<nr of lines with non-zero exec count>
+ private boolean parseLHLine(String line) {
+ String lineContent = line.substring(LH_MARKER.length());
+ if (lineContent.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid LHL line " + line);
+ return false;
+ }
+ try {
+ int nrLines = Integer.parseInt(lineContent);
+ currentSourceFileCoverage.nrOfLinesWithNonZeroExecution(nrLines);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING, "Tracefile contains an invalid number on LHL line " + line);
+ return false;
+ }
+ return true;
+ }
+
+ // LF:<number of instrumented lines>
+ private boolean parseLFLine(String line) {
+ String lineContent = line.substring(LF_MARKER.length());
+ if (lineContent.isEmpty()) {
+ logger.log(Level.WARNING, "Tracefile contains invalid LF line " + line);
+ return false;
+ }
+ try {
+ int nrLines = Integer.parseInt(lineContent);
+ currentSourceFileCoverage.nrOfInstrumentedLines(nrLines);
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING, "Tracefile contains an invalid number on LF line " + line);
+ return false;
+ }
+ return true;
+ }
+}
+
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovPrinter.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovPrinter.java
new file mode 100644
index 0000000000..59737b991a
--- /dev/null
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LcovPrinter.java
@@ -0,0 +1,229 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Prints coverage data stored in a collection of {@link SourceFileCoverage} in a
+ * <a href="http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php"> lcov tracefile format</a>
+ */
+class LcovPrinter {
+ private static final Logger logger = Logger.getLogger(LcovPrinter.class.getName());
+ private final BufferedWriter bufferedWriter;
+
+ private LcovPrinter(BufferedWriter bufferedWriter) {
+ this.bufferedWriter = bufferedWriter;
+ }
+
+ static boolean print(OutputStream outputStream, Coverage coverage) {
+ BufferedWriter bufferedWriter;
+ try (Writer fileWriter = new OutputStreamWriter(outputStream, UTF_8)) {
+ bufferedWriter = new BufferedWriter(fileWriter);
+ LcovPrinter lcovPrinter = new LcovPrinter(bufferedWriter);
+ lcovPrinter.print(coverage);
+ bufferedWriter.close();
+ } catch (IOException exception) {
+ logger.log(Level.SEVERE, "Could not write to output file.");
+ return false;
+ }
+ return true;
+ }
+
+ private boolean print(Coverage coverage) {
+ try {
+ for (SourceFileCoverage sourceFile : coverage.getAllSourceFiles()) {
+ print(sourceFile);
+ }
+ } catch (IOException exception) {
+ logger.log(Level.SEVERE, "Could not write to output file.");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Prints the given source data in an lcov tracefile format.
+ *
+ * Assumes the file is opened and closed outside of this method.
+ */
+ @VisibleForTesting
+ void print(SourceFileCoverage sourceFile) throws IOException {
+ printSFLine(sourceFile);
+ printFNLines(sourceFile);
+ printFNDALines(sourceFile);
+ printFNFLine(sourceFile);
+ printFNHLine(sourceFile);
+ printBRDALines(sourceFile);
+ printBALines(sourceFile);
+ printBRFLine(sourceFile);
+ printBRHLine(sourceFile);
+ printDALines(sourceFile);
+ printLHLine(sourceFile);
+ printLFLine(sourceFile);
+ printEndOfRecordLine();
+ }
+
+ // SF:<absolute path to the source file>
+ private void printSFLine(SourceFileCoverage sourceFile) throws IOException {
+ bufferedWriter.write(LcovConstants.SF_MARKER);
+ bufferedWriter.write(sourceFile.sourceFileName());
+ bufferedWriter.newLine();
+ }
+
+ // FN:<line number of function start>,<function name>
+ private void printFNLines(SourceFileCoverage sourceFile) throws IOException {
+ for (Entry<String, Integer> entry :
+ sourceFile.getAllLineNumbers()) {
+ bufferedWriter.write(LcovConstants.FN_MARKER);
+ bufferedWriter.write(Integer.toString(entry.getValue())); // line number of function start
+ bufferedWriter.write(LcovConstants.LCOV_DELIMITER);
+ bufferedWriter.write(entry.getKey()); // function name
+ bufferedWriter.newLine();
+ }
+ }
+
+ // FNDA:<execution count>,<function name>
+ private void printFNDALines(SourceFileCoverage sourceFile) throws IOException {
+ for (Entry<String, Integer> entry :
+ sourceFile.getAllExecutionCount()) {
+ bufferedWriter.write(LcovConstants.FNDA_MARKER);
+ bufferedWriter.write(Integer.toString(entry.getValue())); // execution count
+ bufferedWriter.write(LcovConstants.LCOV_DELIMITER);
+ bufferedWriter.write(entry.getKey()); // function name
+ bufferedWriter.newLine();
+ }
+ }
+
+ // FNF:<number of functions found>
+ private void printFNFLine(SourceFileCoverage sourceFile) throws IOException {
+ bufferedWriter.write(LcovConstants.FNF_MARKER);
+ bufferedWriter.write(Integer.toString(sourceFile.nrFunctionsFound()));
+ bufferedWriter.newLine();
+ }
+
+ // FNH:<number of functions hit>
+ private void printFNHLine(SourceFileCoverage sourceFile) throws IOException {
+ bufferedWriter.write(LcovConstants.FNH_MARKER);
+ bufferedWriter.write(Integer.toString(sourceFile.nrFunctionsHit()));
+ bufferedWriter.newLine();
+ }
+
+ // BRDA:<line number>,<block number>,<branch number>,<taken>
+ private void printBRDALines(SourceFileCoverage sourceFile) throws IOException {
+ for (BranchCoverage branch : sourceFile.getAllBranches()) {
+ if (branch.blockNumber() == -1 || branch.branchNumber() == -1) {
+ // We skip printing this as a BRDA line and print it later as a BA line.
+ continue;
+ }
+ bufferedWriter.write(LcovConstants.BRDA_MARKER);
+ bufferedWriter.write(Integer.toString(branch.lineNumber()));
+ bufferedWriter.write(LcovConstants.LCOV_DELIMITER);
+ bufferedWriter.write(Integer.toString(branch.blockNumber()));
+ bufferedWriter.write(LcovConstants.LCOV_DELIMITER);
+ bufferedWriter.write(Integer.toString(branch.branchNumber()));
+ bufferedWriter.write(LcovConstants.LCOV_DELIMITER);
+ if (branch.wasExecuted()) {
+ bufferedWriter.write(Integer.toString(branch.nrOfExecutions()));
+ } else {
+ bufferedWriter.write(LcovConstants.TAKEN);
+ }
+ bufferedWriter.newLine();
+ }
+ }
+
+ // BA:<line number>,<taken>
+ private void printBALines(SourceFileCoverage sourceFile) throws IOException {
+ for (BranchCoverage branch : sourceFile.getAllBranches()) {
+ if (branch.branchNumber() != -1 && branch.blockNumber() != -1) {
+ // This branch was already printed with more information as a BRDA line.
+ continue;
+ }
+ bufferedWriter.write(LcovConstants.BA_MARKER);
+ bufferedWriter.write(Integer.toString(branch.lineNumber()));
+ bufferedWriter.write(LcovConstants.LCOV_DELIMITER);
+ // 0 = branch was not executed
+ // 1 = branch was executed but not taken
+ // 2 = branch was executed and taken
+ bufferedWriter.write(branch.wasExecuted() ? "2" : "0");
+ bufferedWriter.newLine();
+ }
+ }
+
+ // BRF:<number of branches found>
+ private void printBRFLine(SourceFileCoverage sourceFile) throws IOException {
+ if (sourceFile.nrBranchesFound() > 0) {
+ bufferedWriter.write(LcovConstants.BRF_MARKER);
+ bufferedWriter.write(Integer.toString(sourceFile.nrBranchesFound()));
+ bufferedWriter.newLine();
+ }
+ }
+
+ // BRH:<number of branches hit>
+ private void printBRHLine(SourceFileCoverage sourceFile) throws IOException {
+ // Only print if there were any branches found.
+ if (sourceFile.nrBranchesFound() > 0) {
+ bufferedWriter.write(LcovConstants.BRH_MARKER);
+ bufferedWriter.write(Integer.toString(sourceFile.nrBranchesHit()));
+ bufferedWriter.newLine();
+ }
+ }
+
+ // DA:<line number>,<execution count>[,<checksum>]
+ private void printDALines(SourceFileCoverage sourceFile) throws IOException {
+ for (LineCoverage lineExecution :
+ sourceFile.getAllLineExecution()) {
+ bufferedWriter.write(LcovConstants.DA_MARKER);
+ bufferedWriter.write(Integer.toString(lineExecution.lineNumber()));
+ bufferedWriter.write(LcovConstants.LCOV_DELIMITER);
+ bufferedWriter.write(Integer.toString(lineExecution.executionCount()));
+ if (lineExecution.checksum() != null) {
+ bufferedWriter.write(LcovConstants.LCOV_DELIMITER);
+ bufferedWriter.write(lineExecution.checksum());
+ }
+ bufferedWriter.newLine();
+ }
+ }
+
+ // LH:<number of lines with a non-zero execution count>
+ private void printLHLine(SourceFileCoverage sourceFile) throws IOException {
+ bufferedWriter.write(LcovConstants.LH_MARKER);
+ bufferedWriter.write(Integer.toString(sourceFile.nrOfLinesWithNonZeroExecution()));
+ bufferedWriter.newLine();
+ }
+
+ // LF:<number of instrumented lines>
+ private void printLFLine(SourceFileCoverage sourceFile) throws IOException {
+ bufferedWriter.write(LcovConstants.LF_MARKER);
+ bufferedWriter.write(Integer.toString(sourceFile.nrOfInstrumentedLines()));
+ bufferedWriter.newLine();
+ }
+
+ // end_of_record
+ private void printEndOfRecordLine() throws IOException {
+ bufferedWriter.write(LcovConstants.END_OF_RECORD_MARKER);
+ bufferedWriter.newLine();
+ }
+}
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LineCoverage.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LineCoverage.java
new file mode 100644
index 0000000000..4e163e2416
--- /dev/null
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/LineCoverage.java
@@ -0,0 +1,46 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import com.google.auto.value.AutoValue;
+import javax.annotation.Nullable;
+
+/**
+ * Stores line execution coverage information.
+ */
+@AutoValue
+abstract class LineCoverage {
+ static LineCoverage create(int lineNumber, int executionCount, String checksum) {
+ return new AutoValue_LineCoverage(lineNumber, executionCount, checksum);
+ }
+
+ static LineCoverage merge(
+ LineCoverage first, LineCoverage second) {
+ assert first.lineNumber() == second.lineNumber();
+ assert (first.checksum() == null && second.checksum() == null)
+ || (first.checksum().equals(second.checksum()));
+ return create(
+ first.lineNumber(),
+ first.executionCount() + second.executionCount(),
+ first.checksum()
+ );
+ }
+
+ abstract int lineNumber();
+ abstract int executionCount();
+ // The current geninfo implementation uses an MD5 hash as checksumming algorithm.
+ @Nullable
+ abstract String checksum(); // optional
+}
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java
index e5bf197c3a..5bac98e80d 100644
--- a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/Main.java
@@ -14,10 +14,24 @@
package com.google.devtools.lcovmerger;
+import static com.google.devtools.lcovmerger.LcovConstants.TRACEFILE_EXTENSION;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+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.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Command line utility to convert raw coverage files to lcov (text) format.
@@ -34,9 +48,52 @@ public class Main {
System.exit(1);
}
- LcovMerger lcovMerger = new LcovMerger(flags.get("coverage_dir"), flags.get("output_file"));
- boolean success = lcovMerger.merge();
- System.exit(success ? 0 : 1);
+ List<File> lcovTracefiles = getLcovTracefiles(flags.get("coverage_dir"));
+ if (lcovTracefiles.isEmpty()) {
+ logger.log(Level.SEVERE, "No lcov file found.");
+ System.exit(1);
+ }
+ Coverage coverage = new Coverage();
+ for (File tracefile : lcovTracefiles) {
+ try {
+ List<SourceFileCoverage> sourceFilesCoverage =
+ LcovParser.parse(new FileInputStream(tracefile));
+ for (SourceFileCoverage sourceFileCoverage : sourceFilesCoverage) {
+ coverage.add(sourceFileCoverage);
+ }
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Tracefile " + tracefile.getAbsolutePath() + " was deleted");
+ System.exit(1);
+ }
+ }
+ int exitStatus = 0;
+ String outputFile = flags.get("output_file");
+ try {
+ File coverageFile = new File(outputFile);
+ LcovPrinter.print(new FileOutputStream(coverageFile), coverage);
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Could not write to output file " + outputFile);
+ exitStatus = 1;
+ }
+ System.exit(exitStatus);
+ }
+
+ /**
+ * Returns a list of all the files with a “.dat” extension found recursively under the given
+ * directory.
+ */
+ @VisibleForTesting
+ static List<File> getLcovTracefiles(String coverageDir) {
+ List<File> datFiles = new ArrayList<>();
+ try (Stream<Path> stream = Files.walk(Paths.get(coverageDir))) {
+ datFiles = stream.filter(p -> p.toString().endsWith(TRACEFILE_EXTENSION))
+ .map(path -> path.toFile())
+ .collect(Collectors.toList());
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, "error reading folder " + coverageDir + ": " + ex.getMessage());
+ }
+
+ return datFiles;
}
/**
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/SourceFileCoverage.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/SourceFileCoverage.java
new file mode 100644
index 0000000000..3e293a789c
--- /dev/null
+++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/SourceFileCoverage.java
@@ -0,0 +1,298 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/*
+ * Stores coverage information for a specific source file.
+ */
+class SourceFileCoverage {
+
+ private final String sourceFileName;
+ private final TreeMap<String, Integer> lineNumbers; // function name to line numbers
+ private final TreeMap<String, Integer> functionsExecution; // function name to execution count
+ private final TreeMap<Integer, BranchCoverage> branches; // line number to branch
+ private final TreeMap<Integer, LineCoverage> lines; // line number to line execution
+
+ private int nrFunctionsFound;
+ private int nrFunctionsHit;
+ private int nrBranchesFound;
+ private int nrBranchesHit;
+ private int nrOfLinesWithNonZeroExecution;
+ private int nrOfInstrumentedLines;
+
+ SourceFileCoverage(String sourcefile) {
+ this.sourceFileName = sourcefile;
+ this.functionsExecution = new TreeMap<>();
+ this.lineNumbers = new TreeMap<>();
+ this.lines = new TreeMap<>();
+ this.branches = new TreeMap<>();
+ }
+
+ SourceFileCoverage(SourceFileCoverage other) {
+ this.sourceFileName = other.sourceFileName;
+
+ this.functionsExecution = new TreeMap<>();
+ this.lineNumbers = new TreeMap<>();
+ this.lines = new TreeMap<>();
+ this.branches = new TreeMap<>();
+
+ this.lineNumbers.putAll(other.lineNumbers);
+ this.functionsExecution.putAll(other.functionsExecution);
+ this.branches.putAll(other.branches);
+ this.lines.putAll(other.lines);
+
+ this.nrFunctionsFound = other.nrFunctionsFound;
+ this.nrFunctionsHit = other.nrFunctionsHit;
+ this.nrBranchesFound = other.nrBranchesFound;
+ this.nrBranchesHit = other.nrBranchesHit;
+ this.nrOfLinesWithNonZeroExecution = other.nrOfLinesWithNonZeroExecution;
+ this.nrOfInstrumentedLines = other.nrOfInstrumentedLines;
+ }
+
+ /*
+ * Returns the merged functions found in the two given {@code SourceFileCoverage}s.
+ */
+ @VisibleForTesting
+ static TreeMap<String, Integer> mergeLineNumbers(SourceFileCoverage s1, SourceFileCoverage s2) {
+ TreeMap<String, Integer> merged = new TreeMap<>();
+ merged.putAll(s1.lineNumbers);
+ merged.putAll(s2.lineNumbers);
+ return merged;
+ }
+
+ /*
+ *
+ * Returns the merged execution count found in the two given {@code SourceFileCoverage}s.
+ */
+ @VisibleForTesting
+ static TreeMap<String, Integer> mergeFunctionsExecution(
+ SourceFileCoverage s1, SourceFileCoverage s2) {
+ return Stream.of(
+ s1.functionsExecution, s2.functionsExecution)
+ .map(Map::entrySet)
+ .flatMap(Collection::stream)
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ Map.Entry::getValue,
+ Integer::sum,
+ TreeMap::new
+ ));
+ }
+
+ /*
+ *
+ * Returns the merged branches found in the two given {@code SourceFileCoverage}s.
+ */
+ @VisibleForTesting
+ static TreeMap<Integer, BranchCoverage> mergeBranches(
+ SourceFileCoverage s1, SourceFileCoverage s2) {
+ return Stream.of(s1.branches, s2.branches)
+ .map(Map::entrySet)
+ .flatMap(Collection::stream)
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ Map.Entry::getValue,
+ BranchCoverage::merge,
+ TreeMap::new
+ )
+ );
+ }
+
+ static int getNumberOfBranchesHit(SourceFileCoverage sourceFileCoverage) {
+ return (int) sourceFileCoverage.branches.entrySet().stream()
+ .filter(branch -> branch.getValue().wasExecuted())
+ .count();
+ }
+
+ /*
+ * Returns the merged line execution found in the two given {@code SourceFileCoverage}s.
+ */
+ @VisibleForTesting
+ static TreeMap<Integer, LineCoverage> mergeLines(
+ SourceFileCoverage s1, SourceFileCoverage s2) {
+ return Stream.of(s1.lines, s2.lines)
+ .map(Map::entrySet)
+ .flatMap(Collection::stream)
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ Map.Entry::getValue,
+ LineCoverage::merge,
+ TreeMap::new
+ )
+ );
+ }
+
+ private static int getNumberOfExecutedLines(SourceFileCoverage sourceFileCoverage) {
+ return (int) sourceFileCoverage.lines.entrySet().stream()
+ .filter(line -> line.getValue().executionCount() > 0)
+ .count();
+ }
+
+ /**
+ * Merges all the fields of {@code other} with the current {@link SourceFileCoverage} into a new
+ * {@link SourceFileCoverage}
+ *
+ * Assumes both the current and the given {@link SourceFileCoverage} have the same
+ * {@code sourceFileName}.
+ *
+ * @return a new {@link SourceFileCoverage} that contains the merged coverage.
+ */
+ static SourceFileCoverage merge(SourceFileCoverage source1, SourceFileCoverage source2) {
+ assert source1.sourceFileName.equals(source2.sourceFileName);
+ SourceFileCoverage merged = new SourceFileCoverage(source2.sourceFileName);
+
+ merged.addAllLineNumbers(mergeLineNumbers(source1, source2));
+ merged.addAllFunctionsExecution(mergeFunctionsExecution(source1, source2));
+ merged.addAllBranches(mergeBranches(source1, source2));
+ merged.addAllLines(mergeLines(source1, source2));
+
+ merged.nrBranchesHit(getNumberOfBranchesHit(merged));
+ merged.nrOfLinesWithNonZeroExecution(getNumberOfExecutedLines(merged));
+ merged.nrFunctionsFound(merged.lineNumbers.size());
+ merged.nrFunctionsHit(merged.functionsExecution.size());
+ merged.nrBranchesFound(merged.branches.size());
+ merged.nrOfInstrumentedLines(merged.lines.size());
+ return merged;
+ }
+
+ String sourceFileName() {
+ return sourceFileName;
+ }
+
+ int nrFunctionsFound() {
+ return nrFunctionsFound;
+ }
+
+ void nrFunctionsFound(int nrFunctionsFound) {
+ this.nrFunctionsFound = nrFunctionsFound;
+ }
+
+ int nrFunctionsHit() {
+ return nrFunctionsHit;
+ }
+
+ void nrFunctionsHit(int nrFunctionsHit) {
+ this.nrFunctionsHit = nrFunctionsHit;
+ }
+
+ int nrBranchesFound() {
+ return nrBranchesFound;
+ }
+
+ void nrBranchesFound(int nrBranchesFound) {
+ this.nrBranchesFound = nrBranchesFound;
+ }
+
+ int nrBranchesHit() {
+ return nrBranchesHit;
+ }
+
+ void nrBranchesHit(int nrBranchesHit) {
+ this.nrBranchesHit = nrBranchesHit;
+ }
+
+ int nrOfLinesWithNonZeroExecution() {
+ return nrOfLinesWithNonZeroExecution;
+ }
+
+ void nrOfLinesWithNonZeroExecution(int nrOfLinesWithNonZeroExecution) {
+ this.nrOfLinesWithNonZeroExecution = nrOfLinesWithNonZeroExecution;
+ }
+
+ int nrOfInstrumentedLines() {
+ return nrOfInstrumentedLines;
+ }
+
+ void nrOfInstrumentedLines(int nrOfInstrumentedLines) {
+ this.nrOfInstrumentedLines = nrOfInstrumentedLines;
+ }
+
+ Collection<LineCoverage> getAllLineExecution() {
+ return lines.values();
+ }
+
+ @VisibleForTesting
+ TreeMap<String, Integer> getLineNumbers() {
+ return lineNumbers;
+ }
+
+
+ Set<Entry<String, Integer>> getAllLineNumbers() {
+ return lineNumbers.entrySet();
+ }
+
+ @VisibleForTesting
+ TreeMap<String, Integer> getFunctionsExecution() {
+ return functionsExecution;
+ }
+
+ Set<Entry<String, Integer>> getAllExecutionCount() {
+ return functionsExecution.entrySet();
+ }
+
+ Collection<BranchCoverage> getAllBranches() {
+ return branches.values();
+ }
+
+ @VisibleForTesting
+ Map<Integer, LineCoverage> getLines() {
+ return lines;
+ }
+
+ void addLineNumber(String functionName, Integer lineNumber) {
+ this.lineNumbers.put(functionName, lineNumber);
+ }
+
+ void addAllLineNumbers(TreeMap<String, Integer> lineNumber) {
+ this.lineNumbers.putAll(lineNumber);
+ }
+
+ void addFunctionExecution(String functionName, Integer executionCount) {
+ this.functionsExecution.put(functionName, executionCount);
+ }
+
+ void addAllFunctionsExecution(TreeMap<String, Integer> functionsExecution) {
+ this.functionsExecution.putAll(functionsExecution);
+ }
+
+ void addBranch(Integer lineNumber, BranchCoverage branch) {
+ this.branches.put(lineNumber, branch);
+ }
+
+ void addAllBranches(TreeMap<Integer, BranchCoverage> branches) {
+ this.branches.putAll(branches);
+ }
+
+ void addLine(Integer lineNumber, LineCoverage line) {
+ this.lines.put(lineNumber, line);
+ }
+
+ void addAllLines(TreeMap<Integer, LineCoverage> lines) {
+ this.lines.putAll(lines);
+ }
+}
+
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/BUILD b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/BUILD
new file mode 100644
index 0000000000..daf81b51d3
--- /dev/null
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/BUILD
@@ -0,0 +1,108 @@
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"]) # Apache 2.0
+
+java_test(
+ name = "BranchCoverageTest",
+ srcs = ["BranchCoverageTest.java"],
+ deps = [
+ "//third_party:junit4",
+ "//third_party:truth",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:BranchCoverage",
+ ],
+)
+
+java_test(
+ name = "LineCoverageTest",
+ srcs = ["LineCoverageTest.java"],
+ deps = [
+ "//third_party:junit4",
+ "//third_party:truth",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:LineCoverage",
+ ],
+)
+
+java_test(
+ name = "SourceFileCoverageTest",
+ srcs = ["SourceFileCoverageTest.java"],
+ deps = [
+ ":LcovMergerTestUtils",
+ ":LineCoverageTest",
+ "//third_party:junit4",
+ "//third_party:truth",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:LineCoverage",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:SourceFileCoverage",
+ ],
+)
+
+java_test(
+ name = "LcovPrinterTest",
+ srcs = ["LcovPrinterTest.java"],
+ deps = [
+ ":LcovMergerTestUtils",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:Coverage",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:LcovConstants",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:LcovPrinter",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:SourceFileCoverage",
+ ],
+)
+
+java_test(
+ name = "LcovParserTest",
+ srcs = ["LcovParserTest.java"],
+ deps = [
+ ":LcovMergerTestUtils",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:Coverage",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:LcovConstants",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:LcovParser",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:SourceFileCoverage",
+ ],
+)
+
+java_test(
+ name = "CoverageTest",
+ srcs = ["CoverageTest.java"],
+ deps = [
+ ":LcovMergerTestUtils",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:Coverage",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:SourceFileCoverage",
+ ],
+)
+
+java_test(
+ name = "MainTest",
+ srcs = ["MainTest.java"],
+ deps = [
+ ":LcovMergerTestUtils",
+ "//third_party:junit4",
+ "//third_party:truth",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:MainLibrary",
+ ],
+)
+
+java_library(
+ name = "LcovMergerTestUtils",
+ testonly = 1,
+ srcs = ["LcovMergerTestUtils.java"],
+ deps = [
+ "//third_party:guava",
+ "//third_party:truth",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:LineCoverage",
+ "//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:SourceFileCoverage",
+ ],
+)
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = ["//visibility:public"],
+)
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/BranchCoverageTest.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/BranchCoverageTest.java
new file mode 100644
index 0000000000..bd527e52b8
--- /dev/null
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/BranchCoverageTest.java
@@ -0,0 +1,156 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@BranchCoverageData}.
+ */
+@RunWith(JUnit4.class)
+public class BranchCoverageTest {
+
+ private static final int BRANCH1_LINE_NR = 10;
+ private static final int BRANCH1_BLOCK_NR = 3;
+ private static final int BRANCH1_BRANCH_NR = 2;
+ private static final boolean BRANCH1_WAS_EXECUTED = false;
+ private static final int BRANCH1_NR_EXECUTIONS = 0;
+
+ private static final boolean BRANCH1_OTHER_TRACEFILE_WAS_EXECUTED = true;
+ private static final int BRANCH1_OTHER_TRACEFILE_NR_EXECUTIONS = 5;
+
+ private static final int BRANCH2_LINE_NR = 20;
+ private static final int BRANCH2_BLOCK_NR = 7;
+ private static final int BRANCH2_BRANCH_NR = 2;
+ private static final boolean BRANCH2_WAS_EXECUTED = false;
+ private static final int BRANCH2_NR_EXECUTIONS = 0;
+
+ private static final boolean BRANCH2_OTHER_TRACEFILE_WAS_EXECUTED = false;
+ private static final int BRANCH2_OTHER_TRACEFILE_NR_EXECUTIONS = 0;
+
+ static final BranchCoverage getBranch1CoverageData() {
+ return BranchCoverage.create(
+ BRANCH1_LINE_NR,
+ BRANCH1_BLOCK_NR,
+ BRANCH1_BRANCH_NR,
+ BRANCH1_WAS_EXECUTED,
+ BRANCH1_NR_EXECUTIONS
+ );
+ }
+
+ static final BranchCoverage getBranch2CoverageData() {
+ return BranchCoverage.create(
+ BRANCH2_LINE_NR,
+ BRANCH2_BLOCK_NR,
+ BRANCH2_BRANCH_NR,
+ BRANCH2_WAS_EXECUTED,
+ BRANCH2_NR_EXECUTIONS
+ );
+ }
+
+ static final BranchCoverage getBranch1OtherTracefileCoverageData() {
+ return BranchCoverage.create(
+ BRANCH1_LINE_NR,
+ BRANCH1_BLOCK_NR,
+ BRANCH1_BRANCH_NR,
+ BRANCH1_OTHER_TRACEFILE_WAS_EXECUTED,
+ BRANCH1_OTHER_TRACEFILE_NR_EXECUTIONS
+ );
+ }
+
+ static final BranchCoverage getBranch2OtherTracefileCoverageData() {
+ return BranchCoverage.create(
+ BRANCH2_LINE_NR,
+ BRANCH2_BLOCK_NR,
+ BRANCH2_BRANCH_NR,
+ BRANCH2_OTHER_TRACEFILE_WAS_EXECUTED,
+ BRANCH2_OTHER_TRACEFILE_NR_EXECUTIONS
+ );
+ }
+
+ @Test
+ public void testMergeBranch1() {
+ BranchCoverage branch1 = getBranch1CoverageData();
+ BranchCoverage branch1OtherTracefile = getBranch1OtherTracefileCoverageData();
+ BranchCoverage merged = BranchCoverage.merge(branch1, branch1OtherTracefile);
+ assertThat(merged.lineNumber()).isEqualTo(branch1.lineNumber());
+ assertThat(merged.blockNumber()).isEqualTo(branch1.blockNumber());
+ assertThat(merged.branchNumber()).isEqualTo(branch1.branchNumber());
+ assertThat(merged.wasExecuted()).isTrue();
+ assertThat(merged.nrOfExecutions()).isEqualTo(
+ branch1.nrOfExecutions() + branch1OtherTracefile.nrOfExecutions());
+ }
+
+ @Test
+ public void testMergeBranch2() {
+ BranchCoverage branch2 = getBranch2CoverageData();
+ BranchCoverage branch2OtherTracefile = getBranch2OtherTracefileCoverageData();
+ BranchCoverage merged = BranchCoverage.merge(branch2, branch2OtherTracefile);
+ assertThat(merged.lineNumber()).isEqualTo(branch2.lineNumber());
+ assertThat(merged.blockNumber()).isEqualTo(branch2.blockNumber());
+ assertThat(merged.branchNumber()).isEqualTo(branch2.branchNumber());
+ assertThat(merged.wasExecuted()).isFalse();
+ assertThat(merged.nrOfExecutions()).isEqualTo(0);
+ }
+
+ @Test
+ public void testMergeBranch1Branch2AssertationError() {
+ BranchCoverage branch1 = getBranch1CoverageData();
+ BranchCoverage branch2 = getBranch2CoverageData();
+ try {
+ BranchCoverage.merge(branch1, branch2);
+ } catch (AssertionError er) {
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testcreateBranchCoverageDataInvalidWasExecutedTrue() {
+ try {
+ BranchCoverage.create(
+ BRANCH1_LINE_NR,
+ BRANCH1_BLOCK_NR,
+ BRANCH1_BRANCH_NR,
+ true,
+ 0
+ );
+ } catch (AssertionError er) {
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testcreateBranchCoverageDataInvalidWasExecutedFalse() {
+ try {
+ BranchCoverage.create(
+ BRANCH1_LINE_NR,
+ BRANCH1_BLOCK_NR,
+ BRANCH1_BRANCH_NR,
+ false,
+ 10
+ );
+ } catch (AssertionError er) {
+ return;
+ }
+ fail();
+ }
+}
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/CoverageTest.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/CoverageTest.java
new file mode 100644
index 0000000000..724a2095a5
--- /dev/null
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/CoverageTest.java
@@ -0,0 +1,81 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.assertMergedSourceFile;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.assertTracefile1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createLinesExecution1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createLinesExecution2;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createSourceFile1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createSourceFile2;
+
+import com.google.common.collect.Iterables;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for LcovMerger.
+ */
+@RunWith(JUnit4.class)
+public class CoverageTest {
+
+ private Coverage coverage;
+
+ @Before
+ public void initializeCoverage() {
+ coverage = new Coverage();
+ }
+
+ @Test
+ public void testOneTracefile() {
+ SourceFileCoverage sourceFileCoverage =
+ createSourceFile1(createLinesExecution1());
+ coverage.add(sourceFileCoverage);
+ assertThat(coverage.getAllSourceFiles()).hasSize(1);
+ assertTracefile1(Iterables.get(coverage.getAllSourceFiles(), 0));
+ }
+
+ @Test
+ public void testTwoOverlappingTracefiles() {
+ int[] linesExecution1 = createLinesExecution1();
+ int[] linesExecution2 = createLinesExecution2();
+ SourceFileCoverage sourceFileCoverage1 = createSourceFile1(linesExecution1);
+ SourceFileCoverage sourceFileCoverage2 = createSourceFile2(linesExecution2);
+
+ coverage.add(sourceFileCoverage1);
+ coverage.add(sourceFileCoverage2);
+
+ assertThat(coverage.getAllSourceFiles()).hasSize(1);
+ SourceFileCoverage merged = Iterables.get(coverage.getAllSourceFiles(), 0);
+ assertMergedSourceFile(merged, linesExecution1, linesExecution2);
+ }
+
+ @Test
+ public void testTwoTracefiles() {
+ SourceFileCoverage sourceFileCoverage1 =
+ createSourceFile1(createLinesExecution1());
+ SourceFileCoverage sourceFileCoverage2 = createSourceFile1(
+ "SOME_OTHER_FILENAME", createLinesExecution1());
+
+ coverage.add(sourceFileCoverage1);
+ coverage.add(sourceFileCoverage2);
+ assertThat(coverage.getAllSourceFiles()).hasSize(2);
+ assertTracefile1(Iterables.get(coverage.getAllSourceFiles(), 0));
+ assertTracefile1(Iterables.get(coverage.getAllSourceFiles(), 1));
+ }
+}
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovMergerTest.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovMergerTest.java
deleted file mode 100644
index 8460b7eb5c..0000000000
--- a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovMergerTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2016 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.lcovmerger;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.devtools.build.lib.testutil.Scratch;
-import com.google.devtools.build.lib.vfs.FileSystemUtils;
-import com.google.devtools.build.lib.vfs.util.FileSystems;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * Test for LcovMerger.
- */
-@RunWith(JUnit4.class)
-public class LcovMergerTest {
-
- private final Scratch scratch =
- new Scratch(FileSystemUtils.getWorkingDirectory(FileSystems.getJavaIoFileSystem()));
-
- @Test
- public void testZeroDatFile() throws IOException {
- scratch.dir("dir0");
-
- File merged = new File("dir0Merged.dat");
- File dir = new File("dir0");
- LcovMerger merger = new LcovMerger(dir.getAbsolutePath(), merged.getAbsolutePath());
- boolean success = merger.merge();
- assertThat(success).isFalse();
-
- assertThat(merged.exists()).isFalse();
- }
-
- @Test
- public void testOneDatFile() throws IOException {
- String content = "This is an lcov file.";
- scratch.file("dir1/jvcov.dat", content);
-
- File merged = new File("dir1Merged.dat");
- File dir = new File("dir1");
- LcovMerger merger = new LcovMerger(dir.getAbsolutePath(), merged.getAbsolutePath());
- boolean success = merger.merge();
- assertThat(success).isTrue();
-
- assertThat(merged.exists()).isTrue();
- String readContent = new String(Files.readAllBytes(merged.toPath())).trim();
- assertThat(readContent).isEqualTo(content);
- }
-
- @Test
- public void testTwoDatFiles() throws IOException {
- scratch.file("dir2/jvcov1.dat", "This is an lcov file.");
- scratch.file("dir2/jvcov2.dat", "This is another lcov file.");
-
- File merged = new File("dir2Merged.dat");
- File dir = new File("dir2");
- LcovMerger merger = new LcovMerger(dir.getAbsolutePath(), merged.getAbsolutePath());
- boolean success = merger.merge();
- assertThat(success).isFalse();
-
- assertThat(merged.exists()).isFalse();
- }
-}
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovMergerTestUtils.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovMergerTestUtils.java
new file mode 100644
index 0000000000..abaed3186d
--- /dev/null
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovMergerTestUtils.java
@@ -0,0 +1,424 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Helper class for creating and parsing lcov tracefiles and the necessary data structured used by
+ * {@code LcovMerger}.
+ *
+ * The tests are floating around 2 main tracefiles (numbered with 1 and 2 throughout the tests code
+ * base). The tracefiles refer the same source file with different coverage data, making them good
+ * candidates for being merged.
+ *
+ * There are multiple static variables defined for information extracted from each of
+ * tracefile{1,2} (e.g. the number of lines found, the number of functions hit, lines execution).
+ *
+ * Additionally, the 2 tracefiles may be used in tests multiple times with different source
+ * filenames so they could be considered as different entries in the merged tracefile.
+ */
+public class LcovMergerTestUtils {
+
+ // The content of tracefile1.
+ static final ImmutableList<String> TRACEFILE1 = ImmutableList.of(
+ "SF:SOURCE_FILENAME",
+ "FN:10,file1-func1",
+ "FN:20,file1-func2",
+ "FN:25,file1-func3",
+ "FNDA:3,file1-func1",
+ "FNDA:5,file1-func2",
+ "FNDA:0,file1-func3",
+ "FNF:3",
+ "FNH:2",
+ "DA:10,3",
+ "DA:11,3",
+ "DA:12,30",
+ "DA:13,30",
+ "DA:15,3",
+ "DA:16,0",
+ "DA:17,0",
+ "DA:19,3",
+ "DA:20,5",
+ "DA:21,5",
+ "DA:22,5",
+ "DA:23,5",
+ "DA:25,0",
+ "DA:26,0",
+ "LH:10",
+ "LF:14",
+ "end_of_record"
+ );
+
+ static final ImmutableList<String> TRACEFILE1_DIFFERENT_NAME = ImmutableList.of(
+ "SF:DIFFERENT_SOURCE_FILENAME",
+ "FN:10,file1-func1",
+ "FN:20,file1-func2",
+ "FN:25,file1-func3",
+ "FNDA:3,file1-func1",
+ "FNDA:5,file1-func2",
+ "FNDA:0,file1-func3",
+ "FNF:3",
+ "FNH:2",
+ "DA:10,3",
+ "DA:11,3",
+ "DA:12,30",
+ "DA:13,30",
+ "DA:15,3",
+ "DA:16,0",
+ "DA:17,0",
+ "DA:19,3",
+ "DA:20,5",
+ "DA:21,5",
+ "DA:22,5",
+ "DA:23,5",
+ "DA:25,0",
+ "DA:26,0",
+ "LH:10",
+ "LF:14",
+ "end_of_record"
+ );
+
+ // The content of tracefile2.
+ static final ImmutableList<String> TRACEFILE2 = ImmutableList.of(
+ "SF:SOURCE_FILENAME",
+ "FN:10,file1-func1",
+ "FN:20,file1-func2",
+ "FN:25,file1-func3",
+ "FNDA:2,file1-func1",
+ "FNDA:3,file1-func2",
+ "FNDA:2,file1-func3",
+ "FNF:3",
+ "FNH:3",
+ "DA:10,2",
+ "DA:11,2",
+ "DA:12,20",
+ "DA:13,20",
+ "DA:15,2",
+ "DA:16,2",
+ "DA:17,2",
+ "DA:19,0",
+ "DA:20,3",
+ "DA:21,3",
+ "DA:22,3",
+ "DA:23,3",
+ "DA:25,2",
+ "DA:26,2",
+ "LH:13",
+ "LF:14",
+ "end_of_record"
+ );
+
+ // The content of a tracefile after tracefile1 and tracefile2 were merged together.
+ static final ImmutableList<String> MERGED_TRACEFILE = ImmutableList.of(
+ "SF:SOURCE_FILENAME",
+ "FN:10,file1-func1",
+ "FN:20,file1-func2",
+ "FN:25,file1-func3",
+ "FNDA:5,file1-func1",
+ "FNDA:8,file1-func2",
+ "FNDA:2,file1-func3",
+ "FNF:3",
+ "FNH:3",
+ "DA:10,5",
+ "DA:11,5",
+ "DA:12,50",
+ "DA:13,50",
+ "DA:15,5",
+ "DA:16,2",
+ "DA:17,2",
+ "DA:19,3",
+ "DA:20,8",
+ "DA:21,8",
+ "DA:22,8",
+ "DA:23,8",
+ "DA:25,2",
+ "DA:26,2",
+ "LH:14",
+ "LF:14",
+ "end_of_record"
+ );
+
+ static final String SOURCE_FILENAME = "SOURCE_FILENAME";
+ static final int NR_FUNCTIONS_FOUND = 3;
+ static final int NR_FUNCTIONS_HIT_TRACEFILE1 = 2;
+ static final int NR_FUNCTIONS_HIT_TRACEFILE2 = 3;
+
+ static final String FUNC_1 = "file1-func1";
+ static final int FUNC_1_LINE_NR = 10;
+ static final int FUNC_1_NR_EXECUTED_LINES_TRACEFILE1 = 3;
+ static final int FUNC_1_NR_EXECUTED_LINES_TRACEFILE2 = 2;
+
+ static final String FUNC_2 = "file1-func2";
+ static final int FUNC_2_LINE_NR = 20;
+ static final int FUNC_2_NR_EXECUTED_LINES_TRACEFILE1 = 5;
+ static final int FUNC_2_NR_EXECECUTED_LINES_TRACEFILE2 = 3;
+
+ static final String FUNC_3 = "file1-func3";
+ static final int FUNC_3_LINE_NR = 25;
+ static final int FUNC_3_NR_EXECUTED_LINES_TRACEFILE1 = 0;
+ static final int FUNC_3_NR_EXECUTED_LINES_TRACEFILE2 = 2;
+
+ static final int NR_LINES_FOUND = 14;
+ static final int NR_LINES_HIT_TRACEFILE1 = 10;
+ static final int NR_LINES_HIT_TRACEFILE2 = 13;
+
+ static final int MAX_LINES_IN_FILE = 27;
+
+ static int[] createLinesExecution1() {
+ int[] lineExecutionCountForTracefile = new int[MAX_LINES_IN_FILE];
+ for (int i = 0; i < MAX_LINES_IN_FILE; ++i) {
+ lineExecutionCountForTracefile[i] = -1; // no corresponding DA line for line i
+ }
+
+ lineExecutionCountForTracefile[10] = 3; // DA:10,3
+ lineExecutionCountForTracefile[11] = 3; // DA:11,3
+ lineExecutionCountForTracefile[12] = 30; // DA:12,30
+ lineExecutionCountForTracefile[13] = 30; // DA:13,30
+ lineExecutionCountForTracefile[15] = 3; // DA:15,3
+ lineExecutionCountForTracefile[16] = 0; // DA:16,0
+ lineExecutionCountForTracefile[17] = 0; // DA:17,0
+ lineExecutionCountForTracefile[19] = 3; // DA:19,3
+ lineExecutionCountForTracefile[20] = 5; // DA:20,5
+ lineExecutionCountForTracefile[21] = 5; // DA:21,5
+ lineExecutionCountForTracefile[22] = 5; // DA:22,5
+ lineExecutionCountForTracefile[23] = 5; // DA:23,5
+ lineExecutionCountForTracefile[25] = 0; // DA:25,0
+ lineExecutionCountForTracefile[26] = 0; // DA:26,0
+ return lineExecutionCountForTracefile;
+ }
+
+ static int[] createLinesExecution2() {
+ int[] lineExecutionCountForTracefile = new int[MAX_LINES_IN_FILE];
+ for (int i = 0; i < MAX_LINES_IN_FILE; ++i) {
+ lineExecutionCountForTracefile[i] = -1; // no corresponding DA line for line i
+ }
+
+ lineExecutionCountForTracefile[10] = 2; // DA:10,2
+ lineExecutionCountForTracefile[11] = 2; // DA:11,2
+ lineExecutionCountForTracefile[12] = 20; // DA:12,20
+ lineExecutionCountForTracefile[13] = 20; // DA:13,20
+ lineExecutionCountForTracefile[15] = 2; // DA:15,2
+ lineExecutionCountForTracefile[16] = 2; // DA:16,2
+ lineExecutionCountForTracefile[17] = 2; // DA:17,2
+ lineExecutionCountForTracefile[19] = 0; // DA:19,0
+ lineExecutionCountForTracefile[20] = 3; // DA:20,3
+ lineExecutionCountForTracefile[21] = 3; // DA:21,3
+ lineExecutionCountForTracefile[22] = 3; // DA:22,3
+ lineExecutionCountForTracefile[23] = 3; // DA:23,3
+ lineExecutionCountForTracefile[25] = 2; // DA:25,2
+ lineExecutionCountForTracefile[26] = 2; // DA:26,2
+ return lineExecutionCountForTracefile;
+ }
+
+ static SourceFileCoverage createSourceFile1(int[] lineExecutionCountForTracefile) {
+ return createSourceFile1(SOURCE_FILENAME, lineExecutionCountForTracefile);
+ }
+
+ // Create source file coverage data, excluding branch coverage
+ static SourceFileCoverage createSourceFile1(String sourceFilename, int[] lineExecutionCount) {
+ SourceFileCoverage sourceFile = new SourceFileCoverage(sourceFilename);
+
+ sourceFile.addLineNumber(FUNC_1, FUNC_1_LINE_NR);
+ sourceFile.addFunctionExecution(FUNC_1, FUNC_1_NR_EXECUTED_LINES_TRACEFILE1);
+
+ sourceFile.addLineNumber(FUNC_2, FUNC_2_LINE_NR);
+ sourceFile.addFunctionExecution(FUNC_2, FUNC_2_NR_EXECUTED_LINES_TRACEFILE1);
+
+ sourceFile.addLineNumber(FUNC_3, FUNC_3_LINE_NR);
+ sourceFile.addFunctionExecution(FUNC_3, FUNC_3_NR_EXECUTED_LINES_TRACEFILE1);
+
+ sourceFile.nrFunctionsFound(NR_FUNCTIONS_FOUND);
+ sourceFile.nrFunctionsHit(NR_FUNCTIONS_HIT_TRACEFILE1);
+
+
+ for (int line = FUNC_1_LINE_NR; line < MAX_LINES_IN_FILE; line++) {
+ if (lineExecutionCount[line] >= 0) {
+ sourceFile.addLine(
+ line, LineCoverage.create(
+ line, lineExecutionCount[line], null));
+ }
+ }
+
+ sourceFile.nrOfLinesWithNonZeroExecution(NR_LINES_HIT_TRACEFILE1);
+ sourceFile.nrOfInstrumentedLines(NR_LINES_FOUND);
+
+ return sourceFile;
+ }
+
+ // Create source file coverage data, excluding branch coverage
+ static SourceFileCoverage createSourceFile2(int[] lineExecutionCount) {
+ SourceFileCoverage sourceFileCoverage = new SourceFileCoverage(SOURCE_FILENAME);
+
+ sourceFileCoverage.addLineNumber(FUNC_1, FUNC_1_LINE_NR);
+ sourceFileCoverage.addFunctionExecution(FUNC_1, FUNC_1_NR_EXECUTED_LINES_TRACEFILE2);
+
+ sourceFileCoverage.addLineNumber(FUNC_2, FUNC_2_LINE_NR);
+ sourceFileCoverage.addFunctionExecution(FUNC_2, FUNC_2_NR_EXECECUTED_LINES_TRACEFILE2);
+
+ sourceFileCoverage.addLineNumber(FUNC_3, FUNC_3_LINE_NR);
+ sourceFileCoverage.addFunctionExecution(FUNC_3, FUNC_3_NR_EXECUTED_LINES_TRACEFILE2);
+
+ sourceFileCoverage.nrFunctionsFound(NR_FUNCTIONS_FOUND);
+ sourceFileCoverage.nrFunctionsHit(NR_FUNCTIONS_HIT_TRACEFILE2);
+
+
+ for (int line = FUNC_1_LINE_NR; line < MAX_LINES_IN_FILE; line++) {
+ if (lineExecutionCount[line] >= 0) {
+ sourceFileCoverage.addLine(
+ line, LineCoverage.create(
+ line, lineExecutionCount[line], null));
+ }
+ }
+
+ sourceFileCoverage.nrOfLinesWithNonZeroExecution(NR_LINES_HIT_TRACEFILE2);
+ sourceFileCoverage.nrOfInstrumentedLines(NR_LINES_FOUND);
+
+ return sourceFileCoverage;
+ }
+
+ private static void assertLinesExecution_tracefile1(Map<Integer, LineCoverage> lines) {
+ int[] lineExecution = createLinesExecution1();
+
+ assertThat(lines.size()).isEqualTo(NR_LINES_FOUND);
+
+ for (int line = 10; line < lineExecution.length; line++) {
+ if (lineExecution[line] >= 0) {
+ LineCoverage lineCoverage = lines.get(line);
+ assertThat(lineCoverage.executionCount()).isEqualTo(lineExecution[line]);
+ assertThat(lineCoverage.lineNumber()).isEqualTo(line);
+ assertThat(lineCoverage.checksum()).isNull();
+ }
+ }
+ }
+
+ private static void assertLines_tracefile2(Map<Integer, LineCoverage> lines) {
+ int[] lineExecution = createLinesExecution2();
+
+ assertThat(lines.size()).isEqualTo(NR_LINES_FOUND);
+
+ for (int lineIndex = 10; lineIndex < lineExecution.length; lineIndex++) {
+ if (lineExecution[lineIndex] >= 0) {
+ LineCoverage line = lines.get(lineIndex);
+ assertThat(line.executionCount()).isEqualTo(lineExecution[lineIndex]);
+ assertThat(line.lineNumber()).isEqualTo(lineIndex);
+ assertThat(line.checksum()).isNull();
+ }
+ }
+ }
+
+ static void assertTracefile1(SourceFileCoverage sourceFile) {
+ Map<String, Integer> lineNumbers = sourceFile.getLineNumbers();
+ assertThat(lineNumbers.size()).isEqualTo(3);
+ assertThat(lineNumbers.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3);
+ assertThat(lineNumbers.get(FUNC_1)).isEqualTo(FUNC_1_LINE_NR);
+ assertThat(lineNumbers.get(FUNC_2)).isEqualTo(FUNC_2_LINE_NR);
+ assertThat(lineNumbers.get(FUNC_3)).isEqualTo(FUNC_3_LINE_NR);
+
+ Map<String, Integer> functionsExecution = sourceFile.getFunctionsExecution();
+ assertThat(functionsExecution.size()).isEqualTo(3);
+ assertThat(functionsExecution.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3);
+ assertThat(functionsExecution.get(FUNC_1)).isEqualTo(FUNC_1_NR_EXECUTED_LINES_TRACEFILE1);
+ assertThat(functionsExecution.get(FUNC_2)).isEqualTo(FUNC_2_NR_EXECUTED_LINES_TRACEFILE1);
+ assertThat(functionsExecution.get(FUNC_3)).isEqualTo(FUNC_3_NR_EXECUTED_LINES_TRACEFILE1);
+
+ assertLinesExecution_tracefile1(sourceFile.getLines());
+
+ assertThat(sourceFile.nrOfInstrumentedLines()).isEqualTo(14);
+ assertThat(sourceFile.nrOfLinesWithNonZeroExecution()).isEqualTo(10);
+ }
+
+ static void assertTracefile2(SourceFileCoverage sourceFile) {
+ Map<String, Integer> lineNumbers = sourceFile.getLineNumbers();
+ assertThat(lineNumbers.size()).isEqualTo(3);
+ assertThat(lineNumbers.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3);
+ assertThat(lineNumbers.get(FUNC_1)).isEqualTo(FUNC_1_LINE_NR);
+ assertThat(lineNumbers.get(FUNC_2)).isEqualTo(FUNC_2_LINE_NR);
+ assertThat(lineNumbers.get(FUNC_3)).isEqualTo(FUNC_3_LINE_NR);
+
+ Map<String, Integer> functionsExecution = sourceFile.getFunctionsExecution();
+ assertThat(functionsExecution.size()).isEqualTo(3);
+ assertThat(functionsExecution.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3);
+ assertThat(functionsExecution.get(FUNC_1)).isEqualTo(FUNC_1_NR_EXECUTED_LINES_TRACEFILE2);
+ assertThat(functionsExecution.get(FUNC_2)).isEqualTo(FUNC_2_NR_EXECECUTED_LINES_TRACEFILE2);
+ assertThat(functionsExecution.get(FUNC_3)).isEqualTo(FUNC_3_NR_EXECUTED_LINES_TRACEFILE2);
+
+ assertLines_tracefile2(sourceFile.getLines());
+
+ assertThat(sourceFile.nrOfInstrumentedLines()).isEqualTo(14);
+ assertThat(sourceFile.nrOfLinesWithNonZeroExecution()).isEqualTo(13);
+ }
+
+ static void assertMergedLineNumbers(TreeMap<String, Integer> lineNumbers) {
+ assertThat(lineNumbers.size()).isEqualTo(3);
+ assertThat(lineNumbers.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3);
+ assertThat(lineNumbers.get(FUNC_1)).isEqualTo(FUNC_1_LINE_NR);
+ assertThat(lineNumbers.get(FUNC_2)).isEqualTo(FUNC_2_LINE_NR);
+ assertThat(lineNumbers.get(FUNC_3)).isEqualTo(FUNC_3_LINE_NR);
+ }
+
+ static void assertMergedFunctionsExecution(TreeMap<String, Integer> functionsExecution) {
+ assertThat(functionsExecution.size()).isEqualTo(3);
+ assertThat(functionsExecution.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3);
+ assertThat(functionsExecution.get(FUNC_1)).isEqualTo(
+ FUNC_1_NR_EXECUTED_LINES_TRACEFILE1 + FUNC_1_NR_EXECUTED_LINES_TRACEFILE2);
+ assertThat(functionsExecution.get(FUNC_2)).isEqualTo(
+ FUNC_2_NR_EXECUTED_LINES_TRACEFILE1 + FUNC_2_NR_EXECECUTED_LINES_TRACEFILE2);
+ assertThat(functionsExecution.get(FUNC_3)).isEqualTo(
+ FUNC_3_NR_EXECUTED_LINES_TRACEFILE1 + FUNC_3_NR_EXECUTED_LINES_TRACEFILE2);
+ }
+
+ static void assertMergedLines(
+ Map<Integer, LineCoverage> lines,
+ int[] linesExecution1,
+ int[] linesExecution2) {
+ assertThat(lines.size()).isEqualTo(14);
+
+ for (int lineIndex = 10; lineIndex < MAX_LINES_IN_FILE; ++lineIndex) {
+ int totalExecutionCount = 0;
+ boolean wasInstrumented = false;
+ if (linesExecution1[lineIndex] >= 0) {
+ totalExecutionCount += linesExecution1[lineIndex];
+ wasInstrumented = true;
+ }
+ if (linesExecution2[lineIndex] >= 0) {
+ totalExecutionCount += linesExecution2[lineIndex];
+ wasInstrumented = true;
+ }
+ if (wasInstrumented) {
+ LineCoverage line = lines.get(lineIndex);
+ assertThat(line.executionCount()).isEqualTo(totalExecutionCount);
+ assertThat(line.lineNumber()).isEqualTo(lineIndex);
+ assertThat(line.checksum()).isNull();
+ }
+ }
+ }
+
+ static void assertMergedSourceFile(
+ SourceFileCoverage merged, int[] linesExecution1, int[] linesExecution2) {
+ assertMergedLineNumbers(merged.getLineNumbers());
+ assertMergedFunctionsExecution(merged.getFunctionsExecution());
+ assertMergedLines(merged.getLines(), linesExecution1, linesExecution2);
+
+ assertThat(merged.nrFunctionsFound()).isEqualTo(NR_FUNCTIONS_FOUND);
+ assertThat(merged.nrFunctionsHit()).isEqualTo(NR_FUNCTIONS_FOUND);
+ assertThat(merged.nrOfLinesWithNonZeroExecution()).isEqualTo(14);
+ assertThat(merged.nrOfInstrumentedLines()).isEqualTo(14);
+ }
+}
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovParserTest.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovParserTest.java
new file mode 100644
index 0000000000..e94fb4091c
--- /dev/null
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovParserTest.java
@@ -0,0 +1,73 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.TRACEFILE1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.TRACEFILE2;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.assertTracefile1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.assertTracefile2;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Joiner;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@LcovParser}.
+ */
+@RunWith(JUnit4.class)
+public class LcovParserTest {
+
+ @Test
+ public void testParseInvalidTracefile() throws IOException {
+ List<SourceFileCoverage> sourceFiles =
+ LcovParser.parse(new ByteArrayInputStream("Invalid lcov tracefile".getBytes(UTF_8)));
+ assertThat(sourceFiles).isEmpty();
+ }
+
+ @Test
+ public void testParseTracefileWithOneSourcefile() throws IOException {
+ List<SourceFileCoverage> sourceFiles = LcovParser.parse(
+ new ByteArrayInputStream(Joiner.on("\n").join(TRACEFILE1).getBytes(UTF_8)));
+ assertThat(sourceFiles).hasSize(1);
+ assertTracefile1(sourceFiles.get(0));
+ }
+
+ @Test
+ public void testParseTracefileWithTwoSourcefiles() throws IOException {
+ List<String> tracefile2ModifiedLines = new ArrayList<>();
+ tracefile2ModifiedLines.addAll(TRACEFILE2);
+ tracefile2ModifiedLines.set(0, "SF:BSOME_OTHER_FILE_THAT_IS_NOT_MERGED");
+
+ List<String> tracefileLines = new ArrayList<>();
+ tracefileLines.addAll(TRACEFILE1);
+ tracefileLines.addAll(tracefile2ModifiedLines);
+
+ InputStream inputStream =
+ new ByteArrayInputStream(Joiner.on("\n").join(tracefileLines).getBytes(UTF_8));
+ List<SourceFileCoverage> sourceFiles = LcovParser.parse(inputStream);
+
+ assertThat(sourceFiles).hasSize(2);
+ assertTracefile1(sourceFiles.get(0));
+ assertTracefile2(sourceFiles.get(1));
+ }
+}
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovPrinterTest.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovPrinterTest.java
new file mode 100644
index 0000000000..3844730f73
--- /dev/null
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LcovPrinterTest.java
@@ -0,0 +1,94 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.TRACEFILE1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.TRACEFILE1_DIFFERENT_NAME;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createLinesExecution1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createSourceFile1;
+
+import com.google.common.base.Splitter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@LcovPrinter}.
+ */
+@RunWith(JUnit4.class)
+public class LcovPrinterTest {
+
+ private SourceFileCoverage sourceFileCoverage1;
+ private SourceFileCoverage sourceFileCoverage2;
+ private ByteArrayOutputStream byteOutputStream;
+ private Coverage coverage;
+
+ @Before
+ public void init() {
+ sourceFileCoverage1 = createSourceFile1(createLinesExecution1());
+ sourceFileCoverage2 = LcovMergerTestUtils.createSourceFile1(
+ TRACEFILE1_DIFFERENT_NAME.get(0).substring(3), createLinesExecution1());
+ byteOutputStream = new ByteArrayOutputStream();
+ coverage = new Coverage();
+ }
+
+ @Test
+ public void testPrintTwoFiles() throws IOException {
+ coverage.add(sourceFileCoverage1);
+ coverage.add(sourceFileCoverage2);
+
+ assertThat(LcovPrinter.print(byteOutputStream, coverage)).isTrue();
+ byteOutputStream.close();
+
+ Iterable<String> fileLines = Splitter.on('\n').split(byteOutputStream.toString());
+
+ List<String> tracefiles = new ArrayList<>();
+ tracefiles.addAll(TRACEFILE1_DIFFERENT_NAME);
+ tracefiles.addAll(TRACEFILE1);
+
+ // Last line of the file will always be a newline.
+ assertThat(fileLines).hasSize(tracefiles.size() + 1);
+ int lineIndex = 0;
+ for (String line : fileLines) {
+ if (lineIndex == tracefiles.size()) {
+ break;
+ }
+ assertThat(line).isEqualTo(tracefiles.get(lineIndex++));
+ }
+ }
+
+ @Test
+ public void testPrintOneFile() throws IOException {
+ coverage.add(sourceFileCoverage1);
+ assertThat(LcovPrinter.print(byteOutputStream, coverage)).isTrue();
+ byteOutputStream.close();
+ Iterable<String> fileLines = Splitter.on('\n').split(byteOutputStream.toString());
+ // Last line of the file will always be a newline.
+ assertThat(fileLines).hasSize(TRACEFILE1.size() + 1);
+ int lineIndex = 0;
+ for (String line : fileLines) {
+ if (lineIndex == TRACEFILE1.size()) {
+ break;
+ }
+ assertThat(line).isEqualTo(TRACEFILE1.get(lineIndex++));
+ }
+ }
+}
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LineCoverageTest.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LineCoverageTest.java
new file mode 100644
index 0000000000..b398f3442e
--- /dev/null
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/LineCoverageTest.java
@@ -0,0 +1,126 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@LineExecutionCoverageData}.
+ */
+@RunWith(JUnit4.class)
+public class LineCoverageTest {
+ private static final int LINE1_NR = 10;
+ private static final int LINE1_EXECUTION_COUNT = 10;
+ private static final String LINE1_CHECKSUM = "90345njksdf2";
+ private static final int LINE1_OTHER_TRACEFILE_EXECUTION_COUNT = 5;
+
+ private static final int LINE2_NR = 20;
+ private static final int LINE2_EXECUTION_COUNT = 3;
+ private static final String LINE2_CHECKSUM = null;
+ private static final int LINE2_OTHER_TRACEFILE_EXECUTION_COUNT = 5;
+
+ static LineCoverage getLine1CoverageData() {
+ return LineCoverage.create(
+ LINE1_NR,
+ LINE1_EXECUTION_COUNT,
+ LINE1_CHECKSUM
+ );
+ }
+
+ static LineCoverage getLine1CoverageDataDifferentChecksum() {
+ return LineCoverage.create(
+ LINE1_NR,
+ LINE1_EXECUTION_COUNT,
+ LINE2_CHECKSUM
+ );
+ }
+
+ static LineCoverage getLine1CoverageDataOtherTracefile() {
+ return LineCoverage.create(
+ LINE1_NR,
+ LINE1_OTHER_TRACEFILE_EXECUTION_COUNT,
+ LINE1_CHECKSUM
+ );
+ }
+
+ static LineCoverage getLine2CoverageData() {
+ return LineCoverage.create(
+ LINE2_NR,
+ LINE2_EXECUTION_COUNT,
+ LINE2_CHECKSUM
+ );
+ }
+
+ static LineCoverage getLine2CoverageDataOtherTracefile() {
+ return LineCoverage.create(
+ LINE2_NR,
+ LINE2_OTHER_TRACEFILE_EXECUTION_COUNT,
+ LINE2_CHECKSUM
+ );
+ }
+
+ @Test
+ public void testMergeLine1() {
+ LineCoverage line1 = getLine1CoverageData();
+ LineCoverage line1OtherTracefile = getLine1CoverageDataOtherTracefile();
+ LineCoverage merged = LineCoverage.merge(line1, line1OtherTracefile);
+
+ assertThat(merged.lineNumber()).isEqualTo(LINE1_NR);
+ assertThat(merged.executionCount()).isEqualTo(
+ LINE1_EXECUTION_COUNT + LINE1_OTHER_TRACEFILE_EXECUTION_COUNT);
+ assertThat(merged.checksum()).isEqualTo(LINE1_CHECKSUM);
+ }
+
+ @Test
+ public void testMergeLine2() {
+ LineCoverage line2 = getLine2CoverageData();
+ LineCoverage line2OtherTracefile = getLine2CoverageDataOtherTracefile();
+ LineCoverage merged = LineCoverage.merge(line2, line2OtherTracefile);
+
+ assertThat(merged.lineNumber()).isEqualTo(LINE2_NR);
+ assertThat(merged.executionCount()).isEqualTo(
+ LINE2_EXECUTION_COUNT + LINE2_OTHER_TRACEFILE_EXECUTION_COUNT);
+ assertThat(merged.checksum()).isEqualTo(null);
+ }
+
+ @Test
+ public void testMergeLine1WithLine2() {
+ LineCoverage line1 = getLine1CoverageData();
+ LineCoverage line2 = getLine2CoverageData();
+ try {
+ LineCoverage.merge(line1, line2);
+ } catch (AssertionError er) {
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testMergeLine1DifferentChecksum() {
+ LineCoverage line1 = getLine1CoverageData();
+ LineCoverage line1DiffrentChecksum = getLine1CoverageDataDifferentChecksum();
+ try {
+ LineCoverage.merge(line1, line1DiffrentChecksum);
+ } catch (AssertionError er) {
+ return;
+ }
+ fail();
+ }
+}
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/MainTest.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/MainTest.java
new file mode 100644
index 0000000000..07be4215e5
--- /dev/null
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/MainTest.java
@@ -0,0 +1,58 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for {@link Main}.
+ */
+@RunWith(JUnit4.class)
+public class MainTest {
+
+ private Path coverageDir;
+
+ @Before
+ public void createCoverageDirectory() throws IOException {
+ coverageDir = Files.createTempDirectory("coverage-dir");
+ }
+
+ @Test
+ public void testMainEmptyCoverageDir() {
+ assertThat(Main.getLcovTracefiles(coverageDir.toAbsolutePath().toString())).isEmpty();
+ }
+
+ @Test
+ public void testMainGetLcovTracefiles() throws IOException {
+ Path ccCoverageDir = Files.createTempDirectory(coverageDir, "cc_coverage");
+ Path javaCoverageDir = Files.createTempDirectory(coverageDir, "java_coverage");
+
+ Files.createTempFile(ccCoverageDir, "tracefile1", ".dat");
+ Files.createTempFile(javaCoverageDir, "tracefile2", ".dat");
+
+ List<File> tracefiles = Main.getLcovTracefiles(coverageDir.toAbsolutePath().toString());
+ assertThat(tracefiles).hasSize(2);
+ }
+}
diff --git a/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/SourceFileCoverageTest.java b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/SourceFileCoverageTest.java
new file mode 100644
index 0000000000..9b22297e4e
--- /dev/null
+++ b/tools/test/LcovMerger/javatests/com/google/devtools/lcovmerger/SourceFileCoverageTest.java
@@ -0,0 +1,80 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.lcovmerger;
+
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.assertMergedFunctionsExecution;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.assertMergedLineNumbers;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.assertMergedLines;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.assertMergedSourceFile;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.assertTracefile1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createLinesExecution1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createLinesExecution2;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createSourceFile1;
+import static com.google.devtools.lcovmerger.LcovMergerTestUtils.createSourceFile2;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link SourceFileCoverage}.
+ */
+@RunWith(JUnit4.class)
+public class SourceFileCoverageTest {
+
+ private int[] linesExecution1;
+ private int[] linesExecution2;
+ private SourceFileCoverage sourceFile1;
+ private SourceFileCoverage sourceFile2;
+
+ @Before
+ public void initializeExecutionCountTracefiles() {
+ linesExecution1 = createLinesExecution1();
+ linesExecution2 = createLinesExecution2();
+
+ sourceFile1 = createSourceFile1(linesExecution1);
+ sourceFile2 = createSourceFile2(linesExecution2);
+ }
+
+ @Test
+ public void testCopyConstructor() {
+ assertTracefile1(new SourceFileCoverage(sourceFile1));
+ }
+
+ @Test
+ public void testMergeFunctionNameToLineNumber() {
+ assertMergedLineNumbers(SourceFileCoverage.mergeLineNumbers(sourceFile1, sourceFile2));
+ }
+
+ @Test
+ public void testMergeFunctionNameToExecutionCount() {
+ assertMergedFunctionsExecution(
+ SourceFileCoverage.mergeFunctionsExecution(sourceFile1, sourceFile2));
+ }
+
+ @Test
+ public void testMergeLineNumberToLineExecution() {
+ assertMergedLines(
+ SourceFileCoverage.mergeLines(sourceFile1, sourceFile2), linesExecution1, linesExecution2);
+ }
+
+ @Test
+ public void testMerge() {
+ assertMergedSourceFile(
+ SourceFileCoverage.merge(sourceFile1, sourceFile2), linesExecution1, linesExecution2);
+ }
+
+}