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.data;
021    
022    import java.text.ParseException;
023    import java.util.Locale;
024    
025    import javax.xml.stream.XMLStreamException;
026    
027    import org.apache.commons.lang.StringUtils;
028    import org.codehaus.staxmate.in.ElementFilter;
029    import org.codehaus.staxmate.in.SMEvent;
030    import org.codehaus.staxmate.in.SMHierarchicCursor;
031    import org.codehaus.staxmate.in.SMInputCursor;
032    import org.sonar.api.utils.ParsingUtils;
033    import org.sonar.api.utils.StaxParser.XmlStreamHandler;
034    
035    public class SurefireStaxHandler implements XmlStreamHandler {
036    
037      private UnitTestIndex index;
038    
039      public SurefireStaxHandler(UnitTestIndex index) {
040        this.index = index;
041      }
042    
043      public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException {
044        SMInputCursor testSuite = rootCursor.constructDescendantCursor(new ElementFilter("testsuite"));
045        SMEvent testSuiteEvent;
046        while ((testSuiteEvent = testSuite.getNext()) != null) {
047          if (testSuiteEvent.compareTo(SMEvent.START_ELEMENT) == 0) {
048            String testSuiteClassName = testSuite.getAttrValue("name");
049            if (StringUtils.contains(testSuiteClassName, "$")) {
050              // test suites for inner classes are ignored
051              return;
052            }
053            SMInputCursor testCase = testSuite.childCursor(new ElementFilter("testcase"));
054            SMEvent event;
055            while ((event = testCase.getNext()) != null) {
056              if (event.compareTo(SMEvent.START_ELEMENT) == 0) {
057                String testClassName = getClassname(testCase, testSuiteClassName);
058                UnitTestClassReport classReport = index.index(testClassName);
059                parseTestCase(testCase, classReport);
060              }
061            }
062          }
063        }
064      }
065    
066      private String getClassname(SMInputCursor testCaseCursor, String defaultClassname) throws XMLStreamException {
067        String testClassName = testCaseCursor.getAttrValue("classname");
068        return StringUtils.defaultIfBlank(testClassName, defaultClassname);
069      }
070    
071      private void parseTestCase(SMInputCursor testCaseCursor, UnitTestClassReport report) throws XMLStreamException {
072        report.add(parseTestResult(testCaseCursor));
073      }
074    
075      private void setStackAndMessage(UnitTestResult result, SMInputCursor stackAndMessageCursor) throws XMLStreamException {
076        result.setMessage(stackAndMessageCursor.getAttrValue("message"));
077        String stack = stackAndMessageCursor.collectDescendantText();
078        result.setStackTrace(stack);
079      }
080    
081      private UnitTestResult parseTestResult(SMInputCursor testCaseCursor) throws XMLStreamException {
082        UnitTestResult detail = new UnitTestResult();
083        String name = getTestCaseName(testCaseCursor);
084        detail.setName(name);
085    
086        String status = UnitTestResult.STATUS_OK;
087        long duration = getTimeAttributeInMS(testCaseCursor);
088    
089        SMInputCursor childNode = testCaseCursor.descendantElementCursor();
090        if (childNode.getNext() != null) {
091          String elementName = childNode.getLocalName();
092          if ("skipped".equals(elementName)) {
093            status = UnitTestResult.STATUS_SKIPPED;
094            // bug with surefire reporting wrong time for skipped tests
095            duration = 0L;
096    
097          } else if ("failure".equals(elementName)) {
098            status = UnitTestResult.STATUS_FAILURE;
099            setStackAndMessage(detail, childNode);
100    
101          } else if ("error".equals(elementName)) {
102            status = UnitTestResult.STATUS_ERROR;
103            setStackAndMessage(detail, childNode);
104          }
105        }
106        while (childNode.getNext() != null) {
107          // make sure we loop till the end of the elements cursor
108        }
109        detail.setDurationMilliseconds(duration);
110        detail.setStatus(status);
111        return detail;
112      }
113    
114      private long getTimeAttributeInMS(SMInputCursor testCaseCursor) throws XMLStreamException {
115        // hardcoded to Locale.ENGLISH see http://jira.codehaus.org/browse/SONAR-602
116        try {
117          Double time = ParsingUtils.parseNumber(testCaseCursor.getAttrValue("time"), Locale.ENGLISH);
118          return !Double.isNaN(time) ? new Double(ParsingUtils.scaleValue(time * 1000, 3)).longValue() : 0L;
119        } catch (ParseException e) {
120          throw new XMLStreamException(e);
121        }
122      }
123    
124      private String getTestCaseName(SMInputCursor testCaseCursor) throws XMLStreamException {
125        String classname = testCaseCursor.getAttrValue("classname");
126        String name = testCaseCursor.getAttrValue("name");
127        if (StringUtils.contains(classname, "$")) {
128          return StringUtils.substringAfter(classname, "$") + "/" + name;
129        }
130        return name;
131      }
132    
133    }