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 */ 020package org.sonar.plugins.surefire.api; 021 022import java.io.File; 023import java.io.FilenameFilter; 024import java.util.Map; 025 026import javax.xml.stream.XMLStreamException; 027 028import org.apache.commons.lang.StringUtils; 029import org.sonar.api.batch.SensorContext; 030import org.sonar.api.measures.CoreMetrics; 031import org.sonar.api.measures.Measure; 032import org.sonar.api.measures.Metric; 033import org.sonar.api.resources.Project; 034import org.sonar.api.resources.Resource; 035import org.sonar.api.utils.ParsingUtils; 036import org.sonar.api.utils.SonarException; 037import org.sonar.api.utils.StaxParser; 038import org.sonar.plugins.surefire.data.SurefireStaxHandler; 039import org.sonar.plugins.surefire.data.UnitTestClassReport; 040import org.sonar.plugins.surefire.data.UnitTestIndex; 041 042/** 043 * @since 2.4 044 */ 045public abstract class AbstractSurefireParser { 046 047 public void collect(Project project, SensorContext context, File reportsDir) { 048 File[] xmlFiles = getReports(reportsDir); 049 050 if (xmlFiles.length == 0) { 051 // See http://jira.codehaus.org/browse/SONAR-2371 052 if (project.getModules().isEmpty()) { 053 context.saveMeasure(CoreMetrics.TESTS, 0.0); 054 } 055 } else { 056 parseFiles(context, xmlFiles); 057 } 058 } 059 060 private File[] getReports(File dir) { 061 if (dir == null || !dir.isDirectory() || !dir.exists()) { 062 return new File[0]; 063 } 064 File[] unitTestResultFiles = findXMLFilesStartingWith(dir, "TEST-"); 065 if (unitTestResultFiles.length == 0) { 066 // maybe there's only a test suite result file 067 unitTestResultFiles = findXMLFilesStartingWith(dir, "TESTS-"); 068 } 069 return unitTestResultFiles; 070 } 071 072 private File[] findXMLFilesStartingWith(File dir, final String fileNameStart) { 073 return dir.listFiles(new FilenameFilter() { 074 public boolean accept(File dir, String name) { 075 return name.startsWith(fileNameStart) && name.endsWith(".xml"); 076 } 077 }); 078 } 079 080 private void parseFiles(SensorContext context, File[] reports) { 081 UnitTestIndex index = new UnitTestIndex(); 082 parseFiles(reports, index); 083 sanitize(index); 084 save(index, context); 085 } 086 087 private void parseFiles(File[] reports, UnitTestIndex index) { 088 SurefireStaxHandler staxParser = new SurefireStaxHandler(index); 089 StaxParser parser = new StaxParser(staxParser, false); 090 for (File report : reports) { 091 try { 092 parser.parse(report); 093 } catch (XMLStreamException e) { 094 throw new SonarException("Fail to parse the Surefire report: " + report, e); 095 } 096 } 097 } 098 099 private void sanitize(UnitTestIndex index) { 100 for (String classname : index.getClassnames()) { 101 if (StringUtils.contains(classname, "$")) { 102 // Surefire reports classes whereas sonar supports files 103 String parentClassName = StringUtils.substringBefore(classname, "$"); 104 index.merge(classname, parentClassName); 105 } 106 } 107 } 108 109 private void save(UnitTestIndex index, SensorContext context) { 110 for (Map.Entry<String, UnitTestClassReport> entry : index.getIndexByClassname().entrySet()) { 111 UnitTestClassReport report = entry.getValue(); 112 if (report.getTests() > 0) { 113 Resource resource = getUnitTestResource(entry.getKey()); 114 double testsCount = report.getTests() - report.getSkipped(); 115 saveMeasure(context, resource, CoreMetrics.SKIPPED_TESTS, report.getSkipped()); 116 saveMeasure(context, resource, CoreMetrics.TESTS, testsCount); 117 saveMeasure(context, resource, CoreMetrics.TEST_ERRORS, report.getErrors()); 118 saveMeasure(context, resource, CoreMetrics.TEST_FAILURES, report.getFailures()); 119 saveMeasure(context, resource, CoreMetrics.TEST_EXECUTION_TIME, report.getDurationMilliseconds()); 120 double passedTests = testsCount - report.getErrors() - report.getFailures(); 121 if (testsCount > 0) { 122 double percentage = passedTests * 100d / testsCount; 123 saveMeasure(context, resource, CoreMetrics.TEST_SUCCESS_DENSITY, ParsingUtils.scaleValue(percentage)); 124 } 125 saveResults(context, resource, report); 126 } 127 } 128 } 129 130 private void saveMeasure(SensorContext context, Resource resource, Metric metric, double value) { 131 if (!Double.isNaN(value)) { 132 context.saveMeasure(resource, metric, value); 133 } 134 } 135 136 private void saveResults(SensorContext context, Resource resource, UnitTestClassReport report) { 137 context.saveMeasure(resource, new Measure(CoreMetrics.TEST_DATA, report.toXml())); 138 } 139 140 protected abstract Resource<?> getUnitTestResource(String classKey); 141 142}