| // 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); |
| } |
| } |
| |