stackErr
stackErr

Reputation: 4170

Plist to JUnit XML via XSL

I am trying to convert Apples plist file into a JUnit XML format so Hudson/Jenkins can read the results.

I have a plist file output similar to this:

<plist><dict><array>
        <dict>
            <key>LogType</key>
            <string>Pass</string>
            <key>Message</key>
            <string>Test 1</string>
            <key>Timestamp</key>
            <date>2012-10-26T09:42:41Z</date>
            <key>Type</key>
            <integer>4</integer>
         </dict>
         <dict>.....</dict>
         .
         .
         .
         <dict>
            <key>LogType</key>
            <string>Pass</string>
            <key>Message</key>
            <string>Test 1</string>
            <key>Timestamp</key>
            <date>2012-10-26T09:43:27Z</date>
            <key>Type</key>
            <integer>5</integer>
         </dict>
         <dict>
            <key>LogType</key>
            <string>Fail</string>
            <key>Message</key>
            <string>Inserting content to a group</string>
            <key>Timestamp</key>
            <date>2012-10-26T09:49:13Z</date>
            <key>Type</key>
            <integer>4</integer>
        </dict>
        <dict>.....</dict>
        .
        .
        .
        <dict>
            <key>LogType</key>
            <string>Error</string>
            <key>Message</key>
            <string>VerboseError: target.frontMostApp().mainWindow().buttons()[3] could not be tapped</string>
            <key>Screenshot</key>
            <string></string>
            <key>Timestamp</key>
            <date>2012-10-26T09:50:12Z</date>
            <key>Type</key>
            <integer>3</integer>
        </dict>
        <dict>
            <key>LogType</key>
            <string>Fail</string>
            <key>Message</key>
            <string>Inserting content to a group</string>
            <key>Screenshot</key>
            <string></string>
            <key>Timestamp</key>
            <date>2012-10-26T09:50:13Z</date>
            <key>Type</key>
            <integer>7</integer>
        </dict>
</array></dict>
</plist>

which I need to convert to JUnit XML. Here is the output iI expect for the plist fiel above:

<?xml version="1.0"?>
<testsuites>
    <testsuite>
        <testcase classname="Test 1" name="Test 1"/>
        <testcase classname="Inserting content to a group" name="Inserting content to a group">
            <failure>Inserting content to a group - VerboseError: target.frontMostApp().mainWindow().buttons()[3] could not be tapped< </failure>
        </testcase>
    </testsuite>
</testsuites>

Currently I have this XSL:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <testsuites>
            <testsuite>
                <xsl:for-each select="plist/dict/array/dict">
                    <xsl:if test="integer = 4">
                        <testcase>
                            <xsl:attribute name="classname"><xsl:value-of select="string[2]" /></xsl:attribute>
                            <xsl:attribute name="name"><xsl:value-of select="string[2]"/></xsl:attribute>
                            <xsl:if test="string[1] = 'Fail'">
                                <failure>
                                    <xsl:attribute name="type"><xsl:value-of select="integer" /></xsl:attribute><xsl:value-of select="string[2]" />                                           
                                </failure>
                            </xsl:if>
                        </testcase>
                    </xsl:if>
                </xsl:for-each>
            </testsuite>
        </testsuites>
    </xsl:template>
</xsl:stylesheet>

How can I edit the XSL above to find any error messages in between the two dict/string[2] = 'Test A' and insert the message into the value of <failure>? I am not sure how to do this because the Error message is contained in another <dict> node.

EDIT:

Ok I have broken this down into pseudo ish code:

  1. Count all the nodes with integer = 4.

  2. Find out the position of each of the nodes with integer = 4 and store in a variable

  3. Go through each node with integer = 4 and find any string[1] = 'Fail' before next node with integer = 4.

    1. If any string[1] = 'Fail' then find and string[1] = 'Error' before next node with integer = 4 and after previous node with integer = 4.

      1. If any string[1] = 'Error', output failure with string[2] from current node and string[2] from previous node with integer = 4.
    2. Else output failure with string[2] from previous node with integer = 4.

