package com.youdevise.hudson.slavestatus;
import com.youdevise.hudson.slavestatus.Daemon.RunResult;
import com.youdevise.hudson.slavestatus.Daemon.RunType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.BindException;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
public class SlaveListenerTest {
private static final String STARTUP_LOG_MESSAGE = "Slave-status listener starting";
private static final String WAITING_LOG_MESSAGE = "Slave-status listener waiting for connection";
private static final String GOT_CONNECTION_LOG_MESSAGE = "Slave-status listener got connection";
private static final String READ_INPUT_LOG_MESSAGE = "Slave-status listener read input";
private static final String WROTE_OUTPUT_LOG_MESSAGE = "Slave-status listener wrote output";
private static final String FLUSHED_AND_CLOSED_LOG_MESSAGE = "Slave-status listener flushed and closed connection";
private static final String HTTP_OUTPUT
= "HTTP/1.0 200 OK\n"
+ "Content-Type: text/xml\n"
+ "Server: Hudson slave-status plugin\n"
+ "\n"
+ "<slave><test>Hello</test></slave>";
private SlaveListener listener;
private MockLogger logger;
private MockHTTPListener httpListener;
@Before
public void setUp() {
httpListener = new MockHTTPListener();
logger = new MockLogger();
listener = new SlaveListener(0, RunType.ONCE_ONLY, new DummyStatusReporter());
listener.setLogger(logger);
listener.setHTTPListener(httpListener);
}
@Test
public void canBeSerialised() throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new ByteArrayOutputStream());
out.writeObject(listener);
}
@Test
public void sendsSameResponseForAnyConnection() throws Throwable {
assertEquals(RunResult.CONTINUE, listener.call());
assertTrue("Should wait for connection", httpListener.waitForConnectionCalled);
assertTrue("Should read all incoming bytes", httpListener.allBytesRead);
assertEquals(HTTP_OUTPUT, new String(httpListener.outputStream.toByteArray()));
assertTrue("Should flush and close", httpListener.flushAndCloseCalled);
}
@Test
public void logsWhenAllGoesWell() throws Throwable {
listener.call();
logger.verifyLogs(new LogRecord(Level.INFO, STARTUP_LOG_MESSAGE),
new LogRecord(Level.FINE, WAITING_LOG_MESSAGE),
new LogRecord(Level.FINE, GOT_CONNECTION_LOG_MESSAGE),
new LogRecord(Level.FINE, READ_INPUT_LOG_MESSAGE),
new LogRecord(Level.FINE, WROTE_OUTPUT_LOG_MESSAGE),
new LogRecord(Level.FINE, FLUSHED_AND_CLOSED_LOG_MESSAGE));
}
@SuppressWarnings("serial")
@Test
public void logsAndDiesOnIOExceptionDuringWait() throws Throwable {
httpListener = new MockHTTPListener() {
@Override
public void waitForConnection() throws IOException {
throw new BindException("Address already in use");
}
};
listener = new SlaveListener(0, RunType.ONCE_ONLY);
listener.setLogger(logger);
listener.setHTTPListener(httpListener);
assertEquals(RunResult.ABORT, listener.call());
logger.verifyLogs(new LogRecord(Level.INFO, STARTUP_LOG_MESSAGE),
new LogRecord(Level.FINE, WAITING_LOG_MESSAGE),
logger.makeThrowableLogRecord(Level.SEVERE, new BindException()));
}
@SuppressWarnings("serial")
@Test
public void logsAndContinuesOnIOExceptionDuringRead() throws Throwable {
httpListener = new MockHTTPListener() {
@Override
public InputStream getInputStream() {
return new InputStream() {
@Override
public int read() throws IOException {
throw new IOException();
}
};
}
};
listener = new SlaveListener(0, RunType.ONCE_ONLY);
listener.setLogger(logger);
listener.setHTTPListener(httpListener);
assertEquals(RunResult.CONTINUE, listener.call());
logger.verifyLogs(new LogRecord(Level.INFO, STARTUP_LOG_MESSAGE),
new LogRecord(Level.FINE, WAITING_LOG_MESSAGE),
new LogRecord(Level.FINE, GOT_CONNECTION_LOG_MESSAGE),
logger.makeThrowableLogRecord(Level.SEVERE, new IOException()));
}
@SuppressWarnings("serial")
@Test
public void logsAndContinuesOnIOExceptionDuringWrite() throws Throwable {
httpListener = new MockHTTPListener() {
@Override
public OutputStream getOutputStream() {
return new OutputStream() {
@Override
public void write(int b) throws IOException {
throw new IOException();
}
};
}
};
listener = new SlaveListener(0, RunType.ONCE_ONLY);
listener.setLogger(logger);
listener.setHTTPListener(httpListener);
assertEquals(RunResult.CONTINUE, listener.call());
logger.verifyLogs(new LogRecord(Level.INFO, STARTUP_LOG_MESSAGE),
new LogRecord(Level.FINE, WAITING_LOG_MESSAGE),
new LogRecord(Level.FINE, GOT_CONNECTION_LOG_MESSAGE),
new LogRecord(Level.FINE, READ_INPUT_LOG_MESSAGE),
logger.makeThrowableLogRecord(Level.SEVERE, new IOException()));
}
}
class MockHTTPListener implements HTTPListener {
private static final long serialVersionUID = 1L;
private static final String HTTP_INPUT_HEADERS
= "GET / HTTP/1.1\n"
+ "Host: localhost:8080\n"
+ "User-Agent: Mozilla/5.0\n"
+ "\n";
private static final byte BYTES_TO_READ[] = HTTP_INPUT_HEADERS.getBytes();
public boolean waitForConnectionCalled = false;
public boolean allBytesRead = false;
public transient ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
public boolean flushAndCloseCalled = false;
public void waitForConnection() throws IOException {
waitForConnectionCalled = true;
}
public InputStream getInputStream() {
return new InputStream() {
private int i = 0;
@Override
public int read() throws IOException {
if (i >= BYTES_TO_READ.length) {
return -1;
}
if (i == BYTES_TO_READ.length - 1) {
allBytesRead = true;
}
return BYTES_TO_READ[i++];
}
};
}
public OutputStream getOutputStream() { return outputStream; }
public void flushAndClose() {
flushAndCloseCalled = true;
}
}
class DummyStatusReporter implements StatusReporter, Serializable {
private static final long serialVersionUID = 1L;
public String getName() { return "test"; }
public String getContent() { return "Hello"; }
}