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