// 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 lineNumbers; // function name to line numbers private final TreeMap functionsExecution; // function name to execution count private final TreeMap branches; // line number to branch private final TreeMap lines; // line number to line execution 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); } /* * Returns the merged functions found in the two given {@code SourceFileCoverage}s. */ @VisibleForTesting static TreeMap mergeLineNumbers(SourceFileCoverage s1, SourceFileCoverage s2) { TreeMap 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 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 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 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)); return merged; } String sourceFileName() { return sourceFileName; } int nrFunctionsFound() { return functionsExecution.size(); } int nrFunctionsHit() { return (int) functionsExecution.entrySet().stream() .filter(function -> function.getValue() > 0) .count(); } int nrBranchesFound() { return branches.size(); } int nrBranchesHit() { return getNumberOfBranchesHit(this); } int nrOfLinesWithNonZeroExecution() { return getNumberOfExecutedLines(this); } int nrOfInstrumentedLines() { return this.lines.size(); } Collection getAllLineExecution() { return lines.values(); } @VisibleForTesting TreeMap getLineNumbers() { return lineNumbers; } Set> getAllLineNumbers() { return lineNumbers.entrySet(); } @VisibleForTesting TreeMap getFunctionsExecution() { return functionsExecution; } Set> getAllExecutionCount() { return functionsExecution.entrySet(); } Collection getAllBranches() { return branches.values(); } @VisibleForTesting Map getLines() { return lines; } void addLineNumber(String functionName, Integer lineNumber) { this.lineNumbers.put(functionName, lineNumber); } void addAllLineNumbers(TreeMap lineNumber) { this.lineNumbers.putAll(lineNumber); } void addFunctionExecution(String functionName, Integer executionCount) { this.functionsExecution.put(functionName, executionCount); } void addAllFunctionsExecution(TreeMap functionsExecution) { this.functionsExecution.putAll(functionsExecution); } void addBranch(Integer lineNumber, BranchCoverage branch) { if (this.branches.get(lineNumber) != null) { this.branches.put(lineNumber, BranchCoverage.merge(this.branches.get(lineNumber), branch)); return; } this.branches.put(lineNumber, branch); } void addAllBranches(TreeMap branches) { this.branches.putAll(branches); } void addLine(Integer lineNumber, LineCoverage line) { if (this.lines.get(lineNumber) != null) { this.lines.put(lineNumber, LineCoverage.merge(line, this.lines.get(lineNumber))); return; } this.lines.put(lineNumber, line); } void addAllLines(TreeMap lines) { this.lines.putAll(lines); } }