Sunday, March 1, 2009

Spring 3.0 Quartz Batch With Maven2

Goal


The purpose of this entry is to provide a short tutorial for setting up a Maven2 project that uses Spring 3.0 to schedule a Quartz batch job, to inject Spring-managed beans into this Quartz-managed job, to use Spring to test the job, and to setup a distributable, runnable assembly for the project.

Setup a New Maven Project


Setting up a new Maven project in Eclipse with the m2eclipse plug-in is straightforward, even without the fancy pom editors. For this example, we can create a simple project (skip archetype selection) and package the target as a jar.

New Maven Project


Add Repositories and Register Dependencies


Because Spring 3.0.0.M1 is currently not available in the main Maven repositories, we will need to add custom repositories to our pom.xml (Thanks to Chris Beams for his instructions).
We are particularly interested in org.springframework.context.support, which contains the Spring-Quartz bridge that has been separated out from the core spring.jar between Spring 2.5 and 3.0; org.springframework.transaction, which the Spring-Quartz bridge depends on; commons-collections, which the Spring-Quartz bridge also depends on; and quartz from OpenSymphony.
Our new pom.xml should look like this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>timezra.blog.spring_batch</groupId>
  <artifactId>timezra.blog.spring_batch</artifactId>
  <name>timezra.blog.spring_batch</name>
  <version>0.0.1-SNAPSHOT</version>
  <description>Spring Batch Example</description>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>org.springframework.context.support</artifactId>
      <version>3.0.0.M1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>org.springframework.transaction</artifactId>
      <version>3.0.0.M1</version>
    </dependency>
    <dependency>
      <groupId>org.opensymphony.quartz</groupId>
      <artifactId>quartz</artifactId>
      <version>1.6.1</version>
    </dependency>
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
  </dependencies>
  <repositories>
    <repository>
      <id>SpringSource Enterprise Bundle Repository - External Bundle Milestones</id>
      <url>http://repository.springsource.com/maven/bundles/milestone</url>
    </repository>
    <repository>
      <id>SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases</id>
      <url>http://repository.springsource.com/maven/bundles/release</url>
    </repository>
    <repository>
      <id>SpringSource Enterprise Bundle Repository - External Bundle Releases</id>
      <url>http://repository.springsource.com/maven/bundles/external</url>
    </repository>
  </repositories>
</project>


NB: As Spring 3.0.0 becomes generally available, registering custom repositories will no longer be necessary, and the necessary versions of the Spring dependencies may change.

Create the Spring Application


We are now ready to register a batch job with Spring in a /src/main/resources/applicationContext.xml file. This registration requires three components: a job declaration, a trigger that depends on the registered job, and a scheduler that depends on the trigger. In our case, we would also like to declare a separate Spring-managed bean and inject that as a dependency into the job. A sample applicationContext.xml would look like this, where our (unimplemented) job will run every five seconds:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd"
>

  <bean id="myBean" class="timezra.blog.spring_batch.MyBean">
    <property name="name" value="My Name" />
  </bean>

  <bean name="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="timezra.blog.spring_batch.MyJob" />
    <property name="jobDataAsMap">
      <map>
        <entry key="myBean">
          <ref bean="myBean" />
        </entry>
      </map>
    </property>
  </bean>
  <bean id="jobDetailTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="cronExpression" value="0/5 * * * * ?" />
  </bean>
  <bean id="schedulerFactoryBean"
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
      <list>
        <ref bean="jobDetailTrigger" />
      </list>
    </property>
  </bean>
</beans>


NB: Spring injects the myBean dependency through the jobDataAsMap property on the JobDetailBean.

The MyBean.java implementation can be as simple as

package timezra.blog.spring_batch;

public class MyBean {

  private String name;

  public String getName() {
    return name;
  }

  public void setName(final String name) {
    this.name = name;
  }
}



MyJob.java contains the logic for our batch job.

