package rescuecore2.connection; import static org.junit.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.InterruptedIOException; import java.io.InputStream; import java.io.OutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import org.junit.Test; import org.junit.Before; import org.junit.After; import rescuecore2.messages.Message; import rescuecore2.misc.Pair; public class StreamConnectionTest extends ConnectionTestCommon { private static final byte[] TEST_DATA = {0x01, 0x02, 0x03, 0x04}; private static final byte[] EXPECTED_TEST_OUTPUT = {0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04}; private static final byte[] GOOD_INPUT = {0x00, 0x00, 0x00, 0x08, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00}; private static final byte[] NEGATIVE_SIZE_INPUT = {(byte)0x80, 0x00, 0x00, 0x00, // Malformed size 0x00, 0x00, 0x00, 0x10, // Good size 0x00, 0x00, 0x00, 0x04, // Size of URN 0x54, 0x65, 0x73, 0x74, // URN ('Test') 0x00, 0x00, 0x00, 0x04, // Size of message 0x00, 0x00, 0x00, 0x00 // Message data }; // One byte short private static final byte[] SHORT_CONTENT = {0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03}; private static final byte[] SHORT_SIZE_FIELD = {0x00, 0x00, 0x01}; private static final String MESSAGE_URN = "Test"; @Override protected Pair<Connection, Connection> makeConnectionPair() throws IOException { PipedInputStream serverIn = new PipedInputStream(); PipedInputStream clientIn = new PipedInputStream(); PipedOutputStream serverOut = new PipedOutputStream(clientIn); PipedOutputStream clientOut = new PipedOutputStream(serverIn); Connection client = new StreamConnection(clientIn, clientOut); Connection server = new StreamConnection(serverIn, serverOut); return new Pair<Connection, Connection>(client, server); } @Test public void testSendBytes() throws IOException, InterruptedException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); TestInputStream in = new TestInputStream(); TestOutputStream out = new TestOutputStream(bout); StreamConnection c = new StreamConnection(in, out); c.startup(); c.sendBytes(TEST_DATA); // Wait for a bit Thread.sleep(DELAY); assertArrayEquals(EXPECTED_TEST_OUTPUT, bout.toByteArray()); } @Test public void testIOExceptionOnReadSize() throws IOException, InterruptedException { TestInputStream in = new TestInputStream(GOOD_INPUT); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); TestConnectionListener l = new TestConnectionListener(); c.addConnectionListener(l); in.setFailOnRead(true); // Should fail immediately c.startup(); Thread.sleep(DELAY); assertFalse(c.isAlive()); assertEquals(0, l.getMessageCount()); } @Test public void testIOExceptionOnReadSize2() throws IOException, InterruptedException { TestInputStream in = new TestInputStream(GOOD_INPUT); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); TestConnectionListener l = new TestConnectionListener(); c.addConnectionListener(l); in.setFailOnRead(2); // Should fail after reading two size bytes c.startup(); Thread.sleep(DELAY); assertFalse(c.isAlive()); assertEquals(0, l.getMessageCount()); } @Test public void testIOExceptionOnReadContent() throws IOException, InterruptedException { TestInputStream in = new TestInputStream(GOOD_INPUT); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); TestConnectionListener l = new TestConnectionListener(); c.addConnectionListener(l); in.setFailOnRead(6); // Should fail after reading the size header and 2 bytes of input c.startup(); Thread.sleep(DELAY); assertFalse(c.isAlive()); assertEquals(0, l.getMessageCount()); } @Test public void testNegativeSizeInput() throws IOException, InterruptedException { System.err.println("Test negative size input"); try { registry.registerMessageFactory(new TestMessageFactory("StreamConnectionTest factory", MESSAGE_URN)); TestInputStream in = new TestInputStream(NEGATIVE_SIZE_INPUT); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); TestConnectionListener l = new TestConnectionListener(); c.setRegistry(registry); c.addConnectionListener(l); c.startup(); // Should ignore the first negative size field then read a message with urn 'T' l.waitForMessages(1, TIMEOUT); assertEquals(1, l.getMessageCount()); assertEquals(MESSAGE_URN, l.getMessage(0).getURN()); } finally { System.err.println("Test negative size input finished"); } } @Test public void testShortContent() throws IOException, InterruptedException { TestInputStream in = new TestInputStream(SHORT_CONTENT); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); TestConnectionListener l = new TestConnectionListener(); c.addConnectionListener(l); c.startup(); // Should not read any messages at all l.waitForMessages(1, TIMEOUT); assertEquals(0, l.getMessageCount()); assertFalse(c.isAlive()); } @Test public void testShortSizeField() throws IOException, InterruptedException { TestInputStream in = new TestInputStream(SHORT_SIZE_FIELD); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); TestConnectionListener l = new TestConnectionListener(); c.addConnectionListener(l); c.startup(); // Should not read any messages at all l.waitForMessages(1, TIMEOUT); assertEquals(0, l.getMessageCount()); assertFalse(c.isAlive()); } @Test public void testExceptionOnOutputFlush() throws IOException { TestInputStream in = new TestInputStream(); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); out.setFailOnFlush(true); c.startup(); assertTrue(c.isAlive()); c.shutdown(); assertFalse(c.isAlive()); } @Test public void testExceptionOnOutputClose() throws IOException { TestInputStream in = new TestInputStream(); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); out.setFailOnClose(true); c.startup(); assertTrue(c.isAlive()); c.shutdown(); assertFalse(c.isAlive()); } @Test public void testExceptionOnInputClose() throws IOException { TestInputStream in = new TestInputStream(); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); in.setFailOnClose(true); c.startup(); assertTrue(c.isAlive()); c.shutdown(); assertFalse(c.isAlive()); } @Test public void testInterruptedDuringShutdown() throws IOException { TestInputStream in = new TestInputStream(); TestOutputStream out = new TestOutputStream(); Connection c = new StreamConnection(in, out); c.startup(); assertTrue(c.isAlive()); Thread.currentThread().interrupt(); c.shutdown(); assertFalse(c.isAlive()); } private class TestInputStream extends InputStream { private InputStream upstream; private boolean failOnRead; private boolean failOnClose; private int failCount; public TestInputStream() { this((InputStream)null); } public TestInputStream(byte[] bytes) { this(new ByteArrayInputStream(bytes)); } public TestInputStream(InputStream upstream) { this.upstream = upstream; failOnRead = false; failOnClose = false; failCount = -1; } @Override public int read() throws IOException { if (--failCount == 0) { failOnRead = true; } if (failOnRead) { throw new IOException("Fail on read"); } if (upstream != null) { return upstream.read(); } else { // Block while (true) { try { Thread.sleep(DELAY); } catch (InterruptedException e) { throw new InterruptedIOException(); } } } } @Override public void close() throws IOException { if (failOnClose) { throw new IOException("Fail on close"); } if (upstream != null) { upstream.close(); } } public void setFailOnRead(boolean b) { failOnRead = b; } public void setFailOnRead(int count) { failCount = count; } public void setFailOnClose(boolean b) { failOnClose = b; } } private class TestOutputStream extends OutputStream { private OutputStream downstream; private boolean failOnWrite; private boolean failOnFlush; private boolean failOnClose; public TestOutputStream() { this(null); } public TestOutputStream(OutputStream downstream) { this.downstream = downstream; failOnWrite = false; failOnFlush = false; failOnClose = false; } @Override public void write(int i) throws IOException { if (failOnWrite) { throw new IOException("Fail on write"); } if (downstream != null) { downstream.write(i); } } @Override public void flush() throws IOException { if (failOnFlush) { throw new IOException("Fail on flush"); } if (downstream != null) { downstream.flush(); } } @Override public void close() throws IOException { if (failOnClose) { throw new IOException("Fail on close"); } if (downstream != null) { downstream.close(); } } public void setFailOnWrite(boolean b) { failOnWrite = b; } public void setFailOnFlush(boolean b) { failOnFlush = b; } public void setFailOnClose(boolean b) { failOnClose = b; } } }