/*
* Copyright Terracotta, Inc.
*
* 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 org.ehcache.core;
import org.ehcache.Status;
import org.ehcache.core.spi.store.Store;
import org.ehcache.core.statistics.CacheOperationOutcomes;
import org.ehcache.core.spi.store.StoreAccessException;
import org.ehcache.core.spi.function.Function;
import org.ehcache.core.statistics.BulkOps;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.slf4j.LoggerFactory;
import org.terracotta.statistics.jsr166e.LongAdder;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import static org.ehcache.core.EhcacheBasicBulkUtil.KEY_SET_A;
import static org.ehcache.core.EhcacheBasicBulkUtil.KEY_SET_B;
import static org.ehcache.core.EhcacheBasicBulkUtil.KEY_SET_C;
import static org.ehcache.core.EhcacheBasicBulkUtil.fanIn;
import static org.ehcache.core.EhcacheBasicBulkUtil.getEntryMap;
import static org.ehcache.core.EhcacheBasicBulkUtil.getNullEntryMap;
import static org.ehcache.core.EhcacheBasicBulkUtil.union;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Provides testing of basic GET_ALL operations on an {@code Ehcache}.
*
* @author Clifford W. Johnson
*/
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public class EhcacheBasicGetAllTest extends EhcacheBasicCrudBase {
@Test
public void testGetAllNull() throws Exception {
final Ehcache<String, String> ehcache = this.getEhcache();
try {
ehcache.getAll(null);
fail();
} catch (NullPointerException e) {
// Expected
}
}
@Test
public void testGetAllNullKey() throws Exception {
final Set<String> keys = new LinkedHashSet<String>();
for (final String key : KEY_SET_A) {
keys.add(key);
if ("keyA2".equals(key)) {
keys.add(null); // Add a null element
}
}
final Ehcache<String, String> ehcache = this.getEhcache();
try {
ehcache.getAll(keys);
fail();
} catch (NullPointerException e) {
// Expected
}
}
/**
* Tests {@link EhcacheWithLoaderWriter#getAll(Set)} for
* <ul>
* <li>empty request key set</li>
* <li>no {@code CacheLoaderWriter}</li>
* </ul>
*/
@Test
public void testGetAllEmptyRequestNoLoader() throws Exception {
final FakeStore fakeStore = new FakeStore(Collections.<String, String>emptyMap());
this.store = spy(fakeStore);
final Ehcache<String, String> ehcache = this.getEhcache();
final Map<String, String> actual = ehcache.getAll(Collections.<String>emptySet());
assertThat(actual, is(notNullValue()));
assertThat(actual.isEmpty(), is(true));
verify(this.store, never()).bulkComputeIfAbsent(eq(Collections.<String>emptySet()), getAnyIterableFunction());
verify(this.spiedResilienceStrategy, never()).getAllFailure(eq(Collections.<String>emptySet()), any(StoreAccessException.class));
validateStatsNoneof(ehcache);
validateStats(ehcache, EnumSet.of(CacheOperationOutcomes.GetAllOutcome.SUCCESS));
}
/**
* Tests {@link EhcacheWithLoaderWriter#getAll(Set)} for
* <ul>
* <li>non-empty request key set</li>
* <li>no {@link Store} entries match</li>
* <li>no {@code CacheLoaderWriter}</li>
* </ul>
*/
@Test
public void testGetAllStoreNoMatchNoLoader() throws Exception {
final FakeStore fakeStore = new FakeStore(getEntryMap(KEY_SET_B));
this.store = spy(fakeStore);
final Ehcache<String, String> ehcache = this.getEhcache();
final Map<String, String> actual = ehcache.getAll(KEY_SET_A);
assertThat(actual, equalTo(getNullEntryMap(KEY_SET_A)));
verify(this.store).bulkComputeIfAbsent(eq(KEY_SET_A), getAnyIterableFunction());
assertThat(fakeStore.getEntryMap(), equalTo(getEntryMap(KEY_SET_B)));
verifyZeroInteractions(this.spiedResilienceStrategy);
validateStatsNoneof(ehcache);
validateStats(ehcache, EnumSet.of(CacheOperationOutcomes.GetAllOutcome.SUCCESS));
validateBulkCounters(ehcache, 0, KEY_SET_A.size());
}
/**
* Tests {@link EhcacheWithLoaderWriter#getAll(Set)} for
* <ul>
* <li>non-empty request key set</li>
* <li>all {@link Store} entries match</li>
* <li>{@link Store#bulkComputeIfAbsent} throws before accessing loader</li>
* <li>no {@code CacheLoaderWriter}</li>
* </ul>
*/
@Test
public void testGetAllStoreAllMatchStoreAccessExceptionBeforeNoLoader() throws Exception {
final FakeStore fakeStore = new FakeStore(getEntryMap(KEY_SET_A, KEY_SET_B));
this.store = spy(fakeStore);
doThrow(new StoreAccessException("")).when(this.store)
.bulkComputeIfAbsent(getAnyStringSet(), getAnyIterableFunction());
final Ehcache<String, String> ehcache = this.getEhcache();
final Set<String> fetchKeys = fanIn(KEY_SET_A, KEY_SET_B);
final Map<String, String> actual = ehcache.getAll(fetchKeys);
assertThat(actual, equalTo(getNullEntryMap(fetchKeys)));
verify(this.store).bulkComputeIfAbsent(eq(fetchKeys), getAnyIterableFunction());
// ResilienceStrategy invoked: no assertion for Store content
verify(this.spiedResilienceStrategy).getAllFailure(eq(fetchKeys), any(StoreAccessException.class));
validateStatsNoneof(ehcache);
validateStats(ehcache, EnumSet.of(CacheOperationOutcomes.GetAllOutcome.FAILURE));
validateBulkCounters(ehcache, 0, 0);
}
/**
* Tests {@link EhcacheWithLoaderWriter#getAll(Set)} for
* <ul>
* <li>non-empty request key set</li>
* <li>all {@link Store} entries match</li>
* <li>no {@code CacheLoaderWriter}</li>
* </ul>
*/
@Test
public void testGetAllStoreAllMatchNoLoader() throws Exception {
final FakeStore fakeStore = new FakeStore(getEntryMap(KEY_SET_A, KEY_SET_B));
this.store = spy(fakeStore);
final Ehcache<String, String> ehcache = this.getEhcache();
final Set<String> fetchKeys = fanIn(KEY_SET_A, KEY_SET_B);
final Map<String, String> actual = ehcache.getAll(fetchKeys);
assertThat(actual, equalTo(getEntryMap(fetchKeys)));
verify(this.store).bulkComputeIfAbsent(eq(fetchKeys), getAnyIterableFunction());
assertThat(fakeStore.getEntryMap(), equalTo(getEntryMap(KEY_SET_A, KEY_SET_B)));
verifyZeroInteractions(this.spiedResilienceStrategy);
validateStatsNoneof(ehcache);
validateStats(ehcache, EnumSet.of(CacheOperationOutcomes.GetAllOutcome.SUCCESS));
validateBulkCounters(ehcache, fetchKeys.size(), 0);
}
/**
* Tests {@link EhcacheWithLoaderWriter#getAll(Set)} for
* <ul>
* <li>non-empty request key set</li>
* <li>no {@link Store} entries match</li>
* <li>{@link Store#bulkComputeIfAbsent} throws before accessing loader</li>
* <li>no {@code CacheLoaderWriter}</li>
* </ul>
*/
@Test
public void testGetAllStoreNoMatchStoreAccessExceptionBeforeNoLoader() throws Exception {
final FakeStore fakeStore = new FakeStore(getEntryMap(KEY_SET_B));
this.store = spy(fakeStore);
doThrow(new StoreAccessException("")).when(this.store)
.bulkComputeIfAbsent(getAnyStringSet(), getAnyIterableFunction());
final Ehcache<String, String> ehcache = this.getEhcache();
final Map<String, String> actual = ehcache.getAll(KEY_SET_A);
assertThat(actual, equalTo(getNullEntryMap(KEY_SET_A)));
verify(this.store).bulkComputeIfAbsent(eq(KEY_SET_A), getAnyIterableFunction());
// ResilienceStrategy invoked: no assertion for Store content
verify(this.spiedResilienceStrategy)
.getAllFailure(eq(KEY_SET_A), any(StoreAccessException.class));
validateStatsNoneof(ehcache);
validateStats(ehcache, EnumSet.of(CacheOperationOutcomes.GetAllOutcome.FAILURE));
validateBulkCounters(ehcache, 0, 0);
}
/**
* Tests {@link EhcacheWithLoaderWriter#getAll(Set)} for
* <ul>
* <li>non-empty request key set</li>
* <li>some {@link Store} entries match</li>
* <li>no {@code CacheLoaderWriter}</li>
* </ul>
*/
@Test
public void testGetAllStoreSomeMatchNoLoader() throws Exception {
final FakeStore fakeStore = new FakeStore(getEntryMap(KEY_SET_A, KEY_SET_B));
this.store = spy(fakeStore);
final Ehcache<String, String> ehcache = this.getEhcache();
final Set<String> fetchKeys = fanIn(KEY_SET_A, KEY_SET_C);
final Map<String, String> actual = ehcache.getAll(fetchKeys);
assertThat(actual, equalTo(union(getEntryMap(KEY_SET_A), getNullEntryMap(KEY_SET_C))));
verify(this.store).bulkComputeIfAbsent(eq(fetchKeys), getAnyIterableFunction());
assertThat(fakeStore.getEntryMap(), equalTo(getEntryMap(KEY_SET_A, KEY_SET_B)));
verifyZeroInteractions(this.spiedResilienceStrategy);
validateStatsNoneof(ehcache);
validateStats(ehcache, EnumSet.of(CacheOperationOutcomes.GetAllOutcome.SUCCESS));
validateBulkCounters(ehcache, KEY_SET_A.size(), KEY_SET_C.size());
}
/**
* Tests {@link EhcacheWithLoaderWriter#getAll(Set)} for
* <ul>
* <li>non-empty request key set</li>
* <li>some {@link Store} entries match</li>
* <li>{@link Store#bulkComputeIfAbsent} throws before accessing loader</li>
* <li>no {@code CacheLoaderWriter}</li>
* </ul>
*/
@Test
public void testGetAllStoreSomeMatchStoreAccessExceptionBeforeNoLoader() throws Exception {
final FakeStore fakeStore = new FakeStore(getEntryMap(KEY_SET_A, KEY_SET_B));
this.store = spy(fakeStore);
doThrow(new StoreAccessException("")).when(this.store)
.bulkComputeIfAbsent(getAnyStringSet(), getAnyIterableFunction());
final Ehcache<String, String> ehcache = this.getEhcache();
final Set<String> fetchKeys = fanIn(KEY_SET_A, KEY_SET_C);
final Map<String, String> actual = ehcache.getAll(fetchKeys);
assertThat(actual, equalTo(getNullEntryMap(fetchKeys)));
verify(this.store).bulkComputeIfAbsent(eq(fetchKeys), getAnyIterableFunction());
// ResilienceStrategy invoked: no assertion for Store content
verify(this.spiedResilienceStrategy).getAllFailure(eq(fetchKeys), any(StoreAccessException.class));
validateStatsNoneof(ehcache);
validateStats(ehcache, EnumSet.of(CacheOperationOutcomes.GetAllOutcome.FAILURE));
validateBulkCounters(ehcache, 0, 0);
}
private void validateStatsNoneof(Ehcache<String, String> cache) {
validateStats(cache, EnumSet.noneOf(CacheOperationOutcomes.GetOutcome.class));
if (!(cache instanceof Ehcache)) {
validateStats(cache, EnumSet.noneOf(CacheOperationOutcomes.CacheLoadingOutcome.class));
}
}
/**
* Gets an initialized {@link Ehcache Ehcache} instance
*
* @return a new {@code Ehcache} instance
*/
private Ehcache<String, String> getEhcache() {
final Ehcache<String, String> ehcache = new Ehcache<String, String>(CACHE_CONFIGURATION, this.store, cacheEventDispatcher, LoggerFactory.getLogger(Ehcache.class + "-" + "EhcacheBasicGetAllTest"));
ehcache.init();
assertThat("cache not initialized", ehcache.getStatus(), Matchers.is(Status.AVAILABLE));
this.spiedResilienceStrategy = this.setResilienceStrategySpy(ehcache);
return ehcache;
}
static void validateBulkCounters(InternalCache<?, ?> ehcache, int expectedHitCount, int expectedMissCount) {
LongAdder hitAdder = ehcache.getBulkMethodEntries().get(BulkOps.GET_ALL_HITS);
LongAdder missAdder = ehcache.getBulkMethodEntries().get(BulkOps.GET_ALL_MISS);
int hitCount = hitAdder == null ? 0 : hitAdder.intValue();
int missCount = missAdder == null ? 0 : missAdder.intValue();
assertThat(hitCount, is(expectedHitCount));
assertThat(missCount, is(expectedMissCount));
}
/**
* Returns a Mockito {@code any} Matcher for {@code java.util.Set<String>}.
*
* @return a Mockito {@code any} matcher for {@code Set<String>}.
*/
@SuppressWarnings("unchecked")
static Set<? extends String> getAnyStringSet() {
return any(Set.class); // unchecked
}
/**
* Returns a Mockito {@code any} Matcher for {@link Function} over {@code java.lang.Iterable}.
*
* @return a Mockito {@code any} matcher for {@code Function}.
*/
@SuppressWarnings("unchecked")
static Function<Iterable<? extends String>, Iterable<? extends Map.Entry<? extends String, ? extends String>>> getAnyIterableFunction() {
return any(Function.class); // unchecked
}
}