package timezra.blog.spring_batch;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.StatefulJob;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class MyJob extends QuartzJobBean implements StatefulJob {

  private MyBean myBean;

  public MyJob() {
    System.out.println("MyJob.MyJob()");
  }

  @Override
  protected void executeInternal(final JobExecutionContext context) throws JobExecutionException {
    System.out.println("myBean's name=[" + myBean.getName() + "] and its instance is [" + myBean + "].");
  }

  public void setMyBean(final MyBean myBean) {
    this.myBean = myBean;
  }
}


NB: For this example, we have created a mutator so myBean can be injected, we print whenever a new Job is constructed, and when the job runs, we print the name of myBean and its instance.

Create a Main Runner


We can easily create a runner for the batch job just to get a feel for what is happening as the Spring container is initialized, as Spring starts the Quartz scheduler and as the Quartz scheduler runs. We simply need to create an ApplicationContext with our applicationContext.xml.

package timezra.blog.spring_batch;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  public static void main(final String[] args) {
    new ClassPathXmlApplicationContext("/applicationContext.xml");
  }
}


If we run this file as a java application, we see that every five seconds, a new instance of our job is created (which is not necessarily surprising) and the job is executed with the same instance of myBean (which should be anticipated).

Scope MyBean as Prototype


Suppose we want a new instance of myBean for each execution of the job. If Spring were managing the lifecycle of the job instances, this might be as straightforward as declaring the myBean's scope as prototype in the applicationContext.xml.
What happens? The myBean instance that is printed for each run of the job is the same. Quartz, not Spring, is managing the construction of these jobs.
What happens if we specify that the JobDetailBean is a prototype? The instance of myBean in the job is still the same.
Fortunately, it is possible to get a handle on the Spring ApplicationContext from the job by changing only a few lines of code in our applicationContext.xml and in MyJob.java.

Add the Application Context to the Job Data Map


To inject the ApplicationContext into a Job, we need to specify a key for it, so we can retrieve it from the JobExecutionContext's data map. We also will no longer inject the dependency into the job since we will retrieve it directly from the ApplicationContext.
The updated applicationContext.xml should look like this:

  ....
  <bean name="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="timezra.blog.spring_batch.MyJob" />
    <property name="applicationContextJobDataKey" value="applicationContext" />
  </bean>
  ....


NB: we can set the ApplicationContext's key by specifying the applicationContextJobDataKey property for the JobDetailBean.

The updated MyJob.java now retrieves myBean directly from the ApplicationContext.

package timezra.blog.spring_batch;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.StatefulJob;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class MyJob extends QuartzJobBean implements StatefulJob {

  public MyJob() {
    System.out.println("MyJob.MyJob()");
  }

  @Override
  protected void executeInternal(final JobExecutionContext context) throws JobExecutionException {
    final BeanFactory applicationContext = (BeanFactory) context.getMergedJobDataMap().get("applicationContext");
    final MyBean myBean = (MyBean) applicationContext.getBean("myBean");
    System.out.println("myBean's name=[" + myBean.getName() + "] and its instance is [" + myBean + "].");
  }
}



Test The Batch Job Instance


As we have demonstrated, Spring is not managing the lifecycle of our quartz job directly, only of the JobDetailBean. Whenever I test a Spring-managed bean, I personally prefer that Spring injects an instance of the bean into the test, rather than to call the constructor for that bean directly. After all, the bean under test is a dependency of the test case itself.
While it may not be obvious how we can get an instance of our job inside the test case for that job, it is not difficult, only slightly roundabout. We must specify a JobFactory for the SchedulerFactoryBean explicitly in our applicationContext.xml.

  ....
  <bean id="jobFactory" class="org.springframework.scheduling.quartz.SpringBeanJobFactory"/>
  <bean id="schedulerFactoryBean"
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
      <list>
        <ref bean="jobDetailTrigger" />
      </list>
    </property>
    <property name="jobFactory" ref="jobFactory" />
  </bean>
  ....



