// 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.testing.coverage; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.jacoco.core.analysis.IBundleCoverage; import org.jacoco.core.analysis.IClassCoverage; import org.jacoco.core.analysis.ICounter; import org.jacoco.core.analysis.IMethodCoverage; import org.jacoco.core.analysis.IPackageCoverage; import org.jacoco.core.analysis.ISourceFileCoverage; import org.jacoco.core.data.ExecutionData; import org.jacoco.core.data.SessionInfo; import org.jacoco.report.IReportGroupVisitor; import org.jacoco.report.IReportVisitor; import org.jacoco.report.ISourceFileLocator; /** * Simple lcov formatter to be used with lcov_merger.par. * * <p>The lcov format is documented here: http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php */ public class JacocoLCOVFormatter { public IReportVisitor createVisitor( final File output, final Map<String, BranchCoverageDetail> branchCoverageDetail) { return new IReportVisitor() { private Map<String, Map<String, IClassCoverage>> sourceToClassCoverage = new TreeMap<>(); private Map<String, ISourceFileCoverage> sourceToFileCoverage = new TreeMap<>(); @Override public void visitInfo(List<SessionInfo> sessionInfos, Collection<ExecutionData> executionData) throws IOException {} @Override public void visitEnd() throws IOException { try (FileWriter fileWriter = new FileWriter(output, true); PrintWriter printWriter = new PrintWriter(fileWriter)) { for (String sourceFile : sourceToClassCoverage.keySet()) { processSourceFile(printWriter, sourceFile); } } } @Override public void visitBundle(IBundleCoverage bundle, ISourceFileLocator locator) throws IOException { // Jacoco's API is geared towards HTML/XML reports which have a hierarchical nature. The // following loop would call the report generators for packages, classes, methods, and // finally link the source view (which would be generated by walking the actual source file // and annotating the coverage data). For lcov, we don't really need the source file, but // we need to output FN/FNDA pairs with method coverage, which means we need to index this // information and process everything at the end. for (IPackageCoverage pkgCoverage : bundle.getPackages()) { for (IClassCoverage clsCoverage : pkgCoverage.getClasses()) { String fileName = clsCoverage.getPackageName() + "/" + clsCoverage.getSourceFileName(); if (!sourceToClassCoverage.containsKey(fileName)) { sourceToClassCoverage.put(fileName, new TreeMap<String, IClassCoverage>()); } sourceToClassCoverage.get(fileName).put(clsCoverage.getName(), clsCoverage); } for (ISourceFileCoverage srcCoverage : pkgCoverage.getSourceFiles()) { sourceToFileCoverage.put( srcCoverage.getPackageName() + "/" + srcCoverage.getName(), srcCoverage); } } } @Override public IReportGroupVisitor visitGroup(String name) throws IOException { return null; } private void processSourceFile(PrintWriter writer, String sourceFile) { writer.printf("SF:%s\n", sourceFile); ISourceFileCoverage srcCoverage = sourceToFileCoverage.get(sourceFile); if (srcCoverage != null) { // List methods, including methods from nested classes, in FN/FNDA pairs for (IClassCoverage clsCoverage : sourceToClassCoverage.get(sourceFile).values()) { for (IMethodCoverage mthCoverage : clsCoverage.getMethods()) { String name = constructFunctionName(mthCoverage, clsCoverage.getName()); writer.printf("FN:%d,%s\n", mthCoverage.getFirstLine(), name); writer.printf("FNDA:%d,%s\n", mthCoverage.getMethodCounter().getCoveredCount(), name); } } for (IClassCoverage clsCoverage : sourceToClassCoverage.get(sourceFile).values()) { BranchCoverageDetail detail = branchCoverageDetail.get(clsCoverage.getName()); if (detail != null) { for (int line : detail.linesWithBranches()) { int numBranches = detail.getBranches(line); boolean executed = detail.getExecutedBit(line); if (executed) { for (int branchIdx = 0; branchIdx < numBranches; branchIdx++) { if (detail.getTakenBit(line, branchIdx)) { writer.printf("BA:%d,%d\n", line, 2); // executed, taken } else { writer.printf("BA:%d,%d\n", line, 1); // executed, not taken } } } else { for (int branchIdx = 0; branchIdx < numBranches; branchIdx++) { writer.printf("BA:%d,%d\n", line, 0); // not executed } } } } } // List of DA entries matching source lines int firstLine = srcCoverage.getFirstLine(); int lastLine = srcCoverage.getLastLine(); for (int line = firstLine; line <= lastLine; line++) { ICounter instructionCounter = srcCoverage.getLine(line).getInstructionCounter(); if (instructionCounter.getTotalCount() != 0) { writer.printf("DA:%d,%d\n", line, instructionCounter.getCoveredCount()); } } } writer.println("end_of_record"); } private String constructFunctionName(IMethodCoverage mthCoverage, String clsName) { // The lcov spec doesn't of course cover Java formats, so we output the method signature. // lcov_merger doesn't seem to care about these entries. return clsName + "::" + mthCoverage.getName() + " " + mthCoverage.getDesc(); } }; } }