package com.bazaarvoice.ostrich.pool;
import com.bazaarvoice.ostrich.MultiThreadedServiceFactory;
import com.bazaarvoice.ostrich.ServiceEndPoint;
import com.codahale.metrics.MetricRegistry;
import com.google.common.collect.Lists;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class MultiThreadedClientServiceCacheTest {
private ServiceEndPoint _endPoint;
private MultiThreadedServiceFactory<Service> _factory;
private List<MultiThreadedClientServiceCache<?>> _caches = Lists.newArrayList();
private MetricRegistry _metricRegistry = new MetricRegistry();
@SuppressWarnings ("unchecked")
@Before
public void setup() {
_factory = mock(MultiThreadedServiceFactory.class);
when(_factory.getServiceName()).thenReturn(Service.class.getSimpleName());
when(_factory.create(any(ServiceEndPoint.class))).thenAnswer(new Answer<Service>() {
@Override
public Service answer(InvocationOnMock invocation)
throws Throwable {
return mock(Service.class);
}
});
_endPoint = newEndPoint("id", "name");
}
@After
public void teardown() {
for (MultiThreadedClientServiceCache<?> cache : _caches) {
cache.close();
}
}
@Test (expected = NullPointerException.class)
public void testCheckOutFromNullEndPoint()
throws Exception {
newCache().checkOut(null);
}
@Test (expected = NullPointerException.class)
public void testCheckInNullHandle()
throws Exception {
newCache().checkIn(null);
}
@Test (expected = NullPointerException.class)
public void testCheckInToNullEndPoint()
throws Exception {
Service service = mock(Service.class);
ServiceHandle<Service> handle = new ServiceHandle<>(service, null);
newCache().checkIn(handle);
}
@Test (expected = NullPointerException.class)
public void testCheckInNullServiceInstance()
throws Exception {
ServiceHandle<Service> handle = new ServiceHandle<>(null, _endPoint);
newCache().checkIn(handle);
}
@Test (expected = NullPointerException.class)
public void testEvictNullEndPoint() {
newCache().evict(null);
}
@Test
public void testFactoryExceptionIsPropagated() {
NullPointerException exception = mock(NullPointerException.class);
when(_factory.create(any(ServiceEndPoint.class))).thenThrow(exception);
try {
newCache().checkOut(_endPoint);
fail();
} catch (Exception caught) {
assertSame(exception, caught);
}
}
@Test
public void testServiceInstancesAreReused()
throws Exception {
Service service = mock(Service.class);
when(_factory.create(any(ServiceEndPoint.class))).thenReturn(service);
MultiThreadedClientServiceCache<Service> cache = newCache();
ServiceHandle<Service> handle1 = cache.checkOut(_endPoint);
ServiceHandle<Service> handle2 = cache.checkOut(_endPoint);
ServiceHandle<Service> handle3 = cache.checkOut(_endPoint);
ServiceHandle<Service> handle4 = cache.checkOut(_endPoint);
assertSame(service, handle1.getService());
assertSame(service, handle2.getService());
assertSame(service, handle3.getService());
assertSame(service, handle4.getService());
assertEquals(1, cache.getNumActiveInstances(_endPoint));
assertEquals(1, cache.getNumIdleInstances(_endPoint));
}
@Test
public void testRegisterCheckOutEvict()
throws Exception {
Service service = mock(Service.class);
when(_factory.create(any(ServiceEndPoint.class))).thenReturn(service);
MultiThreadedClientServiceCache<Service> cache = newCache(0);
cache.register(_endPoint);
assertEquals(1, cache.getNumActiveInstances(_endPoint));
assertEquals(1, cache.getNumIdleInstances(_endPoint));
ServiceHandle<Service> handle = cache.checkOut(_endPoint);
assertSame(service, handle.getService());
assertEquals(1, cache.getNumActiveInstances(_endPoint));
assertEquals(1, cache.getNumIdleInstances(_endPoint));
cache.evict(_endPoint);
verify(_factory, timeout(100).times(1)).destroy(_endPoint, handle.getService());
assertEquals(0, cache.getNumActiveInstances(_endPoint));
assertEquals(0, cache.getNumIdleInstances(_endPoint));
}
@Test
public void testCheckOutRegisterEvict()
throws Exception {
Service service = mock(Service.class);
when(_factory.create(any(ServiceEndPoint.class))).thenReturn(service);
MultiThreadedClientServiceCache<Service> cache = newCache(0);
ServiceHandle<Service> handle = cache.checkOut(_endPoint);
assertSame(service, handle.getService());
assertEquals(1, cache.getNumActiveInstances(_endPoint));
assertEquals(1, cache.getNumIdleInstances(_endPoint));
cache.register(_endPoint);
assertEquals(1, cache.getNumActiveInstances(_endPoint));
assertEquals(1, cache.getNumIdleInstances(_endPoint));
cache.evict(_endPoint);
verify(_factory, timeout(100).times(1)).destroy(_endPoint, handle.getService());
assertEquals(0, cache.getNumActiveInstances(_endPoint));
assertEquals(0, cache.getNumIdleInstances(_endPoint));
}
@Test
public void testCheckOutEvictCheckOut()
throws Exception {
Service service = mock(Service.class);
when(_factory.create(any(ServiceEndPoint.class))).thenReturn(service);
MultiThreadedClientServiceCache<Service> cache = newCache(0);
ServiceHandle<Service> handle1 = cache.checkOut(_endPoint);
assertSame(service, handle1.getService());
assertEquals(1, cache.getNumActiveInstances(_endPoint));
assertEquals(1, cache.getNumIdleInstances(_endPoint));
cache.evict(_endPoint);
verify(_factory, timeout(100).times(1)).destroy(_endPoint, handle1.getService());
assertEquals(0, cache.getNumActiveInstances(_endPoint));
assertEquals(0, cache.getNumIdleInstances(_endPoint));
ServiceHandle<Service> handle2 = cache.checkOut(_endPoint);
assertSame(service, handle2.getService());
assertEquals(1, cache.getNumActiveInstances(_endPoint));
assertEquals(1, cache.getNumIdleInstances(_endPoint));
assertNotSame(handle1, handle2);
}
@Test
public void testEvictedEndPointDestroyedManualEviction()
throws Exception {
MultiThreadedClientServiceCache<Service> cache = newCache(0);
ServiceHandle<Service> handle = cache.checkOut(_endPoint);
cache.checkIn(handle);
cache.evict(_endPoint);
verify(_factory, timeout(100).times(1)).destroy(_endPoint, handle.getService());
}
@Test
public void testEvictedEndPointWhileServiceInstanceCheckedOut()
throws Exception {
Service validService = mock(Service.class);
ServiceEndPoint validEndPoint = newEndPoint("valid", "name");
when(_factory.create(validEndPoint)).thenReturn(validService);
Service invalidService = mock(Service.class);
ServiceEndPoint invalidEndPoint = newEndPoint("invalid", "name");
when(_factory.create(invalidEndPoint)).thenReturn(invalidService);
MultiThreadedClientServiceCache<Service> cache = newCache(0);
cache.register(invalidEndPoint);
cache.register(validEndPoint);
ServiceHandle<Service> invalidHandle = cache.checkOut(invalidEndPoint);
ServiceHandle<Service> validHandle = cache.checkOut(validEndPoint);
cache.evict(invalidEndPoint);
cache.checkIn(invalidHandle);
cache.checkIn(validHandle);
verify(_factory, timeout(100).times(1)).destroy(invalidEndPoint, invalidService);
verify(_factory, never()).destroy(validEndPoint, validService);
}
@Test
public void testEvictedEndPointWhileDuplicateServiceInstancesCheckedOut()
throws Exception {
Service service = mock(Service.class);
when(_factory.create(any(ServiceEndPoint.class))).thenReturn(service);
MultiThreadedClientServiceCache<Service> cache = newCache(0);
ServiceHandle<Service> handle1 = cache.checkOut(_endPoint);
ServiceHandle<Service> handle2 = cache.checkOut(_endPoint);
cache.evict(_endPoint);
cache.checkIn(handle1);
cache.checkIn(handle2);
assertSame(service, handle1.getService());
assertSame(service, handle2.getService());
verify(_factory, timeout(100).times(1)).destroy(_endPoint, service);
}
@Test (expected = NullPointerException.class)
public void testNumIdleNullEndPoint() {
MultiThreadedClientServiceCache<Service> cache = newCache();
cache.getNumIdleInstances(null);
}
@Test (expected = NullPointerException.class)
public void testNumActiveNullEndPoint() {
MultiThreadedClientServiceCache<Service> cache = newCache();
cache.getNumActiveInstances(null);
}
@Test
public void testNumActiveInstances()
throws Exception {
MultiThreadedClientServiceCache<Service> cache = newCache();
cache.register(_endPoint);
assertEquals(1, cache.getNumActiveInstances(_endPoint));
assertEquals(0, cache.getNumActiveInstances(newEndPoint("new", "endPoint")));
}
@Test
public void testNumIdleInstances()
throws Exception {
MultiThreadedClientServiceCache<Service> cache = newCache();
cache.register(_endPoint);
assertEquals(1, cache.getNumIdleInstances(_endPoint));
assertEquals(0, cache.getNumActiveInstances(newEndPoint("new", "endPoint")));
}
@Test
public void testCloseDestroysCachedInstances()
throws Exception {
MultiThreadedClientServiceCache<Service> cache = newCache();
ServiceHandle<Service> handle = cache.checkOut(_endPoint);
cache.checkIn(handle);
cache.close();
verify(_factory).destroy(_endPoint, handle.getService());
}
@Test
public void testMultipleClose() {
MultiThreadedClientServiceCache<Service> cache = newCache();
cache.close();
cache.close();
}
private MultiThreadedClientServiceCache<Service> newCache() {
return newCache(1);
}
private MultiThreadedClientServiceCache<Service> newCache(int ttl) {
MultiThreadedClientServiceCache<Service> cache = new MultiThreadedClientServiceCache<>(_factory, ServiceCacheBuilder.buildDefaultExecutor(), ttl, ttl, _metricRegistry);
_caches.add(cache);
return cache;
}
private ServiceEndPoint newEndPoint(String id, String name) {
ServiceEndPoint endPoint = mock(ServiceEndPoint.class);
when(endPoint.getId()).thenReturn(id);
when(endPoint.getServiceName()).thenReturn(name);
when(endPoint.getPayload()).thenReturn("");
return endPoint;
}
public static interface Service {
}
}