We will also declare a couple of new test-scoped dependencies in our pom.xml.

    ....
    <dependency>
      <scope>test</scope>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.5</version>
    </dependency>
    <dependency>
      <scope>test</scope>
      <groupId>org.springframework</groupId>
      <artifactId>org.springframework.test</artifactId>
      <version>3.0.0.M1</version>
    </dependency>
    ....



Finally, we can create a JUnit test case, MyJobTest.java, that simulates the triggering of the job. Whether or not wiring together the scheduler, trigger and factory is better than instantiating the job directly depends on your own preference. This technique is simply one approach.

package timezra.blog.spring_batch;

import java.util.Date;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.impl.calendar.CronCalendar;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.CronTriggerBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext.xml" })
public class MyJobTest {

  @Autowired
  private SpringBeanJobFactory jobFactory;

  @Autowired
  private SchedulerFactoryBean schedulerFactory;

  @Autowired
  private CronTriggerBean trigger;

  @Test
  public void theJobPrintsToStandardOut() throws Exception {
    final TriggerFiredBundle bundle = new TriggerFiredBundle(trigger.getJobDetail(), trigger,
        new CronCalendar(trigger.getCronExpression()), false, new Date(), new Date(), trigger
            .getPreviousFireTime(), trigger.getNextFireTime());
    final Job job = jobFactory.newJob(bundle);
    job.execute(new JobExecutionContext(schedulerFactory.getScheduler(), bundle, job));
  }
}



Create an Assembly


Now that we have a batch process, a test suite and a runner, we can create a distribution. Thanks to a comment by Valerio Schiavoni, creating an executable jar with all its dependencies is easy in Maven2. We can add an incantation to our pom.xml that configures the jar to execute our Main.java:

  ....
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>timezra.blog.spring_batch.Main</mainClass>
            </manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ....


We will assemble this distribution by invoking Run As -> Maven assembly:assembly from inside Eclipse.
We can run the jar from the command-line by invoking java -jar timezra.blog.spring_batch-0.0.1-SNAPSHOT-jar-with-dependencies.jar.

We now have a robust infrastructure for easily adding new jobs to our runner, for testing those jobs, and for creating an executable distribution.
The final project setup.

Wednesday, February 18, 2009

STIQ, Eclipse, Ant and Hudson

Goal:


By the end of this entry, the user will have the tools for running a StoryTestIQ project from Eclipse and will be able to automate the test runs, either serially or in parallel, from a Continuous Integration server such as Hudson.

Prepare STIQ


