/src/main/java/com/atlassian/bamboo/plugin/dotnet/tests/mstest/MSTestXmlTestResultsParser.java
Java | 286 lines | 233 code | 29 blank | 24 comment | 27 complexity | d2d7d263c347b3d9fe143c85749904f5 MD5 | raw file
Possible License(s): BSD-3-Clause
- package com.atlassian.bamboo.plugin.dotnet.tests.mstest;
- import com.atlassian.bamboo.plugin.dotnet.tests.TestResultContentHandler;
- import com.atlassian.bamboo.plugin.dotnet.tests.TestResultsParser;
- import com.atlassian.bamboo.results.tests.TestResults;
- import com.atlassian.bamboo.resultsummary.tests.TestCaseResultError;
- import com.atlassian.bamboo.resultsummary.tests.TestCaseResultErrorImpl;
- import com.atlassian.bamboo.resultsummary.tests.TestState;
- import com.atlassian.fugue.Option;
- import com.google.common.collect.ImmutableList;
- import com.google.common.collect.ImmutableMap;
- import com.google.common.collect.Lists;
- import net.jcip.annotations.NotThreadSafe;
- import org.apache.log4j.Logger;
- import org.dom4j.Document;
- import org.dom4j.DocumentFactory;
- import org.dom4j.Node;
- import org.dom4j.Element;
- import org.dom4j.io.SAXReader;
- import org.jetbrains.annotations.NotNull;
- import org.jetbrains.annotations.Nullable;
- import java.io.InputStream;
- import java.util.List;
- import java.util.Map;
- import java.util.Iterator;
- import java.util.HashMap;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- /**
- * @author Ross Rowe
- */
- @NotThreadSafe
- public class MSTestXmlTestResultsParser extends TestResultContentHandler implements TestResultsParser
- {
- private static final Logger log = Logger.getLogger(MSTestXmlTestResultsParser.class);
- private List<TestResults> failedTests;
- private List<TestResults> inconclusiveTests;
- private List<TestResults> passedTests;
- private static final String VS_2006_PREFIX = "a";
- private static final String VS_2010_PREFIX = "b";
- private static final Pattern DURATION_FORMAT = Pattern.compile("(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d+)");
- /**
- * @param inputStream
- */
- @Override
- public void parse(InputStream inputStream)
- {
- log.info("Beginning parse of MSTest results file");
- failedTests = Lists.newArrayList();
- passedTests = Lists.newArrayList();
- inconclusiveTests = Lists.newArrayList();
- try
- {
- Map<String, String> uris = ImmutableMap.of(
- VS_2006_PREFIX, "http://microsoft.com/schemas/VisualStudio/TeamTest/2006",
- VS_2010_PREFIX, "http://microsoft.com/schemas/VisualStudio/TeamTest/2010");
- DocumentFactory factory = new DocumentFactory();
- factory.setXPathNamespaceURIs(uris);
- // parse or create a document
- SAXReader reader = new SAXReader();
- reader.setDocumentFactory(factory);
- Document document = reader.read(inputStream);
- parse(document);
- log.info("Finished parse of MSTest results file");
- }
- catch (Exception e)
- {
- log.error("Failed to parse xml test results. File was null.", e);
- }
- }
- private static String getContent(final Element element)
- {
- if (element.isTextOnly())
- {
- return element.getText();
- }
- final StringBuilder sb = new StringBuilder();
- final Iterator<Element> iterator = element.elementIterator();
- while (iterator.hasNext())
- {
- sb.append(iterator.next().asXML());
- }
- return sb.toString();
- }
- /**
- * @param document
- */
- private void parse(Document document)
- {
- String namespacePrefix = "";
- List results = document.selectNodes("//Results/*");
- if (results.isEmpty())
- {
- results = getVS2006Results(document);
- namespacePrefix = VS_2006_PREFIX + ":";
- }
- if (results.isEmpty())
- {
- results = getVS2010Results(document);
- namespacePrefix = VS_2010_PREFIX + ":";
- }
- final Map<String, Node> unitTestNodes = cacheUnitTestNodes(document, namespacePrefix);
- for (Object object : results)
- {
- Node testNode = (Node) object;
- String testName = testNode.selectSingleNode("@testName").getStringValue();
- //default className to be the same as testName
- String className = testName;
- //find class for object id
- String objectId = testNode.selectSingleNode("@testId").getStringValue();
- //find className
- Node unitTestNode = unitTestNodes.get(objectId);
- if (unitTestNode != null)
- {
- className = unitTestNode.selectSingleNode(namespacePrefix + "TestMethod/@className").getStringValue();
- //className may include version/culture information, so grab everything before the first ,
- className = className.split(",")[0];
- }
- // duration will be in hh:mm:ss.mmmmmmm format
- final String testDuration = parseDuration(testNode);
- final Node outcomeAttr = testNode.selectSingleNode("@outcome");
- // MSTest doesn't add the outcome attribute in a TestResult when the test raises an error.
- // This situation occurs when an exception is thrown but none was expected.
- final String result = (outcomeAttr != null) ? outcomeAttr.getStringValue() : "Failed";
- if (result == null)
- {
- continue;
- }
- final TestResults testResult = new TestResults(className, testName, testDuration);
- Node stdOutData = testNode.selectSingleNode(namespacePrefix + "Output/" + namespacePrefix+"StdOut");
- if (stdOutData != null && stdOutData.hasContent())
- {
- Element e = (Element)stdOutData;
- testResult.setSystemOut(getContent(e));
- }
- if (result.equalsIgnoreCase("Passed"))
- {
- testResult.setState(TestState.SUCCESS);
- passedTests.add(testResult);
- }
- else if (result.equalsIgnoreCase("Ignored"))
- {
- testResult.setState(TestState.SKIPPED);
- inconclusiveTests.add(testResult);
- }
- else
- {
- // if we have an error, store it
- final Option<TestCaseResultError> error = parseError(testNode, namespacePrefix);
- if (error.isDefined())
- {
- testResult.addError(error.get());
- }
- if (result.equalsIgnoreCase("Inconclusive"))
- {
- testResult.setState(TestState.SKIPPED);
- inconclusiveTests.add(testResult);
- }
- else if (result.equalsIgnoreCase("NotExecuted"))
- {
- testResult.setState(TestState.SKIPPED);
- inconclusiveTests.add(testResult);
- }
- else
- {
- testResult.setState(TestState.FAILED);
- failedTests.add(testResult);
- }
- }
- }
- }
- @NotNull
- private static Map<String, Node> cacheUnitTestNodes(final Node document, final String namespacePrefix)
- {
- final Map<String, Node> cachedTestInfo = new HashMap<>();
- final List<Element> cachedNodes = document.selectNodes("//" + namespacePrefix + "UnitTest");
- for (final Element e : cachedNodes)
- {
- cachedTestInfo.put(e.attributeValue("id"), e);
- }
- return cachedTestInfo;
- }
- private String parseDuration(@NotNull Node testNode)
- {
- // duration will be in hh:mm:ss.mmmmmmm format
- final Node durationAttr = testNode.selectSingleNode("@duration");
- if (durationAttr != null)
- {
- return convertDuration(durationAttr.getStringValue());
- }
- return "";
- }
- private Option<TestCaseResultError> parseError(@NotNull Node testNode, @NotNull String namespacePrefix)
- {
- final Node messageNode = getErrorMessageNode(testNode, namespacePrefix);
- if (messageNode != null)
- {
- return Option.<TestCaseResultError>some(new TestCaseResultErrorImpl(messageNode.getStringValue()));
- }
- return Option.none();
- }
- @Nullable
- private Node getErrorMessageNode(@NotNull Node testNode, @NotNull String namespacePrefix)
- {
- if (namespacePrefix.equals(VS_2006_PREFIX))
- {
- return testNode.selectSingleNode(namespacePrefix + "Output");
- }
- else
- {
- return testNode.selectSingleNode(namespacePrefix + "Output/" + namespacePrefix + "ErrorInfo/" + namespacePrefix + "Message");
- }
- }
- /**
- * Convert hh:mm:ss.mmmmmmm format to ss.mmmmmmm which Bamboo can parse
- * @param input
- * @return
- */
- private String convertDuration(String input)
- {
- Matcher matcher = DURATION_FORMAT.matcher(input);
- if (matcher.matches())
- {
- final int hh = Integer.parseInt(matcher.group(1));
- final int mm = Integer.parseInt(matcher.group(2));
- final int ss = Integer.parseInt(matcher.group(3));
- return String.format("%d.%s", ((hh * 60) + mm) * 60 + ss, matcher.group(4));
- }
- else
- {
- log.warn(String.format("Cannot parse duration string [%s]", input));
- return "0.0";
- }
- }
- private List getVS2006Results(Document document)
- {
- return document.selectNodes("//" + VS_2006_PREFIX + ":Results/*");
- }
- private List getVS2010Results(Document document)
- {
- return document.selectNodes("//" + VS_2010_PREFIX + ":Results/*");
- }
- @Override
- public ImmutableList<TestResults> getSuccessfulTests()
- {
- return ImmutableList.copyOf(passedTests);
- }
- @Override
- public ImmutableList<TestResults> getFailedTests()
- {
- return ImmutableList.copyOf(failedTests);
- }
- @Override
- public ImmutableList<TestResults> getInconclusiveTests()
- {
- return ImmutableList.copyOf(inconclusiveTests);
- }
- }