@Test
  public void testCloseIdle() throws Exception {
    final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);

    final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn1.isOpen()).thenReturn(true);
    final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn2.isOpen()).thenReturn(true);

    Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2);

    final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);

    final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
    final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
    Assert.assertNotNull(entry1);
    final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
    final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
    Assert.assertNotNull(entry2);

    entry1.updateExpiry(0, TimeUnit.MILLISECONDS);
    pool.release(entry1, true);

    Thread.sleep(200L);

    entry2.updateExpiry(0, TimeUnit.MILLISECONDS);
    pool.release(entry2, true);

    pool.closeIdle(50, TimeUnit.MILLISECONDS);

    Mockito.verify(conn1).close();
    Mockito.verify(conn2, Mockito.never()).close();

    PoolStats totals = pool.getTotalStats();
    Assert.assertEquals(1, totals.getAvailable());
    Assert.assertEquals(0, totals.getLeased());
    PoolStats stats = pool.getStats("somehost");
    Assert.assertEquals(1, stats.getAvailable());
    Assert.assertEquals(0, stats.getLeased());

    pool.closeIdle(-1, TimeUnit.MILLISECONDS);

    Mockito.verify(conn2).close();

    totals = pool.getTotalStats();
    Assert.assertEquals(0, totals.getAvailable());
    Assert.assertEquals(0, totals.getLeased());
    stats = pool.getStats("somehost");
    Assert.assertEquals(0, stats.getAvailable());
    Assert.assertEquals(0, stats.getLeased());
  }
  @Test
  public void testValidateConnectionStale() throws Exception {
    final HttpConnection conn = Mockito.mock(HttpConnection.class);
    Mockito.when(conn.isOpen()).thenReturn(true);
    Mockito.when(conn.isStale()).thenReturn(false);

    final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
    Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn);

    final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
    pool.setValidateAfterInactivity(5);

    final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
    final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
    Assert.assertNotNull(entry1);

    pool.release(entry1, true);

    Thread.sleep(10);

    Mockito.verify(connFactory, Mockito.times(1)).create("somehost");
    Mockito.when(conn.isStale()).thenReturn(true);

    final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
    final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
    Assert.assertNotNull(entry2);
    Assert.assertNotSame(entry1, entry2);

    Mockito.verify(conn, Mockito.times(1)).isStale();
    Mockito.verify(conn, Mockito.times(1)).close();
    Mockito.verify(connFactory, Mockito.times(2)).create("somehost");
  }
  @Test
  public void testLeaseCancel() throws Exception {
    final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);

    final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn1.isOpen()).thenReturn(true);
    Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);

    final LocalConnPool pool = new LocalConnPool(connFactory, 1, 1);

    final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
    final GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
    t1.start();

    t1.join(GRACE_PERIOD);
    Assert.assertTrue(future1.isDone());
    final LocalPoolEntry entry1 = t1.getEntry();
    Assert.assertNotNull(entry1);

    final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
    final GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
    t2.start();

    Thread.sleep(5);

    Assert.assertFalse(future2.isDone());
    Assert.assertFalse(future2.isCancelled());

    future2.cancel(true);
    t2.join(GRACE_PERIOD);
    Assert.assertTrue(future2.isDone());
    Assert.assertTrue(future2.isCancelled());
    future2.cancel(true);
    future2.cancel(true);
  }
  @Test
  public void testShutdown() throws Exception {
    final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);

    final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn1.isOpen()).thenReturn(true);
    Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
    final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn2.isOpen()).thenReturn(true);
    Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn2);

    final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
    final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
    final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
    Assert.assertNotNull(entry1);
    final Future<LocalPoolEntry> future2 = pool.lease("otherhost", null);
    final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
    Assert.assertNotNull(entry2);

    pool.release(entry2, true);

    final PoolStats totals = pool.getTotalStats();
    Assert.assertEquals(1, totals.getAvailable());
    Assert.assertEquals(1, totals.getLeased());

    pool.shutdown();
    Assert.assertTrue(pool.isShutdown());
    pool.shutdown();
    pool.shutdown();

    Mockito.verify(conn1, Mockito.atLeastOnce()).close();
    Mockito.verify(conn2, Mockito.atLeastOnce()).close();

    try {
      pool.lease("somehost", null);
      Assert.fail("IllegalStateException should have been thrown");
    } catch (final IllegalStateException expected) {
    }
    // Ignored if shut down
    pool.release(new LocalPoolEntry("somehost", Mockito.mock(HttpConnection.class)), true);
  }
  @Test
  public void testLeaseRelease() throws Exception {
    final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn1.isOpen()).thenReturn(true);
    final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn2.isOpen()).thenReturn(true);

    final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
    Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
    Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn2);

    final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
    final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
    final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
    Assert.assertNotNull(entry1);
    final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
    final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
    Assert.assertNotNull(entry2);
    final Future<LocalPoolEntry> future3 = pool.lease("otherhost", null);
    final LocalPoolEntry entry3 = future3.get(1, TimeUnit.SECONDS);
    Assert.assertNotNull(entry3);

    PoolStats totals = pool.getTotalStats();
    Assert.assertEquals(0, totals.getAvailable());
    Assert.assertEquals(3, totals.getLeased());

    final LocalPoolEntry entry = future1.get();
    Assert.assertSame(entry1, entry);

    pool.release(entry1, true);
    pool.release(entry2, true);
    pool.release(entry3, false);
    Mockito.verify(conn1, Mockito.never()).close();
    Mockito.verify(conn2, Mockito.times(1)).close();

    totals = pool.getTotalStats();
    Assert.assertEquals(2, totals.getAvailable());
    Assert.assertEquals(0, totals.getLeased());
  }
  @Test
  public void testStatefulConnectionRedistributionOnPerRouteMaxLimit() throws Exception {
    final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);

    final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn1.isOpen()).thenReturn(true);
    final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn2.isOpen()).thenReturn(true);
    final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn3.isOpen()).thenReturn(true);
    Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2, conn3);

    final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
    pool.setMaxPerRoute("somehost", 2);
    pool.setMaxTotal(2);

    final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
    final GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
    t1.start();

    t1.join(GRACE_PERIOD);
    Assert.assertTrue(future1.isDone());
    final LocalPoolEntry entry1 = t1.getEntry();
    Assert.assertNotNull(entry1);

    final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
    final GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
    t2.start();

    t2.join(GRACE_PERIOD);
    Assert.assertTrue(future2.isDone());
    final LocalPoolEntry entry2 = t2.getEntry();
    Assert.assertNotNull(entry2);

    PoolStats totals = pool.getTotalStats();
    Assert.assertEquals(0, totals.getAvailable());
    Assert.assertEquals(2, totals.getLeased());
    Assert.assertEquals(0, totals.getPending());

    entry1.setState("some-stuff");
    pool.release(entry1, true);
    entry2.setState("some-stuff");
    pool.release(entry2, true);

    Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));

    final Future<LocalPoolEntry> future3 = pool.lease("somehost", "some-other-stuff");
    final GetPoolEntryThread t3 = new GetPoolEntryThread(future3);
    t3.start();

    t3.join(GRACE_PERIOD);
    Assert.assertTrue(future3.isDone());
    final LocalPoolEntry entry3 = t3.getEntry();
    Assert.assertNotNull(entry3);

    Mockito.verify(connFactory, Mockito.times(3)).create(Mockito.eq("somehost"));

    Mockito.verify(conn1).close();
    Mockito.verify(conn2, Mockito.never()).close();

    totals = pool.getTotalStats();
    Assert.assertEquals(1, totals.getAvailable());
    Assert.assertEquals(1, totals.getLeased());
  }
  @Test
  public void testConnectionRedistributionOnTotalMaxLimit() throws Exception {
    final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);

    final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn1.isOpen()).thenReturn(true);
    final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn2.isOpen()).thenReturn(true);
    final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn3.isOpen()).thenReturn(true);
    Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2, conn3);

    final HttpConnection conn4 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn4.isOpen()).thenReturn(true);
    final HttpConnection conn5 = Mockito.mock(HttpConnection.class);
    Mockito.when(conn5.isOpen()).thenReturn(true);
    Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn4, conn5);

    final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
    pool.setMaxPerRoute("somehost", 2);
    pool.setMaxPerRoute("otherhost", 2);
    pool.setMaxTotal(2);

    final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
    final GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
    t1.start();
    final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
    final GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
    t2.start();

    t1.join(GRACE_PERIOD);
    Assert.assertTrue(future1.isDone());
    final LocalPoolEntry entry1 = t1.getEntry();
    Assert.assertNotNull(entry1);
    t2.join(GRACE_PERIOD);
    Assert.assertTrue(future2.isDone());
    final LocalPoolEntry entry2 = t2.getEntry();
    Assert.assertNotNull(entry2);

    final Future<LocalPoolEntry> future3 = pool.lease("otherhost", null);
    final GetPoolEntryThread t3 = new GetPoolEntryThread(future3);
    t3.start();
    final Future<LocalPoolEntry> future4 = pool.lease("otherhost", null);
    final GetPoolEntryThread t4 = new GetPoolEntryThread(future4);
    t4.start();

    Assert.assertFalse(t3.isDone());
    Assert.assertFalse(t4.isDone());

    Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));
    Mockito.verify(connFactory, Mockito.never()).create(Mockito.eq("otherhost"));

    PoolStats totals = pool.getTotalStats();
    Assert.assertEquals(0, totals.getAvailable());
    Assert.assertEquals(2, totals.getLeased());

    pool.release(entry1, true);
    pool.release(entry2, true);

    t3.join(GRACE_PERIOD);
    Assert.assertTrue(future3.isDone());
    final LocalPoolEntry entry3 = t3.getEntry();
    Assert.assertNotNull(entry3);
    t4.join(GRACE_PERIOD);
    Assert.assertTrue(future4.isDone());
    final LocalPoolEntry entry4 = t4.getEntry();
    Assert.assertNotNull(entry4);

    Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));
    Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("otherhost"));

    totals = pool.getTotalStats();
    Assert.assertEquals(0, totals.getAvailable());
    Assert.assertEquals(2, totals.getLeased());

    final Future<LocalPoolEntry> future5 = pool.lease("somehost", null);
    final GetPoolEntryThread t5 = new GetPoolEntryThread(future5);
    t5.start();
    final Future<LocalPoolEntry> future6 = pool.lease("otherhost", null);
    final GetPoolEntryThread t6 = new GetPoolEntryThread(future6);
    t6.start();

    pool.release(entry3, true);
    pool.release(entry4, true);

    t5.join(GRACE_PERIOD);
    Assert.assertTrue(future5.isDone());
    final LocalPoolEntry entry5 = t5.getEntry();
    Assert.assertNotNull(entry5);
    t6.join(GRACE_PERIOD);
    Assert.assertTrue(future6.isDone());
    final LocalPoolEntry entry6 = t6.getEntry();
    Assert.assertNotNull(entry6);

    Mockito.verify(connFactory, Mockito.times(3)).create(Mockito.eq("somehost"));
    Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("otherhost"));

    totals = pool.getTotalStats();
    Assert.assertEquals(0, totals.getAvailable());
    Assert.assertEquals(2, totals.getLeased());

    pool.release(entry5, true);
    pool.release(entry6, true);

    totals = pool.getTotalStats();
    Assert.assertEquals(2, totals.getAvailable());
    Assert.assertEquals(0, totals.getLeased());
  }