First, we will need to download the STIQ binary. The latest version, as of this entry, is 2.0 RC5.
From the extracted distribution, delete all the /*.bat files. For this example, we will be running STIQ from inside Eclipse and from Ant. We can also delete the following unused files and directories: /repository/FrameworkTests, /repository/ProjectRoot/IntegrationTests/SmokeTests, /repository/ProjectRoot/SprintTests/SprintOne (the existing test suite is buggy and needs to be re-created), /bin/lib/jdbc-csv-mock.jar, /bin/lib/junit-3.8.1.jar, /bin/lib/ojdbc-14.jar, /bin/lib/sqlserver-1.0.809.jar.
If you need to use a specific database driver for your tests, then add it to /bin/lib, and configure it in storytestiq.properties.
For this example, we will add the latest jquery production download to /repository/extensions/display as well. As of this writing, the latest production distribution is jquery-1.3.1.min.js.

STIQ in Eclipse


To run STIQ from inside Eclipse, we can create a new plug-in project. For this example, the project is named timezra.blog.stiq. Because STIQ distributes its library files in /bin and /bin/lib, we will make sure to use build instead of bin for the compilation output folder.
In the plug-in MANIFEST.MF, we will also need to specify these dependencies: org.eclipse.core.runtime, org.eclipse.swt and org.eclipse.jface.
We can then copy the prepared STIQ distribution directly into the root of the project.

We will then add the required STIQ libraries to the classpath of the MANIFEST.MF: /bin/storytestiq.jar, /bin/lib/fitlibrary-1.1.jar, /bin/lib/selenium-server.jar, /bin/lib/Tidy.jar.
Finally, in order to run STIQ as a Java application from inside Eclipse, we can create a Main.java:

package timezra.blog.stiq;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Map.Entry;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import fitnesse.FitNesse;

public class Main {

    public static void main(final String[] args) throws Exception {
        loadProperties("storytestiq.properties");

        FitNesse fitnesse = null;
        try {
            fitnesse = FitNesse.startFitNesse(new String[] { "-o", "-p", "9999", "-r", "repository" });
            startBrowser();
        } finally {
            if (fitnesse != null) {
                fitnesse.stop();
            }
        }
    }

    private static void loadProperties(final String fileName) {
        final Properties properties = new Properties();
        try {
            properties.load(new FileInputStream(fileName));
            for (final Entry<Object, Object> entry : properties.entrySet()) {
                System.setProperty((String) entry.getKey(), (String) entry.getValue());
            }
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

    private static void startBrowser() {
        final Display display = new Display();
        final Shell shell = new Shell(display);
        final Browser browser = new Browser(shell, SWT.NONE);
        GridDataFactory.fillDefaults().span(3, 1).grab(true, true).applyTo(browser);
        GridLayoutFactory.fillDefaults().numColumns(3).generateLayout(shell);
        shell.open();
        browser.setUrl("http://localhost:9999/stiq/runner.hta?startPage=/ProjectRoot");
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        display.dispose();
    }
}



We can begin building acceptance tests by running Main.java as a Java application. For this example, I have setup two sets of SprintTests, all tagged as integration.

The STIQ project in Eclipse.
Your STIQ project in Eclipse should look similar.

STIQ and Ant


To run the the example STIQ tests from Ant, we can replace the existing /runner.xml with another version that uses better formatting and has options for running all integration tests serially or SprintTests in parallel.

<project name="STIQ-Runner" default="usage">

    <description>StoryTestIQ (STIQ) is a mashup of Selenium and FitNesse. This file is the STIQ runner.</description>

    <dirname file="${ant.file.STIQ-Runner}" property="install.dir" />
    <property file="${install.dir}/storytestiq.properties" />
    <property name="stiq-results" location="${install.dir}/stiq-results" />
    <property environment="env" />
    <loadfile property="jquery" srcfile="${install.dir}/repository/extensions/display/jquery-1.3.1.min.js" />

    <path id="stiq-classpath">
        <fileset dir="${install.dir}/bin">
            <include name="**/*.jar" />
        </fileset>
    </path>

    <target name="usage">
        <echo level="info">
            clean: remove any test result artifacts
            run-integration-tests: runs all STIQ IntegrationTests
            run-sprint-tests: runs STIQ Sprint Tests in Parallel
        </echo>
    </target>

    <target name="clean">
        <delete failonerror="false" includeemptydirs="true">
            <fileset dir="${stiq-results}">
                <include name="*/**" />
            </fileset>
        </delete>
    </target>

    <target name="init">
        <tstamp>
            <format property="today" pattern="MMMM d yyyy hh:mm a" locale="en,US" />
        </tstamp>
    </target>

    <target name="run-integration-tests" depends="clean, init">
        <run-stiq-suite start-page="/.ProjectRoot" suite-page="IntegrationTests" output-dir="${stiq-results}" />
        <publish-results-as-html />
        <fail>
            <condition>
                <not>
                    <equals arg1="${stiq-suite-result}" arg2="0" />
                </not>
            </condition>
        </fail>
    </target>

    <target name="run-sprint-tests" depends="clean, init">
        <parallel threadcount="2" >
            <run-stiq-suite start-page="/.ProjectRoot.SprintTests" suite-page="SprintOne" output-dir="${stiq-results}/SprintOne" port="9991" result-property="sprint.one.result" />
            <run-stiq-suite start-page="/.ProjectRoot.SprintTests" suite-page="SprintTwo" output-dir="${stiq-results}/SprintTwo" port="9992" result-property="sprint.two.result" />
        </parallel>
        <concat append="true" destfile="${stiq-results}/stiq-results.xml">&lt;parallel&gt;</concat>
        <concat append="true" destfile="${stiq-results}/stiq-results.xml">
            <fileset dir="${stiq-results}">
                <include name="*/stiq-results.xml" />
            </fileset>
        </concat>
        <concat append="true" destfile="${stiq-results}/stiq-results.xml">&lt;/parallel&gt;</concat>
        <publish-results-as-html />
        <fail>
            <condition>
                <not>
                    <and>
                        <equals arg1="${sprint.one.result}" arg2="0" />
                        <equals arg1="${sprint.two.result}" arg2="0" />
                    </and>
                </not>
            </condition>
        </fail>
    </target>

    <macrodef name="run-stiq-suite">
        <attribute name="output-dir" default="${install.dir}" />
        <attribute name="port" default="9999" />
        <attribute name="repository-path" default="repository" />
        <attribute name="result-property" default="stiq-suite-result" />
        <attribute name="start-page" />
        <attribute name="suite-page" />
        <sequential>
            <mkdir dir="@{output-dir}" />
            <echo level="info">Executing suite [@{suite-page}] in STIQ.</echo>
            <java classname="fitnesse.runner.FitnessRunner" classpathref="stiq-classpath" fork="true" resultproperty="@{result-property}">
                <sysproperty key="STIQDatabaseDriver" value="${STIQDatabaseDriver}" />
                <sysproperty key="STIQDatabaseConnectionString" value="${STIQDatabaseConnectionString}" />
                <sysproperty key="STIQDatabaseUsername" value="${STIQDatabaseUsername}" />
                <sysproperty key="STIQDatabasePassword" value="${STIQDatabasePassword}" />
                <arg line="@{port}" />
                <arg line="@{repository-path}" />
                <arg line="@{start-page}" />
                <arg line="@{suite-page}" />
                <arg line="@{output-dir}" />
            </java>
        </sequential>
    </macrodef>

    <macrodef name="publish-results-as-html">
        <sequential>
            <xslt basedir="${stiq-results}" destdir="${stiq-results}" includes="stiq-results.xml" out="stiq-results.html" style="stiq.xsl">
                <param name="applicationPath" expression="${install.dir}" />
                <param name="buildDate" expression="${today}" />
                <param name="buildNumber" expression="${env.BUILD_NUMBER}" />
                <param name="jquery" expression="${jquery}" />
            </xslt>
        </sequential>
    </macrodef>
