I run CruiseControl.NET and am trying to construct an XSL stylesheet that extracts certain information about broken unit tests from the XML build log. For each broken unit test, I would like to obtain in XSL the unit test’s name, the class name in which the unit test resides, and the failure message. Currently, I can obtain the unit test name and failure message, but I am having trouble obtaining the class name. I think this is because the unit test’s class name is only contained in a different area of the XML build log, and I am a noob at XSL. Here is a pared-down sample of my XSL stylesheet:
<!-- Unit tests -->
<xsl:template match="/">
<table border="1" width="100%">
<tr>
<th align="left">Class</th>
<th align="left">Method</th>
<th align="left">Message</th>
</tr>
<xsl:apply-templates select="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='Results']/*[local-name()='UnitTestResult']"/>
</table>
</xsl:template>
<!-- Failed uint test -->
<xsl:template match="*[local-name()='UnitTestResult'][@outcome='Failed']">
<tr>
<td>
<xsl:apply-templates select="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='TestDefinitions']/*[local-name()='UnitTest'][@id=@testId]"/>
</td>
<td>
<xsl:value-of select="@testName"/>
</td>
<td>
<xsl:value-of select="*[local-name()='Output']/*[local-name()='ErrorInfo']/*[local-name()='Message']"/>
</td>
</tr>
</xsl:template>
<!-- Failed unit test class name -->
<xsl:template match="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='TestDefinitions']/*[local-name()='UnitTest']">
<xsl:value-of select="@className"/>
</xsl:template>
Here is a pared-down sample XML build log:
<cruisecontrol>
<build>
<TestRun>
<TestDefinitions>
<UnitTest name="MyUnitTest" storage="Test.dll" id="b17e5b2a-47d0-5f78-1750-07c8ac14518c">
<TestMethod className="UnitTests.cs" name="MyUnitTest" />
</UnitTest>
...
</TestDefinitions>
<Results>
<UnitTestResult testId="b17e5b2a-47d0-5f78-1750-07c8ac14518c" testName="MyUnitTest" outcome="Failed">
<Output>
<ErrorInfo>
<Message>Assert.AreEqual failed</Message>
</ErrorInfo>
</Output>
</UnitTestResult>
...
</Results>
</TestRun>
</build>
</cruisecontrol>
Here is the current output:
Unit test class name:
Unit test method name: MyUnitTest
Failure message: Assert.AreEqual failed
Here is the desired output:
Unit test class name: UnitTests.cs
Unit test method name: MyUnitTest
Failure message: Assert.AreEqual failed
My problem is that the class name is always empty. I am trying to use the testId attribute of the UnitTestResult node to reference the correct UnitTest node elsewhere in the document. What XSL or Xpath magic do I need to accomplish my goal?
At first glance it looks like this is your problem:
Specifically, the
[@id=@testId]part. You’re trying to look up aUnitTestbased on itsidattribute from thetestidattribute of aUnitTestResult. The problem is in this context[@id=@testId]means “look for UnitTest elements whose id and testId attributes match.”What you really want is to use the
current()function, which allows you to have more than one context within a filter, like this:Also, like Lucero commented, you’ll want to remove your
local-name()calls since it’ll make everything simpler:As an alternative to using a long XPath expression for your lookups you can use keys instead. They let you define a key, which you can use for shorter lookups later.
Define a key like this:
Then use it like this:
That saves you from having to use the current() function. Either way it’ll work.
Using the
local-name()function might indicate that you have a namespace problem. If thebuildandTestRunelements are in different namespaces, you can’t simply query them by using/cruisecontrol/build/TestRun/. That will query elements from the default namespace only. IfTestRunis in another namespace, you’ll need to define that namespace in the XSLT file and use its prefix in your expressions, like this:/cruisecontrol/build/ns:TestRunwherensis the namespace prefix in question. Whatlocal-name()does is it ignores the namespace part of an element’s name, bypassing defining the namespace properly. It’s also useful when you don’t know what namespace(s) are going to be used in the source document.