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}