001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
019 */
020 package org.sonar.plugins.jacoco;
021
022 import com.google.common.annotations.VisibleForTesting;
023 import com.google.common.base.Strings;
024 import org.apache.commons.lang.StringUtils;
025 import org.jacoco.core.analysis.*;
026 import org.jacoco.core.data.ExecutionDataReader;
027 import org.jacoco.core.data.ExecutionDataStore;
028 import org.jacoco.core.data.SessionInfoStore;
029 import org.jacoco.core.runtime.WildcardMatcher;
030 import org.sonar.api.batch.SensorContext;
031 import org.sonar.api.measures.CoverageMeasuresBuilder;
032 import org.sonar.api.measures.Measure;
033 import org.sonar.api.resources.JavaFile;
034 import org.sonar.api.resources.Project;
035 import org.sonar.api.utils.SonarException;
036
037 import java.io.File;
038 import java.io.FileInputStream;
039 import java.io.IOException;
040 import java.util.Collection;
041
042 /**
043 * @author Evgeny Mandrikov
044 */
045 public abstract class AbstractAnalyzer {
046
047 public final void analyse(Project project, SensorContext context) {
048 final File buildOutputDir = project.getFileSystem().getBuildOutputDir();
049 if (!buildOutputDir.exists()) {
050 JaCoCoUtils.LOG.info("Project coverage is set to 0% as build output directory doesn't exists: {}", buildOutputDir);
051 return;
052 }
053 String path = getReportPath(project);
054 File jacocoExecutionData = project.getFileSystem().resolvePath(path);
055
056 WildcardMatcher excludes = new WildcardMatcher(Strings.nullToEmpty(getExcludes(project)));
057 try {
058 readExecutionData(jacocoExecutionData, buildOutputDir, context, excludes);
059 } catch (IOException e) {
060 throw new SonarException(e);
061 }
062 }
063
064 public final void readExecutionData(File jacocoExecutionData, File buildOutputDir, SensorContext context, WildcardMatcher excludes) throws IOException {
065 SessionInfoStore sessionInfoStore = new SessionInfoStore();
066 ExecutionDataStore executionDataStore = new ExecutionDataStore();
067
068 if (jacocoExecutionData == null || !jacocoExecutionData.exists() || !jacocoExecutionData.isFile()) {
069 JaCoCoUtils.LOG.info("Project coverage is set to 0% as no JaCoCo execution data has been dumped: {}", jacocoExecutionData);
070 } else {
071 JaCoCoUtils.LOG.info("Analysing {}", jacocoExecutionData);
072 ExecutionDataReader reader = new ExecutionDataReader(new FileInputStream(jacocoExecutionData));
073 reader.setSessionInfoVisitor(sessionInfoStore);
074 reader.setExecutionDataVisitor(executionDataStore);
075 reader.read();
076 }
077
078 CoverageBuilder coverageBuilder = new CoverageBuilder();
079 Analyzer analyzer = new Analyzer(executionDataStore, coverageBuilder);
080 analyzeAll(analyzer, buildOutputDir);
081
082 int analyzedResources = 0;
083 for (ISourceFileCoverage coverage : coverageBuilder.getSourceFiles()) {
084 JavaFile resource = getResource(coverage);
085 // Do not save measures on resource which doesn't exist in the context
086 if (context.getResource(resource) != null) {
087 if (!isExcluded(coverage, excludes)) {
088 analyzeFile(resource, coverage, context);
089 }
090 analyzedResources++;
091 }
092 }
093 if (analyzedResources == 0) {
094 JaCoCoUtils.LOG.warn("Coverage information was not collected. Perhaps you forget to include debug information into compiled classes?");
095 }
096 }
097
098 private static boolean isExcluded(ISourceFileCoverage coverage, WildcardMatcher excludesMatcher) {
099 String name = coverage.getPackageName() + "/" + coverage.getName();
100 return excludesMatcher.matches(name);
101 }
102
103 @VisibleForTesting
104 static JavaFile getResource(ISourceFileCoverage coverage) {
105 String packageName = StringUtils.replaceChars(coverage.getPackageName(), '/', '.');
106 String fileName = StringUtils.substringBeforeLast(coverage.getName(), ".");
107 return new JavaFile(packageName, fileName);
108 }
109
110 /**
111 * Copied from {@link Analyzer#analyzeAll(File)} in order to add logging.
112 */
113 private void analyzeAll(Analyzer analyzer, File file) {
114 if (file.isDirectory()) {
115 for (File f : file.listFiles()) {
116 analyzeAll(analyzer, f);
117 }
118 } else {
119 try {
120 analyzer.analyzeAll(file);
121 } catch (Exception e) {
122 JaCoCoUtils.LOG.warn("Exception during analysis of file " + file.getAbsolutePath(), e);
123 }
124 }
125 }
126
127 private void analyzeFile(JavaFile resource, ISourceFileCoverage coverage, SensorContext context) {
128 CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create();
129 for (int lineId = coverage.getFirstLine(); lineId <= coverage.getLastLine(); lineId++) {
130 final int hits;
131 ILine line = coverage.getLine(lineId);
132 switch (line.getInstructionCounter().getStatus()) {
133 case ICounter.FULLY_COVERED:
134 case ICounter.PARTLY_COVERED:
135 hits = 1;
136 break;
137 case ICounter.NOT_COVERED:
138 hits = 0;
139 break;
140 case ICounter.EMPTY:
141 continue;
142 default:
143 JaCoCoUtils.LOG.warn("Unknown status for line {} in {}", lineId, resource);
144 continue;
145 }
146 builder.setHits(lineId, hits);
147
148 ICounter branchCounter = line.getBranchCounter();
149 int conditions = branchCounter.getTotalCount();
150 if (conditions > 0) {
151 int coveredConditions = branchCounter.getCoveredCount();
152 builder.setConditions(lineId, conditions, coveredConditions);
153 }
154 }
155
156 saveMeasures(context, resource, builder.createMeasures());
157 }
158
159 protected abstract void saveMeasures(SensorContext context, JavaFile resource, Collection<Measure> measures);
160
161 protected abstract String getReportPath(Project project);
162
163 protected abstract String getExcludes(Project project);
164
165 }