001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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 org.apache.commons.lang.StringUtils;
023    import org.sonar.api.batch.SensorContext;
024    import org.sonar.api.measures.CoreMetrics;
025    import org.sonar.api.measures.Measure;
026    import org.sonar.api.measures.Metric;
027    import org.sonar.api.resources.Project;
028    import org.sonar.api.resources.Resource;
029    import org.sonar.api.utils.ParsingUtils;
030    import org.sonar.api.utils.SonarException;
031    import org.sonar.api.utils.StaxParser;
032    import org.sonar.plugins.surefire.data.SurefireStaxHandler;
033    import org.sonar.plugins.surefire.data.UnitTestClassReport;
034    import org.sonar.plugins.surefire.data.UnitTestIndex;
035    
036    import java.io.File;
037    import java.io.FilenameFilter;
038    import java.util.Map;
039    
040    import javax.xml.stream.XMLStreamException;
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        return dir.listFiles(new FilenameFilter() {
065          public boolean accept(File dir, String name) {
066            return name.startsWith("TEST") && name.endsWith(".xml");
067          }
068        });
069      }
070    
071      private void parseFiles(SensorContext context, File[] reports) {
072        UnitTestIndex index = new UnitTestIndex();
073        parseFiles(reports, index);
074        sanitize(index);
075        save(index, context);
076      }
077    
078      private void parseFiles(File[] reports, UnitTestIndex index) {
079        SurefireStaxHandler staxParser = new SurefireStaxHandler(index);
080        StaxParser parser = new StaxParser(staxParser, false);
081        for (File report : reports) {
082          try {
083            parser.parse(report);
084          } catch (XMLStreamException e) {
085            throw new SonarException("Fail to parse the Surefire report: " + report, e);
086          }
087        }
088      }
089    
090      private void sanitize(UnitTestIndex index) {
091        for (String classname : index.getClassnames()) {
092          if (StringUtils.contains(classname, "$")) {
093            // Surefire reports classes whereas sonar supports files
094            String parentClassName = StringUtils.substringBefore(classname, "$");
095            index.merge(classname, parentClassName);
096          }
097        }
098      }
099    
100      private void save(UnitTestIndex index, SensorContext context) {
101        for (Map.Entry<String, UnitTestClassReport> entry : index.getIndexByClassname().entrySet()) {
102          UnitTestClassReport report = entry.getValue();
103          if (report.getTests() > 0) {
104            Resource resource = getUnitTestResource(entry.getKey());
105            double testsCount = report.getTests() - report.getSkipped();
106            saveMeasure(context, resource, CoreMetrics.SKIPPED_TESTS, report.getSkipped());
107            saveMeasure(context, resource, CoreMetrics.TESTS, testsCount);
108            saveMeasure(context, resource, CoreMetrics.TEST_ERRORS, report.getErrors());
109            saveMeasure(context, resource, CoreMetrics.TEST_FAILURES, report.getFailures());
110            saveMeasure(context, resource, CoreMetrics.TEST_EXECUTION_TIME, report.getDurationMilliseconds());
111            double passedTests = testsCount - report.getErrors() - report.getFailures();
112            if (testsCount > 0) {
113              double percentage = passedTests * 100d / testsCount;
114              saveMeasure(context, resource, CoreMetrics.TEST_SUCCESS_DENSITY, ParsingUtils.scaleValue(percentage));
115            }
116            saveResults(context, resource, report);
117          }
118        }
119      }
120    
121      private void saveMeasure(SensorContext context, Resource resource, Metric metric, double value) {
122        if (!Double.isNaN(value)) {
123          context.saveMeasure(resource, metric, value);
124        }
125      }
126    
127      private void saveResults(SensorContext context, Resource resource, UnitTestClassReport report) {
128        context.saveMeasure(resource, new Measure(CoreMetrics.TEST_DATA, report.toXml()));
129      }
130    
131      protected abstract Resource<?> getUnitTestResource(String classKey);
132    
133    }