package com.youdevise.hudson.slavestatus;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import javax.servlet.ServletException;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.Computer;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import hudson.model.Descriptor.FormException;
import hudson.remoting.Callable;
import hudson.remoting.Future;
import hudson.remoting.VirtualChannel;
import hudson.security.ACL;
import hudson.slaves.NodeDescriptor;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.RetentionStrategy;
import hudson.util.ClockDifference;
import hudson.util.DescribableList;
import org.junit.Before;
import org.junit.Test;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertEquals;
public class SlaveListenerInitiatorTest {
private static final String COMPUTER_NAME_1 = "slave 1";
private static final String COMPUTER_NAME_2 = "slave 2";
private static final String STARTUP_LOG_MESSAGE = "Initialising slave-status plugin";
private static final String SLAVE_1_START_LOG_MESSAGE = "Starting slave-status listener on " + COMPUTER_NAME_1;
private static final String SLAVE_2_START_LOG_MESSAGE = "Starting slave-status listener on " + COMPUTER_NAME_2;
private static final int PORT_1 = 2230;
private static final int PORT_2 = 2232;
private MockChannel channel1;
private Computer computer1;
private MockChannel channel2;
private Computer computer2;
private MockLogger logger;
private SlaveListenerInitiator initiator;
@Before
public void setUp() throws FormException {
channel1 = new MockChannel();
computer1 = new MockComputer(COMPUTER_NAME_1, channel1);
channel2 = new MockChannel();
computer2 = new MockComputer(COMPUTER_NAME_2, channel2);
logger = new MockLogger();
initiator = new SlaveListenerInitiator(PORT_1, logger);
}
@Test
public void startsListenersWhenSlavesStart() throws FormException {
initiator.register(new DummyReporter());
initiator.onOnline(computer1, null);
initiator.onOnline(computer2, null);
assertNotNull(channel1.callable);
checkCallable(channel1, PORT_1, DummyReporter.class);
assertNotNull(channel2.callable);
checkCallable(channel2, PORT_1, DummyReporter.class);
}
@Test
public void canChangePortOnTheFly() throws FormException {
initiator.onOnline(computer1, null);
assertNotNull(channel1.callable);
checkCallable(channel1, PORT_1);
initiator.setPort(PORT_2);
initiator.onOnline(computer2, null);
assertNotNull(channel2.callable);
checkCallable(channel2, PORT_2);
}
@SuppressWarnings("unchecked")
private void checkCallable(MockChannel channel, int port, Class ...additionalReporterClasses) {
Callable callable = channel.callable;
assertEquals(SlaveListener.class, callable.getClass());
SlaveListener listener = (SlaveListener) callable;
assertEquals(port, listener.getPort());
List<Class> expectedReporterClasses = new ArrayList<Class>();
expectedReporterClasses.add(IsRunningReporter.class);
expectedReporterClasses.add(MemoryReporter.class);
expectedReporterClasses.addAll(Arrays.asList(additionalReporterClasses));
int i=0;
List<StatusReporter> actualReporterClasses = listener.getReporters();
for (Class reporterClass : expectedReporterClasses) {
assertEquals(reporterClass, actualReporterClasses.get(i++).getClass());
}
}
@Test
public void logsOnConstruction() {
logger.verifyLogs(new LogRecord(Level.INFO, STARTUP_LOG_MESSAGE));
}
@Test
public void logsWhenSlavesStart() {
initiator.onOnline(computer1, null);
initiator.onOnline(computer2, null);
logger.verifyLogs(new LogRecord(Level.INFO, STARTUP_LOG_MESSAGE),
new LogRecord(Level.INFO, SLAVE_1_START_LOG_MESSAGE),
new LogRecord(Level.INFO, SLAVE_2_START_LOG_MESSAGE));
}
@Test
public void logsExceptionWhenSlaveCallFails() throws FormException {
channel1.shouldThrowException = true;
initiator.onOnline(computer1, null);
logger.verifyLogs(new LogRecord(Level.INFO, STARTUP_LOG_MESSAGE),
new LogRecord(Level.INFO, SLAVE_1_START_LOG_MESSAGE),
logger.makeThrowableLogRecord(Level.SEVERE, new IOException()));
}
}
class MockChannel implements VirtualChannel {
@SuppressWarnings("unchecked")
public Callable callable;
public boolean shouldThrowException = false;
public <V, T extends Throwable> Future<V> callAsync(Callable<V, T> callable) throws IOException {
if (shouldThrowException) { throw new IOException(); }
this.callable = callable;
return null;
}
public <V, T extends Throwable> V call(Callable<V, T> callable) throws IOException, T, InterruptedException { return null; }
public void close() throws IOException { }
public <T> T export(Class<T> type, T instance) { return null; }
public void join() throws InterruptedException { }
public void join(long arg0) throws InterruptedException { }
}
class MockComputer extends Computer {
private final String name;
private final VirtualChannel channel;
public MockComputer(String name, VirtualChannel channel) throws FormException {
super(new MockNode());
this.name = name;
this.channel = channel;
}
@Override public VirtualChannel getChannel() { return channel; }
@Override public String getName() { return name; }
@Override public java.util.concurrent.Future<?> _connect(boolean forceReconnect) { return null; }
@Override public void doLaunchSlaveAgent(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { }
@Override public Charset getDefaultCharset() { return null; }
@Override public List<LogRecord> getLogRecords() throws IOException, InterruptedException { return null; }
@Override public RetentionStrategy<Computer> getRetentionStrategy() { return null; }
@Override public boolean isConnecting() { return false; }
}
class MockNode extends Node {
public String getNodeName() { return null; }
public Computer createComputer() { return null; }
public Launcher createLauncher(TaskListener listener) { return null; }
@Override public FilePath createPath(String absolutePath) { return null; }
@Override public Set<Label> getAssignedLabels() { return null; }
public ClockDifference getClockDifference() throws IOException, InterruptedException { return null; }
public NodeDescriptor getDescriptor() { return null; }
public String getLabelString() { return null; }
public Set<Label> getDynamicLabels() { return null; }
public String getNodeDescription() { return null; }
public int getNumExecutors() { return 0; }
public FilePath getRootPath() { return null; }
@Override public Label getSelfLabel() { return null; }
public FilePath getWorkspaceFor(TopLevelItem item) { return null; }
@Override public void setNodeName(String name) { }
@Override public ACL getACL() { return null; }
@Override
public Mode getMode() { return null; }
@Override
public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() { return null; }
}
@SuppressWarnings("serial")
class DummyReporter implements StatusReporter {
public String getName() { return "test"; }
public String getContent() { return "Hello"; }
}