diff options
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); + } + +} |