darrell 2003/11/26 06:18:38 Modified: proposals/imap2 build-test.xml proposals/imap2/java/org/apache/james/imapserver ImapSessionImpl.java proposals/imap2/java/org/apache/james/imapserver/commands CloseCommand.java ExpungeCommand.java proposals/imap2/test/org/apache/james/remotemanager RemoteManagerLogin.test proposals/imap2/test/org/apache/james/test AbstractProtocolTest.java FileProtocolSessionBuilder.java ProtocolSession.java Added: proposals/imap2/test/org/apache/james/imapserver TestConcurrentSessions.java proposals/imap2/test/org/apache/james/imapserver/concurrent Concurrent.todo.txt ExistsResponse.test FetchResponse.test Log: - Better in-process protocol testing in Imap: now handles multi-session protocol tests. - Added a few tests for concurrent mailbox access. Revision Changes Path 1.7 +9 -0 james-server/proposals/imap2/build-test.xml Index: build-test.xml =================================================================== RCS file: /home/cvs/james-server/proposals/imap2/build-test.xml,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- build-test.xml 13 Jul 2003 12:04:29 -0000 1.6 +++ build-test.xml 26 Nov 2003 14:18:37 -0000 1.7 @@ -133,6 +133,7 @@ <test name="org.apache.james.imapserver.TestOtherCommandsInSelectedState"/> <test name="org.apache.james.imapserver.TestSelectedCommandsInSelectedState"/> <test name="org.apache.james.imapserver.TestCompound"/> + <test name="org.apache.james.imapserver.TestConcurrentSessions"/> </junit> </target> @@ -171,12 +172,20 @@ <test name="org.apache.james.imapserver.TestOtherCommandsInSelectedState"/> <test name="org.apache.james.imapserver.TestSelectedCommandsInSelectedState"/> <test name="org.apache.james.imapserver.TestCompound"/> + <test name="org.apache.james.imapserver.TestConcurrentSessions"/> </junit> </target> <target name="build-and-test"> <ant antfile="proposals/imap2/build.xml" target="main"/> <antcall target="unit-tests"/> + </target> + + <target name="copytests"> + <mkdir dir="${build.dir}/tests"/> + <copy todir="${build.dir}/tests"> + <fileset dir="${test.dir}" includes="**/*.test"/> + </copy> </target> </project> 1.7 +3 -1 james-server/proposals/imap2/java/org/apache/james/imapserver/ImapSessionImpl.java Index: ImapSessionImpl.java =================================================================== RCS file: /home/cvs/james-server/proposals/imap2/java/org/apache/james/imapserver/ImapSessionImpl.java,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- ImapSessionImpl.java 21 Jul 2003 23:31:04 -0000 1.6 +++ ImapSessionImpl.java 26 Nov 2003 14:18:37 -0000 1.7 @@ -177,6 +177,8 @@ public void deselect() { this.state = ImapSessionState.AUTHENTICATED; + // TODO is there more to do here, to cleanup the mailbox. + this.selectedMailbox = null; } public void setSelected( ImapMailbox mailbox, boolean readOnly ) 1.5 +3 -2 james-server/proposals/imap2/java/org/apache/james/imapserver/commands/CloseCommand.java Index: CloseCommand.java =================================================================== RCS file: /home/cvs/james-server/proposals/imap2/java/org/apache/james/imapserver/commands/CloseCommand.java,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- CloseCommand.java 13 Jul 2003 06:04:56 -0000 1.4 +++ CloseCommand.java 26 Nov 2003 14:18:37 -0000 1.5 @@ -90,7 +90,8 @@ mailbox.expunge(); } session.deselect(); - + +// Don't send unsolicited responses on close. session.unsolicitedResponses( response ); response.commandComplete( this ); } 1.7 +1 -2 james-server/proposals/imap2/java/org/apache/james/imapserver/commands/ExpungeCommand.java Index: ExpungeCommand.java =================================================================== RCS file: /home/cvs/james-server/proposals/imap2/java/org/apache/james/imapserver/commands/ExpungeCommand.java,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- ExpungeCommand.java 23 Oct 2003 01:55:12 -0000 1.6 +++ ExpungeCommand.java 26 Nov 2003 14:18:37 -0000 1.7 @@ -91,7 +91,6 @@ ImapMailbox mailbox = session.getSelected(); mailbox.expunge(); - System.out.println("here"); session.unsolicitedResponses( response ); response.commandComplete( this ); 1.1 james-server/proposals/imap2/test/org/apache/james/imapserver/TestConcurrentSessions.java Index: TestConcurrentSessions.java =================================================================== //////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2003, Wotif.com. All rights reserved. // // This is unpublished proprietary source code of Wotif.com. // The copyright notice above does not evidence any actual or intended // publication of such source code. // //////////////////////////////////////////////////////////////////////////////// package org.apache.james.imapserver; import org.apache.james.imapserver.TestCommandsInAuthenticatedState; import junit.framework.Test; import junit.framework.TestSuite; /** * @author <a href="mailto:[EMAIL PROTECTED]">Darrell DeBoer</a> * @version $Id: TestConcurrentSessions.java,v 1.1 2003/11/26 14:18:38 darrell Exp $ */ public class TestConcurrentSessions extends TestCommandsInAuthenticatedState { public TestConcurrentSessions(String fileName) { super(fileName); } /** * Runs all tests which verify the behaviour of IMAP under multiple concurrent sessions. */ public static Test suite() throws Exception { TestSuite suite = new TestSuite(); // Not valid in this state suite.addTest( new TestConcurrentSessions( "concurrent/FetchResponse" ) ); suite.addTest( new TestConcurrentSessions( "concurrent/ExistsResponse" ) ); return suite; } } 1.1 james-server/proposals/imap2/test/org/apache/james/imapserver/concurrent/Concurrent.todo.txt Index: Concurrent.todo.txt =================================================================== Simple tests: 1 Send FETCH response when another session performs a STORE against a message (concurrent/FetchResponse.test) 2 Send EXISTS and RECENT responses when a message is added (concurrent/ExistsResponse.test) - still need RECENT 3 Expunge response when another session performs a EXPUNGE against the mailbox From RFC2180 3. Deletion/Renaming of a multi-accessed mailbox ### Need to pick one of these 3... 3.1. The server MAY fail the DELETE/RENAME command of a multi-accessed mailbox 3.3. The server MAY allow the DELETE/RENAME of a multi-accessed mailbox, but disconnect all other clients who have the mailbox accessed by sending a untagged BYE response. 3.4. The server MAY allow the RENAME of a multi-accessed mailbox by simply changing the name attribute on the mailbox. ----------------------------------------------------------------------- 4.1. Fetching of expunged messages 4.1.2 The server MAY allow the EXPUNGE of a multi-accessed mailbox, and on subsequent FETCH commands return FETCH responses only for non-expunged messages and a tagged NO. C2: B001 FETCH 3:5 ENVELOPE S2: * 3 FETCH ENVELOPE . . . (ENVELOPE info returned) S2: B001 NO Some of the requested messages no longer exist ### Return what you can, and send a "no": i like this. -0--------------------------------------------------------------------- 4.2. Storing of expunged messages 4.2.1 If the ".SILENT" suffix is used, and the STORE completed successfully for all the non-expunged messages, the server SHOULD return a tagged OK. 4.2.2. If the ".SILENT" suffix is not used, and only expunged messages are referenced, the server SHOULD return only a tagged NO. 4.2.3. If the ".SILENT" suffix is not used, and a mixture of expunged and non-expunged messages are referenced, the server MAY set the flags and return a FETCH response for the non-expunged messages along with a tagged NO. ### Update what you can, and return "NO" 4.2.4. If the ".SILENT" suffix is not used, and a mixture of expunged and non-expunged messages are referenced, the server MAY return an untagged NO and not set any flags. ### Don't update anything, and return "NO" 1.1 james-server/proposals/imap2/test/org/apache/james/imapserver/concurrent/ExistsResponse.test Index: ExistsResponse.test =================================================================== # Tests that appending a message from one session triggers an EXISTS response # in a concurrent session on the same mailbox # TODO: # a) Check sending of EXISTS response when using other selected state commands. # eg FETCH, STORE, COPY... # b) Check sending of EXISTS response when using other non-selected state commands # eg CREATE, APPEND... # c) Get RECENT working SESSION: 1 C: 1a CREATE existsresponse S: 1a OK CREATE completed. C: 1b STATUS existsresponse (MESSAGES) S: \* STATUS existsresponse \(MESSAGES 0\) S: 1b OK STATUS completed SESSION: 2 C: 2a SELECT existsresponse S: \* FLAGS \(\\Answered \\Deleted \\Draft \\Flagged \\Seen\) S: \* 0 EXISTS S: \* 0 RECENT S: \* OK \[UIDVALIDITY \d+\] S: \* OK No messages unseen S: \* OK \[PERMANENTFLAGS \(\\Answered \\Deleted \\Draft \\Flagged \\Seen\)\] S: 2a OK \[READ-WRITE\] SELECT completed SESSION: 1 C: 1c APPEND existsresponse (\Deleted) {310+} C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST) C: From: Fred Foobar <[EMAIL PROTECTED]> C: Subject: afternoon meeting C: To: [EMAIL PROTECTED] C: Message-Id: <[EMAIL PROTECTED]> C: MIME-Version: 1.0 C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII C: C: Hello Joe, do you think we can meet at 3:30 tomorrow? C: S: 1c OK APPEND completed SESSION: 2 C: 2b NOOP S: \* 1 EXISTS #S: \* 1 RECENT S: 2b OK NOOP completed C: 2c DELETE existsresponse S: 2c OK DELETE completed 1.1 james-server/proposals/imap2/test/org/apache/james/imapserver/concurrent/FetchResponse.test Index: FetchResponse.test =================================================================== # Tests that updates made by one session trigger a fetch response # in a concurrent session on the same mailbox # TODO: Check sending of fetch responses when using other selected state commands. # eg FETCH, STORE, COPY... SESSION: 1 C: 1a CREATE multibox S: 1a OK CREATE completed. C: 1b STATUS multibox (MESSAGES) S: \* STATUS multibox \(MESSAGES 0\) S: 1b OK STATUS completed C: 1c APPEND multibox (\Deleted) {310+} C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST) C: From: Fred Foobar <[EMAIL PROTECTED]> C: Subject: afternoon meeting C: To: [EMAIL PROTECTED] C: Message-Id: <[EMAIL PROTECTED]> C: MIME-Version: 1.0 C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII C: C: Hello Joe, do you think we can meet at 3:30 tomorrow? C: S: 1c OK APPEND completed C: 1d SELECT multibox S: \* FLAGS \(\\Answered \\Deleted \\Draft \\Flagged \\Seen\) S: \* 1 EXISTS S: \* \d+ RECENT S: \* OK \[UIDVALIDITY \d+\] S: \* OK \[UNSEEN 1\] Message 1 is the first unseen S: \* OK \[PERMANENTFLAGS \(\\Answered \\Deleted \\Draft \\Flagged \\Seen\)\] S: 1d OK \[READ-WRITE\] SELECT completed SESSION: 2 C: 2a SELECT multibox S: \* FLAGS \(\\Answered \\Deleted \\Draft \\Flagged \\Seen\) S: \* 1 EXISTS S: \* \d+ RECENT S: \* OK \[UIDVALIDITY \d+\] S: \* OK \[UNSEEN 1\] Message 1 is the first unseen S: \* OK \[PERMANENTFLAGS \(\\Answered \\Deleted \\Draft \\Flagged \\Seen\)\] S: 2a OK \[READ-WRITE\] SELECT completed SESSION: 1 C: 1e STORE 1 FLAGS (\Deleted) S: \* 1 FETCH \(FLAGS \(\\Deleted\)\) S: 1e OK STORE completed # On NOOP, we get the Fetch Response from the Session1 update. SESSION: 2 C: 2b NOOP S: \* 1 FETCH \(FLAGS \(\\Deleted\)\) S: 2b OK NOOP completed C: 2c CLOSE S: 2c OK CLOSE completed SESSION: 1 C: 1f CLOSE S: 1f OK CLOSE completed C: 1g DELETE multibox S: 1g OK DELETE completed 1.2 +1 -1 james-server/proposals/imap2/test/org/apache/james/remotemanager/RemoteManagerLogin.test Index: RemoteManagerLogin.test =================================================================== RCS file: /home/cvs/james-server/proposals/imap2/test/org/apache/james/remotemanager/RemoteManagerLogin.test,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- RemoteManagerLogin.test 22 Nov 2002 02:09:54 -0000 1.1 +++ RemoteManagerLogin.test 26 Nov 2003 14:18:38 -0000 1.2 @@ -1,4 +1,4 @@ -S: JAMES Remote Administration Tool 2.1a1-cvs +S: JAMES Remote Administration Tool .* S: Please enter your login and password S: Login id: C: root 1.8 +123 -37 james-server/proposals/imap2/test/org/apache/james/test/AbstractProtocolTest.java Index: AbstractProtocolTest.java =================================================================== RCS file: /home/cvs/james-server/proposals/imap2/test/org/apache/james/test/AbstractProtocolTest.java,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- AbstractProtocolTest.java 8 Mar 2003 21:13:58 -0000 1.7 +++ AbstractProtocolTest.java 26 Nov 2003 14:18:38 -0000 1.8 @@ -65,6 +65,7 @@ import org.apache.james.imapserver.ImapSessionImpl; import org.apache.james.imapserver.ImapTest; import org.apache.james.imapserver.JamesImapHost; +import org.apache.james.imapserver.ProtocolException; import org.apache.james.imapserver.store.MailboxException; import org.apache.james.userrepository.AbstractUsersRepository; import org.apache.james.userrepository.DefaultUser; @@ -79,6 +80,10 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.OutputStream; import java.net.Socket; import java.util.HashMap; import java.util.Iterator; @@ -151,11 +156,17 @@ private void runSocketProtocolSessions() throws Exception { - Socket socket = new Socket( host, port ); - socket.setSoTimeout( timeout ); + Socket[] socket = new Socket[testElements.getSessionCount()]; + PrintWriter[] out = new PrintWriter[socket.length]; + BufferedReader[] in = new BufferedReader[socket.length]; + + for (int i = 0; i < socket.length; i++) { + socket[i] = new Socket(host, port); + socket[i].setSoTimeout(timeout); + out[i] = new PrintWriter(socket[i].getOutputStream()); + in[i] = new BufferedReader(new InputStreamReader(socket[i].getInputStream())); + } - PrintWriter out = new PrintWriter( socket.getOutputStream(), true ); - BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); try { preElements.runLiveSession( out, in ); testElements.runLiveSession( out, in ); @@ -165,9 +176,11 @@ fail( e.getMessage() ); } - out.close(); - in.close(); - socket.close(); + for (int i = 0; i < socket.length; i++) { + out[i].close(); + in[i].close(); + socket[i].close(); + } } /** @@ -175,42 +188,39 @@ * This does not require that James be running, and is useful for rapid development and * debugging. * - * Instead of sending requests to a socket, requests are written to a CharArrayWriter, - * which then constructs a Reader which can be given to the ImapRequestHandler. - * Likewise, server responses are written to a CHarArrayWriter, and the associate reader - * is parsed to ensure that the responses match those expected. + * Instead of sending requests to a socket connected to a running instance of James, + * this method uses the [EMAIL PROTECTED] MockImapServer} to simplify testing. One mock instance + * is required per protocol session/connection. These share the same underlying + * Mailboxes, because of the way [EMAIL PROTECTED] #getImapSession()} works. */ private void runLocalProtocolSessions() throws Exception { - ByteArrayOutputStream clientRequestCollector = new ByteArrayOutputStream(); - PrintWriter clientOut = new PrintWriter( clientRequestCollector ); - preElements.writeClient( clientOut ); - testElements.writeClient( clientOut ); - postElements.writeClient( clientOut ); - - InputStream clientIn = new ByteArrayInputStream( clientRequestCollector.toByteArray() ); - clientRequestCollector.close(); - - ByteArrayOutputStream serverResponseCollector = new ByteArrayOutputStream(); - serverResponseCollector.write( "* OK IMAP4rev1 Server XXX ready".getBytes() ); - serverResponseCollector.write( '\r' ); - serverResponseCollector.write( '\n' ); - - ImapSession session = getImapSession(); - ImapRequestHandler requestHandler = new ImapRequestHandler(); - while( requestHandler.handleRequest( clientIn, serverResponseCollector, session ) ) {}; - - InputStream serverInstream = new ByteArrayInputStream( serverResponseCollector.toByteArray() ); - BufferedReader serverIn = new BufferedReader( new InputStreamReader( serverInstream ) ); + MockImapServer[] socket = new MockImapServer[testElements.getSessionCount()]; + PrintWriter[] out = new PrintWriter[socket.length]; + BufferedReader[] in = new BufferedReader[socket.length]; + + for (int i = 0; i < socket.length; i++) { + socket[i] = new MockImapServer(getImapSession()); + out[i] = socket[i].getWriter(); + in[i] = socket[i].getReader(); + socket[i].start(); + } try { - preElements.testResponse( serverIn ); - testElements.testResponse( serverIn ); - postElements.testResponse( serverIn ); - } - catch ( ProtocolSession.InvalidServerResponseException e ) { - fail( e.getMessage() ); + preElements.runLiveSession( out, in ); + testElements.runLiveSession( out, in ); + postElements.runLiveSession( out, in ); + } + catch ( ProtocolSession.InvalidServerResponseException e ) { + fail( e.getMessage() ); + } + + for (int i = 0; i < socket.length; i++) { + out[i].close(); + in[i].close(); + socket[i].stopServer(); } + } /** @@ -266,6 +276,82 @@ protected void doUpdateUser( User user ) { users.put( user.getUserName(), user ); + } + } + + /** + * A simple test utility which allows testing of Imap commands without + * deployment of the full server. Piped input and output streams are used + * to provide the equivilant of a socket interface. + */ + private class MockImapServer extends Thread { + private ImapRequestHandler handler = new ImapRequestHandler(); + + private PipedInputStream requestInputStream; + private PipedOutputStream requestOutputStream; + + private PipedInputStream responseInputStream; + private PipedOutputStream responseOutputStream; + + private ImapSession session; + private ProtocolException exception; + + private boolean running; + + /** + * Creates a MockImapServer, with a handler for the session provided. + */ + public MockImapServer(ImapSession session) throws IOException { + this.session = session; + requestOutputStream = new PipedOutputStream(); + requestInputStream = new PipedInputStream(requestOutputStream); + + responseInputStream = new PipedInputStream(); + responseOutputStream = new PipedOutputStream(responseInputStream); + + } + + /** + * Core loop where Imap commands are handled + */ + public void run() { + running = true; + try { + responseOutputStream.write( "* OK IMAP4rev1 Server XXX ready".getBytes() ); + responseOutputStream.write( '\r' ); + responseOutputStream.write( '\n' ); + } catch (IOException e) { + throw new RuntimeException("Couldn't write welcome message", e); + } + + while (running) { + try { + handler.handleRequest(requestInputStream, responseOutputStream, session); + } catch (ProtocolException e) { + exception = e; + break; + } + } + + } + + /** + * @return A writer which is used to send commands to the mock server. + */ + public PrintWriter getWriter() { + return new PrintWriter(requestOutputStream); + } + + /** + * @return A reader which is used to read responses from the mock server. + */ + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(responseInputStream)); + } + + /** stop the running server thread.*/ + public void stopServer() { + running = false; } } } 1.7 +14 -5 james-server/proposals/imap2/test/org/apache/james/test/FileProtocolSessionBuilder.java Index: FileProtocolSessionBuilder.java =================================================================== RCS file: /home/cvs/james-server/proposals/imap2/test/org/apache/james/test/FileProtocolSessionBuilder.java,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- FileProtocolSessionBuilder.java 8 Mar 2003 21:13:58 -0000 1.6 +++ FileProtocolSessionBuilder.java 26 Nov 2003 14:18:38 -0000 1.7 @@ -78,6 +78,7 @@ private static final String OPEN_UNORDERED_BLOCK_TAG = "SUB {"; private static final String CLOSE_UNORDERED_BLOCK_TAG = "}"; private static final String COMMENT_TAG = "#"; + private static final String SESSION_TAG = "SESSION:"; /** * Builds a ProtocolSession by reading lines from the test file @@ -122,9 +123,10 @@ String fileName ) throws Exception { + int sessionNumber = -1; BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); String next; - int lineNumber = 1; + int lineNumber = -1; while ( ( next = reader.readLine() ) != null ) { String location = fileName + ":" + lineNumber; if ( next.startsWith( CLIENT_TAG ) ) { @@ -132,14 +134,14 @@ if ( next.length() > 3 ) { clientMsg = next.substring( 3 ); } - session.CL( clientMsg ); + session.CL( sessionNumber, clientMsg ); } else if ( next.startsWith( SERVER_TAG ) ) { String serverMsg = ""; if ( next.length() > 3 ) { serverMsg = next.substring( 3 ); } - session.SL( serverMsg, location ); + session.SL( sessionNumber, serverMsg, location ); } else if ( next.startsWith( OPEN_UNORDERED_BLOCK_TAG ) ) { List unorderedLines = new ArrayList( 5 ); @@ -155,11 +157,18 @@ lineNumber++; } - session.SUB( unorderedLines, location ); + session.SUB( sessionNumber, unorderedLines, location ); } else if ( next.startsWith( COMMENT_TAG ) || next.trim().length() == 0 ) { // ignore these lines. + } + else if ( next.startsWith(SESSION_TAG)) { + String number = next.substring(SESSION_TAG.length()).trim(); + if (number.length() == 0) { + throw new Exception("No session number specified"); + } + sessionNumber = Integer.parseInt(number); } else { String prefix = next; 1.7 +140 -86 james-server/proposals/imap2/test/org/apache/james/test/ProtocolSession.java Index: ProtocolSession.java =================================================================== RCS file: /home/cvs/james-server/proposals/imap2/test/org/apache/james/test/ProtocolSession.java,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- ProtocolSession.java 8 Mar 2003 21:13:58 -0000 1.6 +++ ProtocolSession.java 26 Nov 2003 14:18:38 -0000 1.7 @@ -78,19 +78,29 @@ */ public class ProtocolSession { + private int maxSessionNumber; protected List testElements = new ArrayList(); private static final Perl5Util perl = new Perl5Util(); /** - * Executes the ProtocolSession in real time against the reader and writer + * Returns the number of sessions required to run this ProtocolSession. + * If the number of readers and writers provided is less than this number, + * an exception will occur when running the tests. + */ + public int getSessionCount() { + return maxSessionNumber + 1; + } + + /** + * Executes the ProtocolSession in real time against the readers and writers * supplied, writing client requests and reading server responses in the order - * that they appear in the test elements. + * that they appear in the test elements. The index of a reader/writer in the array + * corresponds to the number of the session. * If an exception occurs, no more test elements are executed. * @param out The client requests are written to here. * @param in The server responses are read from here. */ - public void runLiveSession( PrintWriter out, BufferedReader in ) throws Exception - { + public void runLiveSession(PrintWriter[] out, BufferedReader[] in) throws InvalidServerResponseException { for ( Iterator iter = testElements.iterator(); iter.hasNext(); ) { Object obj = iter.next(); if ( obj instanceof ProtocolElement ) { @@ -101,70 +111,54 @@ } /** - * Write an entire client session to the specified PrintWriter. Server - * responses are not collected, but clients may collect themfor later - * testing with [EMAIL PROTECTED] #testResponse}. - * @param out The client requests are written to here. + * adds a new Client request line to the test elements */ - public void writeClient( PrintWriter out ) throws Exception + public void CL( String clientLine ) { - Iterator iterator = testElements.iterator(); - while ( iterator.hasNext() ) { - ProtocolElement element = (ProtocolElement) iterator.next(); - if ( element instanceof ClientRequest ) { - element.testProtocol( out, null ); - } - } + testElements.add( new ClientRequest( clientLine ) ); } /** - * Reads Server responses from the supplied Buffered reader, ensuring that - * they match the expected responses for the protocol session. This permits - * clients to run a session asynchronously, by first writing the client requests - * with [EMAIL PROTECTED] #writeClient} and later testing the responses. - * @param in The server responses are read from here. + * adds a new Server Response line to the test elements, with the specified location. */ - public void testResponse( BufferedReader in ) throws Exception + public void SL( String serverLine, String location ) { - Iterator iterator = testElements.iterator(); - while ( iterator.hasNext() ) { - ProtocolElement element = (ProtocolElement) iterator.next(); - if ( element instanceof ServerResponse ) { - element.testProtocol( null, in ); - } - } + testElements.add( new ServerResponse( serverLine, location ) ); } /** - * adds a new Client request line to the test elements + * adds a new Server Unordered Block to the test elements. */ - public void CL( String clientLine ) + public void SUB( List serverLines, String location ) { - testElements.add( new ClientRequest( clientLine ) ); + testElements.add( new ServerUnorderedBlockResponse( serverLines, location ) ); } /** - * adds a new Server Response line to the test elements, with the specified location. + * adds a new Client request line to the test elements */ - public void SL( String serverLine, String location ) + public void CL( int sessionNumber, String clientLine ) { - testElements.add( new ServerResponse( serverLine, location ) ); + this.maxSessionNumber = Math.max(this.maxSessionNumber, sessionNumber); + testElements.add( new ClientRequest( sessionNumber, clientLine ) ); } /** - * adds a new Server Unordered Block to the test elements. + * adds a new Server Response line to the test elements, with the specified location. */ - public void SUB( List serverLines, String location ) + public void SL( int sessionNumber, String serverLine, String location ) { - testElements.add( new ServerUnorderedBlockResponse( serverLines, location ) ); + this.maxSessionNumber = Math.max(this.maxSessionNumber, sessionNumber); + testElements.add( new ServerResponse( sessionNumber, serverLine, location ) ); } /** - * Adds a ProtocolElement to the test elements. + * adds a new Server Unordered Block to the test elements. */ - public void addProtocolElement( ProtocolElement element ) + public void SUB( int sessionNumber, List serverLines, String location ) { - testElements.add( element ); + this.maxSessionNumber = Math.max(this.maxSessionNumber, sessionNumber); + testElements.add( new ServerUnorderedBlockResponse( sessionNumber, serverLines, location ) ); } /** @@ -172,6 +166,7 @@ */ private class ClientRequest implements ProtocolElement { + private int sessionNumber; private String message; /** @@ -179,18 +174,43 @@ */ public ClientRequest( String message ) { + this(-1, message); + } + + /** + * Initialises the ClientRequest, with a message and session number. + * @param sessionNumber + * @param message + */ + public ClientRequest(int sessionNumber, String message) { + this.sessionNumber = sessionNumber; this.message = message; } /** - * Writes the request message to the PrintWriter. + * Writes the request message to the PrintWriters. If the sessionNumber == -1, + * the request is written to *all* supplied writers, otherwise, only the + * writer for this session is writted to. */ - public void testProtocol( PrintWriter out, BufferedReader in ) + public void testProtocol( PrintWriter[] out, BufferedReader[] in ) { - out.write( message ); - out.write( '\r' ); - out.write( '\n' ); - out.flush(); + if (sessionNumber < 0) { + for (int i = 0; i < out.length; i++) { + PrintWriter printWriter = out[i]; + writeMessage(printWriter); + } + } + else { + PrintWriter writer = out[sessionNumber]; + writeMessage(writer); + } + } + + private void writeMessage(PrintWriter writer) { + writer.write(message); + writer.write('\r'); + writer.write('\n'); + writer.flush(); } } @@ -201,6 +221,7 @@ */ private class ServerResponse implements ProtocolElement { + private int sessionNumber; private String expectedLine; protected String location; @@ -212,22 +233,49 @@ */ public ServerResponse( String expectedPattern, String location ) { + this(-1, expectedPattern, location); + } + + /** + * Sets up a server response. + * @param sessionNumber The number of session for a multi-session test + * @param expectedPattern A Perl regular expression pattern used to test + * the line recieved. + * @param location A descriptive value to use in error messages. + */ + public ServerResponse( int sessionNumber, String expectedPattern, String location ) + { + this.sessionNumber = sessionNumber; this.expectedLine = expectedPattern; this.location = location; } /** * Reads a line from the supplied reader, and tests that it matches - * the expected regular expression. + * the expected regular expression. If the sessionNumber == -1, then all + * readers are tested, otherwise, only the reader for this session is tested. * @param out Is ignored. * @param in The server response is read from here. * @throws InvalidServerResponseException If the actual server response didn't * match the regular expression expected. */ - public void testProtocol( PrintWriter out, BufferedReader in ) + public void testProtocol( PrintWriter[] out, BufferedReader[] in ) throws InvalidServerResponseException { - String testLine = readLine( in ); + if (sessionNumber < 0) { + for (int i = 0; i < in.length; i++) { + BufferedReader reader = in[i]; + checkResponse(reader); + } + } + else { + BufferedReader reader = in[sessionNumber]; + checkResponse(reader); + } + } + + protected void checkResponse(BufferedReader reader) throws InvalidServerResponseException { + String testLine = readLine(reader); if ( ! match( expectedLine, testLine ) ) { String errMsg = "\nLocation: " + location + "\nExcpected: " + expectedLine + @@ -260,12 +308,11 @@ { try { return in.readLine(); - } - catch ( IOException e ) { + } catch (IOException e) { String errMsg = "\nLocation: " + location + - "\nExpected: " + expectedLine + - "\nReason: Server Timeout."; - throw new InvalidServerResponseException( errMsg ); + "\nExpected: " + expectedLine + + "\nReason: Server Timeout."; + throw new InvalidServerResponseException(errMsg); } } } @@ -286,7 +333,21 @@ */ public ServerUnorderedBlockResponse( List expectedLines, String location ) { - super( "<Unordered Block>", location ); + this(-1, expectedLines, location); + } + + /** + * Sets up a ServerUnorderedBlockResponse with the list of expected lines. + * @param sessionNumber The number of the session to expect this block, + * for a multi-session test. + * @param expectedLines A list containing a reqular expression for each + * expected line. + * @param location A descriptive location string for error messages. + */ + public ServerUnorderedBlockResponse( int sessionNumber, + List expectedLines, String location ) + { + super( sessionNumber, "<Unordered Block>", location ); this.expectedLines = expectedLines; } @@ -294,46 +355,39 @@ * Reads lines from the server response and matches them against the * list of expected regular expressions. Each regular expression in the * expected list must be matched by only one server response line. - * @param out Is ignored. - * @param in Server responses are read from here. + * @param reader Server responses are read from here. * @throws InvalidServerResponseException If a line is encountered which doesn't * match one of the expected lines. */ - public void testProtocol( PrintWriter out, BufferedReader in ) - throws InvalidServerResponseException - { - List testLines = new ArrayList( expectedLines ); - while ( testLines.size() > 0 ) - { - String actualLine = readLine( in ); - boolean foundMatch = false; + protected void checkResponse(BufferedReader reader) throws InvalidServerResponseException { + List testLines = new ArrayList(expectedLines); + while (testLines.size() > 0) { + String actualLine = readLine(reader); - for ( int i = 0; i < testLines.size(); i++ ) - { - String expected = (String)testLines.get( i ); - if ( match( expected, actualLine )) - { + boolean foundMatch = false; + for (int i = 0; i < testLines.size(); i++) { + String expected = (String) testLines.get(i); + if (match(expected, actualLine)) { foundMatch = true; - testLines.remove( expected ); + testLines.remove(expected); break; } } - if (! foundMatch ) - { + if (!foundMatch) { StringBuffer errMsg = new StringBuffer() - .append( "\nLocation: " ) - .append( location ) - .append( "\nExpected one of: " ); + .append("\nLocation: ") + .append(location) + .append("\nExpected one of: "); Iterator iter = expectedLines.iterator(); - while ( iter.hasNext() ) { - errMsg.append( "\n " ); - errMsg.append( iter.next() ); + while (iter.hasNext()) { + errMsg.append("\n "); + errMsg.append(iter.next()); } - errMsg.append("\nActual: " ) - .append( actualLine ); + errMsg.append("\nActual: ") + .append(actualLine); - throw new InvalidServerResponseException( errMsg.toString() ); + throw new InvalidServerResponseException(errMsg.toString()); } } } @@ -344,7 +398,7 @@ * read responses from the server, or both. Implementations should test the server * response against an expected response, and throw an exception on mismatch. */ - interface ProtocolElement + private interface ProtocolElement { /** * Executes the ProtocolElement against the supplied read and writer. @@ -353,7 +407,7 @@ * @throws InvalidServerResponseException If the actual server response * doesn't match the one expected. */ - void testProtocol( PrintWriter out, BufferedReader in ) + void testProtocol( PrintWriter[] out, BufferedReader[] in ) throws InvalidServerResponseException; }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]