/*
* Copyright 2011 Google Inc. All Rights Reserved.
*
* 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 com.googlecode.concurrentlinkedhashmap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap.Builder;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import static com.googlecode.concurrentlinkedhashmap.IsValidConcurrentLinkedHashMap.valid;
import static com.googlecode.concurrentlinkedhashmap.IsValidLinkedDeque.validLinkedDeque;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.doThrow;
import static org.mockito.MockitoAnnotations.initMocks;
/**
* A testing harness for simplifying the unit tests.
*
* @author ben.manes@gmail.com (Ben Manes)
*/
public abstract class AbstractTest {
private static boolean debug;
private long capacity;
@Mock protected EvictionListener<Integer, Integer> listener;
@Captor protected ArgumentCaptor<Runnable> catchUpTask;
@Mock protected ScheduledExecutorService executor;
@Mock protected Weigher<Integer> weigher;
/** Retrieves the maximum weighted capacity to build maps with. */
protected final long capacity() {
return capacity;
}
/* ---------------- Logging methods -------------- */
protected static void info(String message, Object... args) {
if (args.length == 0) {
System.out.println(message);
} else {
System.out.printf(message + "\n", args);
}
}
protected static void debug(String message, Object... args) {
if (debug) {
info(message, args);
}
}
/* ---------------- Testing aspects -------------- */
@Parameters("debug")
@BeforeSuite(alwaysRun = true)
public static void initSuite(@Optional("false") boolean debugMode) {
debug = debugMode;
}
@Parameters("capacity")
@BeforeClass(alwaysRun = true)
public void initClass(long capacity) {
this.capacity = capacity;
initMocks(this);
}
@AfterMethod(alwaysRun = true)
public void validateIfSuccessful(ITestResult result) {
try {
if (result.isSuccess()) {
for (Object param : result.getParameters()) {
validate(param);
}
}
} catch (AssertionError caught) {
result.setStatus(ITestResult.FAILURE);
result.setThrowable(caught);
}
initMocks(this);
}
/** Validates the state of the injected parameter. */
private static void validate(Object param) {
if (param instanceof ConcurrentLinkedHashMap<?, ?>) {
assertThat((ConcurrentLinkedHashMap<?, ?>) param, is(valid()));
} else if (param instanceof LinkedDeque<?>) {
assertThat((LinkedDeque<?>) param, is(validLinkedDeque()));
}
}
/* ---------------- Map providers -------------- */
/** Provides a builder with the capacity set. */
@DataProvider(name = "builder")
public Object[][] providesBuilder() {
return new Object[][] {{
new Builder<Object, Object>().maximumWeightedCapacity(capacity())
}};
}
/** Provides an empty map for test methods. */
@DataProvider(name = "emptyMap")
public Object[][] providesEmptyMap() {
return new Object[][] {{ newEmptyMap() }};
}
/** Creates a map with the default capacity. */
protected <K, V> ConcurrentLinkedHashMap<K, V> newEmptyMap() {
return new Builder<K, V>()
.maximumWeightedCapacity(capacity())
.build();
}
/** Provides a guarded map for test methods. */
@DataProvider(name = "guardedMap")
public Object[][] providesGuardedMap() {
return new Object[][] {{ newGuarded() }};
}
/** Creates a map that fails if an eviction occurs. */
protected <K, V> ConcurrentLinkedHashMap<K, V> newGuarded() {
EvictionListener<K, V> guardingListener = guardingListener();
return new Builder<K, V>()
.maximumWeightedCapacity(capacity())
.listener(guardingListener)
.build();
}
/** Provides a warmed map for test methods. */
@DataProvider(name = "warmedMap")
public Object[][] providesWarmedMap() {
return new Object[][] {{ newWarmedMap() }};
}
/** Creates a map with warmed to capacity. */
protected ConcurrentLinkedHashMap<Integer, Integer> newWarmedMap() {
ConcurrentLinkedHashMap<Integer, Integer> map = newEmptyMap();
warmUp(map, 0, capacity());
return map;
}
@DataProvider(name = "emptyWeightedMap")
public Object[][] providesEmptyWeightedMap() {
return new Object[][] {{ newEmptyWeightedMap() }};
}
private <K, V> ConcurrentLinkedHashMap<K, Iterable<V>> newEmptyWeightedMap() {
return new Builder<K, Iterable<V>>()
.maximumWeightedCapacity(capacity())
.weigher(Weighers.<V>iterable())
.build();
}
@DataProvider(name = "guardedWeightedMap")
public Object[][] providesGuardedWeightedMap() {
return new Object[][] {{ newGuardedWeightedMap() }};
}
private <K, V> ConcurrentLinkedHashMap<K, Iterable<V>> newGuardedWeightedMap() {
EvictionListener<K, Iterable<V>> guardingListener = guardingListener();
return new Builder<K, Iterable<V>>()
.maximumWeightedCapacity(capacity())
.weigher(Weighers.<V>iterable())
.listener(guardingListener)
.build();
}
@SuppressWarnings("unchecked")
private <K, V> EvictionListener<K, V> guardingListener() {
EvictionListener<K, V> guardingListener = (EvictionListener<K, V>) listener;
doThrow(new AssertionError()).when(guardingListener)
.onEviction(Mockito.<K>any(), Mockito.<V>any());
return guardingListener;
}
/* ---------------- Weigher providers -------------- */
@DataProvider(name = "singletonEntryWeigher")
public Object[][] providesSingletonEntryWeigher() {
return new Object[][] {{ Weighers.entrySingleton() }};
}
@DataProvider(name = "singletonWeigher")
public Object[][] providesSingletonWeigher() {
return new Object[][] {{ Weighers.singleton() }};
}
@DataProvider(name = "byteArrayWeigher")
public Object[][] providesByteArrayWeigher() {
return new Object[][] {{ Weighers.byteArray() }};
}
@DataProvider(name = "iterableWeigher")
public Object[][] providesIterableWeigher() {
return new Object[][] {{ Weighers.iterable() }};
}
@DataProvider(name = "collectionWeigher")
public Object[][] providesCollectionWeigher() {
return new Object[][] {{ Weighers.collection() }};
}
@DataProvider(name = "listWeigher")
public Object[][] providesListWeigher() {
return new Object[][] {{ Weighers.list() }};
}
@DataProvider(name = "setWeigher")
public Object[][] providesSetWeigher() {
return new Object[][] {{ Weighers.set() }};
}
@DataProvider(name = "mapWeigher")
public Object[][] providesMapWeigher() {
return new Object[][] {{ Weighers.map() }};
}
/* ---------------- Utility methods ------------- */
/**
* Populates the map with the half-closed interval [start, end) where the
* value is the negation of the key.
*/
protected static void warmUp(Map<Integer, Integer> map, int start, long end) {
for (Integer i = start; i < end; i++) {
assertThat(map.put(i, -i), is(nullValue()));
}
}
}