/* * Sonar PHP Plugin * Copyright (C) 2010 Sonar PHP Plugin * dev@sonar.codehaus.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ package org.sonar.plugins.php.phpunit; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.BatchExtension; import org.sonar.api.batch.SensorContext; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.PropertiesBuilder; import org.sonar.api.resources.Project; import org.sonar.api.utils.SonarException; import org.sonar.plugins.php.api.PhpFile; import org.sonar.plugins.php.api.PhpPackage; import org.sonar.plugins.php.phpunit.xml.CoverageNode; import org.sonar.plugins.php.phpunit.xml.FileNode; import org.sonar.plugins.php.phpunit.xml.LineNode; import org.sonar.plugins.php.phpunit.xml.MetricsNode; import org.sonar.plugins.php.phpunit.xml.ProjectNode; import com.thoughtworks.xstream.XStream; /** * The Class PhpUnitCoverageResultParser. */ public class PhpUnitCoverageResultParser implements BatchExtension { /** The logger. */ private static Logger logger = LoggerFactory.getLogger(PhpUnitCoverageResultParser.class); /** The project. */ private Project project; /** The context. */ private SensorContext context; /** The class by package. */ private Map<String, PackageNode> classByPackage; /** * Instantiates a new php unit coverage result parser. * * @param project * the project * @param context * the context */ public PhpUnitCoverageResultParser(Project project, SensorContext context) { super(); this.project = project; this.context = context; classByPackage = new HashMap<String, PackageNode>(); } /** * Parses the. * * @param coverageReportFile * the coverage report file */ public void parse(File coverageReportFile) { if (coverageReportFile == null) { insertZeroWhenNoReports(); } else { logger.info("Parsing file : {0}", coverageReportFile.getName()); parseFile(context, coverageReportFile); } } /** * Insert zero when no reports. */ private void insertZeroWhenNoReports() { context.saveMeasure(CoreMetrics.COVERAGE, 0d); } /** * Parses the file. * * @param context * the context * @param coverageReportFile * the coverage report file * @param project * the project */ private void parseFile(SensorContext context, File coverageReportFile) { CoverageNode coverage = getCoverage(coverageReportFile); // Used to record the number of covered statements (lines) double totalStatements = 0; double coveredStatements = 0; List<ProjectNode> projects = coverage.getProjects(); if (projects != null && !projects.isEmpty()) { ProjectNode projectNode = projects.get(0); for (FileNode file : projectNode.getFiles()) { cumulateResults(file); } MetricsNode metrics = projectNode.getMetrics(); totalStatements += metrics.getTotalStatementsCount(); coveredStatements += metrics.getCoveredStatements(); saveMeasures(classByPackage); } context.saveMeasure(CoreMetrics.LINES_TO_COVER, totalStatements); context.saveMeasure(CoreMetrics.UNCOVERED_LINES, totalStatements - coveredStatements); } /** * Cumulate results. * * @param file * the file */ private void cumulateResults(FileNode file) { PhpFile phpFile = PhpFile.getInstance(project).fromAbsolutePath(file.getName(), project); if (phpFile == null) { logger.info(file.getName() + " can't be found amoung the project source directories"); return; } PhpPackage phpPackage = phpFile.getParent(); PackageNode packageNode = classByPackage.get(phpPackage.getName()); if (packageNode == null) { packageNode = new PackageNode(); classByPackage.put(phpPackage.getName(), packageNode); } packageNode.addClassByFileNode(file, phpFile); } /** * Save measures for all classes. * * @param classByPackage * the class by package * @return the double */ private void saveMeasures(Map<String, PackageNode> classByPackage) { // For each package Collection<PackageNode> nodes = classByPackage.values(); for (PackageNode node : nodes) { // Saves the class measures and adds its coverage percent Map<FileNode, PhpFile> classByFileNode = node.getClassByFileNode(); for (Entry<FileNode, PhpFile> entry : classByFileNode.entrySet()) { saveClassMeasure(entry.getKey(), entry.getValue()); } } } /** * Saves one class measure and calls {@link #saveLineMeasure(LineNode, PropertiesBuilder, String))} for each class lines. * * @param fileNode * the file node * @param phpFile * the php file * @return the double */ private void saveClassMeasure(FileNode fileNode, PhpFile phpFile) { // Properties builder will generate the data associate with COVERAGE_LINE_HITS_DATA metrics. // This should look like (lineNumner=Count) : 1=0;2=1;3=1.... PropertiesBuilder<Integer, Integer> lineHits = new PropertiesBuilder<Integer, Integer>(CoreMetrics.COVERAGE_LINE_HITS_DATA); for (LineNode line : fileNode.getLines()) { saveLineMeasure(line, lineHits); } MetricsNode metrics = fileNode.getMetrics(); context.saveMeasure(phpFile, lineHits.build()); // Save uncovered statements (lines) double totalStatementsCount = metrics.getTotalStatementsCount(); double uncoveredLines = totalStatementsCount - metrics.getCoveredStatements(); context.saveMeasure(phpFile, CoreMetrics.LINES_TO_COVER, totalStatementsCount); context.saveMeasure(phpFile, CoreMetrics.UNCOVERED_LINES, uncoveredLines); } /** * Save line measure. * * @param line * the line * @param lineHits * the line hits * @param fileName * the class name */ private void saveLineMeasure(LineNode line, PropertiesBuilder<Integer, Integer> lineHits) { lineHits.add(line.getNum(), line.getCount()); } /** * Gets the coverage. * * @param coverageReportFile * the coverage report file * @return the coverage */ private CoverageNode getCoverage(File coverageReportFile) { InputStream inputStream = null; try { XStream xstream = new XStream(); xstream.setClassLoader(getClass().getClassLoader()); xstream.aliasSystemAttribute("classType", "class"); xstream.processAnnotations(CoverageNode.class); xstream.processAnnotations(ProjectNode.class); xstream.processAnnotations(FileNode.class); xstream.processAnnotations(MetricsNode.class); xstream.processAnnotations(LineNode.class); inputStream = new FileInputStream(coverageReportFile); return (CoverageNode) xstream.fromXML(inputStream); } catch (IOException e) { throw new SonarException("Can't read pUnit report : " + coverageReportFile.getName(), e); } finally { IOUtils.closeQuietly(inputStream); } } /** * The Class PackageNode. */ private static class PackageNode { /** The class by file node. */ private Map<FileNode, PhpFile> classByFileNode; /** * Gets the class by file node. * * @return the class by file node */ public Map<FileNode, PhpFile> getClassByFileNode() { return classByFileNode; } /** * Instantiates a new package node. * * @param phpPackage * the php package */ public PackageNode() { super(); classByFileNode = new HashMap<FileNode, PhpFile>(); } /** * Adds the class by file node. * * @param fileNode * the file node * @param phpFile * the php file */ public void addClassByFileNode(FileNode fileNode, PhpFile phpFile) { classByFileNode.put(fileNode, phpFile); } } }