/*
* Copyright (C) 2011-2014 Chris Vest (mr.chrisvest@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package stormpot;
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import static java.lang.Double.NaN;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static stormpot.AlloKit.*;
public class ConfigTest {
private Config<GenericPoolable> config;
@Before public void
setUp() {
config = new Config<>();
}
@Test public void
sizeMustBeSettable() {
config.setSize(123);
assertTrue(config.getSize() == 123);
}
@Test public void
defaultAllocatorIsNull() {
assertThat(config.getAllocator(), is(nullValue()));
}
@Test public void
defaultReallocatorIsNull() {
assertThat(config.getReallocator(), is(nullValue()));
}
@Test public void
mustAdaptAllocatorsToReallocators() {
Allocator<GenericPoolable> allocator = allocator();
Config<GenericPoolable> cfg = config.setAllocator(allocator);
Reallocator<GenericPoolable> reallocator = cfg.getReallocator();
ReallocatingAdaptor<GenericPoolable> adaptor =
(ReallocatingAdaptor<GenericPoolable>) reallocator;
assertThat(adaptor.unwrap(), sameInstance(allocator));
}
@Test public void
mustNotReAdaptConfiguredReallocators() {
Reallocator<GenericPoolable> expected =
new ReallocatingAdaptor<>(null);
config.setAllocator(expected);
Reallocator<GenericPoolable> actual = config.getReallocator();
assertThat(actual, sameInstance(expected));
}
@Test public void
allocatorMustBeSettable() {
Allocator<GenericPoolable> allocator = allocator();
Config<GenericPoolable> cfg = config.setAllocator(allocator);
assertThat(cfg.getAllocator(), sameInstance(allocator));
}
@Test public void
mustHaveTimeBasedDeallocationRuleAsDefault() {
assertThat(config.getExpiration(),
instanceOf(TimeSpreadExpiration.class));
}
@Test public void
deallocationRuleMustBeSettable() {
Expiration<Poolable> expectedRule = info -> false;
config.setExpiration(expectedRule);
@SuppressWarnings("unchecked")
Expiration<Poolable> actualRule =
(Expiration<Poolable>) config.getExpiration();
assertThat(actualRule, is(expectedRule));
}
@Test public void
metricsRecorderMustBeSettable() {
MetricsRecorder expected =
new FixedMeanMetricsRecorder(1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
config.setMetricsRecorder(expected);
MetricsRecorder actual = config.getMetricsRecorder();
assertThat(actual, is(expected));
}
@Test public void
getAdaptedReallocatorMustReturnNullIfNoAllocatorConfigured() {
assertNull(config.getAdaptedReallocator());
}
@Test public void
getAdaptedReallocatorMustReturnNullWhenNoAllocatorConfiguredEvenIfMetricsRecorderIsConfigured() {
config.setMetricsRecorder(new LastSampleMetricsRecorder());
assertNull(config.getAdaptedReallocator());
}
@Test public void
getAdaptedReallocatorMustAdaptConfiguredAllocatorIfNoMetricsRecorderConfigured()
throws Exception {
CountingAllocator allocator = allocator();
config.setAllocator(allocator);
config.getAdaptedReallocator().allocate(new NullSlot());
assertThat(allocator.countAllocations(), is(1));
}
@Test public void
getAdaptedReallocatorMustNotAdaptConfiguredReallocatorIfNoMetricsRecorderConfigured()
throws Exception {
CountingReallocator reallocator = reallocator();
config.setAllocator(reallocator);
Slot slot = new NullSlot();
Reallocator<GenericPoolable> adaptedReallocator = config.getAdaptedReallocator();
GenericPoolable obj = adaptedReallocator.allocate(slot);
adaptedReallocator.reallocate(slot, obj);
assertThat(reallocator.countAllocations(), is(1));
assertThat(reallocator.countReallocations(), is(1));
}
private void verifyLatencies(
MetricsRecorder recorder,
Matcher<Double> alloc, Matcher<Double> allocFail,
Matcher<Double> dealloc,
Matcher<Double> realloc, Matcher<Double> reallocFail) {
assertThat(recorder.getAllocationLatencyPercentile(0.0), alloc);
assertThat(recorder.getAllocationFailureLatencyPercentile(0.0), allocFail);
assertThat(recorder.getDeallocationLatencyPercentile(0.0), dealloc);
assertThat(recorder.getReallocationLatencyPercentile(0.0), realloc);
assertThat(recorder.getReallocationFailurePercentile(0.0), reallocFail);
}
@Test public void
getAdaptedReallocatorMustInstrumentAllocateMethodOnAllocatorIfMetricsRecorderConfigured()
throws Exception {
MetricsRecorder r = new LastSampleMetricsRecorder();
config.setMetricsRecorder(r);
config.setAllocator(allocator(alloc($new, $throw(new Exception()))));
Reallocator<GenericPoolable> adaptedReallocator = config.getAdaptedReallocator();
verifyLatencies(r, is(NaN), is(NaN), is(NaN), is(NaN), is(NaN));
adaptedReallocator.allocate(new NullSlot());
verifyLatencies(r, is(not(NaN)), is(NaN), is(NaN), is(NaN), is(NaN));
try {
adaptedReallocator.allocate(new NullSlot());
fail("second allocation did not throw as expected");
} catch (Exception ignore) {}
verifyLatencies(r, is(not(NaN)), is(not(NaN)), is(NaN), is(NaN), is(NaN));
}
@Test public void
getAdaptedReallocatorMustInstrumentAllocateMethodOnReallocatorIfMetricsRecorderConfigured()
throws Exception {
MetricsRecorder r = new LastSampleMetricsRecorder();
config.setMetricsRecorder(r);
config.setAllocator(reallocator(alloc($new, $throw(new Exception()))));
Reallocator<GenericPoolable> adaptedReallocator = config.getAdaptedReallocator();
verifyLatencies(r, is(NaN), is(NaN), is(NaN), is(NaN), is(NaN));
adaptedReallocator.allocate(new NullSlot());
verifyLatencies(r, is(not(NaN)), is(NaN), is(NaN), is(NaN), is(NaN));
try {
adaptedReallocator.allocate(new NullSlot());
fail("allocation did not throw as expected");
} catch (Exception ignore) {}
verifyLatencies(r, is(not(NaN)), is(not(NaN)), is(NaN), is(NaN), is(NaN));
}
@Test public void
getAdaptedReallocatorMustInstrumentDeallocateMethodOnAllocatorIfMetricsRecorderConfigured()
throws Exception {
MetricsRecorder r = new LastSampleMetricsRecorder();
config.setMetricsRecorder(r);
config.setAllocator(allocator());
Reallocator<GenericPoolable> adaptedReallocator = config.getAdaptedReallocator();
verifyLatencies(r, is(NaN), is(NaN), is(NaN), is(NaN), is(NaN));
GenericPoolable obj = adaptedReallocator.allocate(new NullSlot());
verifyLatencies(r, is(not(NaN)), is(NaN), is(NaN), is(NaN), is(NaN));
adaptedReallocator.deallocate(obj);
verifyLatencies(r, is(not(NaN)), is(NaN), is(not(NaN)), is(NaN), is(NaN));
}
@Test public void
getAdaptedReallocatorMustInstrumentThrowingDeallocateMethodOnAllocatorIfMetricsRecorderConfigured()
throws Exception {
MetricsRecorder r = new LastSampleMetricsRecorder();
config.setMetricsRecorder(r);
config.setAllocator(allocator(dealloc($throw(new Exception()))));
Reallocator<GenericPoolable> adaptedReallocator = config.getAdaptedReallocator();
verifyLatencies(r, is(NaN), is(NaN), is(NaN), is(NaN), is(NaN));
GenericPoolable obj = adaptedReallocator.allocate(new NullSlot());
verifyLatencies(r, is(not(NaN)), is(NaN), is(NaN), is(NaN), is(NaN));
try {
adaptedReallocator.deallocate(obj);
fail("deallocation did not throw as expected");
} catch (Exception e) {
// ignore
}
verifyLatencies(r, is(not(NaN)), is(NaN), is(not(NaN)), is(NaN), is(NaN));
}
@Test public void
getAdaptedReallocatorMustInstrumentDeallocateMethodOnReallocatorIfMetricsRecorderConfigured()
throws Exception {
MetricsRecorder r = new LastSampleMetricsRecorder();
config.setMetricsRecorder(r);
config.setAllocator(reallocator());
Reallocator<GenericPoolable> adaptedReallocator = config.getAdaptedReallocator();
verifyLatencies(r, is(NaN), is(NaN), is(NaN), is(NaN), is(NaN));
GenericPoolable obj = adaptedReallocator.allocate(new NullSlot());
verifyLatencies(r, is(not(NaN)), is(NaN), is(NaN), is(NaN), is(NaN));
adaptedReallocator.deallocate(obj);
verifyLatencies(r, is(not(NaN)), is(NaN), is(not(NaN)), is(NaN), is(NaN));
}
@Test public void
getAdaptedReallocatorMustInstrumentThrowingDeallocateMethodOnReallocatorIfMetricsRecorderConfigured()
throws Exception {
MetricsRecorder r = new LastSampleMetricsRecorder();
config.setMetricsRecorder(r);
config.setAllocator(reallocator(dealloc($throw(new Exception()))));
Reallocator<GenericPoolable> adaptedReallocator = config.getAdaptedReallocator();
verifyLatencies(r, is(NaN), is(NaN), is(NaN), is(NaN), is(NaN));
GenericPoolable obj = adaptedReallocator.allocate(new NullSlot());
verifyLatencies(r, is(not(NaN)), is(NaN), is(NaN), is(NaN), is(NaN));
try {
adaptedReallocator.deallocate(obj);
fail("deallocation did not throw as expected");
} catch (Exception e) {
// ignore
}
verifyLatencies(r, is(not(NaN)), is(NaN), is(not(NaN)), is(NaN), is(NaN));
}
@Test public void
getAdaptedReallocatorMustInstrumentReallocateMethodOnReallocatorIfMetricsRecorderConfigured()
throws Exception {
MetricsRecorder r = new LastSampleMetricsRecorder();
config.setMetricsRecorder(r);
config.setAllocator(reallocator(realloc($new, $throw(new Exception()))));
Reallocator<GenericPoolable> adaptedReallocator = config.getAdaptedReallocator();
verifyLatencies(r, is(NaN), is(NaN), is(NaN), is(NaN), is(NaN));
GenericPoolable obj = adaptedReallocator.allocate(new NullSlot());
verifyLatencies(r, is(not(NaN)), is(NaN), is(NaN), is(NaN), is(NaN));
adaptedReallocator.reallocate(new NullSlot(), obj);
verifyLatencies(r, is(not(NaN)), is(NaN), is(NaN), is(not(NaN)), is(NaN));
try {
adaptedReallocator.reallocate(new NullSlot(), obj);
fail("reallocation did not throw as expected");
} catch (Exception ignore) {}
verifyLatencies(r, is(not(NaN)), is(NaN), is(NaN), is(not(NaN)), is(not(NaN)));
}
@Test public void
defaultThreadFactoryMustCreateThreadsWithStormpotNameSignature() {
ThreadFactory factory = config.getThreadFactory();
Thread thread = factory.newThread(null);
assertThat(thread.getName(), containsString("Stormpot"));
}
@Test public void
threadFactoryMustBeSettable() {
ThreadFactory factory = r -> null;
config.setThreadFactory(factory);
assertThat(config.getThreadFactory(), sameInstance(factory));
}
@Test public void
preciseLeakDetectionMustBeEnabledByDefault() {
assertTrue(config.isPreciseLeakDetectionEnabled());
}
@Test public void
preciseLeakDetectionMustBeSettable() {
config.setPreciseLeakDetectionEnabled(false);
assertFalse(config.isPreciseLeakDetectionEnabled());
}
@Test public void
backgroundExpirationIsDisabledByDefault() {
assertFalse(config.isBackgroundExpirationEnabled());
}
@Test public void
backgroundExpirationMystBeSettable() {
config.setBackgroundExpirationEnabled(true);
assertTrue(config.isBackgroundExpirationEnabled());
}
@Test public void
allSetterMethodsMustReturnTheSameConfigInstance() throws Exception {
Method[] methods = Config.class.getDeclaredMethods();
List<Method> setterMethods = new ArrayList<>();
for (Method method : methods) {
if (method.getName().startsWith("set")) {
setterMethods.add(method);
}
}
for (Method setter : setterMethods) {
Class<?> parameterType = setter.getParameterTypes()[0];
Object arg =
parameterType == Boolean.TYPE? true :
parameterType == Integer.TYPE? 1 : null;
Object result = setter.invoke(config, arg);
assertThat("return value of setter " + setter,
result, sameInstance((Object) config));
}
}
@Test public void
allPublicDeclaredMethodsMustBeSynchronized() {
// We don't care about non-overridden public methods of the super-class
// (Object) because they don't operate on the state of the Config object
// anyway.
Method[] methods = Config.class.getDeclaredMethods();
for (Method method : methods) {
int modifiers = method.getModifiers();
int IS_SYNTHETIC = 0x00001000;
if (Modifier.isPublic(modifiers) && (modifiers & IS_SYNTHETIC) == 0) {
// That is, this method is both public AND NOT synthetic.
// We have to exclude synthetic methods because javac generates one for
// bridging the covariant override of clone().
assertTrue("Method '" + method + "' should be synchronized.",
Modifier.isSynchronized(modifiers));
}
}
}
@Test public void
configMustBeCloneable() {
CountingAllocator allocator = AlloKit.allocator();
ExpireKit.CountingExpiration expiration = ExpireKit.expire();
MetricsRecorder metricsRecorder = new LastSampleMetricsRecorder();
ThreadFactory factory = r -> null;
config.setExpiration(expiration);
config.setAllocator(allocator);
config.setBackgroundExpirationEnabled(true);
config.setMetricsRecorder(metricsRecorder);
config.setPreciseLeakDetectionEnabled(false);
config.setSize(42);
config.setThreadFactory(factory);
Config<GenericPoolable> clone = config.clone();
assertThat(clone.getExpiration(), sameInstance(expiration));
assertThat(clone.getAllocator(), sameInstance(allocator));
assertTrue(clone.isBackgroundExpirationEnabled());
assertThat(clone.getMetricsRecorder(), sameInstance(metricsRecorder));
assertFalse(clone.isPreciseLeakDetectionEnabled());
assertThat(clone.getSize(), is(42));
assertThat(clone.getThreadFactory(), sameInstance(factory));
}
}