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.surefire.api;
021    
022    import java.io.File;
023    import java.io.FilenameFilter;
024    import java.util.Map;
025    
026    import javax.xml.stream.XMLStreamException;
027    
028    import org.apache.commons.lang.StringUtils;
029    import org.sonar.api.batch.SensorContext;
030    import org.sonar.api.measures.CoreMetrics;
031    import org.sonar.api.measures.Measure;
032    import org.sonar.api.measures.Metric;
033    import org.sonar.api.resources.Project;
034    import org.sonar.api.resources.Resource;
035    import org.sonar.api.utils.ParsingUtils;
036    import org.sonar.api.utils.SonarException;
037    import org.sonar.api.utils.StaxParser;
038    import org.sonar.plugins.surefire.data.SurefireStaxHandler;
039    import org.sonar.plugins.surefire.data.UnitTestClassReport;
040    import org.sonar.plugins.surefire.data.UnitTestIndex;
041    
042    /**
043     * @since 2.4
044     */
045    public 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    }