You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ri...@apache.org on 2007/08/30 14:19:42 UTC

svn commit: r571129 [13/15] - in /incubator/qpid/trunk/qpid/java: ./ broker/ broker/bin/ broker/distribution/src/main/assembly/ broker/etc/ broker/src/main/java/org/apache/log4j/ broker/src/main/java/org/apache/qpid/configuration/ broker/src/main/java/...

Added: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java?rev=571129&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java (added)
+++ incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java Thu Aug 30 05:19:31 2007
@@ -0,0 +1,633 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.NDC;
+
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.TestUtils;
+import org.apache.qpid.test.framework.clocksynch.UDPClockReference;
+import org.apache.qpid.test.framework.listeners.XMLTestListener;
+import org.apache.qpid.util.ConversationFactory;
+import org.apache.qpid.util.PrettyPrintingUtils;
+
+import uk.co.thebadgerset.junit.extensions.TKTestResult;
+import uk.co.thebadgerset.junit.extensions.TKTestRunner;
+import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator;
+import uk.co.thebadgerset.junit.extensions.listeners.CSVTestListener;
+import uk.co.thebadgerset.junit.extensions.util.CommandLineParser;
+import uk.co.thebadgerset.junit.extensions.util.MathUtils;
+import uk.co.thebadgerset.junit.extensions.util.ParsedProperties;
+import uk.co.thebadgerset.junit.extensions.util.TestContextProperties;
+
+import javax.jms.*;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.util.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * <p/>Implements the coordinator client described in the interop testing specification
+ * (http://cwiki.apache.org/confluence/display/qpid/Interop+Testing+Specification). This coordinator is built on
+ * top of the JUnit testing framework.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Find out what test clients are available. <td> {@link ConversationFactory}
+ * <tr><td> Decorate available tests to run all available clients. <td> {@link DistributedTestDecorator}
+ * <tr><td> Attach XML test result logger.
+ * <tr><td> Terminate the interop testing framework.
+ * </table>
+ *
+ * @todo Should accumulate failures over all tests, and return with success or fail code based on all results. May need
+ *       to write a special TestResult to do this properly. At the moment only the last one used will be tested for
+ *       errors, as the start method creates a fresh one for each test case run.
+ *
+ * @todo Remove hard coding of test cases and put on command line instead.
+ */
+public class Coordinator extends TKTestRunner
+{
+    /** Used for debugging. */
+    private static final Logger log = Logger.getLogger(Coordinator.class);
+
+    /** Used for reporting to the console. */
+    private static final Logger console = Logger.getLogger("CONSOLE");
+
+    /** Defines the possible distributed test engines available to run coordinated test cases with. */
+    public enum TestEngine
+    {
+        /** Specifies the interop test engine. This tests all available clients in pairs. */
+        INTEROP,
+
+        /** Specifies the fanout test engine. This sets up one publisher role, and many reciever roles. */
+        FANOUT
+    }
+
+    /**
+     * Holds the test context properties that provides the default test parameters, plus command line overrides.
+     * This is initialized with the default test parameters, to which command line overrides may be applied.
+     */
+    protected static ParsedProperties testContextProperties =
+        TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+    /** Holds the URL of the broker to coordinate the tests on. */
+    protected String brokerUrl;
+
+    /** Holds the virtual host to coordinate the tests on. If <tt>null</tt>, then the default virtual host is used. */
+    protected String virtualHost;
+
+    /** Holds the list of all clients that enlisted, when the compulsory invite was issued. */
+    protected Set<TestClientDetails> enlistedClients = new HashSet<TestClientDetails>();
+
+    /** Holds the conversation helper for the control conversation. */
+    protected ConversationFactory conversationFactory;
+
+    /** Holds the connection that the coordinating messages are sent over. */
+    protected Connection connection;
+
+    /**
+     * Holds the name of the class of the test currently being run. Ideally passed into the {@link #createTestResult}
+     * method, but as the signature is already fixed for this, the current value gets pushed here as a member variable.
+     */
+    protected String currentTestClassName;
+
+    /** Holds the path of the directory to output test results too, if one is defined. */
+    protected String reportDir;
+
+    /** Holds the coordinating test engine type to run the tests through. */
+    protected TestEngine engine;
+
+    /** Flag that indicates that all test clients should be terminated upon completion of the test cases. */
+    protected boolean terminate;
+
+    /** Flag that indicates the CSV results listener should be used to output results. */
+    protected boolean csvResults;
+
+    /** Flag that indiciates the XML results listener should be used to output results. */
+    protected boolean xmlResults;
+
+    /**
+     * Creates an interop test coordinator on the specified broker and virtual host.
+     *
+     * @param repetitions   The number of times to repeat the test, or test batch size.
+     * @param duration      The length of time to run the tests for. -1 means no duration has been set.
+     * @param threads       The concurrency levels to ramp up to.
+     * @param delay         A delay in milliseconds between test runs.
+     * @param params        The sets of 'size' parameters to pass to test.
+     * @param testCaseName  The name of the test case to run.
+     * @param reportDir     The directory to output the test results to.
+     * @param runName       The name of the test run; used to name the output file.
+     * @param verbose       Whether to print comments during test run.
+     * @param brokerUrl     The URL of the broker to connect to.
+     * @param virtualHost   The virtual host to run all tests on. Optional, may be <tt>null</tt>.
+     * @param engine        The distributed test engine type to run the tests with.
+     * @param terminate     <tt>true</tt> if test client nodes should be terminated at the end of the tests.
+     * @param csv           <tt>true</tt> if the CSV results listener should be attached.
+     * @param xml           <tt>true</tt> if the XML results listener should be attached.
+     */
+    public Coordinator(Integer repetitions, Long duration, int[] threads, int delay, int[] params, String testCaseName,
+        String reportDir, String runName, boolean verbose, String brokerUrl, String virtualHost, TestEngine engine,
+        boolean terminate, boolean csv, boolean xml)
+    {
+        super(repetitions, duration, threads, delay, params, testCaseName, reportDir, runName, verbose);
+
+        log.debug("public Coordinator(Integer repetitions = " + repetitions + " , Long duration = " + duration
+            + ", int[] threads = " + Arrays.toString(threads) + ", int delay = " + delay + ", int[] params = "
+            + Arrays.toString(params) + ", String testCaseName = " + testCaseName + ", String reportDir = " + reportDir
+            + ", String runName = " + runName + ", boolean verbose = " + verbose + ", String brokerUrl = " + brokerUrl
+            + ", String virtualHost =" + virtualHost + ", TestEngine engine = " + engine + ", boolean terminate = "
+            + terminate + ", boolean csv = " + csv + ", boolean xml = " + xml + "): called");
+
+        // Retain the connection parameters.
+        this.brokerUrl = brokerUrl;
+        this.virtualHost = virtualHost;
+        this.reportDir = reportDir;
+        this.engine = engine;
+        this.terminate = terminate;
+        this.csvResults = csv;
+        this.xmlResults = xml;
+    }
+
+    /**
+     * The entry point for the interop test coordinator. This client accepts the following command line arguments:
+     *
+     * <p/><table>
+     * <tr><td> -b         <td> The broker URL.   <td> Mandatory.
+     * <tr><td> -h         <td> The virtual host. <td> Optional.
+     * <tr><td> -o         <td> The directory to output test results to. <td> Optional.
+     * <tr><td> -e         <td> The type of test distribution engine to use. <td> Optional. One of: interop, fanout.
+     * <tr><td> ...        <td> Free arguments. The distributed test cases to run.
+     *                     <td> Mandatory. At least one must be defined.
+     * <tr><td> name=value <td> Trailing argument define name/value pairs. Added to the test contenxt properties.
+     *                     <td> Optional.
+     * </table>
+     *
+     * @param args The command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        NDC.push("coordinator");
+        log.debug("public static void main(String[] args = " + Arrays.toString(args) + "): called");
+        console.info("Qpid Distributed Test Coordinator.");
+
+        // Override the default broker url to be localhost:5672.
+        testContextProperties.setProperty(MessagingTestConfigProperties.BROKER_PROPNAME, "tcp://localhost:5672");
+
+        try
+        {
+            // Use the command line parser to evaluate the command line with standard handling behaviour (print errors
+            // and usage then exist if there are errors).
+            // Any options and trailing name=value pairs are also injected into the test context properties object,
+            // to override any defaults that may have been set up.
+            ParsedProperties options =
+                new ParsedProperties(CommandLineParser.processCommandLine(args,
+                        new CommandLineParser(
+                            new String[][]
+                            {
+                                { "b", "The broker URL.", "broker", "false" },
+                                { "h", "The virtual host to use.", "virtual host", "false" },
+                                { "o", "The name of the directory to output test timings to.", "dir", "false" },
+                                {
+                                    "e", "The test execution engine to use. Default is interop.", "engine", "interop",
+                                    "^interop$|^fanout$", "true"
+                                },
+                                { "t", "Terminate test clients on completion of tests.", null, "false" },
+                                { "-csv", "Output test results in CSV format.", null, "false" },
+                                { "-xml", "Output test results in XML format.", null, "false" },
+                                {
+                                    "-trefaddr", "To specify an alternative to hostname for time singal reference.",
+                                    "address", "false"
+                                },
+                                {
+                                    "c", "The number of tests to run concurrently.", "num", "false",
+                                    MathUtils.SEQUENCE_REGEXP
+                                },
+                                { "r", "The number of times to repeat each test.", "num", "false" },
+                                {
+                                    "d", "The length of time to run the tests for.", "duration", "false",
+                                    MathUtils.DURATION_REGEXP
+                                },
+                                {
+                                    "f", "The maximum rate to call the tests at.", "frequency", "false",
+                                    "^([1-9][0-9]*)/([1-9][0-9]*)$"
+                                },
+                                { "s", "The size parameter to run tests with.", "size", "false", MathUtils.SEQUENCE_REGEXP },
+                                { "v", "Verbose mode.", null, "false" },
+                                { "n", "A name for this test run, used to name the output file.", "name", "true" }
+                            }), testContextProperties));
+
+            // Extract the command line options.
+            String brokerUrl = options.getProperty("b");
+            String virtualHost = options.getProperty("h");
+            String reportDir = options.getProperty("o");
+            reportDir = (reportDir == null) ? "." : reportDir;
+            String testEngine = options.getProperty("e");
+            TestEngine engine = "fanout".equals(testEngine) ? TestEngine.FANOUT : TestEngine.INTEROP;
+            boolean terminate = options.getPropertyAsBoolean("t");
+            boolean csvResults = options.getPropertyAsBoolean("-csv");
+            boolean xmlResults = options.getPropertyAsBoolean("-xml");
+
+            String threadsString = options.getProperty("c");
+            Integer repetitions = options.getPropertyAsInteger("r");
+            String durationString = options.getProperty("d");
+            String paramsString = options.getProperty("s");
+            boolean verbose = options.getPropertyAsBoolean("v");
+            String testRunName = options.getProperty("n");
+
+            int[] threads = (threadsString == null) ? null : MathUtils.parseSequence(threadsString);
+            int[] params = (paramsString == null) ? null : MathUtils.parseSequence(paramsString);
+            Long duration = (durationString == null) ? null : MathUtils.parseDuration(durationString);
+
+            // If broker or virtual host settings were specified as command line options, override the defaults in the
+            // test context properties with them.
+
+            // Collection all of the test cases to be run.
+            Collection<Class<? extends FrameworkBaseCase>> testCaseClasses =
+                new ArrayList<Class<? extends FrameworkBaseCase>>();
+
+            // Scan for available test cases using a classpath scanner.
+            // ClasspathScanner.getMatches(DistributedTestCase.class, "^Test.*", true);
+
+            // Hard code the test classes till the classpath scanner is fixed.
+            // Collections.addAll(testCaseClasses, InteropTestCase1DummyRun.class, InteropTestCase2BasicP2P.class,
+            // InteropTestCase3BasicPubSub.class);
+
+            // Parse all of the free arguments as test cases to run.
+            for (int i = 1; true; i++)
+            {
+                String nextFreeArg = options.getProperty(Integer.toString(i));
+
+                // Terminate the loop once all free arguments have been consumed.
+                if (nextFreeArg == null)
+                {
+                    break;
+                }
+
+                try
+                {
+                    Class nextClass = Class.forName(nextFreeArg);
+
+                    if (FrameworkBaseCase.class.isAssignableFrom(nextClass))
+                    {
+                        testCaseClasses.add(nextClass);
+                        console.info("Found distributed test case: " + nextFreeArg);
+                    }
+                }
+                catch (ClassNotFoundException e)
+                {
+                    console.info("Unable to instantiate the test case: " + nextFreeArg + ".");
+                }
+            }
+
+            // Check that some test classes were actually found.
+            if (testCaseClasses.isEmpty())
+            {
+                throw new RuntimeException(
+                    "No test cases implementing FrameworkBaseCase were specified on the command line.");
+            }
+
+            // Extract the names of all the test classes, to pass to the start method.
+            int i = 0;
+            String[] testClassNames = new String[testCaseClasses.size()];
+
+            for (Class testClass : testCaseClasses)
+            {
+                testClassNames[i++] = testClass.getName();
+            }
+
+            // Create a coordinator and begin its test procedure.
+            Coordinator coordinator =
+                new Coordinator(repetitions, duration, threads, 0, params, null, reportDir, testRunName, verbose, brokerUrl,
+                    virtualHost, engine, terminate, csvResults, xmlResults);
+
+            TestResult testResult = coordinator.start(testClassNames);
+
+            // Return different error codes, depending on whether or not there were test failures.
+            if (testResult.failureCount() > 0)
+            {
+                System.exit(FAILURE_EXIT);
+            }
+            else
+            {
+                System.exit(SUCCESS_EXIT);
+            }
+        }
+        catch (Exception e)
+        {
+            log.debug("Top level handler caught execption.", e);
+            console.info(e.getMessage());
+            System.exit(EXCEPTION_EXIT);
+        }
+    }
+
+    /**
+     * Starts all of the test classes to be run by this coordinator.
+     *
+     * @param testClassNames An array of all the coordinating test case implementations.
+     *
+     * @return A JUnit TestResult to run the tests with.
+     *
+     * @throws Exception Any underlying exceptions are allowed to fall through, and fail the test process.
+     */
+    public TestResult start(String[] testClassNames) throws Exception
+    {
+        log.debug("public TestResult start(String[] testClassNames = " + PrettyPrintingUtils.printArray(testClassNames)
+            + ": called");
+
+        // Connect to the broker.
+        connection = TestUtils.createConnection(TestContextProperties.getInstance());
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+        Destination controlTopic = session.createTopic("iop.control");
+        Destination responseQueue = session.createQueue("coordinator");
+
+        conversationFactory = new ConversationFactory(connection, responseQueue, LinkedBlockingQueue.class);
+        ConversationFactory.Conversation conversation = conversationFactory.startConversation();
+
+        connection.start();
+
+        // Broadcast the compulsory invitation to find out what clients are available to test.
+        Message invite = session.createMessage();
+        invite.setStringProperty("CONTROL_TYPE", "INVITE");
+        invite.setJMSReplyTo(responseQueue);
+
+        conversation.send(controlTopic, invite);
+
+        // Wait for a short time, to give test clients an opportunity to reply to the invitation.
+        Collection<Message> enlists = conversation.receiveAll(0, 500);
+        enlistedClients = extractEnlists(enlists);
+
+        for (TestClientDetails client : enlistedClients)
+        {
+            log.debug("Got enlisted test client: " + client);
+            console.info("Test node " + client.clientName + " available.");
+        }
+
+        // Start the clock reference service running.
+        UDPClockReference clockReference = new UDPClockReference();
+        Thread clockRefThread = new Thread(clockReference);
+        registerShutdownHook(clockReference);
+        clockRefThread.start();
+
+        // Broadcast to all clients to synchronize their clocks against the coordinators clock reference.
+        Message clockSynchRequest = session.createMessage();
+        clockSynchRequest.setStringProperty("CONTROL_TYPE", "CLOCK_SYNCH");
+
+        String localAddress = InetAddress.getByName(InetAddress.getLocalHost().getHostName()).getHostAddress();
+        clockSynchRequest.setStringProperty("ADDRESS", localAddress);
+
+        conversation.send(controlTopic, clockSynchRequest);
+
+        // Run the test in the suite using JUnit.
+        TestResult result = null;
+
+        for (String testClassName : testClassNames)
+        {
+            // Record the current test class, so that the test results can be output to a file incorporating this name.
+            this.currentTestClassName = testClassName;
+
+            result = super.start(new String[] { testClassName });
+        }
+
+        // At this point in time, all tests have completed. Broadcast the shutdown message, if the termination option
+        // was set on the command line.
+        if (terminate)
+        {
+            Message terminate = session.createMessage();
+            terminate.setStringProperty("CONTROL_TYPE", "TERMINATE");
+
+            conversation.send(controlTopic, terminate);
+        }
+
+        return result;
+    }
+
+    /**
+     * For a collection of enlist messages, this method pulls out of the client details for the enlisting clients.
+     *
+     * @param enlists The enlist messages.
+     *
+     * @return A set of enlisting clients, extracted from the enlist messages.
+     *
+     * @throws JMSException Any underlying JMSException is allowed to fall through.
+     */
+    public static Set<TestClientDetails> extractEnlists(Collection<Message> enlists) throws JMSException
+    {
+        log.debug("public static Set<TestClientDetails> extractEnlists(Collection<Message> enlists = " + enlists
+            + "): called");
+
+        Set<TestClientDetails> enlistedClients = new HashSet<TestClientDetails>();
+
+        // Retain the list of all available clients.
+        for (Message enlist : enlists)
+        {
+            TestClientDetails clientDetails = new TestClientDetails();
+            clientDetails.clientName = enlist.getStringProperty("CLIENT_NAME");
+            clientDetails.privateControlKey = enlist.getStringProperty("CLIENT_PRIVATE_CONTROL_KEY");
+
+            String replyType = enlist.getStringProperty("CONTROL_TYPE");
+
+            if ("ENLIST".equals(replyType))
+            {
+                enlistedClients.add(clientDetails);
+            }
+            else if ("DECLINE".equals(replyType))
+            {
+                log.debug("Test client " + clientDetails.clientName + " declined the invite.");
+            }
+            else
+            {
+                log.warn("Got an unknown reply type, " + replyType + ", to the invite.");
+            }
+        }
+
+        return enlistedClients;
+    }
+
+    /**
+     * Runs a test or suite of tests, using the super class implemenation. This method wraps the test to be run
+     * in any test decorators needed to add in the coordinators ability to invite test clients to participate in
+     * tests.
+     *
+     * @param test The test to run.
+     * @param wait Undocumented. Nothing in the JUnit javadocs to say what this is for.
+     *
+     * @return The results of the test run.
+     */
+    public TestResult doRun(Test test, boolean wait)
+    {
+        log.debug("public TestResult doRun(Test \"" + test + "\", boolean " + wait + "): called");
+
+        // Wrap all tests in the test suite with WrappedSuiteTestDecorators. This is quite ugly and a bit baffling,
+        // but the reason it is done is because the JUnit implementation of TestDecorator has some bugs in it.
+        WrappedSuiteTestDecorator targetTest = null;
+
+        if (test instanceof TestSuite)
+        {
+            log.debug("targetTest is a TestSuite");
+
+            TestSuite suite = (TestSuite) test;
+
+            int numTests = suite.countTestCases();
+            log.debug("There are " + numTests + " in the suite.");
+
+            for (int i = 0; i < numTests; i++)
+            {
+                Test nextTest = suite.testAt(i);
+                log.debug("suite.testAt(" + i + ") = " + nextTest);
+
+                if (nextTest instanceof FrameworkBaseCase)
+                {
+                    log.debug("nextTest is a FrameworkBaseCase");
+                }
+            }
+
+            targetTest = new WrappedSuiteTestDecorator(suite);
+            log.debug("Wrapped with a WrappedSuiteTestDecorator.");
+        }
+
+        // Wrap the tests in a suitable distributed test decorator, to perform the invite/test cycle.
+        targetTest = newTestDecorator(targetTest, enlistedClients, conversationFactory, connection);
+
+        // TestSuite suite = new TestSuite();
+        // suite.addTest(targetTest);
+
+        // Wrap the tests in a scaled test decorator to them them as a 'batch' in one thread.
+        // targetTest = new ScaledTestDecorator(targetTest, new int[] { 1 });
+
+        return super.doRun(targetTest, wait);
+    }
+
+    /**
+     * Creates a wrapped test decorator, that is capable of inviting enlisted clients to participate in a specified
+     * test. This is the test engine that sets up the roles and sequences a distributed test case.
+     *
+     * @param targetTest          The test decorator to wrap.
+     * @param enlistedClients     The enlisted clients available to run the test.
+     * @param conversationFactory The conversation factory used to build conversation helper over the specified connection.
+     * @param connection          The connection to talk to the enlisted clients over.
+     *
+     * @return An invititing test decorator, that invites all the enlisted clients to participate in tests, in pairs.
+     */
+    protected DistributedTestDecorator newTestDecorator(WrappedSuiteTestDecorator targetTest,
+        Set<TestClientDetails> enlistedClients, ConversationFactory conversationFactory, Connection connection)
+    {
+        switch (engine)
+        {
+        case FANOUT:
+            return new FanOutTestDecorator(targetTest, enlistedClients, conversationFactory, connection);
+        case INTEROP:
+        default:
+            return new InteropTestDecorator(targetTest, enlistedClients, conversationFactory, connection);
+        }
+    }
+
+    /**
+     * Creates the TestResult object to be used for test runs.
+     *
+     * @return An instance of the test result object.
+     */
+    protected TestResult createTestResult()
+    {
+        log.debug("protected TestResult createTestResult(): called");
+
+        TKTestResult result = new TKTestResult(fPrinter.getWriter(), delay, verbose, testCaseName);
+
+        // Check if a directory to output reports to has been specified and attach test listeners if so.
+        if (reportDir != null)
+        {
+            // Create the report directory if it does not already exist.
+            File reportDirFile = new File(reportDir);
+
+            if (!reportDirFile.exists())
+            {
+                reportDirFile.mkdir();
+            }
+
+            // Create the results file (make the name of this configurable as a command line parameter).
+            Writer timingsWriter;
+
+            // Set up an XML results listener to output the timings to the results file, if requested on the command line.
+            if (xmlResults)
+            {
+                try
+                {
+                    File timingsFile = new File(reportDirFile, "TEST." + currentTestClassName + ".xml");
+                    timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000);
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException("Unable to create the log file to write test results to: " + e, e);
+                }
+
+                XMLTestListener listener = new XMLTestListener(timingsWriter, currentTestClassName);
+                result.addListener(listener);
+                result.addTKTestListener(listener);
+
+                registerShutdownHook(listener);
+            }
+
+            // Set up an CSV results listener to output the timings to the results file, if requested on the command line.
+            if (csvResults)
+            {
+                try
+                {
+                    File timingsFile =
+                        new File(reportDirFile, testRunName + "-" + TIME_STAMP_FORMAT.format(new Date()) + "-timings.csv");
+                    timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000);
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException("Unable to create the log file to write test results to: " + e, e);
+                }
+
+                CSVTestListener listener = new CSVTestListener(timingsWriter);
+                result.addListener(listener);
+                result.addTKTestListener(listener);
+
+                // Register the results listeners shutdown hook to flush its data if the test framework is shutdown
+                // prematurely.
+                registerShutdownHook(listener);
+            }
+
+            // Register the results listeners shutdown hook to flush its data if the test framework is shutdown
+            // prematurely.
+            // registerShutdownHook(listener);
+
+            // Record the start time of the batch.
+            // result.notifyStartBatch();
+
+            // At this point in time the test class has been instantiated, giving it an opportunity to read its parameters.
+            // Inform any test listers of the test properties.
+            result.notifyTestProperties(TestContextProperties.getAccessedProps());
+        }
+
+        return result;
+    }
+}

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java?rev=571129&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java (added)
+++ incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java Thu Aug 30 05:19:31 2007
@@ -0,0 +1,166 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.util.ConversationFactory;
+
+import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+
+import java.util.*;
+
+/**
+ * DistributedTestDecorator is a base class for writing test decorators that invite test clients to participate in
+ * distributed test cases. It provides a helper method, {@link #signupClients}, that broadcasts an invitation and
+ * returns the set of test clients that are available to particiapte in the test.
+ *
+ * <p/>When used to wrap a {@link FrameworkBaseCase} test, it replaces the default {@link CircuitFactory} implementations
+ * with a suitable circuit factory for distributed tests. Concrete implementations can use this to configure the sending
+ * and receiving roles on the test.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Broadcast test invitations and collect enlists. <td> {@link ConversationFactory}.
+ * </table>
+ */
+public abstract class DistributedTestDecorator extends WrappedSuiteTestDecorator
+{
+    /** Used for debugging. */
+    private static final Logger log = Logger.getLogger(DistributedTestDecorator.class);
+
+    /** Holds the contact information for all test clients that are available and that may take part in the test. */
+    Set<TestClientDetails> allClients;
+
+    /** Holds the conversation helper for the control level conversation for coordinating the test through. */
+    ConversationFactory conversationFactory;
+
+    /** Holds the connection that the control conversation is held over. */
+    Connection connection;
+
+    /** Holds the underlying test suite that this decorator wraps. */
+    WrappedSuiteTestDecorator testSuite;
+
+    /** Holds the control topic, on which test invitations are broadcast. */
+    protected Destination controlTopic;
+
+    /**
+     * Creates a wrapped suite test decorator from another one.
+     *
+     * @param suite               The test suite.
+     * @param availableClients    The list of all clients that responded to the compulsory invite.
+     * @param controlConversation The conversation helper for the control level, test coordination conversation.
+     * @param controlConnection   The connection that the coordination messages are sent over.
+     */
+    public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> availableClients,
+        ConversationFactory controlConversation, Connection controlConnection)
+    {
+        super(suite);
+
+        log.debug("public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> allClients = "
+            + availableClients + ", ConversationHelper controlConversation = " + controlConversation + "): called");
+
+        testSuite = suite;
+        allClients = availableClients;
+        conversationFactory = controlConversation;
+        connection = controlConnection;
+
+        // Set up the test control topic.
+        try
+        {
+            controlTopic = conversationFactory.getSession().createTopic("iop.control");
+        }
+        catch (JMSException e)
+        {
+            throw new RuntimeException("Unable to create the coordinating control topic to broadcast test invites on.", e);
+        }
+    }
+
+    /**
+     * Should run all of the tests in the wrapped test suite.
+     *
+     * @param testResult The the results object to monitor the test results with.
+     */
+    public abstract void run(TestResult testResult);
+
+    /**
+     * Should provide the distributed test sequencer to pass to {@link org.apache.qpid.test.framework.FrameworkBaseCase}
+     * tests.
+     *
+     * @return A distributed test sequencer.
+     */
+    public abstract CircuitFactory getTestSequencer();
+
+    /**
+     * Broadcasts an invitation to participate in a coordinating test case to find out what clients are available to
+     * run the test case.
+     *
+     * @param coordTest The coordinating test case to broadcast an inviate for.
+     *
+     * @return A set of test clients that accepted the invitation.
+     */
+    protected Set<TestClientDetails> signupClients(FrameworkBaseCase coordTest)
+    {
+        // Broadcast the invitation to find out what clients are available to test.
+        Set<TestClientDetails> enlists;
+        try
+        {
+            Message invite = conversationFactory.getSession().createMessage();
+
+            ConversationFactory.Conversation conversation = conversationFactory.startConversation();
+
+            invite.setStringProperty("CONTROL_TYPE", "INVITE");
+            invite.setStringProperty("TEST_NAME", coordTest.getTestCaseNameForTestMethod(coordTest.getName()));
+
+            conversation.send(controlTopic, invite);
+
+            // Wait for a short time, to give test clients an opportunity to reply to the invitation.
+            Collection<Message> replies = conversation.receiveAll(allClients.size(), 500);
+            enlists = Coordinator.extractEnlists(replies);
+        }
+        catch (JMSException e)
+        {
+            throw new RuntimeException("There was a JMSException during the invite/enlist conversation.", e);
+        }
+
+        return enlists;
+    }
+
+    /**
+     * Prints a string summarizing this test decorator, mainly for debugging purposes.
+     *
+     * @return String representation for debugging purposes.
+     */
+    public String toString()
+    {
+        return "DistributedTestDecorator: [ testSuite = " + testSuite + " ]";
+    }
+}

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java?rev=571129&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java (added)
+++ incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java Thu Aug 30 05:19:31 2007
@@ -0,0 +1,245 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.DropInTest;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.test.framework.sequencers.FanOutCircuitFactory;
+import org.apache.qpid.util.ConversationFactory;
+
+import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * FanOutTestDecorator is an {@link DistributedTestDecorator} that runs one test client in the sender role, and the remainder
+ * in the receivers role. It also has the capability to listen for new test cases joining the test beyond the initial start
+ * point. This feature can be usefull when experimenting with adding more load, in the form of more test clients, to assess
+ * its impact on a running test.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Execute coordinated test cases. <td> {@link FrameworkBaseCase}
+ * <tr><td> Accept test clients joining a running test.
+ * </table>
+ */
+public class FanOutTestDecorator extends DistributedTestDecorator implements MessageListener
+{
+    /** Used for debugging. */
+    private static final Logger log = Logger.getLogger(FanOutTestDecorator.class);
+
+    /** Holds the currently running test case. */
+    FrameworkBaseCase currentTest = null;
+
+    /**
+     * Creates a wrapped suite test decorator from another one.
+     *
+     * @param suite               The test suite.
+     * @param availableClients    The list of all clients that responded to the compulsory invite.
+     * @param controlConversation The conversation helper for the control level, test coordination conversation.
+     * @param controlConnection   The connection that the coordination messages are sent over.
+     */
+    public FanOutTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> availableClients,
+        ConversationFactory controlConversation, Connection controlConnection)
+    {
+        super(suite, availableClients, controlConversation, controlConnection);
+
+        log.debug("public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> allClients = "
+            + availableClients + ", ConversationHelper controlConversation = " + controlConversation + "): called");
+
+        testSuite = suite;
+        allClients = availableClients;
+        conversationFactory = controlConversation;
+        connection = controlConnection;
+
+        // Sign available clients up to the test.
+        for (Test test : getAllUnderlyingTests())
+        {
+            FrameworkBaseCase coordTest = (FrameworkBaseCase) test;
+
+            // Get all of the clients able to participate in the test.
+            Set<TestClientDetails> enlists = signupClients(coordTest);
+
+            // Check that there were some clients available.
+            if (enlists.size() == 0)
+            {
+                throw new RuntimeException("No clients to test with");
+            }
+
+            // Create a distributed test circuit factory for the test.
+            CircuitFactory circuitFactory = getTestSequencer();
+
+            // Set up the first client in the sender role, and the remainder in the receivers role.
+            Iterator<TestClientDetails> clients = enlists.iterator();
+            circuitFactory.setSender(clients.next());
+
+            while (clients.hasNext())
+            {
+                // Set the sending and receiving client details on the test case.
+                circuitFactory.setReceiver(clients.next());
+            }
+
+            // Pass down the connection to hold the coordinating conversation over.
+            circuitFactory.setConversationFactory(conversationFactory);
+
+            // If the current test case is a drop-in test, set it up as the currently running test for late joiners to
+            // add in to. Otherwise the current test field is set to null, to indicate that late joiners are not allowed.
+            currentTest = (coordTest instanceof DropInTest) ? coordTest : null;
+
+            // Execute the test case.
+            coordTest.setCircuitFactory(circuitFactory);
+        }
+    }
+
+    /**
+     * Broadcasts a test invitation and accepts enlists from participating clients. The wrapped test cases are run
+     * with one test client in the sender role, and the remaining test clients in the receiving role.
+     *
+     * <p/>Any JMSExceptions during the invite/enlist conversation will be allowed to fall through as runtime
+     * exceptions, resulting in the non-completion of the test run.
+     *
+     * @param testResult The the results object to monitor the test results with.
+     *
+     * @todo Better error recovery for failure of the invite/enlist conversation could be added.
+     */
+    public void run(TestResult testResult)
+    {
+        log.debug("public void run(TestResult testResult): called");
+
+        // Listen for late joiners on the control topic.
+        try
+        {
+            conversationFactory.getSession().createConsumer(controlTopic).setMessageListener(this);
+        }
+        catch (JMSException e)
+        {
+            throw new RuntimeException("Unable to set up the message listener on the control topic.", e);
+        }
+
+        // Run all of the test cases in the test suite.
+        /*for (Test test : getAllUnderlyingTests())
+        {
+            FrameworkBaseCase coordTest = (FrameworkBaseCase) test;
+
+            // Get all of the clients able to participate in the test.
+            Set<TestClientDetails> enlists = signupClients(coordTest);
+
+            // Check that there were some clients available.
+            if (enlists.size() == 0)
+            {
+                throw new RuntimeException("No clients to test with");
+            }
+
+            // Create a distributed test circuit factory for the test.
+            CircuitFactory circuitFactory = getTestSequencer();
+
+            // Set up the first client in the sender role, and the remainder in the receivers role.
+            Iterator<TestClientDetails> clients = enlists.iterator();
+            circuitFactory.setSender(clients.next());
+
+            while (clients.hasNext())
+            {
+                // Set the sending and receiving client details on the test case.
+                circuitFactory.setReceiver(clients.next());
+            }
+
+            // Pass down the connection to hold the coordinating conversation over.
+            circuitFactory.setConversationFactory(conversationFactory);
+
+            // If the current test case is a drop-in test, set it up as the currently running test for late joiners to
+            // add in to. Otherwise the current test field is set to null, to indicate that late joiners are not allowed.
+            currentTest = (coordTest instanceof DropInTest) ? coordTest : null;
+
+            // Execute the test case.
+            coordTest.setCircuitFactory(circuitFactory);
+        }*/
+
+        // Run all of the test cases in the test suite.
+        for (Test test : getAllUnderlyingTests())
+        {
+            FrameworkBaseCase coordTest = (FrameworkBaseCase) test;
+
+            coordTest.run(testResult);
+
+            currentTest = null;
+        }
+    }
+
+    /**
+     * Should provide the distributed test sequencer to pass to {@link org.apache.qpid.test.framework.FrameworkBaseCase}
+     * tests.
+     *
+     * @return A distributed test sequencer.
+     */
+    public CircuitFactory getTestSequencer()
+    {
+        return new FanOutCircuitFactory();
+    }
+
+    /**
+     * Listens to incoming messages on the control topic. If the messages are 'join' messages, signalling a new
+     * test client wishing to join the current test, then the new client will be added to the current test in the
+     * receivers role.
+     *
+     * @param message The incoming control message.
+     */
+    public void onMessage(Message message)
+    {
+        try
+        {
+            // Check if the message is from a test client attempting to join a running test, and join it to the current
+            // test case if so.
+            if (message.getStringProperty("CONTROL_TYPE").equals("JOIN") && (currentTest != null))
+            {
+                ((DropInTest) currentTest).lateJoin(message);
+            }
+        }
+        // There is not a lot can be done with this error, so it is deliberately ignored.
+        catch (JMSException e)
+        {
+            log.debug("Unable to process message:" + message);
+        }
+    }
+
+    /**
+     * Prints a string summarizing this test decorator, mainly for debugging purposes.
+     *
+     * @return String representation for debugging purposes.
+     */
+    public String toString()
+    {
+        return "FanOutTestDecorator: [ testSuite = " + testSuite + " ]";
+    }
+}

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java?rev=571129&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java (added)
+++ incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java Thu Aug 30 05:19:31 2007
@@ -0,0 +1,208 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.test.framework.sequencers.InteropCircuitFactory;
+import org.apache.qpid.util.ConversationFactory;
+
+import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator;
+
+import javax.jms.Connection;
+
+import java.util.*;
+
+/**
+ * DistributedTestDecorator is a test decorator, written to implement the interop test specification. Given a list
+ * of enlisted test clients, that are available to run interop tests, this decorator invites them to participate
+ * in each test in the wrapped test suite. Amongst all the clients that respond to the invite, all pairs are formed,
+ * and each pairing (in both directions, but excluding the reflexive pairings) is split into a sender and receivers
+ * role and a test case run between them. Any enlisted combinations that do not accept a test invite are automatically
+ * failed.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Broadcast test invitations and collect enlists. <td> {@link org.apache.qpid.util.ConversationFactory}.
+ * <tr><td> Output test failures for clients unwilling to run the test case. <td> {@link Coordinator}
+ * <tr><td> Execute distributed test cases. <td> {@link FrameworkBaseCase}
+ * <tr><td> Fail non participating pairings. <td> {@link OptOutTestCase}
+ * </table>
+ */
+public class InteropTestDecorator extends DistributedTestDecorator
+{
+    /** Used for debugging. */
+    private static final Logger log = Logger.getLogger(InteropTestDecorator.class);
+
+    /**
+     * Creates a wrapped suite test decorator from another one.
+     *
+     * @param suite               The test suite.
+     * @param availableClients    The list of all clients that responded to the compulsory invite.
+     * @param controlConversation The conversation helper for the control level, test coordination conversation.
+     * @param controlConnection   The connection that the coordination messages are sent over.
+     */
+    public InteropTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> availableClients,
+        ConversationFactory controlConversation, Connection controlConnection)
+    {
+        super(suite, availableClients, controlConversation, controlConnection);
+    }
+
+    /**
+     * Broadcasts a test invitation and accetps enlisting from participating clients. The wrapped test case is
+     * then repeated for every combination of test clients (provided the wrapped test case extends
+     * {@link FrameworkBaseCase}.
+     *
+     * <p/>Any JMSExceptions during the invite/enlist conversation will be allowed to fall through as runtime exceptions,
+     * resulting in the non-completion of the test run.
+     *
+     * @todo Better error recovery for failure of the invite/enlist conversation could be added.
+     *
+     * @param testResult The the results object to monitor the test results with.
+     */
+    public void run(TestResult testResult)
+    {
+        log.debug("public void run(TestResult testResult): called");
+
+        Collection<Test> tests = testSuite.getAllUnderlyingTests();
+
+        for (Test test : getAllUnderlyingTests())
+        {
+            FrameworkBaseCase coordTest = (FrameworkBaseCase) test;
+
+            // Broadcast the invitation to find out what clients are available to test.
+            Set<TestClientDetails> enlists = signupClients(coordTest);
+
+            // Compare the list of willing clients to the list of all available.
+            Set<TestClientDetails> optOuts = new HashSet<TestClientDetails>(allClients);
+            optOuts.removeAll(enlists);
+
+            // Output test failures for clients that will not particpate in the test.
+            Set<List<TestClientDetails>> failPairs = allPairs(optOuts, allClients);
+
+            for (List<TestClientDetails> failPair : failPairs)
+            {
+                // Create a distributed test circuit factory for the test.
+                CircuitFactory circuitFactory = getTestSequencer();
+
+                // Create an automatic failure test for the opted out test pair.
+                FrameworkBaseCase failTest = new OptOutTestCase("testOptOut");
+                circuitFactory.setSender(failPair.get(0));
+                circuitFactory.setReceiver(failPair.get(1));
+                failTest.setCircuitFactory(circuitFactory);
+
+                failTest.run(testResult);
+            }
+
+            // Loop over all combinations of clients, willing to run the test.
+            Set<List<TestClientDetails>> enlistedPairs = allPairs(enlists, enlists);
+
+            for (List<TestClientDetails> enlistedPair : enlistedPairs)
+            {
+                // Create a distributed test circuit factory for the test.
+                CircuitFactory circuitFactory = getTestSequencer();
+
+                // Set the sending and receiving client details on the test circuitFactory.
+                circuitFactory.setSender(enlistedPair.get(0));
+                circuitFactory.setReceiver(enlistedPair.get(1));
+
+                // Pass down the connection to hold the coordination conversation over.
+                circuitFactory.setConversationFactory(conversationFactory);
+
+                // Execute the test case.
+                coordTest.setCircuitFactory(circuitFactory);
+                coordTest.run(testResult);
+            }
+        }
+    }
+
+    /**
+     * Should provide the distributed test sequencer to pass to {@link org.apache.qpid.test.framework.FrameworkBaseCase}
+     * tests.
+     *
+     * @return A distributed test sequencer.
+     */
+    public CircuitFactory getTestSequencer()
+    {
+        return new InteropCircuitFactory();
+    }
+
+    /**
+     * Produces all pairs of combinations of elements from two sets. The ordering of the elements in the pair is
+     * important, that is the pair <l, r> is distinct from <r, l>; both pairs are generated. For any element, i, in
+     * both the left and right sets, the reflexive pair <i, i> is not generated.
+     *
+     * @param left  The left set.
+     * @param right The right set.
+     * @param <E>   The type of the content of the pairs.
+     *
+     * @return All pairs formed from the permutations of all elements of the left and right sets.
+     */
+    private <E> Set<List<E>> allPairs(Set<E> left, Set<E> right)
+    {
+        log.debug("private <E> Set<List<E>> allPairs(Set<E> left = " + left + ", Set<E> right = " + right + "): called");
+
+        Set<List<E>> results = new HashSet<List<E>>();
+
+        // Form all pairs from left to right.
+        // Form all pairs from right to left.
+        for (E le : left)
+        {
+            for (E re : right)
+            {
+                if (!le.equals(re))
+                {
+                    results.add(new Pair<E>(le, re));
+                    results.add(new Pair<E>(re, le));
+                }
+            }
+        }
+
+        log.debug("results = " + results);
+
+        return results;
+    }
+
+    /**
+     * A simple implementation of a pair, using a list.
+     */
+    private class Pair<T> extends ArrayList<T>
+    {
+        /**
+         * Creates a new pair of elements.
+         *
+         * @param first  The first element.
+         * @param second The second element.
+         */
+        public Pair(T first, T second)
+        {
+            super();
+            super.add(first);
+            super.add(second);
+        }
+    }
+}

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java?rev=571129&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java (added)
+++ incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java Thu Aug 30 05:19:31 2007
@@ -0,0 +1,69 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+
+/**
+ * An OptOutTestCase is a test case that automatically fails. It is used when a list of test clients has been generated
+ * from a compulsory invite, but only some of those clients have responded to a specific test case invite. The clients
+ * that did not respond, may automatically be given a fail for some tests.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Fail the test with a suitable reason.
+ * </table>
+ */
+public class OptOutTestCase extends FrameworkBaseCase
+{
+    /**
+     * Creates a new coordinating test case with the specified name.
+     *
+     * @param name The test case name.
+     */
+    public OptOutTestCase(String name)
+    {
+        super(name);
+    }
+
+    /** Generates an appropriate test failure assertion. */
+    public void testOptOut()
+    {
+        CircuitFactory circuitFactory = getCircuitFactory();
+
+        fail("One of " + circuitFactory.getSender() + " and " + getCircuitFactory().getReceivers()
+            + " opted out of the test.");
+    }
+
+    /**
+     * Should provide a translation from the junit method name of a test to its test case name as defined in the
+     * interop testing specification. For example the method "testP2P" might map onto the interop test case name
+     * "TC2_BasicP2P".
+     *
+     * @param methodName The name of the JUnit test method.
+     * @return The name of the corresponding interop test case.
+     */
+    public String getTestCaseNameForTestMethod(String methodName)
+    {
+        return "OptOutTest";
+    }
+}

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java?rev=571129&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java (added)
+++ incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java Thu Aug 30 05:19:31 2007
@@ -0,0 +1,108 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.Session;
+
+/**
+ * TestClientControlledTest provides an interface that classes implementing test cases to run on a {@link TestClient}
+ * node can use. Implementations must be Java beans, that is, to provide a default constructor and to implement the
+ * {@link #getName} method.
+ *
+ * <p/>The methods specified in this interface are called when the {@link TestClient} receives control instructions to
+ * apply to the test. There are control instructions to present the test case with the test invite, so that it may
+ * choose whether or not to participate in the test, assign the test to play the sender or receiver role, start the
+ * test and obtain the test status report.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Supply the name of the test case that this implements.
+ * <tr><td> Accept/Reject invites based on test parameters.
+ * <tr><td> Adapt to assigned roles.
+ * <tr><td> Perform test case actions.
+ * <tr><td> Generate test reports.
+ * </table>
+ */
+public interface TestClientControlledTest
+{
+    /** Defines the possible test case roles that an interop test case can take on. */
+    public enum Roles
+    {
+        /** Specifies the sender role. */
+        SENDER,
+
+        /** Specifies the receivers role. */
+        RECEIVER
+    }
+
+    /**
+     * Should provide the name of the test case that this class implements. The exact names are defined in the
+     * interop testing spec.
+     *
+     * @return The name of the test case that this implements.
+     */
+    public String getName();
+
+    /**
+     * Determines whether the test invite that matched this test case is acceptable.
+     *
+     * @param inviteMessage The invitation to accept or reject.
+     *
+     * @return <tt>true</tt> to accept the invitation, <tt>false</tt> to reject it.
+     *
+     * @throws JMSException Any JMSException resulting from reading the message are allowed to fall through.
+     */
+    public boolean acceptInvite(Message inviteMessage) throws JMSException;
+
+    /**
+     * Assigns the role to be played by this test case. The test parameters are fully specified in the
+     * assignment message. When this method return the test case will be ready to execute.
+     *
+     * @param role              The role to be played; sender or receivers.
+     * @param assignRoleMessage The role assingment message, contains the full test parameters.
+     *
+     * @throws JMSException Any JMSException resulting from reading the message are allowed to fall through.
+     */
+    public void assignRole(Roles role, Message assignRoleMessage) throws JMSException;
+
+    /**
+     * Performs the test case actions. Returning from here, indicates that the sending role has completed its test.
+     *
+     * @param numMessages The number of test messages to send.
+     *
+     * @throws JMSException Any JMSException resulting from reading the message are allowed to fall through.
+     */
+    public void start(int numMessages) throws JMSException;
+
+    /**
+     * Gets a report on the actions performed by the test case in its assigned role.
+     *
+     * @param session The controlSession to create the report message in.
+     *
+     * @return The report message.
+     *
+     * @throws JMSException Any JMSExceptions resulting from creating the report are allowed to fall through.
+     */
+    public Message getReport(Session session) throws JMSException;
+}

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java?rev=571129&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java (added)
+++ incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java Thu Aug 30 05:19:31 2007
@@ -0,0 +1,399 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.listeners;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+
+import uk.co.thebadgerset.junit.extensions.ShutdownHookable;
+import uk.co.thebadgerset.junit.extensions.listeners.TKTestListener;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.*;
+
+/**
+ * Listens for test results for a named test and outputs these in the standard JUnit XML format to the specified
+ * writer.
+ *
+ * <p/>The API for this listener accepts notifications about different aspects of a tests results through different
+ * methods, so some assumption needs to be made as to which test result a notification refers to. For example
+ * {@link #startTest} will be called, then possibly {@link #timing} will be called, even though the test instance is
+ * passed in both cases, it is not enough to distinguish a particular run of the test, as the test case instance may
+ * be being shared between multiple threads, or being run a repeated number of times, and can therfore be re-used
+ * between calls. The listeners make the assumption that, for every test, a unique thread will call {@link #startTest}
+ * and {@link #endTest} to delimit each test. All calls to set test parameters, timings, state and so on, will occur
+ * between the start and end and will be given with the same thread id as the start and end, so the thread id provides
+ * a unqiue value to identify a particular test run against.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Listen to test lifecycle notifications.
+ * <tr><td> Listen to test errors and failures.
+ * <tr><td> Listen to test timings.
+ * <tr><td> Listen to test memory usages.
+ * <tr><td> Listen to parameterized test parameters.
+ * <tr><th> Responsibilities
+ * </table>
+ *
+ * @todo Merge this class with CSV test listener, making the collection of results common to both, and only factoring
+ *       out the results printing code into sub-classes. Provide a simple XML results formatter with the same format as
+ *       the ant XML formatter, and a more structured one for outputing results with timings and summaries from
+ *       performance tests.
+ */
+public class XMLTestListener implements TKTestListener, ShutdownHookable
+{
+    /** Used for debugging. */
+    private static final Logger log = Logger.getLogger(XMLTestListener.class);
+
+    /** The results file writer. */
+    protected Writer writer;
+
+    /** Holds the results for individual tests. */
+    // protected Map<Result, Result> results = new LinkedHashMap<Result, Result>();
+    // protected List<Result> results = new ArrayList<Result>();
+
+    /**
+     * Map for holding results on a per thread basis as they come in. A ThreadLocal is not used as sometimes an
+     * explicit thread id must be used, where notifications come from different threads than the ones that called
+     * the test method.
+     */
+    Map<Long, Result> threadLocalResults = Collections.synchronizedMap(new LinkedHashMap<Long, Result>());
+
+    /**
+     * Holds results for tests that have ended. Transferring these results here from the per-thread results map, means
+     * that the thread id is freed for the thread to generate more results.
+     */
+    List<Result> results = new ArrayList<Result>();
+
+    /** Holds the overall error count. */
+    protected int errors = 0;
+
+    /** Holds the overall failure count. */
+    protected int failures = 0;
+
+    /** Holds the overall tests run count. */
+    protected int runs = 0;
+
+    /** Holds the name of the class that tests are being run for. */
+    String testClassName;
+
+    /**
+     * Creates a new XML results output listener that writes to the specified location.
+     *
+     * @param writer        The location to write results to.
+     * @param testClassName The name of the test class to include in the test results.
+     */
+    public XMLTestListener(Writer writer, String testClassName)
+    {
+        log.debug("public XMLTestListener(Writer writer, String testClassName = " + testClassName + "): called");
+
+        this.writer = writer;
+        this.testClassName = testClassName;
+    }
+
+    /**
+     * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed.
+     *
+     * @param test     The test to resest any results for.
+     * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+     */
+    public void reset(Test test, Long threadId)
+    {
+        log.debug("public void reset(Test test = " + test + ", Long threadId = " + threadId + "): called");
+
+        XMLTestListener.Result r =
+            (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+        r.error = null;
+        r.failure = null;
+
+    }
+
+    /**
+     * Notification that a test started.
+     *
+     * @param test The test that started.
+     */
+    public void startTest(Test test)
+    {
+        log.debug("public void startTest(Test test = " + test + "): called");
+
+        Result newResult = new Result(test.getClass().getName(), ((TestCase) test).getName());
+
+        // Initialize the thread local test results.
+        threadLocalResults.put(Thread.currentThread().getId(), newResult);
+        runs++;
+    }
+
+    /**
+     * Should be called every time a test completes with the run time of that test.
+     *
+     * @param test     The name of the test.
+     * @param nanos    The run time of the test in nanoseconds.
+     * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+     */
+    public void timing(Test test, long nanos, Long threadId)
+    { }
+
+    /**
+     * Should be called every time a test completed with the amount of memory used before and after the test was run.
+     *
+     * @param test     The test which memory was measured for.
+     * @param memStart The total JVM memory used before the test was run.
+     * @param memEnd   The total JVM memory used after the test was run.
+     * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+     */
+    public void memoryUsed(Test test, long memStart, long memEnd, Long threadId)
+    { }
+
+    /**
+     * Should be called every time a parameterized test completed with the int value of its test parameter.
+     *
+     * @param test      The test which memory was measured for.
+     * @param parameter The int parameter value.
+     * @param threadId  Optional thread id if not calling from thread that started the test method. May be null.
+     */
+    public void parameterValue(Test test, int parameter, Long threadId)
+    { }
+
+    /**
+     * Should be called every time a test completes with the current number of test threads running.
+     *
+     * @param test     The test for which the measurement is being generated.
+     * @param threads  The number of tests being run concurrently.
+     * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+     */
+    public void concurrencyLevel(Test test, int threads, Long threadId)
+    { }
+
+    /**
+     * Notifies listeners of the tests read/set properties.
+     *
+     * @param properties The tests read/set properties.
+     */
+    public void properties(Properties properties)
+    { }
+
+    /**
+     * Notification that a test ended.
+     *
+     * @param test The test that ended.
+     */
+    public void endTest(Test test)
+    {
+        log.debug("public void endTest(Test test = " + test + "): called");
+
+        // Move complete test results into the completed tests list.
+        Result r = threadLocalResults.get(Thread.currentThread().getId());
+        results.add(r);
+
+        // Clear all the test results for the thread.
+        threadLocalResults.remove(Thread.currentThread().getId());
+    }
+
+    /**
+     * Called when a test completes. Success, failure and errors. This method should be used when registering an
+     * end test from a different thread than the one that started the test.
+     *
+     * @param test     The test which completed.
+     * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+     */
+    public void endTest(Test test, Long threadId)
+    {
+        log.debug("public void endTest(Test test = " + test + ", Long threadId = " + threadId + "): called");
+
+        // Move complete test results into the completed tests list.
+        Result r =
+            (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+        results.add(r);
+
+        // Clear all the test results for the thread.
+        threadLocalResults.remove(Thread.currentThread().getId());
+    }
+
+    /**
+     * An error occurred.
+     *
+     * @param test The test in which the error occurred.
+     * @param t    The throwable that resulted from the error.
+     */
+    public void addError(Test test, Throwable t)
+    {
+        log.debug("public void addError(Test test = " + test + ", Throwable t = " + t + "): called");
+
+        Result r = threadLocalResults.get(Thread.currentThread().getId());
+        r.error = t;
+        errors++;
+    }
+
+    /**
+     * A failure occurred.
+     *
+     * @param test The test in which the failure occurred.
+     * @param t    The JUnit assertions that led to the failure.
+     */
+    public void addFailure(Test test, AssertionFailedError t)
+    {
+        log.debug("public void addFailure(Test test = " + test + ", AssertionFailedError t = " + t + "): called");
+
+        Result r = threadLocalResults.get(Thread.currentThread().getId());
+        r.failure = t;
+        failures++;
+    }
+
+    /**
+     * Called when a test completes to mark it as a test fail. This method should be used when registering a
+     * failure from a different thread than the one that started the test.
+     *
+     * @param test     The test which failed.
+     * @param e        The assertion that failed the test.
+     * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+     */
+    public void addFailure(Test test, AssertionFailedError e, Long threadId)
+    {
+        log.debug("public void addFailure(Test test, AssertionFailedError e, Long threadId): called");
+
+        Result r =
+            (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+        r.failure = e;
+        failures++;
+    }
+
+    /**
+     * Notifies listeners of the start of a complete run of tests.
+     */
+    public void startBatch()
+    {
+        log.debug("public void startBatch(): called");
+
+        // Reset all results counts.
+        threadLocalResults = Collections.synchronizedMap(new HashMap<Long, Result>());
+        errors = 0;
+        failures = 0;
+        runs = 0;
+
+        // Write out the file header.
+        try
+        {
+            writer.write("<?xml version=\"1.0\" ?>\n");
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException("Unable to write the test results.", e);
+        }
+    }
+
+    /**
+     * Notifies listeners of the end of a complete run of tests.
+     *
+     * @param parameters The optional test parameters to log out with the batch results.
+     */
+    public void endBatch(Properties parameters)
+    {
+        log.debug("public void endBatch(Properties parameters = " + parameters + "): called");
+
+        // Write out the results.
+        try
+        {
+            // writer.write("<?xml version=\"1.0\" ?>\n");
+            writer.write("<testsuite errors=\"" + errors + "\" failures=\"" + failures + "\" tests=\"" + runs + "\" name=\""
+                + testClassName + "\">\n");
+
+            for (Result result : results)
+            {
+                writer.write("  <testcase classname=\"" + result.testClass + "\" name=\"" + result.testName + "\">\n");
+
+                if (result.error != null)
+                {
+                    writer.write("    <error type=\"" + result.error.getClass() + "\">");
+                    result.error.printStackTrace(new PrintWriter(writer));
+                    writer.write("    </error>");
+                }
+                else if (result.failure != null)
+                {
+                    writer.write("    <failure type=\"" + result.failure.getClass() + "\">");
+                    result.failure.printStackTrace(new PrintWriter(writer));
+                    writer.write("    </failure>");
+                }
+
+                writer.write("  </testcase>\n");
+            }
+
+            writer.write("</testsuite>\n");
+            writer.flush();
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException("Unable to write the test results.", e);
+        }
+    }
+
+    /**
+     * Supplies the shutdown hook.
+     *
+     * @return The shut down hook.
+     */
+    public Thread getShutdownHook()
+    {
+        return new Thread(new Runnable()
+                {
+                    public void run()
+                    {
+                        log.debug("XMLTestListener::ShutdownHook: called");
+                    }
+                });
+    }
+
+    /**
+     * Used to capture the results of a particular test run.
+     */
+    protected static class Result
+    {
+        /** Holds the name of the test class. */
+        public String testClass;
+
+        /** Holds the name of the test method. */
+        public String testName;
+
+        /** Holds the exception that caused error in this test. */
+        public Throwable error;
+
+        /** Holds the assertion exception that caused failure in this test. */
+        public AssertionFailedError failure;
+
+        /**
+         * Creates a placeholder for the results of a test.
+         *
+         * @param testClass The test class.
+         * @param testName  The name of the test that was run.
+         */
+        public Result(String testClass, String testName)
+        {
+            this.testClass = testClass;
+            this.testName = testName;
+        }
+    }
+}

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date