node referring to plist/dict/array/dict

Is this possible with XSL?

Upvotes: 0

Views: 1176

Answers (1)

ABach
ABach

Reputation: 3738

Your rules are slightly confusing, but I believe I have a solution for you.

When this XSLT:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
  <xsl:output omit-xml-declaration="no" indent="yes" />
  <xsl:strip-space elements="*" />

  <xsl:template match="/*">
    <testsuites>
      <testsuite>
        <xsl:apply-templates select="/*/*/*/dict[integer = '4']" />
      </testsuite>
    </testsuites>
  </xsl:template>

  <xsl:template match="dict[string[1] = 'Fail']">
    <testcase classname="{string[2]}" name="{string[2]}">
      <failure>
        <xsl:value-of select="string[2]" />
        <xsl:if test="following-sibling::dict[1]/string[1] = 'Error'">    
          <xsl:value-of select="concat( ' - ', following-sibling::dict[string[1] = 'Error'][1]/string[2])" />
        </xsl:if>
      </failure>
    </testcase>
  </xsl:template>

  <xsl:template match="dict[not(string[1] = 'Fail')]">
    <testcase classname="{string[2]}" name="{string[2]}" />
  </xsl:template>
</xsl:stylesheet>

..is applied to the provided XML:

<?xml version="1.0" encoding="utf-8"?>
<plist>
  <dict>
    <array>
      <dict>
        <key>LogType</key>
        <string>Pass</string>
        <key>Message</key>
        <string>Test 1</string>
        <key>Timestamp</key>
        <date>2012-10-26T09:42:41Z</date>
        <key>Type</key>
        <integer>4</integer>
      </dict>
      <dict>
        <key>LogType</key>
        <string>Pass</string>
        <key>Message</key>
        <string>Test 1</string>
        <key>Timestamp</key>
        <date>2012-10-26T09:43:27Z</date>
        <key>Type</key>
        <integer>5</integer>
      </dict>
      <dict>
        <key>LogType</key>
        <string>Fail</string>
        <key>Message</key>
        <string>Inserting content to a group</string>
        <key>Timestamp</key>
        <date>2012-10-26T09:49:13Z</date>
        <key>Type</key>
        <integer>4</integer>
      </dict>
      <dict>
        <key>LogType</key>
        <string>Error</string>
        <key>Message</key>
        <string>VerboseError:
        target.frontMostApp().mainWindow().buttons()[3] could not
        be tapped</string>
        <key>Screenshot</key>
        <string />
        <key>Timestamp</key>
        <date>2012-10-26T09:50:12Z</date>
        <key>Type</key>
        <integer>3</integer>
      </dict>
      <dict>
        <key>LogType</key>
        <string>Fail</string>
        <key>Message</key>
        <string>Inserting content to a group</string>
        <key>Screenshot</key>
        <string />
        <key>Timestamp</key>
        <date>2012-10-26T09:50:13Z</date>
        <key>Type</key>
        <integer>7</integer>
      </dict>
    </array>
  </dict>
</plist>

...the expected result is produced:

<?xml version="1.0"?>
<testsuites>
  <testsuite>
    <testcase classname="Test 1" name="Test 1" />
    <testcase classname="Inserting content to a group"
    name="Inserting content to a group">
      <failure>Inserting content to a group - VerboseError:
      target.frontMostApp().mainWindow().buttons()[3] could not be
      tapped</failure>
    </testcase>
  </testsuite>
</testsuites>

Explanation:

  • The first template creates new <testsuites> and <testsuite> elements. The XSLT processor is then instructed to process all <dict> descendants whose <integer> child has a value of 4.
  • The second template matches all <dict> elements whose first <string> child has a value of "Fail". In this case, a new <testcase> and <failure> element pair is created and given a value that conforms to the rules you have provided.
  • The final template matches all <dict> elements whose first <string> child has a value of anything other than "Fail". In this case, a new <testcase> element is created and given values per your rules.

Upvotes: 1

Related Questions