</project>



NB: if you are using a version of jQuery other than jquery-1.3.1.min.js, this version can be configured among the Ant properties.

There are two STIQ targets in the Ant script: run-integration-tests will run all STIQ tests tagged as integration; run-sprint-tests will run each of the SprintTest suites in parallel. Note that, when using the latter target, if we add more SprintTest suites, we will also need to add new parallel macro calls to run-stiq-suite as well as failure condition checks, and we may want to increase the parallel threadcount.

Note also that the publish-results-as-html macro expects a /stiq.xsl stylesheet for performing the transform between stiq.xml test output and human-readable HTML. This stylesheet, originally written by Kendrick Burson, transforms the output from either an integration or parallel STIQ run into an HTML report.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" indent="yes" />
    <xsl:param name="applicationPath" />
    <xsl:param name="buildDate" />
    <xsl:param name="jquery" />
    <xsl:param name="buildNumber" select="" />

    <!-- ############################################################# -->
    <!-- Template: match / -->
    <xsl:template match="/">
        <html>
            <head>
                <title>STIQ Test Results</title>
                <link rel="stylesheet" type="text/css" media="screen"
                    href="file://{$applicationPath}/repository/extensions/display/selenium-test.css" />
                <xsl:call-template name="writeClientScript" />
            </head>
            <body>
                <table border="0">
                    <tr>
                        <td>
                            Hudson Build #
                            <xsl:value-of select="$buildNumber" />
                            completed on
                            <xsl:value-of select="$buildDate" />
                        </td>
                    </tr>
                </table>
                <br/>
                <xsl:apply-templates select="parallel" />
                <xsl:apply-templates select="stiq" />
            </body>
        </html>
    </xsl:template>

    <!-- ############################################################# -->
    <!-- Template: match <parallel> -->
    <xsl:template match="parallel">
        <table border="0">
            <tr>
                <td align="left">
                    <b>
                        <xsl:text>STIQ Tests total:  </xsl:text>
                        <xsl:value-of select="count(stiq/body/table/tbody/tr[td/a])" />
                        <xsl:text>&#160;&#160;&#160;(</xsl:text>
                        <font color="green">
                            <xsl:text>Passed: </xsl:text><xsl:value-of select = "count(stiq/body/table/tbody/tr[td/a and @class=' status_passed'])" />
                        </font>
                        <xsl:text>&#160;&#160;&#160;</xsl:text>
                        <font color="red">
                            <xsl:text>Failed: </xsl:text><xsl:value-of select = "count(stiq/body/table/tbody/tr[td/a and @class=' status_failed'])" />
                        </font>
                        <xsl:text>)</xsl:text>
                    </b>
                </td>
            </tr>
        </table>
        <br/>
        <xsl:apply-templates select="stiq" />
    </xsl:template>

    <!-- ############################################################# -->
    <!-- Template: match <stiq> -->
    <xsl:template match="stiq">
        <table class="suiteheader">
            <tr>
                <td align="left">
                    <b>
                        <xsl:text>Suite Tests total:  </xsl:text>
                        <xsl:value-of select="count(body/table/tbody/tr[td/a])" />
                        <xsl:text>&#160;&#160;&#160;(</xsl:text>
                        <font color="green">
                            <xsl:text>Passed: </xsl:text><xsl:value-of select = "count(body/table/tbody/tr[td/a and @class=' status_passed'])" />
                        </font>
                        <xsl:text>&#160;&#160;&#160;</xsl:text>
                        <font color="red">
                            <xsl:text>Failed: </xsl:text><xsl:value-of select = "count(body/table/tbody/tr[td/a and @class=' status_failed'])" />
                        </font>
                        <xsl:text>)</xsl:text>
                    </b>
                </td>
            </tr>
        </table>
        <table class="yo">
            <xsl:apply-templates select="body/table/tbody/tr" mode="r1">
                <xsl:sort select="td[1]/a" />
            </xsl:apply-templates>
        </table>
        <br/>
    </xsl:template>

    <!-- ############################################################# -->
    <!-- Template: match <tr> -->
    <xsl:template match="tr" mode="r1">
        <xsl:element name="tr">
            <xsl:attribute name="class">
                <xsl:value-of select="@class"/>
            </xsl:attribute>
        
            <xsl:choose>
                <!-- First Row - Test Suite -->
                <xsl:when test="position() = 1">
                    <!-- First Column - Row Number - blank for test suite name -->
                    <td valign="top">&#160;</td>
                    <!-- Second Column - Test Name -->
                    <td valign="top" colspan="2">
                        <xsl:copy-of select="td[1]/*" />
                    </td>
                </xsl:when>
                
                <!-- Test Results -->    
                <xsl:otherwise>
                    <!-- First Column - Row Number -->
                    <td valign="top" class="{./@class}"><xsl:value-of select="position() - 1"/></td>
                    
                    <!-- Second Column - Test Name + test results in hidden div -->
                    <td valign="top" class="{./@class}">
                    
                        <!-- clickable link to show test results using test name as title -->
                        <a href="javascript:void(0);" onclick="javascript:$('#{generate-id(td[2])}').slideToggle();" title="{td[1]/a/@href}">
                            <xsl:value-of select="td[1]/a" />
                        </a>
                        
                        <!-- Hidden div show results of test. -->
                        <div id="{generate-id(td[2])}" valign="top" style="display:none" class="{./@class}" >
                            <blockquote>                
                                <xsl:apply-templates select="td[2]/*" mode="copy" />
                            </blockquote>
                        </div>
                    </td>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:element>
    </xsl:template>

    <!-- ############################################################# -->
      <!-- Template: copy all nodes and attributes -->
      <xsl:template match="@*|node()" mode="copy">
        <xsl:choose>
            <!-- Hide stiq controls from report == we do not want viewers to accidentally click active links to stiq runtime env -->
            <xsl:when test="local-name() = 'div' and ./@class='actions'">
                <xsl:comment>Remove STIQ Controls from report</xsl:comment>
            </xsl:when>

            <!-- create collapsible sub table for included 'components' -->
            <xsl:when test="local-name() = 'div' and ./@class='included'">
                <div class="stiqincluded">
                    <span>
                         <xsl:if test="./div/table/tbody/tr/@class = ' status_failed'">
                               <xsl:attribute name="style">background-color:#FF9999</xsl:attribute>
                         </xsl:if>
                        <span class="{./span/@class}">
                            <xsl:text>Included page: </xsl:text>>
                            <a href="javascript:void(0);" onclick="javascript:$('#{./div[@class='collapsable']/@id}').slideToggle();">
                                <xsl:value-of select="./span/a"/>
                            </a>
                        </span>
                    </span>
                    <br/>
                    <xsl:apply-templates select="./div" mode="copy"/>
                </div>
            </xsl:when>
            
            <!-- Show table of values -->
            <xsl:when test="local-name() = 'table'">
                <table class="stiqtest">
                    <xsl:apply-templates select="./*" mode="copy"/>
                </table>
            </xsl:when>
            
            <xsl:when test="local-name() = 'a'">
                <span style="color:blue">
                    <xsl:value-of select="." />
                </span>
            </xsl:when>
            
            <!-- Huh?  Don't know what it is so just copy it -->
            <xsl:otherwise>
                <xsl:copy>
                    <xsl:apply-templates select="@*|node()" mode="copy"/>
                </xsl:copy>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
    <xsl:template name="writeClientScript">
        <style>
            <![CDATA[ 
            <!--
            div.stiqincluded
            {
                background: #F9F9F9;
                margin: 5px 0px 5px 0px;
                padding: 2px 2px 2px 2px;
                border: 1px dotted #909090;
            }
            div.collapsable
            {
                display: none;
            }
            table.suiteheader
            {
                margin: 2px;
                padding: 2px;
                border: solid #AAAAAA;
                border-width: 1px 1px 0px 1px;
            }
            table.stiqtest
            {
                background: #EEEEEE;
                margin: 2px;
                padding: 2px;
                border: solid #AAAAAA;
                border-width: 1px 1px 1px 1px;
                font-family: trebuchet ms, verdana, tahoma, arial, sans-serif;
                font-size: 1em;
            }
            .status_done {
                background-color: #EEFFEE;
            }
            .status_passed {
                background-color: #CCFFCC !important;
            }
            .status_failed {
                background-color: #FFCCCC;
            }
            -->
            
]]>
        </style>
        <script LANGUAGE="javascript">
            <xsl:value-of select="$jquery" />
        </script>
    </xsl:template>        
</xsl:stylesheet>



STIQ and Hudson


For this example, we can setup Apache Tomcat as a Windows service to run Hudson.
We will want to ensure that Tomcat is setup to "Allow service to interact with desktop" so that we can view the automated STIQ test run.
The Tomcat Service configuration.
The Windows Service setup for the instance of Tomcat running Hudson should look similar.

From Hudson, we can build a freestyle software project to launch our STIQ tests with a custom workspace (i.e., the path to the Eclipse STIQ project workspace). We can then add a build task to run-sprint-tests with the custom runner.xml, and we can archive the stiq-results/stiq-results.html artifact.

The Hudson STIQ project configuration.
The Hudson configuration for your STIQ project should look similar.

We now have a standard setup for an Eclipse STIQ project, a way to launch STIQ from inside Eclipse, a way to run STIQ tests serially or in parallel from Ant, and a way to automate STIQ test runs from Hudson.