package com.nirima.jenkins.plugins.docker.utils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.ExternalResource; import java.io.IOException; import java.net.ServerSocket; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import static java.lang.System.currentTimeMillis; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.both; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThan; /** * @author lanwen (Merkushev Kirill) */ public class PortUtilsTest { public static final int RETRY_COUNT = 2; public static final int DELAY = (int) SECONDS.toMillis(1); @Rule public SomeServerRule server = new SomeServerRule(); @Rule public ExpectedException ex = ExpectedException.none(); @Test public void shouldConnectToServerSuccessfully() throws Exception { assertThat("Server is up and should connect", PortUtils.connectionCheck(server.host(), server.port()).executeOnce(), is(true)); } @Test public void shouldNotConnectToUnusedPort() throws Exception { assertThat("Unused port should not be connectible", PortUtils.connectionCheck("localhost", 0).executeOnce(), is(false)); } @Test public void shouldWaitForPortAvailableUntilTimeout() throws Exception { long before = currentTimeMillis(); assertThat("Unused port should not be connectible", PortUtils.connectionCheck("localhost", 0).withRetries(RETRY_COUNT) .withEveryRetryWaitFor(DELAY, MILLISECONDS).execute(), is(false)); assertThat("Should wait for timeout", new Date(currentTimeMillis()), greaterThanOrEqualTo(new Date(before + RETRY_COUNT * DELAY))); } @Test public void shouldThrowIllegalStateExOnNotAvailPort() throws Exception { ex.expect(IllegalStateException.class); PortUtils.connectionCheck("localhost", 0).withRetries(RETRY_COUNT).withEveryRetryWaitFor(DELAY, MILLISECONDS) .useSSH().execute(); } @Test public void shouldWaitIfPortAvailableButNotSshUntilTimeout() throws Exception { long before = currentTimeMillis(); assertThat(PortUtils.connectionCheck(server.host(), server.port()).withRetries(RETRY_COUNT) .withEveryRetryWaitFor(DELAY, MILLISECONDS).useSSH().execute(), is(false)); assertThat("Should wait for timeout", new Date(currentTimeMillis()), greaterThanOrEqualTo(new Date(before + RETRY_COUNT * DELAY))); } @Test public void shouldReturnWithoutWaitIfPortAvailable() throws Exception { long before = currentTimeMillis(); assertThat("Used port should be connectible", PortUtils.connectionCheck(server.host(), server.port()).withEveryRetryWaitFor(DELAY, MILLISECONDS).execute(), is(true)); assertThat("Should not wait", new Date(currentTimeMillis()), lessThan(new Date(before + DELAY))); } @Test public void shouldRetryIfPortIsNotAvailableNow() throws Exception { int retries = RETRY_COUNT * 2; long before = currentTimeMillis(); server.stopAndRebindAfter(2 * DELAY, MILLISECONDS); assertThat("Used port should be connectible", PortUtils.connectionCheck(server.host(), server.port()) .withRetries(retries).withEveryRetryWaitFor(DELAY, MILLISECONDS).execute(), is(true)); assertThat("Should wait then retry", new Date(currentTimeMillis()), both(greaterThanOrEqualTo(new Date(before + 2 * DELAY))) .and(lessThan(new Date(before + retries * DELAY)))); } private class SomeServerRule extends ExternalResource { private ServerSocket socket; public int port() { return socket.getLocalPort(); } public String host() { return socket.getInetAddress().getHostAddress(); } public void stopAndRebindAfter(long delay, TimeUnit unit) throws IOException { final int port = port(); socket.close(); Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() { @Override public void run() { try { socket = new ServerSocket(port); } catch (IOException e) { throw new RuntimeException("Can't rebind socket", e); } } }, delay, unit); } @Override protected void before() throws Throwable { socket = new ServerSocket(0); socket.setReuseAddress(true); } @Override protected void after() { try { socket.close(); } catch (IOException e) { // ignore } } } }