From 6d6b33149a156052d6d768ec839b60f3afb62c9e Mon Sep 17 00:00:00 2001 From: Rupert Smith Date: Wed, 25 Jul 2007 12:17:59 +0000 Subject: Refactored interop tests into general distributed test framework. Moved framework under systests from integrationtests. git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2@559419 13f79535-47bb-0310-9956-ffa450edef68 --- .../server/queue/QueueNotificationListener.java | 1 + .../org/apache/qpid/util/CommandLineParser.java | 14 +- java/etc/coding_standards.xml | 3 +- .../qpid/interop/coordinator/Coordinator.java | 453 ------------------- .../qpid/interop/coordinator/DropInTest.java | 51 --- .../qpid/interop/coordinator/FanOutTestCase.java | 179 -------- .../interop/coordinator/FanOutTestDecorator.java | 182 -------- .../qpid/interop/coordinator/InteropTestCase.java | 259 ----------- .../interop/coordinator/InteropTestDecorator.java | 184 -------- .../interop/coordinator/InvitingTestDecorator.java | 151 ------- .../qpid/interop/coordinator/OptOutTestCase.java | 65 --- .../interop/coordinator/TestClientDetails.java | 86 ---- .../qpid/interop/coordinator/XMLTestListener.java | 382 ---------------- .../coordinator/testcases/CircuitTestCase.java | 101 +++++ .../testcases/InteropTestCase1DummyRun.java | 23 +- .../testcases/InteropTestCase2BasicP2P.java | 29 +- .../testcases/InteropTestCase3BasicPubSub.java | 23 +- .../interop/testclient/InteropClientTestCase.java | 4 +- .../apache/qpid/interop/testclient/TestClient.java | 11 +- .../testclient/testcases/TestCase1DummyRun.java | 2 +- .../testclient/testcases/TestCase2BasicP2P.java | 4 +- .../testclient/testcases/TestCase3BasicPubSub.java | 10 +- .../qpid/sustained/SustainedClientTestCase.java | 12 +- .../apache/qpid/sustained/SustainedTestCase.java | 20 +- .../org/apache/qpid/ping/PingDurableClient.java | 27 +- .../org/apache/qpid/ping/PingSendOnlyClient.java | 2 +- .../apache/qpid/requestreply/PingPongProducer.java | 9 +- .../qpid/interop/coordinator/Coordinator.java | 496 +++++++++++++++++++++ .../interop/coordinator/DistributedTestCase.java | 81 ++++ .../coordinator/DistributedTestDecorator.java | 165 +++++++ .../qpid/interop/coordinator/DropInTest.java | 51 +++ .../interop/coordinator/FanOutTestDecorator.java | 200 +++++++++ .../interop/coordinator/InteropTestDecorator.java | 206 +++++++++ .../qpid/interop/coordinator/OptOutTestCase.java | 70 +++ .../interop/coordinator/TestClientDetails.java | 86 ++++ .../qpid/interop/coordinator/XMLTestListener.java | 382 ++++++++++++++++ .../distributedcircuit/DistributedCircuitImpl.java | 116 +++++ .../sequencers/BaseDistributedTestSequencer.java | 129 ++++++ .../sequencers/DistributedTestSequencer.java | 75 ++++ .../sequencers/FanOutTestSequencer.java | 171 +++++++ .../sequencers/InteropTestSequencer.java | 137 ++++++ .../coordinator/sequencers/TestCaseSequencer.java | 66 +++ .../qpid/server/exchange/ImmediateMessageTest.java | 92 ++-- .../qpid/server/exchange/MandatoryMessageTest.java | 104 +++-- .../ReturnUnroutableMandatoryMessageTest.java | 13 +- .../qpid/server/queue/PersistentTestManual.java | 69 +-- .../org/apache/qpid/test/framework/Circuit.java | 6 +- .../apache/qpid/test/framework/CircuitImpl.java | 19 +- .../qpid/test/framework/FrameworkBaseCase.java | 74 +++ .../framework/MessagingTestConfigProperties.java | 18 +- .../org/apache/qpid/test/framework/Receiver.java | 14 +- .../apache/qpid/test/framework/ReceiverImpl.java | 12 +- .../org/apache/qpid/test/framework/TestUtils.java | 57 ++- .../org/apache/qpid/util/ClasspathScanner.java | 234 ++++++++++ .../org/apache/qpid/util/ConversationFactory.java | 479 ++++++++++++++++++++ 55 files changed, 3652 insertions(+), 2257 deletions(-) delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/Coordinator.java delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/DropInTest.java delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestCase.java delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestDecorator.java delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestCase.java delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestDecorator.java delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InvitingTestDecorator.java delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/OptOutTestCase.java delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java delete mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/XMLTestListener.java create mode 100644 java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/CircuitTestCase.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/Coordinator.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/DistributedTestCase.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/DistributedTestDecorator.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/DropInTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestDecorator.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestDecorator.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/OptOutTestCase.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/XMLTestListener.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/distributedcircuit/DistributedCircuitImpl.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/BaseDistributedTestSequencer.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/DistributedTestSequencer.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/FanOutTestSequencer.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/InteropTestSequencer.java create mode 100644 java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/TestCaseSequencer.java create mode 100644 java/systests/src/main/java/org/apache/qpid/util/ClasspathScanner.java create mode 100644 java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java b/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java index d2680ffcc8..959ca03c80 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java +++ b/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java @@ -20,6 +20,7 @@ */ package org.apache.qpid.server.queue; + public interface QueueNotificationListener { void notifyClients(NotificationCheck notification, AMQQueue queue, String notificationMsg); diff --git a/java/common/src/main/java/org/apache/qpid/util/CommandLineParser.java b/java/common/src/main/java/org/apache/qpid/util/CommandLineParser.java index 61955160be..dc73bce28f 100644 --- a/java/common/src/main/java/org/apache/qpid/util/CommandLineParser.java +++ b/java/common/src/main/java/org/apache/qpid/util/CommandLineParser.java @@ -483,9 +483,9 @@ public class CommandLineParser } /** - * If a command line has been parsed, calling this method sets all of its parsed options as system properties. + * If a command line has been parsed, calling this method sets all of its parsed options into the specified properties. */ - public void addCommandLineToSysProperties() + public void addCommandLineToProperties(Properties properties) { if (parsedProperties != null) { @@ -494,7 +494,7 @@ public class CommandLineParser String name = (String) propKey; String value = parsedProperties.getProperty(name); - System.setProperty(name, value); + properties.setProperty(name, value); } } } @@ -606,11 +606,13 @@ public class CommandLineParser * Extracts all name=value pairs from the command line, sets them all as system properties and also returns * a map of properties containing them. * - * @param args The command line. + * @param args The command line. + * @param commandLine The command line parser. + * @param properties The properties object to inject all parsed properties into (optional may be null). * * @return A set of properties containing all name=value pairs from the command line. */ - public static Properties processCommandLine(String[] args, CommandLineParser commandLine) + public static Properties processCommandLine(String[] args, CommandLineParser commandLine, Properties properties) { // Capture the command line arguments or display errors and correct usage and then exit. Properties options = null; @@ -621,7 +623,7 @@ public class CommandLineParser // Add all the trailing command line options (name=value pairs) to system properties. They may be picked up // from there. - commandLine.addCommandLineToSysProperties(); + commandLine.addCommandLineToProperties(properties); } catch (IllegalArgumentException e) { diff --git a/java/etc/coding_standards.xml b/java/etc/coding_standards.xml index 00b1a9516a..8f8b808884 100644 --- a/java/etc/coding_standards.xml +++ b/java/etc/coding_standards.xml @@ -1,8 +1,9 @@ + - + diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/Coordinator.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/Coordinator.java deleted file mode 100644 index 6cc2740596..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/Coordinator.java +++ /dev/null @@ -1,453 +0,0 @@ -/* - * - * 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.interop.coordinator; - -import junit.framework.Test; -import junit.framework.TestResult; -import junit.framework.TestSuite; - -import org.apache.log4j.Logger; - -import org.apache.qpid.interop.coordinator.testcases.InteropTestCase1DummyRun; -import org.apache.qpid.interop.coordinator.testcases.InteropTestCase2BasicP2P; -import org.apache.qpid.interop.coordinator.testcases.InteropTestCase3BasicPubSub; -import org.apache.qpid.test.framework.MessagingTestConfigProperties; -import org.apache.qpid.test.framework.TestUtils; -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.util.CommandLineParser; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; -import uk.co.thebadgerset.junit.extensions.util.TestContextProperties; - -import javax.jms.*; - -import java.io.*; -import java.util.*; -import java.util.concurrent.LinkedBlockingQueue; - -/** - *

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. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Find out what test clients are available. {@link ConversationFactory} - *
Decorate available tests to run all available clients. {@link InvitingTestDecorator} - *
Attach XML test result logger. - *
Terminate the interop testing framework. - *
- * - * @todo Shoud 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); - - /** 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 null, 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 enlistedClients = new HashSet(); - - /** 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; - - /** - * Creates an interop test coordinator on the specified broker and virtual host. - * - * @param brokerUrl The URL of the broker to connect to. - * @param virtualHost The virtual host to run all tests on. Optional, may be null. - * @param reportDir The directory to write out test results to. - * @param engine The distributed test engine type to run the tests with. - */ - public Coordinator(String brokerUrl, String virtualHost, String reportDir, TestEngine engine) - { - log.debug("Coordinator(String brokerUrl = " + brokerUrl + ", String virtualHost = " + virtualHost + "): called"); - - // Retain the connection parameters. - this.brokerUrl = brokerUrl; - this.virtualHost = virtualHost; - this.reportDir = reportDir; - this.engine = engine; - } - - /** - * The entry point for the interop test coordinator. This client accepts the following command line arguments: - * - *

- *
-b The broker URL. Mandatory. - *
-h The virtual host. Optional. - *
-o The directory to output test results to. Optional. - *
-e The type of test distribution engine to use. Optional. One of: interop, fanout. - *
... Free arguments. The distributed test cases to run. - * Mandatory. At least one must be defined. - *
name=value Trailing argument define name/value pairs. Added to the test contenxt properties. - * Optional. - *
- * - * @param args The command line arguments. - */ - public static void main(String[] args) - { - // Override the default broker url to be localhost:5672. - testContextProperties.setProperty(MessagingTestConfigProperties.BROKER_PROPNAME, "tcp://localhost:5672"); - - // 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. - Properties options = - 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" - } - }), testContextProperties); - - // Extract the command line options. - String brokerUrl = options.getProperty("b"); - String virtualHost = options.getProperty("h"); - String reportDir = options.getProperty("o"); - String testEngine = options.getProperty("e"); - TestEngine engine = "fanout".equals(testEngine) ? TestEngine.FANOUT : TestEngine.INTEROP; - reportDir = (reportDir == null) ? "." : reportDir; - - // If broker or virtual host settings were specified as command line options, override the defaults in the - // test context properties with them. - - // Scan for available test cases using a classpath scanner. - // Hard code the test classes till the classpath scanner is fixed. - Collection> testCaseClasses = new ArrayList>(); - // ClasspathScanner.getMatches(InteropTestCase.class, "^Test.*", true); - Collections.addAll(testCaseClasses, InteropTestCase1DummyRun.class, InteropTestCase2BasicP2P.class, - InteropTestCase3BasicPubSub.class); - - // Check that some test classes were actually found. - if (testCaseClasses.isEmpty()) - { - throw new RuntimeException("No test classes implementing InteropTestCase were found on the class path."); - } - - // 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(brokerUrl, virtualHost, reportDir, engine); - - try - { - 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) - { - System.err.println(e.getMessage()); - log.error("Top level handler caught execption.", e); - 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 enlists = conversation.receiveAll(0, 3000); - - enlistedClients = extractEnlists(enlists); - - // 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. - 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 extractEnlists(Collection enlists) throws JMSException - { - log.debug("public static Set extractEnlists(Collection enlists = " + enlists - + "): called"); - - Set enlistedClients = new HashSet(); - - // 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"); - - enlistedClients.add(clientDetails); - } - - 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 InteropTestCase) - { - log.debug("nextTest is a InteropTestCase"); - } - } - - 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(suite, 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 InvitingTestDecorator newTestDecorator(WrappedSuiteTestDecorator targetTest, - Set 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; - - 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); - } - - // Set up an XML results listener to output the timings to the results file. - XMLTestListener listener = new XMLTestListener(timingsWriter, currentTestClassName); - 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); - - // 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; - } -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/DropInTest.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/DropInTest.java deleted file mode 100644 index f7e38fb1ad..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/DropInTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * - * 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.interop.coordinator; - -import javax.jms.JMSException; -import javax.jms.Message; - -/** - * A DropIn test is a test case that can accept late joining test clients into a running test. This can be usefull, - * for interactive experimentation. - * - *

- *
CRC Card
Responsibilities - *
Accept late joining test clients. - *
- */ -public interface DropInTest -{ - /** - * Should accept a late joining client into a running test case. The client will be enlisted with a control message - * with the 'CONTROL_TYPE' field set to the value 'LATEJOIN'. It should also provide values for the fields: - * - *

- *
CLIENT_NAME A unique name for the new client. - *
CLIENT_PRIVATE_CONTROL_KEY The key for the route on which the client receives its control messages. - *
- * - * @param message The late joiners join message. - * - * @throws JMSException Any JMS Exception are allowed to fall through, indicating that the join failed. - */ - public void lateJoin(Message message) throws JMSException; -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestCase.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestCase.java deleted file mode 100644 index ba737dffab..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestCase.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * - * 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.interop.coordinator; - -import org.apache.log4j.Logger; - -import org.apache.qpid.test.framework.TestUtils; -import org.apache.qpid.util.ConversationFactory; - -import javax.jms.Destination; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.Session; - -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * FanOutTestCase is a {@link org.apache.qpid.interop.coordinator.InteropTestCase} across one sending client and - * zero or more receiving clients. Its main purpose is to coordinate the setting up of one test client in the sending - * role and the remainder in the receiving role. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Accept notification of test case participants. - * {@link org.apache.qpid.interop.coordinator.InvitingTestDecorator} - *
Accept JMS Connection to carry out the coordination over. - *
Coordinate the test sequence amongst participants. {@link ConversationFactory} - *
Supply test properties - *
- * - * @todo Gather all the receivers reports. - */ -public abstract class FanOutTestCase extends InteropTestCase -{ - /** Used for debugging. */ - private static final Logger log = Logger.getLogger(FanOutTestCase.class); - - /** The test clients in the receiving role. */ - private List receivers = new LinkedList(); - - /** - * Creates a new coordinating test case with the specified name. - * - * @param name The test case name. - */ - public FanOutTestCase(String name) - { - super(name); - } - - /** - * Adds a receiver to this test. - * - * @param receiver The contact details of the sending client in the test. - */ - public void setReceiver(TestClientDetails receiver) - { - receivers.add(receiver); - } - - /** - * Holds a test coordinating conversation with the test clients. This is the basic implementation of the inner loop - * of Use Case 5. It consists of assigning the test roles, begining the test and gathering the test reports from the - * participants. - * - * @param testProperties The test case definition. - * - * @return The test results from the senders and receivers. The senders report will always be returned first, - * followed by the receivers reports. - * - * @throws JMSException All underlying JMSExceptions are allowed to fall through. - */ - protected Message[] sequenceTest(Map testProperties) throws JMSException - { - log.debug("protected Message[] sequenceTest(Object... testProperties = " + testProperties + "): called"); - - // Create a conversation on the sender clients private control rouete. - Session session = conversationFactory.getSession(); - Destination senderControlTopic = session.createTopic(sender.privateControlKey); - ConversationFactory.Conversation senderConversation = conversationFactory.startConversation(); - - // Assign the sender role to the sending test client. - Message assignSender = conversationFactory.getSession().createMessage(); - setPropertiesOnMessage(assignSender, testProperties); - assignSender.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE"); - assignSender.setStringProperty("ROLE", "SENDER"); - assignSender.setStringProperty("CLIENT_NAME", "Sustained_SENDER"); - - senderConversation.send(senderControlTopic, assignSender); - - // Wait for the sender to confirm its role. - senderConversation.receive(); - - // Assign the receivers roles. - for (TestClientDetails receiver : receivers) - { - assignReceiverRole(receiver, testProperties, true); - } - - // Start the test on the sender. - Message start = session.createMessage(); - start.setStringProperty("CONTROL_TYPE", "START"); - - senderConversation.send(senderControlTopic, start); - - // Wait for the test sender to return its report. - Message senderReport = senderConversation.receive(); - TestUtils.pause(500); - - // Ask the receivers for their reports. - Message statusRequest = session.createMessage(); - statusRequest.setStringProperty("CONTROL_TYPE", "STATUS_REQUEST"); - - // Gather the reports from all of the receiving clients. - - // Return all of the test reports, the senders report first. - return new Message[] { senderReport }; - } - - /** - * Assigns the receiver role to the specified test client that is to act as a receiver during the test. This method - * does not always wait for the receiving clients to confirm their role assignments. This is because this method - * may be called from an 'onMessage' method, when a client is joining the test at a later point in time, and it - * is not possible to do a synchronous receive during an 'onMessage' method. There is a flag to indicate whether - * or not to wait for role confirmations. - * - * @param receiver The test client to assign the receiver role to. - * @param testProperties The test parameters. - * @param confirm Indicates whether role confirmation should be waited for. - * - * @throws JMSException Any JMSExceptions occurring during the conversation are allowed to fall through. - */ - protected void assignReceiverRole(TestClientDetails receiver, Map testProperties, boolean confirm) - throws JMSException - { - log.info("assignReceiverRole(TestClientDetails receiver = " + receiver + ", Map testProperties = " - + testProperties + "): called"); - - // Create a conversation with the receiving test client. - Session session = conversationFactory.getSession(); - Destination receiverControlTopic = session.createTopic(receiver.privateControlKey); - ConversationFactory.Conversation receiverConversation = conversationFactory.startConversation(); - - // Assign the receiver role to the receiving client. - Message assignReceiver = session.createMessage(); - setPropertiesOnMessage(assignReceiver, testProperties); - assignReceiver.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE"); - assignReceiver.setStringProperty("ROLE", "RECEIVER"); - assignReceiver.setStringProperty("CLIENT_NAME", receiver.clientName); - - receiverConversation.send(receiverControlTopic, assignReceiver); - - // Wait for the role confirmation to come back. - if (confirm) - { - receiverConversation.receive(); - } - } -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestDecorator.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestDecorator.java deleted file mode 100644 index 5e3fb51b97..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestDecorator.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * - * 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.interop.coordinator; - -import junit.framework.Test; -import junit.framework.TestResult; - -import org.apache.log4j.Logger; - -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 InvitingTestDecorator} that runs one test client in the sender role, and the remainder - * in the receiver 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. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Execute coordinated test cases. {@link InteropTestCase} - *
Accept test clients joining a running test. - *
- */ -public class FanOutTestDecorator extends InvitingTestDecorator implements MessageListener -{ - /** Used for debugging. */ - private static final Logger log = Logger.getLogger(FanOutTestDecorator.class); - - /** Holds the currently running test case. */ - InteropTestCase 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 availableClients, - ConversationFactory controlConversation, Connection controlConnection) - { - super(suite, availableClients, controlConversation, controlConnection); - - log.debug("public InvitingTestDecorator(WrappedSuiteTestDecorator suite, Set allClients = " - + availableClients + ", ConversationHelper controlConversation = " + controlConversation + "): called"); - - testSuite = suite; - allClients = availableClients; - conversationFactory = controlConversation; - connection = controlConnection; - } - - /** - * 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. - * - *

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"); - - Collection tests = testSuite.getAllUnderlyingTests(); - - // 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 : tests) - { - InteropTestCase coordTest = (InteropTestCase) test; - - // Get all of the clients able to participate in the test. - Set enlists = signupClients(coordTest); - - // Check that there were some clients available. - if (enlists.size() == 0) - { - throw new RuntimeException("No clients to test with"); - } - - // Set up the first client in the sender role, and the remainder in the receiver role. - Iterator clients = enlists.iterator(); - coordTest.setSender(clients.next()); - - while (clients.hasNext()) - { - // Set the sending and receiving client details on the test case. - coordTest.setReceiver(clients.next()); - } - - // Pass down the connection to hold the coordinating conversation over. - coordTest.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.run(testResult); - - currentTest = null; - } - } - - /** - * 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 - * receiver 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 + " ]"; - } -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestCase.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestCase.java deleted file mode 100644 index f895b781f0..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestCase.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * - * 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.interop.coordinator; - -import junit.framework.TestCase; - -import org.apache.log4j.Logger; - -import org.apache.qpid.test.framework.TestUtils; -import org.apache.qpid.util.ConversationFactory; - -import javax.jms.*; - -import java.util.Map; - -/** - * A InteropTestCase is a JUnit test case extension that knows how to coordinate test clients that take part in a - * test case as defined in the interop testing specification - * (http://cwiki.apache.org/confluence/display/qpid/Interop+Testing+Specification). - * - *

The real logic of the test cases built on top of this, is embeded in the comparison of the sender and receiver - * reports. An example test method might look like: - * - *

- * public void testExample()
- * {
- *   Properties testConfig = new Properties();
- *   testConfig.add("TEST_CASE", "example");
- *   ...
- *
- *   Report[] reports = sequenceTest(testConfig);
- *
- *   // Compare sender and receiver reports.
- *   if (report[0] ... report[1] ...)
- *   {
- *     Assert.fail("Sender and receiver reports did not match up.");
- *   }
- * }
- *
- * 
- * - *

- *
CRC Card
Responsibilities Collaborations - *
Accept notification of test case participants. {@link InvitingTestDecorator} - *
Accpet JMS Connection to carry out the coordination over. - *
Coordinate the test sequence amongst participants. {@link ConversationFactory} - *
Supply test properties - *
- */ -public abstract class InteropTestCase extends TestCase -{ - /** Used for debugging. */ - private static final Logger log = Logger.getLogger(InteropTestCase.class); - - /** Holds the contact details for the sending test client. */ - protected TestClientDetails sender; - - /** Holds the contact details for the receving test client. */ - protected TestClientDetails receiver; - - /** Holds the conversation factory over which to coordinate the test. */ - protected ConversationFactory conversationFactory; - - /** - * Creates a new coordinating test case with the specified name. - * - * @param name The test case name. - */ - public InteropTestCase(String name) - { - super(name); - } - - /** - * Sets the sender test client to coordinate the test with. - * - * @param sender The contact details of the sending client in the test. - */ - public void setSender(TestClientDetails sender) - { - log.debug("public void setSender(TestClientDetails sender = " + sender + "): called"); - - this.sender = sender; - } - - /** - * Sets the receiving test client to coordinate the test with. - * - * @param receiver The contact details of the sending client in the test. - */ - public void setReceiver(TestClientDetails receiver) - { - log.debug("public void setReceiver(TestClientDetails receiver = " + receiver + "): called"); - - this.receiver = receiver; - } - - /** - * Supplies the sending test client. - * - * @return The sending test client. - */ - public TestClientDetails getSender() - { - return sender; - } - - /** - * Supplies the receiving test client. - * - * @return The receiving test client. - */ - public TestClientDetails getReceiver() - { - return receiver; - } - - /** - * Returns the name of the current test method of this test class, with the sending and receiving client names - * appended on to it, so that the resulting name unqiuely identifies the test and the clients that participated - * in it. - * - * @return The unique test and client name. - */ - public String getName() - { - if ((sender == null) || (receiver == null)) - { - return super.getName(); - } - else - { - return super.getName() + "_sender_" + sender.clientName + "_receiver_" + receiver.clientName; - } - } - - /** - * Should provide a translation from the junit method name of a test to its test case name as known to the test - * clients that will run the test. The purpose of this is to convert the JUnit method name into the correct test - * case name to place into the test invite. 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 abstract String getTestCaseNameForTestMethod(String methodName); - - /** - * Accepts the conversation factory over which to hold the test coordinating conversation. - * - * @param conversationFactory The conversation factory to coordinate the test over. - */ - public void setConversationFactory(ConversationFactory conversationFactory) - { - this.conversationFactory = conversationFactory; - } - - /** - * Holds a test coordinating conversation with the test clients. This is the basic implementation of the inner - * loop of Use Case 5. It consists of assigning the test roles, begining the test and gathering the test reports - * from the participants. - * - * @param testProperties The test case definition. - * - * @return The test results from the senders and receivers. - * - * @throws JMSException All underlying JMSExceptions are allowed to fall through. - */ - protected Message[] sequenceTest(Map testProperties) throws JMSException - { - log.debug("protected Message[] sequenceTest(Object... testProperties = " + testProperties + "): called"); - - Session session = conversationFactory.getSession(); - Destination senderControlTopic = session.createTopic(sender.privateControlKey); - Destination receiverControlTopic = session.createTopic(receiver.privateControlKey); - - ConversationFactory.Conversation senderConversation = conversationFactory.startConversation(); - ConversationFactory.Conversation receiverConversation = conversationFactory.startConversation(); - - // Assign the sender role to the sending test client. - Message assignSender = conversationFactory.getSession().createMessage(); - setPropertiesOnMessage(assignSender, testProperties); - assignSender.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE"); - assignSender.setStringProperty("ROLE", "SENDER"); - - senderConversation.send(senderControlTopic, assignSender); - - // Assign the receiver role the receiving client. - Message assignReceiver = session.createMessage(); - setPropertiesOnMessage(assignReceiver, testProperties); - assignReceiver.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE"); - assignReceiver.setStringProperty("ROLE", "RECEIVER"); - - receiverConversation.send(receiverControlTopic, assignReceiver); - - // Wait for the senders and receivers to confirm their roles. - senderConversation.receive(); - receiverConversation.receive(); - - // Start the test. - Message start = session.createMessage(); - start.setStringProperty("CONTROL_TYPE", "START"); - - senderConversation.send(senderControlTopic, start); - - // Wait for the test sender to return its report. - Message senderReport = senderConversation.receive(); - TestUtils.pause(500); - - // Ask the receiver for its report. - Message statusRequest = session.createMessage(); - statusRequest.setStringProperty("CONTROL_TYPE", "STATUS_REQUEST"); - - receiverConversation.send(receiverControlTopic, statusRequest); - - // Wait for the receiver to send its report. - Message receiverReport = receiverConversation.receive(); - - return new Message[] { senderReport, receiverReport }; - } - - /** - * Sets properties of different types on a JMS Message. - * - * @param message The message to set properties on. - * @param properties The property name/value pairs to set. - * - * @throws JMSException All underlying JMSExceptions are allowed to fall through. - */ - public void setPropertiesOnMessage(Message message, Map properties) throws JMSException - { - for (Map.Entry entry : properties.entrySet()) - { - String name = entry.getKey(); - Object value = entry.getValue(); - - message.setObjectProperty(name, value); - } - } -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestDecorator.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestDecorator.java deleted file mode 100644 index 85d127110d..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestDecorator.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * - * 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.interop.coordinator; - -import junit.framework.Test; -import junit.framework.TestResult; - -import org.apache.log4j.Logger; - -import org.apache.qpid.util.ConversationFactory; - -import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator; - -import javax.jms.Connection; - -import java.util.*; - -/** - * InvitingTestDecorator 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 receiver - * role and a test case run between them. Any enlisted combinations that do not accept a test invite are automatically - * failed. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Broadcast test invitations and collect enlists. {@link org.apache.qpid.util.ConversationFactory}. - *
Output test failures for clients unwilling to run the test case. {@link Coordinator} - *
Execute coordinated test cases. {@link InteropTestCase} - *
Fail non participating pairings. {@link OptOutTestCase} - *
- */ -public class InteropTestDecorator extends InvitingTestDecorator -{ - /** 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 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 InteropTestCase}. - * - *

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 tests = testSuite.getAllUnderlyingTests(); - - for (Test test : tests) - { - InteropTestCase coordTest = (InteropTestCase) test; - - // Broadcast the invitation to find out what clients are available to test. - Set enlists = signupClients(coordTest); - - // Compare the list of willing clients to the list of all available. - Set optOuts = new HashSet(allClients); - optOuts.removeAll(enlists); - - // Output test failures for clients that will not particpate in the test. - Set> failPairs = allPairs(optOuts, allClients); - - for (List failPair : failPairs) - { - InteropTestCase failTest = new OptOutTestCase("testOptOut"); - failTest.setSender(failPair.get(0)); - failTest.setReceiver(failPair.get(1)); - - failTest.run(testResult); - } - - // Loop over all combinations of clients, willing to run the test. - Set> enlistedPairs = allPairs(enlists, enlists); - - for (List enlistedPair : enlistedPairs) - { - // Set the sending and receiving client details on the test case. - coordTest.setSender(enlistedPair.get(0)); - coordTest.setReceiver(enlistedPair.get(1)); - - // Pass down the connection to hold the coordination conversation over. - coordTest.setConversationFactory(conversationFactory); - - // Execute the test case. - coordTest.run(testResult); - } - } - } - - /** - * Produces all pairs of combinations of elements from two sets. The ordering of the elements in the pair is - * important, that is the pair is distinct from ; both pairs are generated. For any element, i, in - * both the left and right sets, the reflexive pair is not generated. - * - * @param left The left set. - * @param right The right set. - * @param 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 Set> allPairs(Set left, Set right) - { - log.debug("private Set> allPairs(Set left = " + left + ", Set right = " + right + "): called"); - - Set> results = new HashSet>(); - - // 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(le, re)); - results.add(new Pair(re, le)); - } - } - } - - log.debug("results = " + results); - - return results; - } - - /** - * A simple implementation of a pair, using a list. - */ - private class Pair extends ArrayList - { - /** - * 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); - } - } -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InvitingTestDecorator.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InvitingTestDecorator.java deleted file mode 100644 index 1225d74fbf..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/InvitingTestDecorator.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * - * 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.interop.coordinator; - -import junit.framework.TestResult; - -import org.apache.log4j.Logger; - -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.*; - -/** - * InvitingTestDecorator 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(InteropTestCase)}, that broadcasts - * an invitation and return the set of test clients that are available to particiapte in the test. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Broadcast test invitations and collect enlists. {@link ConversationFactory}. - *
- */ -public abstract class InvitingTestDecorator extends WrappedSuiteTestDecorator -{ - /** Used for debugging. */ - private static final Logger log = Logger.getLogger(InvitingTestDecorator.class); - - /** Holds the contact information for all test clients that are available and that may take part in the test. */ - Set 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 {@link InteropTestCase}s 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 InvitingTestDecorator(WrappedSuiteTestDecorator suite, Set availableClients, - ConversationFactory controlConversation, Connection controlConnection) - { - super(suite); - - log.debug("public InvitingTestDecorator(WrappedSuiteTestDecorator suite, Set 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); - - /** - * 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 signupClients(InteropTestCase coordTest) - { - // Broadcast the invitation to find out what clients are available to test. - Set 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 replies = conversation.receiveAll(allClients.size(), 3000); - 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 "InvitingTestDecorator: [ testSuite = " + testSuite + " ]"; - } -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/OptOutTestCase.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/OptOutTestCase.java deleted file mode 100644 index 4332aaf55c..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/OptOutTestCase.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * - * 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.interop.coordinator; - -import junit.framework.Assert; - -/** - * 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, are automatically given a fail for the test. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Fail the test with a suitable reason. - *
- */ -public class OptOutTestCase extends InteropTestCase -{ - /** - * 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() - { - Assert.fail("One of " + getSender() + " and " + getReceiver() + " 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"; - } -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java deleted file mode 100644 index 742375b7bd..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * - * 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.interop.coordinator; - -/** - * TestClientDetails is used to encapsulate information about an interop test client. It pairs together the unique - * name of the client, and the route on which it listens to its control messages. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Record test clients control addresses together with their names. - *
- */ -public class TestClientDetails -{ - /** The test clients name. */ - public String clientName; - - /* The test clients unique sequence number. Not currently used. */ - - /** The routing key of the test clients control topic. */ - public String privateControlKey; - - /** - * Two TestClientDetails are considered to be equal, iff they have the same client name. - * - * @param o The object to compare to. - * - * @return If the object to compare to is a TestClientDetails equal to this one, false otherwise. - */ - public boolean equals(Object o) - { - if (this == o) - { - return true; - } - - if (!(o instanceof TestClientDetails)) - { - return false; - } - - final TestClientDetails testClientDetails = (TestClientDetails) o; - - return !((clientName != null) ? (!clientName.equals(testClientDetails.clientName)) - : (testClientDetails.clientName != null)); - } - - /** - * Computes a hash code compatible with the equals method; based on the client name alone. - * - * @return A hash code for this. - */ - public int hashCode() - { - return ((clientName != null) ? clientName.hashCode() : 0); - } - - /** - * Outputs the client name and address details. Mostly used for debugging purposes. - * - * @return The client name and address. - */ - public String toString() - { - return "TestClientDetails: [ clientName = " + clientName + ", privateControlKey = " + privateControlKey + " ]"; - } -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/XMLTestListener.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/XMLTestListener.java deleted file mode 100644 index 74c86b1d83..0000000000 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/XMLTestListener.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * - * 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.interop.coordinator; - -import junit.framework.AssertionFailedError; -import junit.framework.Test; -import junit.framework.TestCase; - -import org.apache.log4j.Logger; - -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. - * - *

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. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Listen to test lifecycle notifications. - *
Listen to test errors and failures. - *
Listen to test timings. - *
Listen to test memory usages. - *
Listen to parameterized test parameters. - *
Responsibilities - *
- * - * @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 -{ - /** 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 results = new LinkedHashMap(); - // protected List results = new ArrayList(); - - /** - * 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 threadLocalResults = Collections.synchronizedMap(new LinkedHashMap()); - - /** - * 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 results = new ArrayList(); - - /** 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()); - errors = 0; - failures = 0; - runs = 0; - - // Write out the file header. - try - { - writer.write("\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("\n"); - writer.write("\n"); - - for (Result result : results) - { - writer.write(" \n"); - - if (result.error != null) - { - writer.write(" "); - result.error.printStackTrace(new PrintWriter(writer)); - writer.write(" "); - } - else if (result.failure != null) - { - writer.write(" "); - result.failure.printStackTrace(new PrintWriter(writer)); - writer.write(" "); - } - - writer.write(" \n"); - } - - writer.write("\n"); - writer.flush(); - } - catch (IOException e) - { - throw new RuntimeException("Unable to write the test results.", e); - } - } - - /** - * 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; - } - } -} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/CircuitTestCase.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/CircuitTestCase.java new file mode 100644 index 0000000000..81a3e891fd --- /dev/null +++ b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/CircuitTestCase.java @@ -0,0 +1,101 @@ +/* + * + * 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.interop.coordinator.testcases; + +import org.apache.log4j.Logger; + +import org.apache.qpid.interop.coordinator.sequencers.TestCaseSequencer; +import org.apache.qpid.test.framework.Circuit; +import org.apache.qpid.test.framework.FrameworkBaseCase; +import org.apache.qpid.test.framework.MessagingTestConfigProperties; + +import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import uk.co.thebadgerset.junit.extensions.util.TestContextProperties; + +/** + * CircuitTestCase runs a test over a {@link Circuit} controlled by the test parameters. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
+ *
+ * + * @todo When working with test context properties, add overrides to defaults to the singleton instance, but when taking + * a starting point to add specific test case parameters to, take a copy. Use the copy with test case specifics + * to control the test. + */ +public class CircuitTestCase extends FrameworkBaseCase +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(CircuitTestCase.class); + + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public CircuitTestCase(String name) + { + super(name); + } + + /** + * Performs the a basic P2P test case. + * + * @throws Exception Any exceptions are allowed to fall through and fail the test. + */ + public void testBasicP2P() throws Exception + { + log.debug("public void testBasicP2P(): called"); + + // Get the test parameters, any overrides on the command line will have been applied. + ParsedProperties testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults); + + // Customize the test parameters. + testProps.setProperty("TEST_NAME", "DEFAULT_CIRCUIT_TEST"); + testProps.setProperty(MessagingTestConfigProperties.SEND_DESTINATION_NAME_ROOT_PROPNAME, "testqueue"); + + // Get the test sequencer to create test circuits and run the standard test procedure through. + TestCaseSequencer sequencer = getTestSequencer(); + + // Send the test messages, and check that there were no errors doing so. + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); + + // Check that all of the message were sent. + // Check that the receiving end got the same number of messages as the publishing end. + } + + /** + * Should provide a translation from the junit method name of a test to its test case name as known to the test + * clients that will run the test. The purpose of this is to convert the JUnit method name into the correct test + * case name to place into the test invite. 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 "DEFAULT_CIRCUIT_TEST"; + } +} diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase1DummyRun.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase1DummyRun.java index b74a55d964..075d5ecad4 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase1DummyRun.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase1DummyRun.java @@ -20,28 +20,23 @@ */ package org.apache.qpid.interop.coordinator.testcases; -import junit.framework.Assert; - import org.apache.log4j.Logger; -import org.apache.qpid.interop.coordinator.InteropTestCase; - -import javax.jms.Message; +import org.apache.qpid.interop.coordinator.DistributedTestCase; -import java.util.HashMap; -import java.util.Map; +import java.util.Properties; /** - * Coordinates test case 1, from the interop test specification. This test connects up the sender and receiver roles, + * Coordinates test case 1, from the interop test specification. This test connects up the sender and receivers roles, * and gets some dummy test reports from them, in order to check that the test framework itself is operational. * *

*
CRC Card
Responsibilities Collaborations *
Exercises the interop testing framework without actually sending any test messages. - * {@link org.apache.qpid.interop.coordinator.InteropTestCase} + * {@link org.apache.qpid.interop.coordinator.DistributedTestCase} *
*/ -public class InteropTestCase1DummyRun extends InteropTestCase +public class InteropTestCase1DummyRun extends DistributedTestCase { /** Used for debugging. */ private static final Logger log = Logger.getLogger(InteropTestCase1DummyRun.class); @@ -65,13 +60,13 @@ public class InteropTestCase1DummyRun extends InteropTestCase { log.debug("public void testDummyRun(): called"); - Map testConfig = new HashMap(); + Properties testConfig = new Properties(); testConfig.put("TEST_NAME", "TC1_DummyRun"); - Message[] reports = sequenceTest(testConfig); + /*Message[] reports =*/ getTestSequencer().sequenceTest(null, null, testConfig); - // Compare sender and receiver reports. - Assert.assertEquals("Expected to get 2 dummy reports.", 2, reports.length); + // Compare sender and receivers reports. + // Assert.assertEquals("Expected to get 2 dummy reports.", 2, reports.length); } /** diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase2BasicP2P.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase2BasicP2P.java index 406b8b42a6..1b75a13712 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase2BasicP2P.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase2BasicP2P.java @@ -20,28 +20,23 @@ */ package org.apache.qpid.interop.coordinator.testcases; -import junit.framework.Assert; - import org.apache.log4j.Logger; -import org.apache.qpid.interop.coordinator.InteropTestCase; - -import javax.jms.Message; +import org.apache.qpid.interop.coordinator.DistributedTestCase; -import java.util.HashMap; -import java.util.Map; +import java.util.Properties; /** * Implements test case 2, from the interop test specification. This test sets up the TC2_BasicP2P test for 50 - * messages. It checks that the sender and receiver reports both indicate that all the test messages were transmitted + * messages. It checks that the sender and receivers reports both indicate that all the test messages were transmitted * successfully. * *

*
CRC Card
Responsibilities Collaborations - *
Setup p2p test parameters and compare with test output. {@link InteropTestCase} + *
Setup p2p test parameters and compare with test output. {@link DistributedTestCase} *
*/ -public class InteropTestCase2BasicP2P extends InteropTestCase +public class InteropTestCase2BasicP2P extends DistributedTestCase { /** Used for debugging. */ private static final Logger log = Logger.getLogger(InteropTestCase2BasicP2P.class); @@ -65,19 +60,19 @@ public class InteropTestCase2BasicP2P extends InteropTestCase { log.debug("public void testBasicP2P(): called"); - Map testConfig = new HashMap(); - testConfig.put("TEST_NAME", "TC2_BasicP2P"); - testConfig.put("P2P_QUEUE_AND_KEY_NAME", "tc2queue"); + Properties testConfig = new Properties(); + testConfig.setProperty("TEST_NAME", "TC2_BasicP2P"); + testConfig.setProperty("P2P_QUEUE_AND_KEY_NAME", "tc2queue"); testConfig.put("P2P_NUM_MESSAGES", 50); - Message[] reports = sequenceTest(testConfig); + /*Message[] reports =*/ getTestSequencer().sequenceTest(null, null, testConfig); - // Compare sender and receiver reports. - int messagesSent = reports[0].getIntProperty("MESSAGE_COUNT"); + // Compare sender and receivers reports. + /*int messagesSent = reports[0].getIntProperty("MESSAGE_COUNT"); int messagesReceived = reports[1].getIntProperty("MESSAGE_COUNT"); Assert.assertEquals("The requested number of messages were not sent.", 50, messagesSent); - Assert.assertEquals("Sender and receiver messages sent did not match up.", messagesSent, messagesReceived); + Assert.assertEquals("Sender and receivers messages sent did not match up.", messagesSent, messagesReceived);*/ } /** diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase3BasicPubSub.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase3BasicPubSub.java index ebb4cd764e..6d4db4c13c 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase3BasicPubSub.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/testcases/InteropTestCase3BasicPubSub.java @@ -20,24 +20,19 @@ */ package org.apache.qpid.interop.coordinator.testcases; -import junit.framework.Assert; - import org.apache.log4j.Logger; -import org.apache.qpid.interop.coordinator.InteropTestCase; - -import javax.jms.Message; +import org.apache.qpid.interop.coordinator.DistributedTestCase; -import java.util.HashMap; -import java.util.Map; +import java.util.Properties; /** *

*
CRC Card
Responsibilities Collaborations - *
Setup pub/sub test parameters and compare with test output. {@link InteropTestCase} + *
Setup pub/sub test parameters and compare with test output. {@link DistributedTestCase} *
*/ -public class InteropTestCase3BasicPubSub extends InteropTestCase +public class InteropTestCase3BasicPubSub extends DistributedTestCase { /** Used for debugging. */ private static final Logger log = Logger.getLogger(InteropTestCase3BasicPubSub.class); @@ -61,21 +56,21 @@ public class InteropTestCase3BasicPubSub extends InteropTestCase { log.debug("public void testBasicPubSub(): called"); - Map testConfig = new HashMap(); + Properties testConfig = new Properties(); testConfig.put("TEST_NAME", "TC3_BasicPubSub"); testConfig.put("PUBSUB_KEY", "tc3route"); testConfig.put("PUBSUB_NUM_MESSAGES", 10); testConfig.put("PUBSUB_NUM_RECEIVERS", 5); - Message[] reports = sequenceTest(testConfig); + /*Message[] reports =*/ getTestSequencer().sequenceTest(null, null, testConfig); - // Compare sender and receiver reports. - int messagesSent = reports[0].getIntProperty("MESSAGE_COUNT"); + // Compare sender and receivers reports. + /*int messagesSent = reports[0].getIntProperty("MESSAGE_COUNT"); int messagesReceived = reports[1].getIntProperty("MESSAGE_COUNT"); Assert.assertEquals("The requested number of messages were not sent.", 10, messagesSent); Assert.assertEquals("Received messages did not match up to num sent * num receivers.", messagesSent * 5, - messagesReceived); + messagesReceived);*/ } /** diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/InteropClientTestCase.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/InteropClientTestCase.java index 87f09faf1e..216efd3aff 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/InteropClientTestCase.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/InteropClientTestCase.java @@ -47,7 +47,7 @@ public interface InteropClientTestCase extends MessageListener /** Specifies the sender role. */ SENDER, - /** Specifies the receiver role. */ + /** Specifies the receivers role. */ RECEIVER } @@ -74,7 +74,7 @@ public interface InteropClientTestCase extends MessageListener * 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 receiver. + * @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. diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/TestClient.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/TestClient.java index baf8bc033d..26b00aa442 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/TestClient.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/TestClient.java @@ -25,6 +25,7 @@ import org.apache.log4j.Logger; import org.apache.qpid.interop.testclient.testcases.TestCase1DummyRun; import org.apache.qpid.interop.testclient.testcases.TestCase2BasicP2P; import org.apache.qpid.interop.testclient.testcases.TestCase3BasicPubSub; +import org.apache.qpid.sustained.SustainedClientTestCase; import org.apache.qpid.test.framework.MessagingTestConfigProperties; import org.apache.qpid.test.framework.TestUtils; @@ -63,7 +64,10 @@ import java.util.Map; public class TestClient implements MessageListener { /** Used for debugging. */ - private static Logger log = Logger.getLogger(TestClient.class); + private static final Logger log = Logger.getLogger(TestClient.class); + + /** Used for reporting to the console. */ + private static final Logger console = Logger.getLogger("CONSOLE"); /** Holds the default identifying name of the test client. */ public static final String CLIENT_NAME = "java"; @@ -169,7 +173,8 @@ public class TestClient implements MessageListener Collection> testCaseClasses = new ArrayList>(); // ClasspathScanner.getMatches(InteropClientTestCase.class, "^TestCase.*", true); - Collections.addAll(testCaseClasses, TestCase1DummyRun.class, TestCase2BasicP2P.class, TestCase3BasicPubSub.class); + Collections.addAll(testCaseClasses, TestCase1DummyRun.class, TestCase2BasicP2P.class, TestCase3BasicPubSub.class, + SustainedClientTestCase.class); try { @@ -285,7 +290,7 @@ public class TestClient implements MessageListener } else { - log.warn("'" + testName + "' not part of this clients tests."); + log.debug("Received an invite to the test '" + testName + "' but this test is not known."); } } else diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase1DummyRun.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase1DummyRun.java index 9629e79b2c..a8edeef80e 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase1DummyRun.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase1DummyRun.java @@ -80,7 +80,7 @@ public class TestCase1DummyRun implements InteropClientTestCase * 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 receiver. + * @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. diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase2BasicP2P.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase2BasicP2P.java index c93d1ab828..823ed51596 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase2BasicP2P.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase2BasicP2P.java @@ -98,7 +98,7 @@ public class TestCase2BasicP2P implements InteropClientTestCase * 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 receiver. + * @param role The role to be played; sender or receivers. * * @param assignRoleMessage The role assingment message, contains the full test parameters. * @@ -134,7 +134,7 @@ public class TestCase2BasicP2P implements InteropClientTestCase producer = session.createProducer(sendDestination); break; - // Otherwise the receiver role is being assigned, so set this up to listen for messages. + // Otherwise the receivers role is being assigned, so set this up to listen for messages. case RECEIVER: MessageConsumer consumer = session.createConsumer(sendDestination); consumer.setMessageListener(this); diff --git a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase3BasicPubSub.java b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase3BasicPubSub.java index 57e8634006..4cdb07c546 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase3BasicPubSub.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/testcases/TestCase3BasicPubSub.java @@ -30,7 +30,7 @@ import javax.jms.*; /** * Implements test case 3, basic pub/sub. Sends/received a specified number of messages to a specified route on the - * default topic exchange, using the specified number of receiver connections. Produces reports on the actual number of + * default topic exchange, using the specified number of receivers connections. Produces reports on the actual number of * messages sent/received. * *

@@ -99,7 +99,7 @@ public class TestCase3BasicPubSub implements InteropClientTestCase * 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 receiver. + * @param role The role to be played; sender or receivers. * * @param assignRoleMessage The role assingment message, contains the full test parameters. * @@ -143,10 +143,10 @@ public class TestCase3BasicPubSub implements InteropClientTestCase producer = session[0].createProducer(sendDestination); break; - // Otherwise the receiver role is being assigned, so set this up to listen for messages on the required number - // of receiver connections. + // Otherwise the receivers role is being assigned, so set this up to listen for messages on the required number + // of receivers connections. case RECEIVER: - // Create the required number of receiver connections. + // Create the required number of receivers connections. connection = new Connection[numReceivers]; session = new Session[numReceivers]; diff --git a/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedClientTestCase.java b/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedClientTestCase.java index 71ab38ec0a..edc6fdba1e 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedClientTestCase.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedClientTestCase.java @@ -46,7 +46,7 @@ import java.util.concurrent.CountDownLatch; /** * Implements test case 3, basic pub/sub. Sends/received a specified number of messages to a specified route on the - * default topic exchange, using the specified number of receiver connections. Produces reports on the actual number of + * default topic exchange, using the specified number of receivers connections. Produces reports on the actual number of * messages sent/received. * *

CRC Card
@@ -68,7 +68,7 @@ public class SustainedClientTestCase extends TestCase3BasicPubSub implements Exc /** The role to be played by the test. */ private Roles role; - /** The number of receiver connection to use. */ + /** The number of receivers connection to use. */ private int numReceivers; /** The routing key to send them to on the default direct exchange. */ @@ -114,7 +114,7 @@ public class SustainedClientTestCase extends TestCase3BasicPubSub implements Exc * 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 receiver. + * @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. @@ -172,11 +172,11 @@ public class SustainedClientTestCase extends TestCase3BasicPubSub implements Exc break; - // Otherwise the receiver role is being assigned, so set this up to listen for messages on the required number - // of receiver connections. + // Otherwise the receivers role is being assigned, so set this up to listen for messages on the required number + // of receivers connections. case RECEIVER: console.info("Creating Receiver"); - // Create the required number of receiver connections. + // Create the required number of receivers connections. connection = new Connection[numReceivers]; session = new Session[numReceivers]; diff --git a/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedTestCase.java b/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedTestCase.java index 3dd1326d80..84852078f0 100644 --- a/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedTestCase.java +++ b/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedTestCase.java @@ -23,18 +23,17 @@ package org.apache.qpid.sustained; import org.apache.log4j.Logger; import org.apache.qpid.client.AMQSession; +import org.apache.qpid.interop.coordinator.DistributedTestCase; import org.apache.qpid.interop.coordinator.DropInTest; import org.apache.qpid.interop.coordinator.TestClientDetails; -import org.apache.qpid.interop.coordinator.FanOutTestCase; import javax.jms.JMSException; import javax.jms.Message; -import java.util.HashMap; -import java.util.Map; +import java.util.Properties; /** - * SustainedTestCase is a {@link FanOutTestCase} that runs the "Perf_SustainedPubSub" test case. This consists of one + * SustainedTestCase is a {@link org.apache.qpid.interop.coordinator.DistributedTestCase} that runs the "Perf_SustainedPubSub" test case. This consists of one * test client sending, and several receiving, and attempts to find the highest rate at which messages can be broadcast * to the receivers. It is also a {@link DropInTest} to which more test clients may be added during a test run. * @@ -43,7 +42,7 @@ import java.util.Map; *
CRC Card
*
*/ -public class SustainedTestCase extends FanOutTestCase implements DropInTest +public class SustainedTestCase extends DistributedTestCase implements DropInTest { /** Used for debugging. */ Logger log = Logger.getLogger(SustainedTestCase.class); @@ -70,7 +69,7 @@ public class SustainedTestCase extends FanOutTestCase implements DropInTest { log.debug("public void testSinglePubSubCycle(): called"); - Map testConfig = new HashMap(); + Properties testConfig = new Properties(); testConfig.put("TEST_NAME", "Perf_SustainedPubSub"); testConfig.put("SUSTAINED_KEY", SUSTAINED_KEY); testConfig.put("SUSTAINED_NUM_RECEIVERS", Integer.getInteger("numReceives", 2)); @@ -80,7 +79,7 @@ public class SustainedTestCase extends FanOutTestCase implements DropInTest log.info("Created Config: " + testConfig.entrySet().toArray()); - sequenceTest(testConfig); + getTestSequencer().sequenceTest(null, null, testConfig); } /** @@ -98,14 +97,17 @@ public class SustainedTestCase extends FanOutTestCase implements DropInTest */ public void lateJoin(Message message) throws JMSException { + throw new RuntimeException("Not implemented."); + /* // Extract the joining clients details from its join request message. TestClientDetails clientDetails = new TestClientDetails(); clientDetails.clientName = message.getStringProperty("CLIENT_NAME"); clientDetails.privateControlKey = message.getStringProperty("CLIENT_PRIVATE_CONTROL_KEY"); - // Register the joining client, but do block for confirmation as cannot do a synchronous receiver during this + // Register the joining client, but do block for confirmation as cannot do a synchronous receivers during this // method call, as it may have been called from an 'onMessage' method. - assignReceiverRole(clientDetails, new HashMap(), false); + assignReceiverRole(clientDetails, new Properties(), false); + */ } /** diff --git a/java/perftests/src/main/java/org/apache/qpid/ping/PingDurableClient.java b/java/perftests/src/main/java/org/apache/qpid/ping/PingDurableClient.java index 2765986868..c5f71b4774 100644 --- a/java/perftests/src/main/java/org/apache/qpid/ping/PingDurableClient.java +++ b/java/perftests/src/main/java/org/apache/qpid/ping/PingDurableClient.java @@ -20,18 +20,6 @@ */ package org.apache.qpid.ping; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.jms.Destination; -import javax.jms.ExceptionListener; -import javax.jms.JMSException; -import javax.jms.Message; - import org.apache.log4j.Logger; import org.apache.qpid.requestreply.PingPongProducer; @@ -40,6 +28,18 @@ import org.apache.qpid.util.CommandLineParser; import uk.co.thebadgerset.junit.extensions.util.MathUtils; import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import javax.jms.Destination; +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import javax.jms.Message; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; + /** * PingDurableClient is a variation of the {@link PingPongProducer} ping tool. Instead of sending its pings and * receiving replies to them at the same time, this tool sends pings until it is signalled by some 'event' to stop @@ -167,7 +167,8 @@ public class PingDurableClient extends PingPongProducer implements ExceptionList try { // Create a ping producer overriding its defaults with all options passed on the command line. - Properties options = CommandLineParser.processCommandLine(args, new CommandLineParser(new String[][] {})); + Properties options = + CommandLineParser.processCommandLine(args, new CommandLineParser(new String[][] {}), System.getProperties()); PingDurableClient pingProducer = new PingDurableClient(options); // Create a shutdown hook to terminate the ping-pong producer. diff --git a/java/perftests/src/main/java/org/apache/qpid/ping/PingSendOnlyClient.java b/java/perftests/src/main/java/org/apache/qpid/ping/PingSendOnlyClient.java index bbe337ca0a..2879f0c322 100644 --- a/java/perftests/src/main/java/org/apache/qpid/ping/PingSendOnlyClient.java +++ b/java/perftests/src/main/java/org/apache/qpid/ping/PingSendOnlyClient.java @@ -57,7 +57,7 @@ public class PingSendOnlyClient extends PingDurableClient try { // Create a ping producer overriding its defaults with all options passed on the command line. - Properties options = CommandLineParser.processCommandLine(args, new CommandLineParser(new String[][] {})); + Properties options = CommandLineParser.processCommandLine(args, new CommandLineParser(new String[][] {}), System.getProperties()); PingSendOnlyClient pingProducer = new PingSendOnlyClient(options); // Create a shutdown hook to terminate the ping-pong producer. diff --git a/java/perftests/src/main/java/org/apache/qpid/requestreply/PingPongProducer.java b/java/perftests/src/main/java/org/apache/qpid/requestreply/PingPongProducer.java index 5d3df1d9ec..03f5f0549d 100644 --- a/java/perftests/src/main/java/org/apache/qpid/requestreply/PingPongProducer.java +++ b/java/perftests/src/main/java/org/apache/qpid/requestreply/PingPongProducer.java @@ -44,9 +44,7 @@ import java.net.InetAddress; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; -import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -89,7 +87,7 @@ import java.util.concurrent.atomic.AtomicLong; * destinationCount 1 The number of receivers listening to the pings. * timeout 30000 In milliseconds. The timeout to stop waiting for replies. * commitBatchSize 1 The number of messages per transaction in transactional mode. - * uniqueDests true Whether each receiver only listens to one ping destination or all. + * uniqueDests true Whether each receivers only listens to one ping destination or all. * durableDests false Whether or not durable destinations are used. * ackMode AUTO_ACK The message acknowledgement mode. Possible values are: * 0 - SESSION_TRANSACTED @@ -373,7 +371,7 @@ public class PingPongProducer implements Runnable, MessageListener, ExceptionLis protected int _maxPendingSize; /** - * Holds a monitor which is used to synchronize sender and receiver threads, where the sender has elected + * Holds a monitor which is used to synchronize sender and receivers threads, where the sender has elected * to wait until the number of unreceived message is reduced before continuing to send. */ protected Object _sendPauseMonitor = new Object(); @@ -570,7 +568,8 @@ public class PingPongProducer implements Runnable, MessageListener, ExceptionLis { try { - Properties options = CommandLineParser.processCommandLine(args, new CommandLineParser(new String[][] {})); + Properties options = + CommandLineParser.processCommandLine(args, new CommandLineParser(new String[][] {}), System.getProperties()); // Create a ping producer overriding its defaults with all options passed on the command line. PingPongProducer pingProducer = new PingPongProducer(options); diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/Coordinator.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/Coordinator.java new file mode 100644 index 0000000000..28d8ce79a0 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/Coordinator.java @@ -0,0 +1,496 @@ +/* + * + * 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.interop.coordinator; + +import junit.framework.Test; +import junit.framework.TestResult; +import junit.framework.TestSuite; + +import org.apache.log4j.Logger; + +import org.apache.qpid.test.framework.MessagingTestConfigProperties; +import org.apache.qpid.test.framework.TestUtils; +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.util.CommandLineParser; +import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import uk.co.thebadgerset.junit.extensions.util.TestContextProperties; + +import javax.jms.*; + +import java.io.*; +import java.util.*; +import java.util.concurrent.LinkedBlockingQueue; + +/** + *

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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Find out what test clients are available. {@link ConversationFactory} + *
Decorate available tests to run all available clients. {@link DistributedTestDecorator} + *
Attach XML test result logger. + *
Terminate the interop testing framework. + *
+ * + * @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 null, 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 enlistedClients = new HashSet(); + + /** 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; + + /** + * Creates an interop test coordinator on the specified broker and virtual host. + * + * @param brokerUrl The URL of the broker to connect to. + * @param virtualHost The virtual host to run all tests on. Optional, may be null. + * @param reportDir The directory to write out test results to. + * @param engine The distributed test engine type to run the tests with. + */ + public Coordinator(String brokerUrl, String virtualHost, String reportDir, TestEngine engine, boolean terminate) + { + log.debug("Coordinator(String brokerUrl = " + brokerUrl + ", String virtualHost = " + virtualHost + "): called"); + + // Retain the connection parameters. + this.brokerUrl = brokerUrl; + this.virtualHost = virtualHost; + this.reportDir = reportDir; + this.engine = engine; + this.terminate = terminate; + } + + /** + * The entry point for the interop test coordinator. This client accepts the following command line arguments: + * + *

+ *
-b The broker URL. Mandatory. + *
-h The virtual host. Optional. + *
-o The directory to output test results to. Optional. + *
-e The type of test distribution engine to use. Optional. One of: interop, fanout. + *
... Free arguments. The distributed test cases to run. + * Mandatory. At least one must be defined. + *
name=value Trailing argument define name/value pairs. Added to the test contenxt properties. + * Optional. + *
+ * + * @param args The command line arguments. + */ + public static void main(String[] args) + { + 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.", "flag", "false" } + }), 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"); + + // 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> testCaseClasses = + new ArrayList>(); + + // Scan for available test cases using a classpath scanner. + // ClasspathScanner.getMatches(InteropTestCase.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 (DistributedTestCase.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 InteropTestCase 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(brokerUrl, virtualHost, reportDir, engine, terminate); + + 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 enlists = conversation.receiveAll(0, 3000); + + enlistedClients = extractEnlists(enlists); + + // 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 extractEnlists(Collection enlists) throws JMSException + { + log.debug("public static Set extractEnlists(Collection enlists = " + enlists + + "): called"); + + Set enlistedClients = new HashSet(); + + // 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"); + + enlistedClients.add(clientDetails); + } + + 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 DistributedTestCase) + { + log.debug("nextTest is a DistributedTestCase"); + } + } + + 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(suite, 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 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; + + 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); + } + + // Set up an XML results listener to output the timings to the results file. + XMLTestListener listener = new XMLTestListener(timingsWriter, currentTestClassName); + 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); + + // 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; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/DistributedTestCase.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/DistributedTestCase.java new file mode 100644 index 0000000000..96b0d0c33f --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/DistributedTestCase.java @@ -0,0 +1,81 @@ +/* + * + * 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.interop.coordinator; + +import org.apache.log4j.Logger; + +import org.apache.qpid.interop.coordinator.sequencers.DistributedTestSequencer; +import org.apache.qpid.test.framework.FrameworkBaseCase; + +/** + * DistributedTestCase provides a base class implementation of the {@link org.apache.qpid.interop.coordinator.sequencers.DistributedTestSequencer}, taking care of its + * more mundane aspects, such as recording the test pariticipants. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Accept notification of test case participants. + * {@link org.apache.qpid.interop.coordinator.DistributedTestDecorator} + *
Accept JMS Connection to carry out the coordination over. + *
+ */ +public abstract class DistributedTestCase extends FrameworkBaseCase +{ + /** Used for debugging. */ + private final Logger log = Logger.getLogger(DistributedTestCase.class); + + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public DistributedTestCase(String name) + { + super(name); + } + + /** + * Gets the test sequencer for this distributed test, cast as a {@link DistributedTestSequencer}, provided that it + * is one. If the test sequencer is not distributed, this returns null. + */ + public DistributedTestSequencer getDistributedTestSequencer() + { + try + { + return (DistributedTestSequencer) testSequencer; + } + catch (ClassCastException e) + { + return null; + } + } + + /** + * Should provide a translation from the junit method name of a test to its test case name as known to the test + * clients that will run the test. The purpose of this is to convert the JUnit method name into the correct test + * case name to place into the test invite. 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 abstract String getTestCaseNameForTestMethod(String methodName); +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/DistributedTestDecorator.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/DistributedTestDecorator.java new file mode 100644 index 0000000000..e33a5c7228 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/DistributedTestDecorator.java @@ -0,0 +1,165 @@ +/* + * + * 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.interop.coordinator; + +import junit.framework.TestResult; + +import org.apache.log4j.Logger; + +import org.apache.qpid.interop.coordinator.sequencers.DistributedTestSequencer; +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. + * + *

When used to wrap a {@link org.apache.qpid.test.framework.FrameworkBaseCase} test, it replaces the default + * {@link org.apache.qpid.interop.coordinator.sequencers.TestCaseSequencer} implementations with a suitable + * {@link org.apache.qpid.interop.coordinator.sequencers.DistributedTestSequencer}. Concrete implementations + * can use this to configure the sending and receiving roles on the test. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Broadcast test invitations and collect enlists. {@link ConversationFactory}. + *
+ */ +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 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 {@link DistributedTestCase}s 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 availableClients, + ConversationFactory controlConversation, Connection controlConnection) + { + super(suite); + + log.debug("public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set 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 DistributedTestSequencer getDistributedTestSequencer(); + + /** + * 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 signupClients(DistributedTestCase coordTest) + { + // Broadcast the invitation to find out what clients are available to test. + Set 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 replies = conversation.receiveAll(allClients.size(), 3000); + 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 + " ]"; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/DropInTest.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/DropInTest.java new file mode 100644 index 0000000000..f7e38fb1ad --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/DropInTest.java @@ -0,0 +1,51 @@ +/* + * + * 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.interop.coordinator; + +import javax.jms.JMSException; +import javax.jms.Message; + +/** + * A DropIn test is a test case that can accept late joining test clients into a running test. This can be usefull, + * for interactive experimentation. + * + *

+ *
CRC Card
Responsibilities + *
Accept late joining test clients. + *
+ */ +public interface DropInTest +{ + /** + * Should accept a late joining client into a running test case. The client will be enlisted with a control message + * with the 'CONTROL_TYPE' field set to the value 'LATEJOIN'. It should also provide values for the fields: + * + *

+ *
CLIENT_NAME A unique name for the new client. + *
CLIENT_PRIVATE_CONTROL_KEY The key for the route on which the client receives its control messages. + *
+ * + * @param message The late joiners join message. + * + * @throws JMSException Any JMS Exception are allowed to fall through, indicating that the join failed. + */ + public void lateJoin(Message message) throws JMSException; +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestDecorator.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestDecorator.java new file mode 100644 index 0000000000..acace5bd7d --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/FanOutTestDecorator.java @@ -0,0 +1,200 @@ +/* + * + * 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.interop.coordinator; + +import junit.framework.Test; +import junit.framework.TestResult; + +import org.apache.log4j.Logger; + +import org.apache.qpid.interop.coordinator.sequencers.DistributedTestSequencer; +import org.apache.qpid.interop.coordinator.sequencers.FanOutTestSequencer; +import org.apache.qpid.interop.coordinator.sequencers.InteropTestSequencer; +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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Execute coordinated test cases. {@link DistributedTestCase} + *
Accept test clients joining a running test. + *
+ */ +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. */ + DistributedTestCase 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 availableClients, + ConversationFactory controlConversation, Connection controlConnection) + { + super(suite, availableClients, controlConversation, controlConnection); + + log.debug("public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set allClients = " + + availableClients + ", ConversationHelper controlConversation = " + controlConversation + "): called"); + + testSuite = suite; + allClients = availableClients; + conversationFactory = controlConversation; + connection = controlConnection; + } + + /** + * 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. + * + *

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"); + + Collection tests = testSuite.getAllUnderlyingTests(); + + // 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 : tests) + { + DistributedTestCase coordTest = (DistributedTestCase) test; + + // Get all of the clients able to participate in the test. + Set 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 sequencer for the test. + DistributedTestSequencer sequencer = getDistributedTestSequencer(); + + // Set up the first client in the sender role, and the remainder in the receivers role. + Iterator clients = enlists.iterator(); + sequencer.setSender(clients.next()); + + while (clients.hasNext()) + { + // Set the sending and receiving client details on the test case. + sequencer.setReceiver(clients.next()); + } + + // Pass down the connection to hold the coordinating conversation over. + sequencer.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.setTestSequencer(sequencer); + 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 DistributedTestSequencer getDistributedTestSequencer() + { + return new FanOutTestSequencer(); + } + + /** + * 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 + " ]"; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestDecorator.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestDecorator.java new file mode 100644 index 0000000000..7dcc391650 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/InteropTestDecorator.java @@ -0,0 +1,206 @@ +/* + * + * 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.interop.coordinator; + +import junit.framework.Test; +import junit.framework.TestResult; + +import org.apache.log4j.Logger; + +import org.apache.qpid.interop.coordinator.sequencers.DistributedTestSequencer; +import org.apache.qpid.interop.coordinator.sequencers.InteropTestSequencer; +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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Broadcast test invitations and collect enlists. {@link org.apache.qpid.util.ConversationFactory}. + *
Output test failures for clients unwilling to run the test case. {@link Coordinator} + *
Execute distributed test cases. {@link DistributedTestCase} + *
Fail non participating pairings. {@link OptOutTestCase} + *
+ */ +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 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 DistributedTestCase}. + * + *

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 tests = testSuite.getAllUnderlyingTests(); + + for (Test test : tests) + { + DistributedTestCase coordTest = (DistributedTestCase) test; + + // Broadcast the invitation to find out what clients are available to test. + Set enlists = signupClients(coordTest); + + // Compare the list of willing clients to the list of all available. + Set optOuts = new HashSet(allClients); + optOuts.removeAll(enlists); + + // Output test failures for clients that will not particpate in the test. + Set> failPairs = allPairs(optOuts, allClients); + + for (List failPair : failPairs) + { + // Create a distributed test sequencer for the test. + DistributedTestSequencer sequencer = getDistributedTestSequencer(); + + // Create an automatic failure test for the opted out test pair. + DistributedTestCase failTest = new OptOutTestCase("testOptOut"); + sequencer.setSender(failPair.get(0)); + sequencer.setReceiver(failPair.get(1)); + failTest.setTestSequencer(sequencer); + + failTest.run(testResult); + } + + // Loop over all combinations of clients, willing to run the test. + Set> enlistedPairs = allPairs(enlists, enlists); + + for (List enlistedPair : enlistedPairs) + { + // Create a distributed test sequencer for the test. + DistributedTestSequencer sequencer = getDistributedTestSequencer(); + + // Set the sending and receiving client details on the test sequencer. + sequencer.setSender(enlistedPair.get(0)); + sequencer.setReceiver(enlistedPair.get(1)); + + // Pass down the connection to hold the coordination conversation over. + sequencer.setConversationFactory(conversationFactory); + + // Execute the test case. + coordTest.setTestSequencer(sequencer); + 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 DistributedTestSequencer getDistributedTestSequencer() + { + return new InteropTestSequencer(); + } + + /** + * Produces all pairs of combinations of elements from two sets. The ordering of the elements in the pair is + * important, that is the pair is distinct from ; both pairs are generated. For any element, i, in + * both the left and right sets, the reflexive pair is not generated. + * + * @param left The left set. + * @param right The right set. + * @param 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 Set> allPairs(Set left, Set right) + { + log.debug("private Set> allPairs(Set left = " + left + ", Set right = " + right + "): called"); + + Set> results = new HashSet>(); + + // 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(le, re)); + results.add(new Pair(re, le)); + } + } + } + + log.debug("results = " + results); + + return results; + } + + /** + * A simple implementation of a pair, using a list. + */ + private class Pair extends ArrayList + { + /** + * 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); + } + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/OptOutTestCase.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/OptOutTestCase.java new file mode 100644 index 0000000000..f3673583e7 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/OptOutTestCase.java @@ -0,0 +1,70 @@ +/* + * + * 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.interop.coordinator; + +import junit.framework.Assert; + +import org.apache.qpid.interop.coordinator.sequencers.DistributedTestSequencer; + +/** + * 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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Fail the test with a suitable reason. + *
+ */ +public class OptOutTestCase extends DistributedTestCase +{ + /** + * 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() + { + DistributedTestSequencer sequencer = getDistributedTestSequencer(); + + Assert.fail("One of " + sequencer.getSender() + " and " + getDistributedTestSequencer().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"; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java new file mode 100644 index 0000000000..742375b7bd --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java @@ -0,0 +1,86 @@ +/* + * + * 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.interop.coordinator; + +/** + * TestClientDetails is used to encapsulate information about an interop test client. It pairs together the unique + * name of the client, and the route on which it listens to its control messages. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Record test clients control addresses together with their names. + *
+ */ +public class TestClientDetails +{ + /** The test clients name. */ + public String clientName; + + /* The test clients unique sequence number. Not currently used. */ + + /** The routing key of the test clients control topic. */ + public String privateControlKey; + + /** + * Two TestClientDetails are considered to be equal, iff they have the same client name. + * + * @param o The object to compare to. + * + * @return If the object to compare to is a TestClientDetails equal to this one, false otherwise. + */ + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + + if (!(o instanceof TestClientDetails)) + { + return false; + } + + final TestClientDetails testClientDetails = (TestClientDetails) o; + + return !((clientName != null) ? (!clientName.equals(testClientDetails.clientName)) + : (testClientDetails.clientName != null)); + } + + /** + * Computes a hash code compatible with the equals method; based on the client name alone. + * + * @return A hash code for this. + */ + public int hashCode() + { + return ((clientName != null) ? clientName.hashCode() : 0); + } + + /** + * Outputs the client name and address details. Mostly used for debugging purposes. + * + * @return The client name and address. + */ + public String toString() + { + return "TestClientDetails: [ clientName = " + clientName + ", privateControlKey = " + privateControlKey + " ]"; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/XMLTestListener.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/XMLTestListener.java new file mode 100644 index 0000000000..74c86b1d83 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/XMLTestListener.java @@ -0,0 +1,382 @@ +/* + * + * 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.interop.coordinator; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.log4j.Logger; + +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. + * + *

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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Listen to test lifecycle notifications. + *
Listen to test errors and failures. + *
Listen to test timings. + *
Listen to test memory usages. + *
Listen to parameterized test parameters. + *
Responsibilities + *
+ * + * @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 +{ + /** 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 results = new LinkedHashMap(); + // protected List results = new ArrayList(); + + /** + * 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 threadLocalResults = Collections.synchronizedMap(new LinkedHashMap()); + + /** + * 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 results = new ArrayList(); + + /** 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()); + errors = 0; + failures = 0; + runs = 0; + + // Write out the file header. + try + { + writer.write("\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("\n"); + writer.write("\n"); + + for (Result result : results) + { + writer.write(" \n"); + + if (result.error != null) + { + writer.write(" "); + result.error.printStackTrace(new PrintWriter(writer)); + writer.write(" "); + } + else if (result.failure != null) + { + writer.write(" "); + result.failure.printStackTrace(new PrintWriter(writer)); + writer.write(" "); + } + + writer.write(" \n"); + } + + writer.write("\n"); + writer.flush(); + } + catch (IOException e) + { + throw new RuntimeException("Unable to write the test results.", e); + } + } + + /** + * 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; + } + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/distributedcircuit/DistributedCircuitImpl.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/distributedcircuit/DistributedCircuitImpl.java new file mode 100644 index 0000000000..405898f1e6 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/distributedcircuit/DistributedCircuitImpl.java @@ -0,0 +1,116 @@ +/* + * + * 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.interop.coordinator.distributedcircuit; + +import org.apache.qpid.test.framework.Assertion; +import org.apache.qpid.test.framework.Circuit; +import org.apache.qpid.test.framework.Publisher; +import org.apache.qpid.test.framework.Receiver; + +import java.util.List; + +/** + * DistributedCircuitImpl is a distributed implementation of the test {@link Circuit}. Many publishers and receivers + * accross multiple machines may be combined to form a single test circuit. The test circuit extracts reports from + * all of its publishers and receivers, and applies its assertions to these reports. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Supply the publishing and receiving ends of a test messaging circuit. + *
Start the circuit running. + *
Close the circuit down. + *
Take a reading of the circuits state. + *
Apply assertions against the circuits state. + *
Send test messages over the circuit. + *
Perform the default test procedue on the circuit. + *
+ */ +public class DistributedCircuitImpl implements Circuit +{ + /** + * Gets the interface on the publishing end of the circuit. + * + * @return The publishing end of the circuit. + */ + public Publisher getPublisher() + { + throw new RuntimeException("Not Implemented."); + } + + /** + * Gets the interface on the receiving end of the circuit. + * + * @return The receiving end of the circuit. + */ + public Receiver getReceiver() + { + throw new RuntimeException("Not Implemented."); + } + + /** + * Connects and starts the circuit. After this method is called the circuit is ready to send messages. + */ + public void start() + { + throw new RuntimeException("Not Implemented."); + } + + /** + * Checks the test circuit. The effect of this is to gather the circuits state, for both ends of the circuit, + * into a report, against which assertions may be checked. + */ + public void check() + { + throw new RuntimeException("Not Implemented."); + } + + /** + * Closes the circuit. All associated resources are closed. + */ + public void close() + { + throw new RuntimeException("Not Implemented."); + } + + /** + * Applied a list of assertions against the test circuit. The {@link #check()} method should be called before doing + * this, to ensure that the circuit has gathered its state into a report to assert against. + * + * @param assertions The list of assertions to apply. + * @return Any assertions that failed. + */ + public List applyAssertions(List assertions) + { + throw new RuntimeException("Not Implemented."); + } + + /** + * Runs the default test procedure against the circuit, and checks that all of the specified assertions hold. + * + * @param numMessages The number of messages to send using the default test procedure. + * @param assertions The list of assertions to apply. + * @return Any assertions that failed. + */ + public List test(int numMessages, List assertions) + { + throw new RuntimeException("Not Implemented."); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/BaseDistributedTestSequencer.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/BaseDistributedTestSequencer.java new file mode 100644 index 0000000000..ad073014d0 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/BaseDistributedTestSequencer.java @@ -0,0 +1,129 @@ +/* + * + * 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.interop.coordinator.sequencers; + +import org.apache.log4j.Logger; + +import org.apache.qpid.interop.coordinator.DistributedTestCase; +import org.apache.qpid.interop.coordinator.TestClientDetails; +import org.apache.qpid.test.framework.Circuit; +import org.apache.qpid.util.ConversationFactory; + +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +/** + *

+ *
CRC Card
Responsibilities Collaborations + *
+ *
+ */ +public abstract class BaseDistributedTestSequencer implements DistributedTestSequencer +{ + /** Used for debugging. */ + private final Logger log = Logger.getLogger(DistributedTestCase.class); + + /** Holds the contact details for the sending test client. */ + protected TestClientDetails sender; + + /** Holds the contact details for the receving test client. */ + protected List receivers = new LinkedList(); + + /** Holds the conversation factory over which to coordinate the test. */ + protected ConversationFactory conversationFactory; + + /** + * Creates a test circuit for the test, configered by the test parameters specified. + * + * @param testProperties The test parameters. + * @return A test circuit. + */ + public Circuit createCircuit(Properties testProperties) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Sets the sender test client to coordinate the test with. + * + * @param sender The contact details of the sending client in the test. + */ + public void setSender(TestClientDetails sender) + { + log.debug("public void setSender(TestClientDetails sender = " + sender + "): called"); + + this.sender = sender; + } + + /** + * Sets the receiving test client to coordinate the test with. + * + * @param receiver The contact details of the sending client in the test. + */ + public void setReceiver(TestClientDetails receiver) + { + log.debug("public void setReceiver(TestClientDetails receivers = " + receiver + "): called"); + + this.receivers.add(receiver); + } + + /** + * Supplies the sending test client. + * + * @return The sending test client. + */ + public TestClientDetails getSender() + { + return sender; + } + + /** + * Supplies the receiving test client. + * + * @return The receiving test client. + */ + public List getReceivers() + { + return receivers; + } + + /** + * Accepts the conversation factory over which to hold the test coordinating conversation. + * + * @param conversationFactory The conversation factory to coordinate the test over. + */ + public void setConversationFactory(ConversationFactory conversationFactory) + { + this.conversationFactory = conversationFactory; + } + + /** + * Provides the conversation factory for providing the distributed test sequencing conversations over the test + * connection. + * + * @return The conversation factory to create test sequencing conversations with. + */ + public ConversationFactory getConversationFactory() + { + return conversationFactory; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/DistributedTestSequencer.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/DistributedTestSequencer.java new file mode 100644 index 0000000000..9b58107796 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/DistributedTestSequencer.java @@ -0,0 +1,75 @@ +/* + * + * 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.interop.coordinator.sequencers; + +import org.apache.qpid.interop.coordinator.TestClientDetails; +import org.apache.qpid.util.ConversationFactory; + +import java.util.List; + +/** + * A DistributedTestSequencer is a test sequencer that coordinates activity amongst many + * {@link org.apache.qpid.interop.testclient.TestClient}s. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Accept notification of test case participants. + *
Accept JMS Connection to carry out the coordination over. + *
Coordinate a test sequence amongst participants. {@link ConversationFactory} + *
+ */ +public interface DistributedTestSequencer extends TestCaseSequencer +{ + /** + * Sets the sender test client to coordinate the test with. + * + * @param sender The contact details of the sending client in the test. + */ + public void setSender(TestClientDetails sender); + + /** + * Sets the receiving test client to coordinate the test with. + * + * @param receiver The contact details of the sending client in the test. + */ + public void setReceiver(TestClientDetails receiver); + + /** + * Supplies the sending test client. + * + * @return The sending test client. + */ + public TestClientDetails getSender(); + + /** + * Supplies the receiving test client. + * + * @return The receiving test client. + */ + public List getReceivers(); + + /** + * Accepts the conversation factory over which to hold the test coordinating conversation. + * + * @param conversationFactory The conversation factory to coordinate the test over. + */ + public void setConversationFactory(ConversationFactory conversationFactory); +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/FanOutTestSequencer.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/FanOutTestSequencer.java new file mode 100644 index 0000000000..5a41481e21 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/FanOutTestSequencer.java @@ -0,0 +1,171 @@ +/* + * + * 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.interop.coordinator.sequencers; + +import org.apache.log4j.Logger; + +import org.apache.qpid.interop.coordinator.TestClientDetails; +import org.apache.qpid.test.framework.Assertion; +import org.apache.qpid.test.framework.Circuit; +import org.apache.qpid.test.framework.TestUtils; +import org.apache.qpid.util.ConversationFactory; + +import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Session; + +import java.util.List; +import java.util.Properties; + +/** + *

+ *
CRC Card
Responsibilities Collaborations + *
+ *
+ */ +public class FanOutTestSequencer extends BaseDistributedTestSequencer +{ + /** Used for debugging. */ + Logger log = Logger.getLogger(FanOutTestSequencer.class); + + /** + * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles, + * begining the test, gathering the test reports from the participants, and checking for assertion failures against + * the test reports. + * + * @param testCircuit The test circuit. + * @param assertions The list of assertions to apply to the test circuit. + * @param testProperties The test case definition. + */ + public void sequenceTest(Circuit testCircuit, List assertions, Properties testProperties) + { + log.debug("protected Message[] sequenceTest(Object... testProperties = " + testProperties + "): called"); + + TestClientDetails sender = getSender(); + List receivers = getReceivers(); + ConversationFactory conversationFactory = getConversationFactory(); + + try + { + // Create a conversation on the sender clients private control rouete. + Session session = conversationFactory.getSession(); + Destination senderControlTopic = session.createTopic(sender.privateControlKey); + ConversationFactory.Conversation senderConversation = conversationFactory.startConversation(); + + // Assign the sender role to the sending test client. + Message assignSender = conversationFactory.getSession().createMessage(); + TestUtils.setPropertiesOnMessage(assignSender, testProperties); + assignSender.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE"); + assignSender.setStringProperty("ROLE", "SENDER"); + assignSender.setStringProperty("CLIENT_NAME", "Sustained_SENDER"); + + senderConversation.send(senderControlTopic, assignSender); + + // Wait for the sender to confirm its role. + senderConversation.receive(); + + // Assign the receivers roles. + for (TestClientDetails receiver : receivers) + { + assignReceiverRole(receiver, testProperties, true); + } + + // Start the test on the sender. + Message start = session.createMessage(); + start.setStringProperty("CONTROL_TYPE", "START"); + + senderConversation.send(senderControlTopic, start); + + // Wait for the test sender to return its report. + Message senderReport = senderConversation.receive(); + TestUtils.pause(500); + + // Ask the receivers for their reports. + Message statusRequest = session.createMessage(); + statusRequest.setStringProperty("CONTROL_TYPE", "STATUS_REQUEST"); + + // Gather the reports from all of the receiving clients. + + // Return all of the test reports, the senders report first. + // return new Message[] { senderReport }; + } + catch (JMSException e) + { + throw new RuntimeException("Unhandled JMSException."); + } + } + + /** + * Creates a test circuit for the test, configered by the test parameters specified. + * + * @param testProperties The test parameters. + * @return A test circuit. + */ + public Circuit createCircuit(ParsedProperties testProperties) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Assigns the receivers role to the specified test client that is to act as a receivers during the test. This method + * does not always wait for the receiving clients to confirm their role assignments. This is because this method + * may be called from an 'onMessage' method, when a client is joining the test at a later point in time, and it + * is not possible to do a synchronous receive during an 'onMessage' method. There is a flag to indicate whether + * or not to wait for role confirmations. + * + * @param receiver The test client to assign the receivers role to. + * @param testProperties The test parameters. + * @param confirm Indicates whether role confirmation should be waited for. + * + * @throws JMSException Any JMSExceptions occurring during the conversation are allowed to fall through. + */ + protected void assignReceiverRole(TestClientDetails receiver, Properties testProperties, boolean confirm) + throws JMSException + { + log.info("assignReceiverRole(TestClientDetails receivers = " + receiver + ", Map testProperties = " + + testProperties + "): called"); + + ConversationFactory conversationFactory = getConversationFactory(); + + // Create a conversation with the receiving test client. + Session session = conversationFactory.getSession(); + Destination receiverControlTopic = session.createTopic(receiver.privateControlKey); + ConversationFactory.Conversation receiverConversation = conversationFactory.startConversation(); + + // Assign the receivers role to the receiving client. + Message assignReceiver = session.createMessage(); + TestUtils.setPropertiesOnMessage(assignReceiver, testProperties); + assignReceiver.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE"); + assignReceiver.setStringProperty("ROLE", "RECEIVER"); + assignReceiver.setStringProperty("CLIENT_NAME", receiver.clientName); + + receiverConversation.send(receiverControlTopic, assignReceiver); + + // Wait for the role confirmation to come back. + if (confirm) + { + receiverConversation.receive(); + } + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/InteropTestSequencer.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/InteropTestSequencer.java new file mode 100644 index 0000000000..0230bf4d77 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/InteropTestSequencer.java @@ -0,0 +1,137 @@ +/* + * + * 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.interop.coordinator.sequencers; + +import org.apache.log4j.Logger; + +import org.apache.qpid.interop.coordinator.TestClientDetails; +import org.apache.qpid.test.framework.Assertion; +import org.apache.qpid.test.framework.Circuit; +import org.apache.qpid.test.framework.TestUtils; +import org.apache.qpid.util.ConversationFactory; + +import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Session; + +import java.util.List; +import java.util.Properties; + +/** + *

+ *
CRC Card
Responsibilities Collaborations + *
+ *
+ */ +public class InteropTestSequencer extends BaseDistributedTestSequencer +{ + /** Used for debugging. */ + Logger log = Logger.getLogger(InteropTestSequencer.class); + + /** + * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles, + * begining the test, gathering the test reports from the participants, and checking for assertion failures against + * the test reports. + * + * @param testCircuit The test circuit. + * @param assertions The list of assertions to apply to the test circuit. + * @param testProperties The test case definition. + */ + public void sequenceTest(Circuit testCircuit, List assertions, Properties testProperties) + { + log.debug("protected Message[] sequenceTest(Object... testProperties = " + testProperties + "): called"); + + TestClientDetails sender = getSender(); + List receivers = getReceivers(); + ConversationFactory conversationFactory = getConversationFactory(); + + try + { + Session session = conversationFactory.getSession(); + Destination senderControlTopic = session.createTopic(sender.privateControlKey); + Destination receiverControlTopic = session.createTopic(receivers.get(0).privateControlKey); + + ConversationFactory.Conversation senderConversation = conversationFactory.startConversation(); + ConversationFactory.Conversation receiverConversation = conversationFactory.startConversation(); + + // Assign the sender role to the sending test client. + Message assignSender = conversationFactory.getSession().createMessage(); + TestUtils.setPropertiesOnMessage(assignSender, testProperties); + assignSender.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE"); + assignSender.setStringProperty("ROLE", "SENDER"); + + senderConversation.send(senderControlTopic, assignSender); + + // Assign the receivers role the receiving client. + Message assignReceiver = session.createMessage(); + TestUtils.setPropertiesOnMessage(assignReceiver, testProperties); + assignReceiver.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE"); + assignReceiver.setStringProperty("ROLE", "RECEIVER"); + + receiverConversation.send(receiverControlTopic, assignReceiver); + + // Wait for the senders and receivers to confirm their roles. + senderConversation.receive(); + receiverConversation.receive(); + + // Start the test. + Message start = session.createMessage(); + start.setStringProperty("CONTROL_TYPE", "START"); + + senderConversation.send(senderControlTopic, start); + + // Wait for the test sender to return its report. + Message senderReport = senderConversation.receive(); + TestUtils.pause(500); + + // Ask the receivers for its report. + Message statusRequest = session.createMessage(); + statusRequest.setStringProperty("CONTROL_TYPE", "STATUS_REQUEST"); + + receiverConversation.send(receiverControlTopic, statusRequest); + + // Wait for the receivers to send its report. + Message receiverReport = receiverConversation.receive(); + + // return new Message[] { senderReport, receiverReport }; + + // Apply assertions. + } + catch (JMSException e) + { + throw new RuntimeException("JMSException not handled."); + } + } + + /** + * Creates a test circuit for the test, configered by the test parameters specified. + * + * @param testProperties The test parameters. + * @return A test circuit. + */ + public Circuit createCircuit(ParsedProperties testProperties) + { + throw new RuntimeException("Not implemented."); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/TestCaseSequencer.java b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/TestCaseSequencer.java new file mode 100644 index 0000000000..94105f30a8 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/interop/coordinator/sequencers/TestCaseSequencer.java @@ -0,0 +1,66 @@ +/* + * + * 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.interop.coordinator.sequencers; + +import org.apache.qpid.test.framework.Assertion; +import org.apache.qpid.test.framework.Circuit; + +import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; + +import javax.jms.JMSException; +import javax.jms.Message; + +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * A TestCaseSequence is responsibile for creating test circuits appropriate to the context that a test case is + * running in, and providing an implementation of a standard test procedure over a test circuit. + * + *

+ *
CRC Card
Responsibilities + *
Provide a standard test procedure over a test circuit. + *
Construct test circuits appropriate to a tests context. + *
+ */ +public interface TestCaseSequencer +{ + /** + * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles, + * begining the test, gathering the test reports from the participants, and checking for assertion failures against + * the test reports. + * + * @param testCircuit The test circuit. + * @param assertions The list of assertions to apply to the test circuit. + * @param testProperties The test case definition. + */ + public void sequenceTest(Circuit testCircuit, List assertions, Properties testProperties); + + /** + * Creates a test circuit for the test, configered by the test parameters specified. + * + * @param testProperties The test parameters. + * + * @return A test circuit. + */ + public Circuit createCircuit(ParsedProperties testProperties); +} diff --git a/java/systests/src/main/java/org/apache/qpid/server/exchange/ImmediateMessageTest.java b/java/systests/src/main/java/org/apache/qpid/server/exchange/ImmediateMessageTest.java index c2b7a2094f..6dd0a08198 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/exchange/ImmediateMessageTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/exchange/ImmediateMessageTest.java @@ -20,8 +20,8 @@ */ package org.apache.qpid.server.exchange; +import org.apache.qpid.interop.coordinator.sequencers.TestCaseSequencer; import org.apache.qpid.test.framework.Circuit; -import org.apache.qpid.test.framework.CircuitImpl; import org.apache.qpid.test.framework.FrameworkBaseCase; import org.apache.qpid.test.framework.MessagingTestConfigProperties; import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*; @@ -67,6 +67,16 @@ public class ImmediateMessageTest extends FrameworkBaseCase /** Used to read the tests configurable properties through. */ ParsedProperties testProps; + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public ImmediateMessageTest(String name) + { + super(name); + } + /** Check that an immediate message is sent succesfully not using transactions when a consumer is connected. */ public void test_QPID_517_ImmediateOkNoTxP2P() { @@ -74,10 +84,10 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + // Run the default test sequence over the test circuit checking for no errors. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** Check that an immediate message is committed succesfully in a transaction when a consumer is connected. */ @@ -87,10 +97,10 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** Check that an immediate message results in no consumers code, not using transactions, when a consumer is disconnected. */ @@ -100,13 +110,14 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); // Disconnect the consumer. testCircuit.getReceiver().getConsumer().close(); // Send one message and get a linked no consumers exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noConsumersAssertion()))); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noConsumersAssertion()), testProps); } /** Check that an immediate message results in no consumers code, in a transaction, when a consumer is disconnected. */ @@ -116,13 +127,14 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); // Disconnect the consumer. testCircuit.getReceiver().getConsumer().close(); // Send one message and get a linked no consumers exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noConsumersAssertion()))); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noConsumersAssertion()), testProps); } /** Check that an immediate message results in no route code, not using transactions, when no outgoing route is connected. */ @@ -132,14 +144,14 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, false); - // Set up the messaging topology so that only the publishers producer is bound (do not set up the receiver to + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to // collect its messages). testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - // Send one message and get a linked no route exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noRouteAssertion()))); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noRouteAssertion()), testProps); } /** Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is connected. */ @@ -149,14 +161,14 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, false); - // Set up the messaging topology so that only the publishers producer is bound (do not set up the receiver to + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to // collect its messages). testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message and get a linked no consumers exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noRouteAssertion()))); + // Send one message and get a linked no route exception. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noRouteAssertion()), testProps); } /** Check that an immediate message is sent succesfully not using transactions when a consumer is connected. */ @@ -166,10 +178,10 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, true); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** Check that an immediate message is committed succesfully in a transaction when a consumer is connected. */ @@ -179,10 +191,10 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, true); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** Check that an immediate message results in no consumers code, not using transactions, when a consumer is disconnected. */ @@ -195,13 +207,14 @@ public class ImmediateMessageTest extends FrameworkBaseCase // Use durable subscriptions, so that the route remains open with no subscribers. testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); // Disconnect the consumer. testCircuit.getReceiver().getConsumer().close(); // Send one message and get a linked no consumers exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noConsumersAssertion()))); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noConsumersAssertion()), testProps); } /** Check that an immediate message results in no consumers code, in a transaction, when a consumer is disconnected. */ @@ -214,13 +227,14 @@ public class ImmediateMessageTest extends FrameworkBaseCase // Use durable subscriptions, so that the route remains open with no subscribers. testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); // Disconnect the consumer. testCircuit.getReceiver().getConsumer().close(); // Send one message and get a linked no consumers exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noConsumersAssertion()))); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noConsumersAssertion()), testProps); } /** Check that an immediate message results in no route code, not using transactions, when no outgoing route is connected. */ @@ -230,14 +244,14 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, true); - // Set up the messaging topology so that only the publishers producer is bound (do not set up the receiver to + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to // collect its messages). testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - // Send one message and get a linked no route exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noRouteAssertion()))); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noRouteAssertion()), testProps); } /** Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is connected. */ @@ -247,14 +261,14 @@ public class ImmediateMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, true); - // Set up the messaging topology so that only the publishers producer is bound (do not set up the receiver to + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to // collect its messages). testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - // Send one message and get a linked no route exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noRouteAssertion()))); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noRouteAssertion()), testProps); } protected void setUp() throws Exception diff --git a/java/systests/src/main/java/org/apache/qpid/server/exchange/MandatoryMessageTest.java b/java/systests/src/main/java/org/apache/qpid/server/exchange/MandatoryMessageTest.java index 66695b93dc..987a458121 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/exchange/MandatoryMessageTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/exchange/MandatoryMessageTest.java @@ -20,8 +20,8 @@ */ package org.apache.qpid.server.exchange; +import org.apache.qpid.interop.coordinator.sequencers.TestCaseSequencer; import org.apache.qpid.test.framework.Circuit; -import org.apache.qpid.test.framework.CircuitImpl; import org.apache.qpid.test.framework.FrameworkBaseCase; import org.apache.qpid.test.framework.MessagingTestConfigProperties; import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*; @@ -67,6 +67,16 @@ public class MandatoryMessageTest extends FrameworkBaseCase /** Used to read the tests configurable properties through. */ ParsedProperties testProps; + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public MandatoryMessageTest(String name) + { + super(name); + } + /** Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. */ public void test_QPID_508_MandatoryOkNoTxP2P() { @@ -74,10 +84,10 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + // Run the default test sequence over the test circuit checking for no errors. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. */ @@ -87,10 +97,10 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + // Run the default test sequence over the test circuit checking for no errors. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** @@ -103,13 +113,14 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); // Disconnect the consumer. testCircuit.getReceiver().getConsumer().close(); // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** @@ -122,13 +133,14 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); // Disconnect the consumer. testCircuit.getReceiver().getConsumer().close(); // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** Check that an mandatory message results in no route code, not using transactions, when no consumer is connected. */ @@ -138,14 +150,14 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, false); - // Set up the messaging topology so that only the publishers producer is bound (do not set up the receiver to + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to // collect its messages). testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message and get a linked no consumers exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noRouteAssertion()))); + // Send one message and get a linked no route exception. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noRouteAssertion()), testProps); } /** Check that an mandatory message results in no route code, upon transaction commit, when a consumer is connected. */ @@ -155,14 +167,14 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, false); - // Set up the messaging topology so that only the publishers producer is bound (do not set up the receiver to + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to // collect its messages). testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message and get a linked no consumers exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noRouteAssertion()))); + // Send one message and get a linked no route exception. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noRouteAssertion()), testProps); } /** Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. */ @@ -172,10 +184,10 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, true); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + // Run the default test sequence over the test circuit checking for no errors. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. */ @@ -185,10 +197,10 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, true); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + // Run the default test sequence over the test circuit checking for no errors. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** @@ -204,13 +216,14 @@ public class MandatoryMessageTest extends FrameworkBaseCase // Use durable subscriptions, so that the route remains open with no subscribers. testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); // Disconnect the consumer. testCircuit.getReceiver().getConsumer().close(); // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** @@ -226,13 +239,14 @@ public class MandatoryMessageTest extends FrameworkBaseCase // Use durable subscriptions, so that the route remains open with no subscribers. testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); // Disconnect the consumer. testCircuit.getReceiver().getConsumer().close(); // Send one message with no errors. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion()))); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noExceptionsAssertion()), testProps); } /** Check that an mandatory message results in no route code, not using transactions, when no consumer is connected. */ @@ -242,14 +256,14 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, false); testProps.setProperty(PUBSUB_PROPNAME, true); - // Set up the messaging topology so that only the publishers producer is bound (do not set up the receiver to + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to // collect its messages). testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message and get a linked no consumers exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noRouteAssertion()))); + // Send one message and get a linked no route exception. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noRouteAssertion()), testProps); } /** Check that an mandatory message results in no route code, upon transaction commit, when a consumer is connected. */ @@ -259,14 +273,14 @@ public class MandatoryMessageTest extends FrameworkBaseCase testProps.setProperty(TRANSACTED_PROPNAME, true); testProps.setProperty(PUBSUB_PROPNAME, true); - // Set up the messaging topology so that only the publishers producer is bound (do not set up the receiver to + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to // collect its messages). testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); - Circuit testCircuit = CircuitImpl.createCircuit(testProps); - - // Send one message and get a linked no consumers exception. - assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noRouteAssertion()))); + // Send one message and get a linked no route exception. + TestCaseSequencer sequencer = getTestSequencer(); + Circuit testCircuit = sequencer.createCircuit(testProps); + sequencer.sequenceTest(testCircuit, assertionList(testCircuit.getPublisher().noRouteAssertion()), testProps); } protected void setUp() throws Exception diff --git a/java/systests/src/main/java/org/apache/qpid/server/exchange/ReturnUnroutableMandatoryMessageTest.java b/java/systests/src/main/java/org/apache/qpid/server/exchange/ReturnUnroutableMandatoryMessageTest.java index 542adf4708..ebbffd8a71 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/exchange/ReturnUnroutableMandatoryMessageTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/exchange/ReturnUnroutableMandatoryMessageTest.java @@ -24,7 +24,6 @@ package org.apache.qpid.server.exchange; import junit.framework.TestCase; import org.apache.log4j.Logger; import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.util.TestApplicationRegistry; import org.apache.qpid.server.util.NullApplicationRegistry; import org.apache.qpid.client.*; import org.apache.qpid.client.transport.TransportConnection; @@ -127,8 +126,8 @@ public class ReturnUnroutableMandatoryMessageTest extends TestCase implements Ex con.start(); TextMessage tm = (TextMessage) consumer.receive(1000L); - assertTrue("No message routed to receiver", tm != null); - assertTrue("Wrong message routed to receiver: " + tm.getText(), "msg3".equals(tm.getText())); + assertTrue("No message routed to receivers", tm != null); + assertTrue("Wrong message routed to receivers: " + tm.getText(), "msg3".equals(tm.getText())); try { @@ -194,8 +193,8 @@ public class ReturnUnroutableMandatoryMessageTest extends TestCase implements Ex con.start(); TextMessage tm = (TextMessage) consumer.receive(1000L); - assertTrue("No message routed to receiver", tm != null); - assertTrue("Wrong message routed to receiver: " + tm.getText(), "msg1".equals(tm.getText())); + assertTrue("No message routed to receivers", tm != null); + assertTrue("Wrong message routed to receivers: " + tm.getText(), "msg1".equals(tm.getText())); try { @@ -260,8 +259,8 @@ public class ReturnUnroutableMandatoryMessageTest extends TestCase implements Ex con.start(); TextMessage tm = (TextMessage) consumer.receive(1000L); - assertTrue("No message routed to receiver", tm != null); - assertTrue("Wrong message routed to receiver: " + tm.getText(), "msg1".equals(tm.getText())); + assertTrue("No message routed to receivers", tm != null); + assertTrue("Wrong message routed to receivers: " + tm.getText(), "msg1".equals(tm.getText())); try { diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/PersistentTestManual.java b/java/systests/src/main/java/org/apache/qpid/server/queue/PersistentTestManual.java index 5abbbd2aae..d01284cb8a 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/PersistentTestManual.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/PersistentTestManual.java @@ -1,39 +1,41 @@ /* - * 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 + * 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 * - * 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. + * 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.server.queue; -import org.apache.qpid.client.AMQConnection; -import org.apache.qpid.client.AMQSession; -import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + import org.apache.qpid.AMQChannelClosedException; import org.apache.qpid.AMQConnectionClosedException; -import org.apache.qpid.util.CommandLineParser; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQSession; import org.apache.qpid.url.URLSyntaxException; -import org.apache.log4j.Logger; +import org.apache.qpid.util.CommandLineParser; -import javax.jms.Session; import javax.jms.JMSException; -import javax.jms.Queue; import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; import javax.jms.TextMessage; + import java.io.IOException; import java.util.Properties; @@ -41,7 +43,6 @@ public class PersistentTestManual { private static final Logger _logger = Logger.getLogger(PersistentTestManual.class); - private static final String QUEUE = "direct://amq.direct//PersistentTest-Queue2?durable='true',exclusive='true'"; protected AMQConnection _connection; @@ -89,7 +90,7 @@ public class PersistentTestManual public void test() throws AMQException, URLSyntaxException { - //Create the Durable Queue + // Create the Durable Queue try { _session.createConsumer(_session.createQueue(QUEUE)).close(); @@ -121,16 +122,17 @@ public class PersistentTestManual System.out.println("Continuing...."); } - //Test queue is still there. - AMQConnection connection = new AMQConnection(_brokerDetails, _username, _password, "DifferentClientID", _virtualpath); + // Test queue is still there. + AMQConnection connection = + new AMQConnection(_brokerDetails, _username, _password, "DifferentClientID", _virtualpath); AMQSession session = (AMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE); try { session.createConsumer(session.createQueue(QUEUE)); - _logger.error("Create consumer succeeded." + - " This shouldn't be allowed as this means the queue didn't exist when it should"); + _logger.error("Create consumer succeeded." + + " This shouldn't be allowed as this means the queue didn't exist when it should"); connection.close(); @@ -189,6 +191,7 @@ public class PersistentTestManual { // } + System.exit(0); } @@ -196,7 +199,7 @@ public class PersistentTestManual { String TEST_TEXT = "init"; - //Create a new session to send producer + // Create a new session to send producer Session session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Queue q = session.createQueue(QUEUE); @@ -204,10 +207,9 @@ public class PersistentTestManual producer.send(session.createTextMessage(TEST_TEXT)); - //create a new consumer on the original session + // create a new consumer on the original session TextMessage m = (TextMessage) _session.createConsumer(q).receive(); - if ((m != null) && m.getText().equals(TEST_TEXT)) { return true; @@ -216,6 +218,7 @@ public class PersistentTestManual { _logger.error("Incorrect values returned from Queue Test:" + m); System.exit(0); + return false; } } @@ -259,8 +262,8 @@ public class PersistentTestManual { PersistentTestManual test; - Properties options = CommandLineParser.processCommandLine(args, new CommandLineParser(new String[][]{})); - + Properties options = + CommandLineParser.processCommandLine(args, new CommandLineParser(new String[][] {}), System.getProperties()); test = new PersistentTestManual(options); try diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/Circuit.java b/java/systests/src/main/java/org/apache/qpid/test/framework/Circuit.java index 74c14ee6c5..a041c0b838 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/Circuit.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/Circuit.java @@ -24,7 +24,7 @@ import java.util.List; /** * A Circuit is the basic test unit against which test cases are to be written. A circuit consists of two 'ends', an - * instigating 'publisher' end and a more passive 'receiver' end. + * instigating 'publisher' end and a more passive 'receivers' end. * *

Once created, the life-cycle of a circuit may be controlled by {@link #start()}ing it, or {@link #close()}ing it. * Once started, the circuit is ready to send messages over. Once closed the circuit can no longer be used. @@ -97,10 +97,10 @@ public interface Circuit */ public List applyAssertions(List assertions); - /** + /* * Sends a message on the test circuit. The exact nature of the message sent is controlled by the test parameters. */ - public void send(); + // public void send(); /** * Runs the default test procedure against the circuit, and checks that all of the specified assertions hold. diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitImpl.java index 70bd16353c..0bcf2e5036 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitImpl.java @@ -20,8 +20,6 @@ */ package org.apache.qpid.test.framework; -import junit.framework.Assert; - import org.apache.qpid.client.AMQSession; import org.apache.qpid.test.framework.MessageMonitor; @@ -49,6 +47,9 @@ import java.util.concurrent.atomic.AtomicLong; * Perform the default test procedure on the circuit. * Provide access to connection and session exception monitors {@link ExceptionMonitor} * + * + * @todo Add ability to create routes with no consumers active on them. Immediate/Mandatory tests are closing consumers + * themsleves to create this scenario. Should make it part of the test configuration. */ public class CircuitImpl implements Circuit { @@ -74,12 +75,12 @@ public class CircuitImpl implements Circuit private ExceptionMonitor exceptionMonitor; /** - * Creates a test circuit using the specified test parameters. The publisher, receiver, connection and + * Creates a test circuit using the specified test parameters. The publisher, receivers, connection and * connection monitor must already have been created, to assemble the circuit. * * @param testProps The test parameters. * @param publisher The test publisher. - * @param receiver The test receiver. + * @param receiver The test receivers. * @param connection The connection. * @param connectionExceptionMonitor The connection exception monitor. */ @@ -93,7 +94,7 @@ public class CircuitImpl implements Circuit this.connectionExceptionMonitor = connectionExceptionMonitor; this.exceptionMonitor = new ExceptionMonitor(); - // Set this as the parent circuit on the publisher and receiver. + // Set this as the parent circuit on the publisher and receivers. publisher.setCircuit(this); receiver.setCircuit(this); } @@ -107,9 +108,11 @@ public class CircuitImpl implements Circuit */ public static Circuit createCircuit(ParsedProperties testProps) { - // Create a standard publisher/receiver test client pair on a shared connection, individual sessions. + // Create a standard publisher/receivers test client pair on a shared connection, individual sessions. try { + // ParsedProperties testProps = new ParsedProperties(testProps); + // Get a unique offset to append to destination names to make them unique to the connection. long uniqueId = uniqueDestsId.incrementAndGet(); @@ -211,7 +214,7 @@ public class CircuitImpl implements Circuit } catch (JMSException e) { - throw new RuntimeException("Could not create publisher/receiver pair due to a JMSException.", e); + throw new RuntimeException("Could not create publisher/receivers pair due to a JMSException.", e); } } @@ -348,7 +351,7 @@ public class CircuitImpl implements Circuit // Apply all of the requested assertions, keeping record of any that fail. List failures = applyAssertions(assertions); - // Clean up the publisher/receiver/session/connections. + // Clean up the publisher/receivers/session/connections. close(); // Return any failed assertions to the caller. diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java index 9ce44305ad..0c24836b27 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java @@ -25,11 +25,18 @@ import junit.framework.TestCase; import org.apache.log4j.NDC; import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.interop.coordinator.sequencers.TestCaseSequencer; import org.apache.qpid.server.registry.ApplicationRegistry; +import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; + +import javax.jms.JMSException; +import javax.jms.Message; + import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Properties; /** * FrameworkBaseCase provides a starting point for writing test cases against the test framework. Its main purpose is @@ -45,6 +52,42 @@ import java.util.List; */ public class FrameworkBaseCase extends TestCase { + /** Holds the test sequencer to create and run test circuits with. */ + protected TestCaseSequencer testSequencer = new DefaultTestSequencer(); + + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public FrameworkBaseCase(String name) + { + super(name); + } + + /** + * Returns the test case sequencer that provides test circuit, and test sequence implementations. The sequencer + * that this base case returns by default is suitable for running a test circuit with both circuit ends colocated + * on the same JVM. + * + * @return The test case sequencer. + */ + protected TestCaseSequencer getTestSequencer() + { + return testSequencer; + } + + /** + * Overrides the default test sequencer. Test decorators can use this to supply distributed test sequencers or other + * test sequencer specializations. + * + * @param sequencer The new test sequencer. + */ + public void setTestSequencer(TestCaseSequencer sequencer) + { + this.testSequencer = sequencer; + } + /** * Creates a list of assertions. * @@ -133,4 +176,35 @@ public class FrameworkBaseCase extends TestCase NDC.pop(); } } + + /** + * DefaultTestSequencer is a test sequencer that creates test circuits with publishing and receiving ends rooted + * on the same JVM. + */ + public class DefaultTestSequencer implements TestCaseSequencer + { + /** + * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles, + * begining the test and gathering the test reports from the participants. + * + * @param testCircuit The test circuit. + * @param assertions The list of assertions to apply to the test circuit. + * @param testProperties The test case definition. + */ + public void sequenceTest(Circuit testCircuit, List assertions, Properties testProperties) + { + assertNoFailures(testCircuit.test(1, assertions)); + } + + /** + * Creates a test circuit for the test, configered by the test parameters specified. + * + * @param testProperties The test parameters. + * @return A test circuit. + */ + public Circuit createCircuit(ParsedProperties testProperties) + { + return CircuitImpl.createCircuit(testProperties); + } + } } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java b/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java index 62229f3c72..3cc4a92886 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java @@ -48,7 +48,7 @@ import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; * destinationCount 1 The number of receivers listening to the pings. * timeout 30000 In milliseconds. The timeout to stop waiting for replies. * commitBatchSize 1 The number of messages per transaction in transactional mode. - * uniqueDests true Whether each receiver only listens to one ping destination or all. + * uniqueDests true Whether each receivers only listens to one ping destination or all. * durableDests false Whether or not durable destinations are used. * ackMode AUTO_ACK The message acknowledgement mode. Possible values are: * 0 - SESSION_TRANSACTED @@ -81,12 +81,6 @@ public class MessagingTestConfigProperties /** Defines the class to use as the initial context factory by default. */ public static final String INITIAL_CONTEXT_FACTORY_DEFAULT = "org.apache.qpid.jndi.PropertiesFileInitialContextFactory"; - /** Holds the name of the default connection factory configuration property. */ - public static final String CONNECTION_PROPNAME = "connectionfactory.broker"; - - /** Defeins the default connection configuration. */ - public static final String CONNECTION_DEFAULT = "amqp://guest:guest@clientid/?brokerlist='vm://:1'"; - /** Holds the name of the property to get the test broker url from. */ public static final String BROKER_PROPNAME = "qpid.test.broker"; @@ -125,16 +119,16 @@ public class MessagingTestConfigProperties /** Holds the default value of the publisher consumer flag. */ public static final boolean PUBLISHER_CONSUMER_BIND_DEFAULT = false; - /** Holds the name of the property to get the bind receiver procuder flag from. */ + /** Holds the name of the property to get the bind receivers procuder flag from. */ public static final String RECEIVER_PRODUCER_BIND_PROPNAME = "receiverProducerBind"; - /** Holds the default value of the receiver producer flag. */ + /** Holds the default value of the receivers producer flag. */ public static final boolean RECEIVER_PRODUCER_BIND_DEFAULT = false; - /** Holds the name of the property to get the bind receiver procuder flag from. */ + /** Holds the name of the property to get the bind receivers procuder flag from. */ public static final String RECEIVER_CONSUMER_BIND_PROPNAME = "receiverConsumerBind"; - /** Holds the default value of the receiver consumer flag. */ + /** Holds the default value of the receivers consumer flag. */ public static final boolean RECEIVER_CONSUMER_BIND_DEFAULT = true; /** Holds the name of the property to get the destination name root from. */ @@ -275,7 +269,7 @@ public class MessagingTestConfigProperties static { defaults.setPropertyIfNull(INITIAL_CONTEXT_FACTORY_PROPNAME, INITIAL_CONTEXT_FACTORY_DEFAULT); - defaults.setPropertyIfNull(CONNECTION_PROPNAME, CONNECTION_DEFAULT); + // defaults.setPropertyIfNull(CONNECTION_PROPNAME, CONNECTION_DEFAULT); defaults.setPropertyIfNull(MESSAGE_SIZE_PROPNAME, MESSAGE_SIZE_DEAFULT); defaults.setPropertyIfNull(PUBLISHER_PRODUCER_BIND_PROPNAME, PUBLISHER_PRODUCER_BIND_DEFAULT); defaults.setPropertyIfNull(PUBLISHER_CONSUMER_BIND_PROPNAME, PUBLISHER_CONSUMER_BIND_DEFAULT); diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java b/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java index a79e29ef67..6e01a7ea4f 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java @@ -22,27 +22,27 @@ package org.apache.qpid.test.framework; /** * A Receiver is a {@link CircuitEnd} that represents one end of a test circuit. Its main purpose is to - * provide assertions that can be applied to test the behaviour of the receiver. + * provide assertions that can be applied to test the behaviour of the receivers. * *

*
CRC Card
Responsibilities - *
Provide assertion that the receiver received no exceptions. - *
Provide assertion that the receiver received all test messages sent to it. + *
Provide assertion that the receivers received no exceptions. + *
Provide assertion that the receivers received all test messages sent to it. *
*/ public interface Receiver extends CircuitEnd { /** - * Provides an assertion that the receiver encountered no exceptions. + * Provides an assertion that the receivers encountered no exceptions. * - * @return An assertion that the receiver encountered no exceptions. + * @return An assertion that the receivers encountered no exceptions. */ public Assertion noExceptionsAssertion(); /** - * Provides an assertion that the receiver got all messages that were sent to it. + * Provides an assertion that the receivers got all messages that were sent to it. * - * @return An assertion that the receiver got all messages that were sent to it. + * @return An assertion that the receivers got all messages that were sent to it. */ public Assertion allMessagesAssertion(); } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/ReceiverImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/ReceiverImpl.java index 06ca8e8cc3..ce783ed9e0 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/ReceiverImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/ReceiverImpl.java @@ -32,8 +32,8 @@ import javax.jms.Session; * Responsibilities Collaborations * Provide a message producer for sending messages. * Provide a message consumer for receiving messages. - * Provide assertion that the receiver received no exceptions. - * Provide assertion that the receiver received all test messages sent to it. + * Provide assertion that the receivers received no exceptions. + * Provide assertion that the receivers received all test messages sent to it. * */ public class ReceiverImpl extends CircuitEndBase implements Receiver @@ -54,9 +54,9 @@ public class ReceiverImpl extends CircuitEndBase implements Receiver } /** - * Provides an assertion that the receiver encountered no exceptions. + * Provides an assertion that the receivers encountered no exceptions. * - * @return An assertion that the receiver encountered no exceptions. + * @return An assertion that the receivers encountered no exceptions. */ public Assertion noExceptionsAssertion() { @@ -64,9 +64,9 @@ public class ReceiverImpl extends CircuitEndBase implements Receiver } /** - * Provides an assertion that the receiver got all messages that were sent to it. + * Provides an assertion that the receivers got all messages that were sent to it. * - * @return An assertion that the receiver got all messages that were sent to it. + * @return An assertion that the receivers got all messages that were sent to it. */ public Assertion allMessagesAssertion() { diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java b/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java index c3d68ce66c..8b3e72ef08 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java @@ -20,6 +20,8 @@ */ package org.apache.qpid.test.framework; +import org.apache.log4j.Logger; + import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*; import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; @@ -27,10 +29,14 @@ import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.JMSException; +import javax.jms.Message; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; +import java.util.Properties; +import java.util.Map; + /** * TestUtils provides static helper methods that are usefull for writing tests against QPid. * @@ -42,18 +48,34 @@ import javax.naming.NamingException; */ public class TestUtils { + /** Used for debugging. */ + private static Logger log = Logger.getLogger(TestUtils.class); + /** - * Establishes a JMS connection using a properties file and qpids built in JNDI implementation. This is a simple - * convenience method for code that does anticipate handling connection failures. All exceptions that indicate - * that the connection has failed, are wrapped as rutime exceptions, preumably handled by a top level failure + * Establishes a JMS connection using a set of properties and qpids built in JNDI implementation. This is a simple + * convenience method for code that does not anticipate handling connection failures. All exceptions that indicate + * that the connection has failed, are wrapped as rutime exceptions, presumably handled by a top level failure * handler. * + *

This utility makes use of the following test parameters from {@link MessagingTestConfigProperties} to control + * the connection creation: + * + *

+ *
{@link MessagingTestConfigProperties#USERNAME_PROPNAME} The username. + *
{@link MessagingTestConfigProperties#PASSWORD_PROPNAME} The password. + *
{@link MessagingTestConfigProperties#VIRTUAL_HOST_PROPNAME} The virtual host name. + *
{@link MessagingTestConfigProperties#BROKER_PROPNAME} The broker URL. + *
{@link MessagingTestConfigProperties#CONNECTION_NAME} The broker name in the initial context. + * * @param messagingProps Connection properties as defined in {@link MessagingTestConfigProperties}. * * @return A JMS conneciton. */ public static Connection createConnection(ParsedProperties messagingProps) { + log.debug("public static Connection createConnection(ParsedProperties messagingProps = " + messagingProps + + "): called"); + try { // Extract the configured connection properties from the test configuration. @@ -62,12 +84,14 @@ public class TestUtils String virtualHost = messagingProps.getProperty(VIRTUAL_HOST_PROPNAME); String brokerUrl = messagingProps.getProperty(BROKER_PROPNAME); - // Set up the broker connection url. + // Create the broker connection url. String connectionString = - "amqp://" + conUsername + ":" + conPassword + "/" + ((virtualHost != null) ? virtualHost : "") + "amqp://" + conUsername + ":" + conPassword + "@clientid/" + ((virtualHost != null) ? virtualHost : "") + "?brokerlist='" + brokerUrl + "'"; - // messagingProps.setProperty(CONNECTION_PROPNAME, connectionString); + // Create properties to create the initial context from, and inject the connection factory configuration + // for the defined connection name into it. + messagingProps.setProperty("connectionfactory." + CONNECTION_NAME, connectionString); Context ctx = new InitialContext(messagingProps); @@ -108,4 +132,25 @@ public class TestUtils throw new RuntimeException("Failed to generate the requested pause length.", e); } } + + /** + * Sets properties of different types on a JMS Message. + * + * @param message The message to set properties on. + * @param properties The property name/value pairs to set. + * + * @throws javax.jms.JMSException All underlying JMSExceptions are allowed to fall through. + * + * @todo Move this helper method somewhere else. For example, TestUtils. + */ + public static void setPropertiesOnMessage(Message message, Map properties) throws JMSException + { + for (Map.Entry entry : properties.entrySet()) + { + String name = entry.getKey().toString(); + Object value = entry.getValue(); + + message.setObjectProperty(name, value); + } + } } diff --git a/java/systests/src/main/java/org/apache/qpid/util/ClasspathScanner.java b/java/systests/src/main/java/org/apache/qpid/util/ClasspathScanner.java new file mode 100644 index 0000000000..bad49060ca --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/util/ClasspathScanner.java @@ -0,0 +1,234 @@ +/* + * + * 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.util; + +import java.io.File; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; + +/** + * An ClasspathScanner scans the classpath for classes that implement an interface or extend a base class and have names + * that match a regular expression. + * + *

In order to test whether a class implements an interface or extends a class, the class must be loaded (unless + * the class files were to be scanned directly). Using this collector can cause problems when it scans the classpath, + * because loading classes will initialize their statics, which in turn may cause undesired side effects. For this + * reason, the collector should always be used with a regular expression, through which the class file names are + * filtered, and only those that pass this filter will be tested. For example, if you define tests in classes that + * end with the keyword "Test" then use the regular expression "Test$" to match this. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Find all classes matching type and name pattern on the classpath. + *
+ * + * @todo Add logic to scan jars as well as directories. + */ +public class ClasspathScanner +{ + private static final Logger log = Logger.getLogger(ClasspathScanner.class); + + /** + * Scans the classpath and returns all classes that extend a specified class and match a specified name. + * There is an flag that can be used to indicate that only Java Beans will be matched (that is, only those classes + * that have a default constructor). + * + * @param matchingClass The class or interface to match. + * @param matchingRegexp The regular expression to match against the class name. + * @param beanOnly Flag to indicate that onyl classes with default constructors should be matched. + * + * @return All the classes that match this collector. + */ + public static Collection> getMatches(Class matchingClass, String matchingRegexp, + boolean beanOnly) + { + log.debug("public static Collection> getMatches(Class matchingClass = " + matchingClass + + ", String matchingRegexp = " + matchingRegexp + ", boolean beanOnly = " + beanOnly + "): called"); + + // Build a compiled regular expression from the pattern to match. + Pattern matchPattern = Pattern.compile(matchingRegexp); + + String classPath = System.getProperty("java.class.path"); + Map> result = new HashMap>(); + + log.debug("classPath = " + classPath); + + // Find matching classes starting from all roots in the classpath. + for (String path : splitClassPath(classPath)) + { + gatherFiles(new File(path), "", result, matchPattern, matchingClass); + } + + return result.values(); + } + + /** + * Finds all matching classes rooted at a given location in the file system. If location is a directory it + * is recursively examined. + * + * @param classRoot The root of the current point in the file system being examined. + * @param classFileName The name of the current file or directory to examine. + * @param result The accumulated mapping from class names to classes that match the scan. + * + * @todo Recursion ok as file system depth is not likely to exhaust the stack. Might be better to replace with + * iteration. + */ + private static void gatherFiles(File classRoot, String classFileName, Map> result, + Pattern matchPattern, Class matchClass) + { + log.debug("private static void gatherFiles(File classRoot = " + classRoot + ", String classFileName = " + + classFileName + ", Map> result, Pattern matchPattern = " + matchPattern + + ", Class matchClass = " + matchClass + "): called"); + + File thisRoot = new File(classRoot, classFileName); + + // If the current location is a file, check if it is a matching class. + if (thisRoot.isFile()) + { + // Check that the file has a matching name. + if (matchesName(thisRoot.getName(), matchPattern)) + { + String className = classNameFromFile(thisRoot.getName()); + + // Check that the class has matching type. + try + { + Class candidateClass = Class.forName(className); + + Class matchedClass = matchesClass(candidateClass, matchClass); + + if (matchedClass != null) + { + result.put(className, matchedClass); + } + } + catch (ClassNotFoundException e) + { + // Ignore this. The matching class could not be loaded. + log.debug("Got ClassNotFoundException, ignoring.", e); + } + } + + return; + } + // Otherwise the current location is a directory, so examine all of its contents. + else + { + String[] contents = thisRoot.list(); + + if (contents != null) + { + for (String content : contents) + { + gatherFiles(classRoot, classFileName + File.separatorChar + content, result, matchPattern, matchClass); + } + } + } + } + + /** + * Checks if the specified class file name corresponds to a class with name matching the specified regular expression. + * + * @param classFileName The class file name. + * @param matchPattern The regular expression pattern to match. + * + * @return true if the class name matches, false otherwise. + */ + private static boolean matchesName(String classFileName, Pattern matchPattern) + { + String className = classNameFromFile(classFileName); + Matcher matcher = matchPattern.matcher(className); + + return matcher.matches(); + } + + /** + * Checks if the specified class to compare extends the base class being scanned for. + * + * @param matchingClass The base class to match against. + * @param toMatch The class to match against the base class. + * + * @return The class to check, cast as an instance of the class to match if the class extends the base class, or + * null otherwise. + */ + private static Class matchesClass(Class matchingClass, Class toMatch) + { + try + { + return matchingClass.asSubclass(toMatch); + } + catch (ClassCastException e) + { + return null; + } + } + + /** + * Takes a classpath (which is a series of paths) and splits it into its component paths. + * + * @param classPath The classpath to split. + * + * @return A list of the component paths that make up the class path. + */ + private static List splitClassPath(String classPath) + { + List result = new LinkedList(); + String separator = System.getProperty("path.separator"); + StringTokenizer tokenizer = new StringTokenizer(classPath, separator); + + while (tokenizer.hasMoreTokens()) + { + result.add(tokenizer.nextToken()); + } + + return result; + } + + /** + * Translates from the filename of a class to its fully qualified classname. Files are named using forward slash + * seperators and end in ".class", whereas fully qualified class names use "." sperators and no ".class" ending. + * + * @param classFileName The filename of the class to translate to a class name. + * + * @return The fully qualified class name. + */ + private static String classNameFromFile(String classFileName) + { + log.debug("private static String classNameFromFile(String classFileName = " + classFileName + "): called"); + + // Remove the .class ending. + String s = classFileName.substring(0, classFileName.length() - ".class".length()); + + // Turn / seperators in . seperators. + String s2 = s.replace(File.separatorChar, '.'); + + // Knock off any leading . caused by a leading /. + if (s2.startsWith(".")) + { + return s2.substring(1); + } + + return s2; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java b/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java new file mode 100644 index 0000000000..0090bec3d0 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java @@ -0,0 +1,479 @@ +/* + * + * 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.util; + +import org.apache.log4j.Logger; + +import javax.jms.*; + +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A conversation helper, uses a message correlation id pattern to match up sent and received messages as a conversation + * over JMS messaging. Incoming message traffic is divided up by correlation id. Each id has a queue (behaviour dependant + * on the queue implementation). Clients of this de-multiplexer can wait on messages, defined by message correlation ids. + * + *

One use of this is as a conversation synchronizer where multiple threads are carrying out conversations over a + * multiplexed messaging route. This can be usefull, as JMS sessions are not multi-threaded. Setting up the conversation + * with synchronous queues will allow these threads to be written in a synchronous style, but with their execution order + * governed by the asynchronous message flow. For example, something like the following code could run a multi-threaded + * conversation (the conversation methods can be called many times in parallel): + * + *

+ * class Initiator
+ * {
+ * ConversationHelper conversation = new ConversationHelper(connection, null,
+ *                                                          java.util.concurrent.LinkedBlockingQueue.class);
+ *
+ * initiateConversation()
+ * {
+ *  try {
+ *   // Exchange greetings.
+ *   conversation.send(sendDestination, conversation.getSession().createTextMessage("Hello."));
+ *   Message greeting = conversation.receive();
+ *
+ *   // Exchange goodbyes.
+ *   conversation.send(conversation.getSession().createTextMessage("Goodbye."));
+ *   Message goodbye = conversation.receive();
+ *  } finally {
+ *   conversation.end();
+ *  }
+ * }
+ * }
+ *
+ * class Responder
+ * {
+ * ConversationHelper conversation = new ConversationHelper(connection, receiveDestination,
+ *                                                          java.util.concurrent.LinkedBlockingQueue.class);
+ *
+ * respondToConversation()
+ * {
+ *   try {
+ *   // Exchange greetings.
+ *   Message greeting = conversation.receive();
+ *   conversation.send(conversation.getSession().createTextMessage("Hello."));
+ *
+ *   // Exchange goodbyes.
+ *   Message goodbye = conversation.receive();
+ *   conversation.send(conversation.getSession().createTextMessage("Goodbye."));
+ *  } finally {
+ *   conversation.end();
+ *  }
+ * }
+ * }
+ * 
+ * + *

Conversation correlation id's are generated on a per thread basis. + * + *

The same session is shared amongst all conversations. Calls to send are therefore synchronized because JMS + * sessions are not multi-threaded. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Associate messages to an ongoing conversation using correlation ids. + *
Auto manage sessions for conversations. + *
Store messages not in a conversation in dead letter box. + *
+ */ +public class ConversationFactory +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(ConversationFactory.class); + + /** Holds a map from correlation id's to queues. */ + private Map> idsToQueues = new HashMap>(); + + /** Holds the connection over which the conversation is conducted. */ + private Connection connection; + + /** Holds the session over which the conversation is conduxted. */ + private Session session; + + /** The message consumer for incoming messages. */ + MessageConsumer consumer; + + /** The message producer for outgoing messages. */ + MessageProducer producer; + + /** The well-known or temporary destination to receive replies on. */ + Destination receiveDestination; + + /** Holds the queue implementation class for the reply queue. */ + Class queueClass; + + /** Used to hold any replies that are received outside of the context of a conversation. */ + BlockingQueue deadLetterBox = new LinkedBlockingQueue(); + + /* Used to hold conversation state on a per thread basis. */ + /* + ThreadLocal threadLocals = + new ThreadLocal() + { + protected Conversation initialValue() + { + Conversation settings = new Conversation(); + settings.conversationId = conversationIdGenerator.getAndIncrement(); + + return settings; + } + }; + */ + + /** Generates new coversation id's as needed. */ + AtomicLong conversationIdGenerator = new AtomicLong(); + + /** + * Creates a conversation helper on the specified connection with the default sending destination, and listening + * to the specified receiving destination. + * + * @param connection The connection to build the conversation helper on. + * @param receiveDestination The destination to listen to for incoming messages. This may be null to use a temporary + * queue. + * @param queueClass The queue implementation class. + * + * @throws JMSException All underlying JMSExceptions are allowed to fall through. + */ + public ConversationFactory(Connection connection, Destination receiveDestination, + Class queueClass) throws JMSException + { + log.debug("public ConversationFactory(Connection connection, Destination receiveDestination = " + receiveDestination + + ", Class queueClass = " + queueClass + "): called"); + + this.connection = connection; + this.queueClass = queueClass; + + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // Check if a well-known receive destination has been provided, or use a temporary queue if not. + this.receiveDestination = (receiveDestination != null) ? receiveDestination : session.createTemporaryQueue(); + + consumer = session.createConsumer(receiveDestination); + producer = session.createProducer(null); + + consumer.setMessageListener(new Receiver()); + } + + /** + * Creates a new conversation context. + * + * @return A new conversation context. + */ + public Conversation startConversation() + { + log.debug("public Conversation startConversation(): called"); + + Conversation conversation = new Conversation(); + conversation.conversationId = conversationIdGenerator.getAndIncrement(); + + return conversation; + } + + /** + * Ensures that the reply queue for a conversation exists. + * + * @param conversationId The conversation correlation id. + */ + private void initQueueForId(long conversationId) + { + if (!idsToQueues.containsKey(conversationId)) + { + idsToQueues.put(conversationId, ReflectionUtils.newInstance(queueClass)); + } + } + + /** + * Clears the dead letter box, returning all messages that were in it. + * + * @return All messages in the dead letter box. + */ + public Collection emptyDeadLetterBox() + { + log.debug("public Collection emptyDeadLetterBox(): called"); + + Collection result = new ArrayList(); + deadLetterBox.drainTo(result); + + return result; + } + + /** + * Gets the session over which the conversation is conducted. + * + * @return The session over which the conversation is conducted. + */ + public Session getSession() + { + // Conversation settings = threadLocals.get(); + + return session; + } + + /** + * Used to hold a conversation context. This consists of a correlating id for the conversation, and a reply + * destination automatically updated to the last received reply-to destination. + */ + public class Conversation + { + /** Holds the correlation id for the context. */ + long conversationId; + + /** + * Holds the send destination for the context. This will automatically be updated to the most recently received + * reply-to destination. + */ + Destination sendDestination; + + /** + * Sends a message to the default sending location. The correlation id of the message will be assigned by this + * method, overriding any previously set value. + * + * @param sendDestination The destination to send to. This may be null to use the last received reply-to + * destination. + * @param message The message to send. + * + * @throws JMSException All undelying JMSExceptions are allowed to fall through. This will also be thrown if no + * send destination is specified and there is no most recent reply-to destination available + * to use. + */ + public void send(Destination sendDestination, Message message) throws JMSException + { + log.debug("public void send(Destination sendDestination = " + sendDestination + ", Message message = " + message + + "): called"); + + // Conversation settings = threadLocals.get(); + // long conversationId = conversationId; + message.setJMSCorrelationID(Long.toString(conversationId)); + message.setJMSReplyTo(receiveDestination); + + // Ensure that the reply queue for this conversation exists. + initQueueForId(conversationId); + + // Check if an overriding send to destination has been set or use the last reply-to if not. + Destination sendTo = null; + + if (sendDestination != null) + { + sendTo = sendDestination; + } + else if (sendDestination != null) + { + sendTo = sendDestination; + } + else + { + throw new JMSException("The send destination was specified, and no most recent reply-to available to use."); + } + + // Send the message. + synchronized (this) + { + producer.send(sendTo, message); + } + } + + /** + * Gets the next message in an ongoing conversation. This method may block until such a message is received. + * + * @return The next incoming message in the conversation. + * + * @throws JMSException All undelying JMSExceptions are allowed to fall through. Thrown if the received message + * did not have its reply-to destination set up. + */ + public Message receive() throws JMSException + { + log.debug("public Message receive(): called"); + + // Conversation settings = threadLocals.get(); + // long conversationId = settings.conversationId; + + // Ensure that the reply queue for this conversation exists. + initQueueForId(conversationId); + + BlockingQueue queue = idsToQueues.get(conversationId); + + try + { + Message result = queue.take(); + + // Keep the reply-to destination to send replies to. + sendDestination = result.getJMSReplyTo(); + + return result; + } + catch (InterruptedException e) + { + return null; + } + } + + /** + * Gets many messages in an ongoing conversation. If a limit is specified, then once that many messages are + * received they will be returned. If a timeout is specified, then all messages up to the limit, received within + * that timespan will be returned. At least one of the message count or timeout should be set to a value of + * 1 or greater. + * + * @param num The number of messages to receive, or all if this is less than 1. + * @param timeout The timeout in milliseconds to receive the messages in, or forever if this is less than 1. + * + * @return All messages received within the count limit and the timeout. + * + * @throws JMSException All undelying JMSExceptions are allowed to fall through. + */ + public Collection receiveAll(int num, long timeout) throws JMSException + { + log.debug("public Collection receiveAll(int num = " + num + ", long timeout = " + timeout + + "): called"); + + // Check that a timeout or message count was set. + if ((num < 1) && (timeout < 1)) + { + throw new IllegalArgumentException("At least one of message count (num) or timeout must be set."); + } + + // Ensure that the reply queue for this conversation exists. + initQueueForId(conversationId); + BlockingQueue queue = idsToQueues.get(conversationId); + + // Used to collect the received messages in. + Collection result = new ArrayList(); + + // Used to indicate when the timeout or message count has expired. + boolean receiveMore = true; + + int messageCount = 0; + + // Receive messages until the timeout or message count expires. + do + { + try + { + Message next = null; + + // Try to receive the message with a timeout if one has been set. + if (timeout > 0) + { + next = queue.poll(timeout, TimeUnit.MILLISECONDS); + + // Check if the timeout expired, and stop receiving if so. + if (next == null) + { + receiveMore = false; + } + } + // Receive the message without a timeout. + else + { + next = queue.take(); + } + + // Increment the message count if a message was received. + messageCount += (next != null) ? 1 : 0; + + // Check if all the requested messages were received, and stop receiving if so. + if ((num > 0) && (messageCount >= num)) + { + receiveMore = false; + } + + // Keep the reply-to destination to send replies to. + sendDestination = (next != null) ? next.getJMSReplyTo() : sendDestination; + + if (next != null) + { + result.add(next); + } + } + catch (InterruptedException e) + { + // Restore the threads interrupted status. + Thread.currentThread().interrupt(); + + // Stop receiving but return the messages received so far. + receiveMore = false; + } + } + while (receiveMore); + + return result; + } + + /** + * Completes the conversation. Any correlation id's pertaining to the conversation are no longer valid, and any + * incoming messages using them will go to the dead letter box. + */ + public void end() + { + log.debug("public void end(): called"); + + // Ensure that the thread local for the current thread is cleaned up. + // Conversation settings = threadLocals.get(); + // long conversationId = settings.conversationId; + // threadLocals.remove(); + + // Ensure that its queue is removed from the queue map. + BlockingQueue queue = idsToQueues.remove(conversationId); + + // Move any outstanding messages on the threads conversation id into the dead letter box. + queue.drainTo(deadLetterBox); + } + } + + /** + * Implements the message listener for this conversation handler. + */ + protected class Receiver implements MessageListener + { + /** + * Handles all incoming messages in the ongoing conversations. These messages are split up by correaltion id + * and placed into queues. + * + * @param message The incoming message. + */ + public void onMessage(Message message) + { + log.debug("public void onMessage(Message message = " + message + "): called"); + + try + { + Long conversationId = Long.parseLong(message.getJMSCorrelationID()); + + // Find the converstaion queue to place the message on. If there is no conversation for the message id, + // the the dead letter box queue is used. + BlockingQueue queue = idsToQueues.get(conversationId); + queue = (queue == null) ? deadLetterBox : queue; + + queue.put(message); + } + catch (JMSException e) + { + throw new RuntimeException(e); + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + } + } +} -- cgit v1.2.1