PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/src/main/java/com/atlassian/bamboo/plugin/dotnet/tests/mstest/MSTestXmlTestResultsParser.java

https://bitbucket.org/atlassian/bamboo-dotnet-plugin/
Java | 286 lines | 233 code | 29 blank | 24 comment | 27 complexity | d2d7d263c347b3d9fe143c85749904f5 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. package com.atlassian.bamboo.plugin.dotnet.tests.mstest;
  2. import com.atlassian.bamboo.plugin.dotnet.tests.TestResultContentHandler;
  3. import com.atlassian.bamboo.plugin.dotnet.tests.TestResultsParser;
  4. import com.atlassian.bamboo.results.tests.TestResults;
  5. import com.atlassian.bamboo.resultsummary.tests.TestCaseResultError;
  6. import com.atlassian.bamboo.resultsummary.tests.TestCaseResultErrorImpl;
  7. import com.atlassian.bamboo.resultsummary.tests.TestState;
  8. import com.atlassian.fugue.Option;
  9. import com.google.common.collect.ImmutableList;
  10. import com.google.common.collect.ImmutableMap;
  11. import com.google.common.collect.Lists;
  12. import net.jcip.annotations.NotThreadSafe;
  13. import org.apache.log4j.Logger;
  14. import org.dom4j.Document;
  15. import org.dom4j.DocumentFactory;
  16. import org.dom4j.Node;
  17. import org.dom4j.Element;
  18. import org.dom4j.io.SAXReader;
  19. import org.jetbrains.annotations.NotNull;
  20. import org.jetbrains.annotations.Nullable;
  21. import java.io.InputStream;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.Iterator;
  25. import java.util.HashMap;
  26. import java.util.regex.Matcher;
  27. import java.util.regex.Pattern;
  28. /**
  29. * @author Ross Rowe
  30. */
  31. @NotThreadSafe
  32. public class MSTestXmlTestResultsParser extends TestResultContentHandler implements TestResultsParser
  33. {
  34. private static final Logger log = Logger.getLogger(MSTestXmlTestResultsParser.class);
  35. private List<TestResults> failedTests;
  36. private List<TestResults> inconclusiveTests;
  37. private List<TestResults> passedTests;
  38. private static final String VS_2006_PREFIX = "a";
  39. private static final String VS_2010_PREFIX = "b";
  40. private static final Pattern DURATION_FORMAT = Pattern.compile("(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d+)");
  41. /**
  42. * @param inputStream
  43. */
  44. @Override
  45. public void parse(InputStream inputStream)
  46. {
  47. log.info("Beginning parse of MSTest results file");
  48. failedTests = Lists.newArrayList();
  49. passedTests = Lists.newArrayList();
  50. inconclusiveTests = Lists.newArrayList();
  51. try
  52. {
  53. Map<String, String> uris = ImmutableMap.of(
  54. VS_2006_PREFIX, "http://microsoft.com/schemas/VisualStudio/TeamTest/2006",
  55. VS_2010_PREFIX, "http://microsoft.com/schemas/VisualStudio/TeamTest/2010");
  56. DocumentFactory factory = new DocumentFactory();
  57. factory.setXPathNamespaceURIs(uris);
  58. // parse or create a document
  59. SAXReader reader = new SAXReader();
  60. reader.setDocumentFactory(factory);
  61. Document document = reader.read(inputStream);
  62. parse(document);
  63. log.info("Finished parse of MSTest results file");
  64. }
  65. catch (Exception e)
  66. {
  67. log.error("Failed to parse xml test results. File was null.", e);
  68. }
  69. }
  70. private static String getContent(final Element element)
  71. {
  72. if (element.isTextOnly())
  73. {
  74. return element.getText();
  75. }
  76. final StringBuilder sb = new StringBuilder();
  77. final Iterator<Element> iterator = element.elementIterator();
  78. while (iterator.hasNext())
  79. {
  80. sb.append(iterator.next().asXML());
  81. }
  82. return sb.toString();
  83. }
  84. /**
  85. * @param document
  86. */
  87. private void parse(Document document)
  88. {
  89. String namespacePrefix = "";
  90. List results = document.selectNodes("//Results/*");
  91. if (results.isEmpty())
  92. {
  93. results = getVS2006Results(document);
  94. namespacePrefix = VS_2006_PREFIX + ":";
  95. }
  96. if (results.isEmpty())
  97. {
  98. results = getVS2010Results(document);
  99. namespacePrefix = VS_2010_PREFIX + ":";
  100. }
  101. final Map<String, Node> unitTestNodes = cacheUnitTestNodes(document, namespacePrefix);
  102. for (Object object : results)
  103. {
  104. Node testNode = (Node) object;
  105. String testName = testNode.selectSingleNode("@testName").getStringValue();
  106. //default className to be the same as testName
  107. String className = testName;
  108. //find class for object id
  109. String objectId = testNode.selectSingleNode("@testId").getStringValue();
  110. //find className
  111. Node unitTestNode = unitTestNodes.get(objectId);
  112. if (unitTestNode != null)
  113. {
  114. className = unitTestNode.selectSingleNode(namespacePrefix + "TestMethod/@className").getStringValue();
  115. //className may include version/culture information, so grab everything before the first ,
  116. className = className.split(",")[0];
  117. }
  118. // duration will be in hh:mm:ss.mmmmmmm format
  119. final String testDuration = parseDuration(testNode);
  120. final Node outcomeAttr = testNode.selectSingleNode("@outcome");
  121. // MSTest doesn't add the outcome attribute in a TestResult when the test raises an error.
  122. // This situation occurs when an exception is thrown but none was expected.
  123. final String result = (outcomeAttr != null) ? outcomeAttr.getStringValue() : "Failed";
  124. if (result == null)
  125. {
  126. continue;
  127. }
  128. final TestResults testResult = new TestResults(className, testName, testDuration);
  129. Node stdOutData = testNode.selectSingleNode(namespacePrefix + "Output/" + namespacePrefix+"StdOut");
  130. if (stdOutData != null && stdOutData.hasContent())
  131. {
  132. Element e = (Element)stdOutData;
  133. testResult.setSystemOut(getContent(e));
  134. }
  135. if (result.equalsIgnoreCase("Passed"))
  136. {
  137. testResult.setState(TestState.SUCCESS);
  138. passedTests.add(testResult);
  139. }
  140. else if (result.equalsIgnoreCase("Ignored"))
  141. {
  142. testResult.setState(TestState.SKIPPED);
  143. inconclusiveTests.add(testResult);
  144. }
  145. else
  146. {
  147. // if we have an error, store it
  148. final Option<TestCaseResultError> error = parseError(testNode, namespacePrefix);
  149. if (error.isDefined())
  150. {
  151. testResult.addError(error.get());
  152. }
  153. if (result.equalsIgnoreCase("Inconclusive"))
  154. {
  155. testResult.setState(TestState.SKIPPED);
  156. inconclusiveTests.add(testResult);
  157. }
  158. else if (result.equalsIgnoreCase("NotExecuted"))
  159. {
  160. testResult.setState(TestState.SKIPPED);
  161. inconclusiveTests.add(testResult);
  162. }
  163. else
  164. {
  165. testResult.setState(TestState.FAILED);
  166. failedTests.add(testResult);
  167. }
  168. }
  169. }
  170. }
  171. @NotNull
  172. private static Map<String, Node> cacheUnitTestNodes(final Node document, final String namespacePrefix)
  173. {
  174. final Map<String, Node> cachedTestInfo = new HashMap<>();
  175. final List<Element> cachedNodes = document.selectNodes("//" + namespacePrefix + "UnitTest");
  176. for (final Element e : cachedNodes)
  177. {
  178. cachedTestInfo.put(e.attributeValue("id"), e);
  179. }
  180. return cachedTestInfo;
  181. }
  182. private String parseDuration(@NotNull Node testNode)
  183. {
  184. // duration will be in hh:mm:ss.mmmmmmm format
  185. final Node durationAttr = testNode.selectSingleNode("@duration");
  186. if (durationAttr != null)
  187. {
  188. return convertDuration(durationAttr.getStringValue());
  189. }
  190. return "";
  191. }
  192. private Option<TestCaseResultError> parseError(@NotNull Node testNode, @NotNull String namespacePrefix)
  193. {
  194. final Node messageNode = getErrorMessageNode(testNode, namespacePrefix);
  195. if (messageNode != null)
  196. {
  197. return Option.<TestCaseResultError>some(new TestCaseResultErrorImpl(messageNode.getStringValue()));
  198. }
  199. return Option.none();
  200. }
  201. @Nullable
  202. private Node getErrorMessageNode(@NotNull Node testNode, @NotNull String namespacePrefix)
  203. {
  204. if (namespacePrefix.equals(VS_2006_PREFIX))
  205. {
  206. return testNode.selectSingleNode(namespacePrefix + "Output");
  207. }
  208. else
  209. {
  210. return testNode.selectSingleNode(namespacePrefix + "Output/" + namespacePrefix + "ErrorInfo/" + namespacePrefix + "Message");
  211. }
  212. }
  213. /**
  214. * Convert hh:mm:ss.mmmmmmm format to ss.mmmmmmm which Bamboo can parse
  215. * @param input
  216. * @return
  217. */
  218. private String convertDuration(String input)
  219. {
  220. Matcher matcher = DURATION_FORMAT.matcher(input);
  221. if (matcher.matches())
  222. {
  223. final int hh = Integer.parseInt(matcher.group(1));
  224. final int mm = Integer.parseInt(matcher.group(2));
  225. final int ss = Integer.parseInt(matcher.group(3));
  226. return String.format("%d.%s", ((hh * 60) + mm) * 60 + ss, matcher.group(4));
  227. }
  228. else
  229. {
  230. log.warn(String.format("Cannot parse duration string [%s]", input));
  231. return "0.0";
  232. }
  233. }
  234. private List getVS2006Results(Document document)
  235. {
  236. return document.selectNodes("//" + VS_2006_PREFIX + ":Results/*");
  237. }
  238. private List getVS2010Results(Document document)
  239. {
  240. return document.selectNodes("//" + VS_2010_PREFIX + ":Results/*");
  241. }
  242. @Override
  243. public ImmutableList<TestResults> getSuccessfulTests()
  244. {
  245. return ImmutableList.copyOf(passedTests);
  246. }
  247. @Override
  248. public ImmutableList<TestResults> getFailedTests()
  249. {
  250. return ImmutableList.copyOf(failedTests);
  251. }
  252. @Override
  253. public ImmutableList<TestResults> getInconclusiveTests()
  254. {
  255. return ImmutableList.copyOf(inconclusiveTests);
  256. }
  257. }