alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (CacheLoadingTest.java)

This example Java source code file (CacheLoadingTest.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

atomicinteger, cacheloader, cachestats, countdownlatch, executionexception, interruptedexception, listenablefuture, loadingcache, log, logging, map, milliseconds, object, override, string, threading, threads, uncheckedexecutionexception, util

The CacheLoadingTest.java Java example source code

/*
 * Copyright (C) 2011 The Guava Authors
 *
 * 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.google.common.cache;

import static com.google.common.cache.TestingCacheLoaders.bulkLoader;
import static com.google.common.cache.TestingCacheLoaders.constantLoader;
import static com.google.common.cache.TestingCacheLoaders.errorLoader;
import static com.google.common.cache.TestingCacheLoaders.exceptionLoader;
import static com.google.common.cache.TestingCacheLoaders.identityLoader;
import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener;
import static com.google.common.truth.Truth.assertThat;
import static java.lang.Thread.currentThread;
import static java.util.Arrays.asList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
import com.google.common.cache.TestingCacheLoaders.CountingLoader;
import com.google.common.cache.TestingCacheLoaders.IdentityLoader;
import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.testing.FakeTicker;
import com.google.common.testing.TestLogHandler;
import com.google.common.util.concurrent.Callables;
import com.google.common.util.concurrent.ExecutionError;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.UncheckedExecutionException;

import junit.framework.TestCase;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.logging.LogRecord;

/**
 * Tests relating to cache loading: concurrent loading, exceptions during loading, etc.
 *
 * @author mike nonemacher
 */
public class CacheLoadingTest extends TestCase {
  TestLogHandler logHandler;

  @Override
  public void setUp() throws Exception {
    super.setUp();
    logHandler = new TestLogHandler();
    LocalCache.logger.addHandler(logHandler);
  }

  @Override
  public void tearDown() throws Exception {
    super.tearDown();
    // TODO(cpovirk): run tests in other thread instead of messing with main thread interrupt status
    currentThread().interrupted();
    LocalCache.logger.removeHandler(logHandler);
  }

  private Throwable popLoggedThrowable() {
    List<LogRecord> logRecords = logHandler.getStoredLogRecords();
    assertEquals(1, logRecords.size());
    LogRecord logRecord = logRecords.get(0);
    logHandler.clear();
    return logRecord.getThrown();
  }

  private void checkNothingLogged() {
    assertTrue(logHandler.getStoredLogRecords().isEmpty());
  }

  private void checkLoggedCause(Throwable t) {
    assertSame(t, popLoggedThrowable().getCause());
  }

  private void checkLoggedInvalidLoad() {
    assertThat(popLoggedThrowable()).isInstanceOf(InvalidCacheLoadException.class);
  }

  public void testLoad() throws ExecutionException {
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(identityLoader());
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    Object key = new Object();
    assertSame(key, cache.get(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    key = new Object();
    assertSame(key, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(2, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    key = new Object();
    cache.refresh(key);
    checkNothingLogged();
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(3, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(key, cache.get(key));
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(3, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());

    Object value = new Object();
    // callable is not called
    assertSame(key, cache.get(key, throwing(new Exception())));
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(3, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(2, stats.hitCount());

    key = new Object();
    assertSame(value, cache.get(key, Callables.returning(value)));
    stats = cache.stats();
    assertEquals(3, stats.missCount());
    assertEquals(4, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(2, stats.hitCount());
  }

  public void testReload() throws ExecutionException {
    final Object one = new Object();
    final Object two = new Object();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFuture(two);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(key);
    checkNothingLogged();
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(2, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(two, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(2, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());
  }

  public void testRefresh() {
    final Object one = new Object();
    final Object two = new Object();
    FakeTicker ticker = new FakeTicker();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFuture(two);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .ticker(ticker)
        .refreshAfterWrite(1, MILLISECONDS)
        .build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(two, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(2, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(2, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(two, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(2, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(3, stats.hitCount());
  }

  public void testRefresh_getIfPresent() {
    final Object one = new Object();
    final Object two = new Object();
    FakeTicker ticker = new FakeTicker();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFuture(two);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .ticker(ticker)
        .refreshAfterWrite(1, MILLISECONDS)
        .build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getIfPresent(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(two, cache.getIfPresent(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(2, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(2, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(two, cache.getIfPresent(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(2, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(3, stats.hitCount());
  }

  public void testBulkLoad_default() throws ExecutionException {
    LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(TestingCacheLoaders.<Integer>identityLoader());
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertEquals(ImmutableMap.of(), cache.getAll(ImmutableList.<Integer>of()));
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertEquals(ImmutableMap.of(1, 1), cache.getAll(asList(1)));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertEquals(ImmutableMap.of(1, 1, 2, 2, 3, 3, 4, 4), cache.getAll(asList(1, 2, 3, 4)));
    stats = cache.stats();
    assertEquals(4, stats.missCount());
    assertEquals(4, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());

    assertEquals(ImmutableMap.of(2, 2, 3, 3), cache.getAll(asList(2, 3)));
    stats = cache.stats();
    assertEquals(4, stats.missCount());
    assertEquals(4, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(3, stats.hitCount());

    // duplicate keys are ignored, and don't impact stats
    assertEquals(ImmutableMap.of(4, 4, 5, 5), cache.getAll(asList(4, 5)));
    stats = cache.stats();
    assertEquals(5, stats.missCount());
    assertEquals(5, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(4, stats.hitCount());
  }

  public void testBulkLoad_loadAll() throws ExecutionException {
    IdentityLoader<Integer> backingLoader = identityLoader();
    CacheLoader<Integer, Integer> loader = bulkLoader(backingLoader);
    LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertEquals(ImmutableMap.of(), cache.getAll(ImmutableList.<Integer>of()));
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertEquals(ImmutableMap.of(1, 1), cache.getAll(asList(1)));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertEquals(ImmutableMap.of(1, 1, 2, 2, 3, 3, 4, 4), cache.getAll(asList(1, 2, 3, 4)));
    stats = cache.stats();
    assertEquals(4, stats.missCount());
    assertEquals(2, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());

    assertEquals(ImmutableMap.of(2, 2, 3, 3), cache.getAll(asList(2, 3)));
    stats = cache.stats();
    assertEquals(4, stats.missCount());
    assertEquals(2, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(3, stats.hitCount());

    // duplicate keys are ignored, and don't impact stats
    assertEquals(ImmutableMap.of(4, 4, 5, 5), cache.getAll(asList(4, 5)));
    stats = cache.stats();
    assertEquals(5, stats.missCount());
    assertEquals(3, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(4, stats.hitCount());
  }

  public void testBulkLoad_extra() throws ExecutionException {
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) throws Exception {
        return new Object();
      }

      @Override
      public Map<Object, Object> loadAll(Iterable keys) throws Exception {
        Map<Object, Object> result = Maps.newHashMap();
        for (Object key : keys) {
          Object value = new Object();
          result.put(key, value);
          // add extra entries
          result.put(value, key);
        }
        return result;
      }
    };
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);

    Object[] lookupKeys = new Object[] { new Object(), new Object(), new Object() };
    Map<Object, Object> result = cache.getAll(asList(lookupKeys));
    assertThat(result.keySet()).containsExactlyElementsIn(asList(lookupKeys));
    for (Map.Entry<Object, Object> entry : result.entrySet()) {
      Object key = entry.getKey();
      Object value = entry.getValue();
      assertSame(value, result.get(key));
      assertNull(result.get(value));
      assertSame(value, cache.asMap().get(key));
      assertSame(key, cache.asMap().get(value));
    }
  }

  public void testBulkLoad_clobber() throws ExecutionException {
    final Object extraKey = new Object();
    final Object extraValue = new Object();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) throws Exception {
        throw new AssertionError();
      }

      @Override
      public Map<Object, Object> loadAll(Iterable keys) throws Exception {
        Map<Object, Object> result = Maps.newHashMap();
        for (Object key : keys) {
          Object value = new Object();
          result.put(key, value);
        }
        result.put(extraKey, extraValue);
        return result;
      }
    };
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);
    cache.asMap().put(extraKey, extraKey);
    assertSame(extraKey, cache.asMap().get(extraKey));

    Object[] lookupKeys = new Object[] { new Object(), new Object(), new Object() };
    Map<Object, Object> result = cache.getAll(asList(lookupKeys));
    assertThat(result.keySet()).containsExactlyElementsIn(asList(lookupKeys));
    for (Map.Entry<Object, Object> entry : result.entrySet()) {
      Object key = entry.getKey();
      Object value = entry.getValue();
      assertSame(value, result.get(key));
      assertSame(value, cache.asMap().get(key));
    }
    assertNull(result.get(extraKey));
    assertSame(extraValue, cache.asMap().get(extraKey));
  }

  public void testBulkLoad_clobberNullValue() throws ExecutionException {
    final Object extraKey = new Object();
    final Object extraValue = new Object();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) throws Exception {
        throw new AssertionError();
      }

      @Override
      public Map<Object, Object> loadAll(Iterable keys) throws Exception {
        Map<Object, Object> result = Maps.newHashMap();
        for (Object key : keys) {
          Object value = new Object();
          result.put(key, value);
        }
        result.put(extraKey, extraValue);
        result.put(extraValue, null);
        return result;
      }
    };
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);
    cache.asMap().put(extraKey, extraKey);
    assertSame(extraKey, cache.asMap().get(extraKey));

    Object[] lookupKeys = new Object[] { new Object(), new Object(), new Object() };
    try {
      cache.getAll(asList(lookupKeys));
      fail();
    } catch (InvalidCacheLoadException expected) {}

    for (Object key : lookupKeys) {
      assertTrue(cache.asMap().containsKey(key));
    }
    assertSame(extraValue, cache.asMap().get(extraKey));
    assertFalse(cache.asMap().containsKey(extraValue));
  }

  public void testBulkLoad_clobberNullKey() throws ExecutionException {
    final Object extraKey = new Object();
    final Object extraValue = new Object();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) throws Exception {
        throw new AssertionError();
      }

      @Override
      public Map<Object, Object> loadAll(Iterable keys) throws Exception {
        Map<Object, Object> result = Maps.newHashMap();
        for (Object key : keys) {
          Object value = new Object();
          result.put(key, value);
        }
        result.put(extraKey, extraValue);
        result.put(null, extraKey);
        return result;
      }
    };
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);
    cache.asMap().put(extraKey, extraKey);
    assertSame(extraKey, cache.asMap().get(extraKey));

    Object[] lookupKeys = new Object[] { new Object(), new Object(), new Object() };
    try {
      cache.getAll(asList(lookupKeys));
      fail();
    } catch (InvalidCacheLoadException expected) {}

    for (Object key : lookupKeys) {
      assertTrue(cache.asMap().containsKey(key));
    }
    assertSame(extraValue, cache.asMap().get(extraKey));
    assertFalse(cache.asMap().containsValue(extraKey));
  }

  public void testBulkLoad_partial() throws ExecutionException {
    final Object extraKey = new Object();
    final Object extraValue = new Object();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) throws Exception {
        throw new AssertionError();
      }

      @Override
      public Map<Object, Object> loadAll(Iterable keys) throws Exception {
        Map<Object, Object> result = Maps.newHashMap();
        // ignore request keys
        result.put(extraKey, extraValue);
        return result;
      }
    };
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);

    Object[] lookupKeys = new Object[] { new Object(), new Object(), new Object() };
    try {
      cache.getAll(asList(lookupKeys));
      fail();
    } catch (InvalidCacheLoadException expected) {}
    assertSame(extraValue, cache.asMap().get(extraKey));
  }

  public void testLoadNull() throws ExecutionException {
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(constantLoader(null));
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.get(new Object());
      fail();
    } catch (InvalidCacheLoadException expected) {}
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getUnchecked(new Object());
      fail();
    } catch (InvalidCacheLoadException expected) {}
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(2, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(new Object());
    checkLoggedInvalidLoad();
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(3, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.get(new Object(), Callables.returning(null));
      fail();
    } catch (InvalidCacheLoadException expected) {}
    stats = cache.stats();
    assertEquals(3, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(4, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (InvalidCacheLoadException expected) {}
    stats = cache.stats();
    assertEquals(4, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(5, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testReloadNull() throws ExecutionException {
    final Object one = new Object();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return null;
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(key);
    checkLoggedInvalidLoad();
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());
  }

  public void testReloadNullFuture() throws ExecutionException {
    final Object one = new Object();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFuture(null);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(key);
    checkLoggedInvalidLoad();
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());
  }

  public void testRefreshNull() {
    final Object one = new Object();
    FakeTicker ticker = new FakeTicker();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFuture(null);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .ticker(ticker)
        .refreshAfterWrite(1, MILLISECONDS)
        .build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    // refreshed
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(2, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(2, stats.loadExceptionCount());
    assertEquals(3, stats.hitCount());
  }

  public void testBulkLoadNull() throws ExecutionException {
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(bulkLoader(constantLoader(null)));
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (InvalidCacheLoadException expected) {}
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testBulkLoadNullMap() throws ExecutionException {
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(new CacheLoader<Object, Object>() {
          @Override
          public Object load(Object key) {
            throw new AssertionError();
          }

          @Override
          public Map<Object, Object> loadAll(Iterable keys) {
            return null;
          }
        });

    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (InvalidCacheLoadException expected) {}
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testLoadError() throws ExecutionException {
    Error e = new Error();
    CacheLoader<Object, Object> loader = errorLoader(e);
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.get(new Object());
      fail();
    } catch (ExecutionError expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getUnchecked(new Object());
      fail();
    } catch (ExecutionError expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(2, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(new Object());
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(3, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    final Error callableError = new Error();
    try {
      cache.get(new Object(), new Callable<Object>() {
        @Override
        public Object call() {
          throw callableError;
        }
      });
      fail();
    } catch (ExecutionError expected) {
      assertSame(callableError, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(3, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(4, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (ExecutionError expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(4, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(5, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testReloadError() throws ExecutionException {
    final Object one = new Object();
    final Error e = new Error();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        throw e;
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(key);
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());
  }

  public void testReloadFutureError() throws ExecutionException {
    final Object one = new Object();
    final Error e = new Error();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFailedFuture(e);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(key);
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());
  }

  public void testRefreshError() {
    final Object one = new Object();
    final Error e = new Error();
    FakeTicker ticker = new FakeTicker();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFailedFuture(e);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .ticker(ticker)
        .refreshAfterWrite(1, MILLISECONDS)
        .build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    // refreshed
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(2, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(2, stats.loadExceptionCount());
    assertEquals(3, stats.hitCount());
  }

  public void testBulkLoadError() throws ExecutionException {
    Error e = new Error();
    CacheLoader<Object, Object> loader = errorLoader(e);
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(bulkLoader(loader));
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (ExecutionError expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testLoadCheckedException() {
    Exception e = new Exception();
    CacheLoader<Object, Object> loader = exceptionLoader(e);
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.get(new Object());
      fail();
    } catch (ExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getUnchecked(new Object());
      fail();
    } catch (UncheckedExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(2, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(new Object());
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(3, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    Exception callableException = new Exception();
    try {
      cache.get(new Object(), throwing(callableException));
      fail();
    } catch (ExecutionException expected) {
      assertSame(callableException, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(3, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(4, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (ExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(4, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(5, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testLoadInterruptedException() {
    Exception e = new InterruptedException();
    CacheLoader<Object, Object> loader = exceptionLoader(e);
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    // Sanity check:
    assertFalse(currentThread().interrupted());

    try {
      cache.get(new Object());
      fail();
    } catch (ExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    assertTrue(currentThread().interrupted());
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getUnchecked(new Object());
      fail();
    } catch (UncheckedExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    assertTrue(currentThread().interrupted());
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(2, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(new Object());
    assertTrue(currentThread().interrupted());
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(3, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    Exception callableException = new InterruptedException();
    try {
      cache.get(new Object(), throwing(callableException));
      fail();
    } catch (ExecutionException expected) {
      assertSame(callableException, expected.getCause());
    }
    assertTrue(currentThread().interrupted());
    stats = cache.stats();
    assertEquals(3, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(4, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (ExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    assertTrue(currentThread().interrupted());
    stats = cache.stats();
    assertEquals(4, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(5, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testReloadCheckedException() {
    final Object one = new Object();
    final Exception e = new Exception();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) throws Exception {
        throw e;
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(key);
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());
  }

  public void testReloadFutureCheckedException() {
    final Object one = new Object();
    final Exception e = new Exception();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFailedFuture(e);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(key);
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());
  }

  public void testRefreshCheckedException() {
    final Object one = new Object();
    final Exception e = new Exception();
    FakeTicker ticker = new FakeTicker();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFailedFuture(e);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .ticker(ticker)
        .refreshAfterWrite(1, MILLISECONDS)
        .build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    // refreshed
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(2, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(2, stats.loadExceptionCount());
    assertEquals(3, stats.hitCount());
  }

  public void testBulkLoadCheckedException() {
    Exception e = new Exception();
    CacheLoader<Object, Object> loader = exceptionLoader(e);
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(bulkLoader(loader));
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (ExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testBulkLoadInterruptedException() {
    Exception e = new InterruptedException();
    CacheLoader<Object, Object> loader = exceptionLoader(e);
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(bulkLoader(loader));
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (ExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    assertTrue(currentThread().interrupted());
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testLoadUncheckedException() throws ExecutionException {
    Exception e = new RuntimeException();
    CacheLoader<Object, Object> loader = exceptionLoader(e);
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.get(new Object());
      fail();
    } catch (UncheckedExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getUnchecked(new Object());
      fail();
    } catch (UncheckedExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(2, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(new Object());
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(2, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(3, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    Exception callableException = new RuntimeException();
    try {
      cache.get(new Object(), throwing(callableException));
      fail();
    } catch (UncheckedExecutionException expected) {
      assertSame(callableException, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(3, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(4, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (UncheckedExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(4, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(5, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testReloadUncheckedException() throws ExecutionException {
    final Object one = new Object();
    final Exception e = new RuntimeException();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) throws Exception {
        throw e;
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(key);
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());
  }

  public void testReloadFutureUncheckedException() throws ExecutionException {
    final Object one = new Object();
    final Exception e = new RuntimeException();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFailedFuture(e);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    cache.refresh(key);
    checkLoggedCause(e);
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());
  }

  public void testRefreshUncheckedException() {
    final Object one = new Object();
    final Exception e = new RuntimeException();
    FakeTicker ticker = new FakeTicker();
    CacheLoader<Object, Object> loader = new CacheLoader() {
      @Override
      public Object load(Object key) {
        return one;
      }

      @Override
      public ListenableFuture<Object> reload(Object key, Object oldValue) {
        return Futures.immediateFailedFuture(e);
      }
    };

    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .ticker(ticker)
        .refreshAfterWrite(1, MILLISECONDS)
        .build(loader);
    Object key = new Object();
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(1, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    // refreshed
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(2, stats.hitCount());

    ticker.advance(1, MILLISECONDS);
    assertSame(one, cache.getUnchecked(key));
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(1, stats.loadSuccessCount());
    assertEquals(2, stats.loadExceptionCount());
    assertEquals(3, stats.hitCount());
  }

  public void testBulkLoadUncheckedException() throws ExecutionException {
    Exception e = new RuntimeException();
    CacheLoader<Object, Object> loader = exceptionLoader(e);
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .recordStats()
        .build(bulkLoader(loader));
    CacheStats stats = cache.stats();
    assertEquals(0, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(0, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());

    try {
      cache.getAll(asList(new Object()));
      fail();
    } catch (UncheckedExecutionException expected) {
      assertSame(e, expected.getCause());
    }
    stats = cache.stats();
    assertEquals(1, stats.missCount());
    assertEquals(0, stats.loadSuccessCount());
    assertEquals(1, stats.loadExceptionCount());
    assertEquals(0, stats.hitCount());
  }

  public void testReloadAfterFailure() throws ExecutionException {
    final AtomicInteger count = new AtomicInteger();
    final Exception e = new IllegalStateException("exception to trigger failure on first load()");
    CacheLoader<Integer, String> failOnceFunction = new CacheLoader() {

      @Override
      public String load(Integer key) throws Exception {
        if (count.getAndIncrement() == 0) {
          throw e;
        }
        return key.toString();
      }
    };
    CountingRemovalListener<Integer, String> removalListener = countingRemovalListener();
    LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
        .removalListener(removalListener)
        .build(failOnceFunction);

    try {
      cache.getUnchecked(1);
      fail();
    } catch (UncheckedExecutionException ue) {
      assertSame(e, ue.getCause());
    }

    assertEquals("1", cache.getUnchecked(1));
    assertEquals(0, removalListener.getCount());

    count.set(0);
    cache.refresh(2);
    checkLoggedCause(e);

    assertEquals("2", cache.getUnchecked(2));
    assertEquals(0, removalListener.getCount());

  }

  public void testReloadAfterValueReclamation() throws InterruptedException, ExecutionException {
    CountingLoader countingLoader = new CountingLoader();
    LoadingCache<Object, Object> cache =
        CacheBuilder.newBuilder().weakValues().build(countingLoader);
    ConcurrentMap<Object, Object> map = cache.asMap();

    int iterations = 10;
    WeakReference<Object> ref = new WeakReference(null);
    int expectedComputations = 0;
    for (int i = 0; i < iterations; i++) {
      // The entry should get garbage collected and recomputed.
      Object oldValue = ref.get();
      if (oldValue == null) {
        expectedComputations++;
      }
      ref = new WeakReference<Object>(cache.getUnchecked(1));
      oldValue = null;
      Thread.sleep(i);
      System.gc();
    }
    assertEquals(expectedComputations, countingLoader.getCount());

    for (int i = 0; i < iterations; i++) {
      // The entry should get garbage collected and recomputed.
      Object oldValue = ref.get();
      if (oldValue == null) {
        expectedComputations++;
      }
      cache.refresh(1);
      checkNothingLogged();
      ref = new WeakReference<Object>(map.get(1));
      oldValue = null;
      Thread.sleep(i);
      System.gc();
    }
    assertEquals(expectedComputations, countingLoader.getCount());
  }

  public void testReloadAfterSimulatedValueReclamation() throws ExecutionException {
    CountingLoader countingLoader = new CountingLoader();
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .concurrencyLevel(1)
        .weakValues()
        .build(countingLoader);

    Object key = new Object();
    assertNotNull(cache.getUnchecked(key));

    CacheTesting.simulateValueReclamation(cache, key);

    // this blocks if computation can't deal with partially-collected values
    assertNotNull(cache.getUnchecked(key));
    assertEquals(1, cache.size());
    assertEquals(2, countingLoader.getCount());

    CacheTesting.simulateValueReclamation(cache, key);
    cache.refresh(key);
    checkNothingLogged();
    assertEquals(1, cache.size());
    assertEquals(3, countingLoader.getCount());
  }

  public void testReloadAfterSimulatedKeyReclamation() throws ExecutionException {
    CountingLoader countingLoader = new CountingLoader();
    LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
        .concurrencyLevel(1)
        .weakKeys()
        .build(countingLoader);

    Object key = new Object();
    assertNotNull(cache.getUnchecked(key));
    assertEquals(1, cache.size());

    CacheTesting.simulateKeyReclamation(cache, key);

    // this blocks if computation can't deal with partially-collected values
    assertNotNull(cache.getUnchecked(key));
    assertEquals(2, countingLoader.getCount());

    CacheTesting.simulateKeyReclamation(cache, key);
    cache.refresh(key);
    checkNothingLogged();
    assertEquals(3, countingLoader.getCount());
  }

  /**
   * Make sure LoadingCache correctly wraps ExecutionExceptions and UncheckedExecutionExceptions.
   */
  public void testLoadingExceptionWithCause() {
    final Exception cause = new Exception();
    final UncheckedExecutionException uee = new UncheckedExecutionException(cause);
    final ExecutionException ee = new ExecutionException(cause);

    LoadingCache<Object, Object> cacheUnchecked =
        CacheBuilder.newBuilder().build(exceptionLoader(uee));
    LoadingCache<Object, Object> cacheChecked =
        CacheBuilder.newBuilder().build(exceptionLoader(ee));

    try {
      cacheUnchecked.get(new Object());
      fail();
    } catch (ExecutionException e) {
      fail();
    } catch (UncheckedExecutionException caughtEe) {
      assertSame(uee, caughtEe.getCause());
    }

    try {
      cacheUnchecked.getUnchecked(new Object());
      fail();
    } catch (UncheckedExecutionException caughtUee) {
      assertSame(uee, caughtUee.getCause());
    }

    cacheUnchecked.refresh(new Object());
    checkLoggedCause(uee);

    try {
      cacheUnchecked.getAll(asList(new Object()));
      fail();
    } catch (ExecutionException e) {
      fail();
    } catch (UncheckedExecutionException caughtEe) {
      assertSame(uee, caughtEe.getCause());
    }

    try {
      cacheChecked.get(new Object());
      fail();
    } catch (ExecutionException caughtEe) {
      assertSame(ee, caughtEe.getCause());
    }

    try {
      cacheChecked.getUnchecked(new Object());
      fail();
    } catch (UncheckedExecutionException caughtUee) {
      assertSame(ee, caughtUee.getCause());
    }

    cacheChecked.refresh(new Object());
    checkLoggedCause(ee);

    try {
      cacheChecked.getAll(asList(new Object()));
      fail();
    } catch (ExecutionException caughtEe) {
      assertSame(ee, caughtEe.getCause());
    }
  }

  public void testBulkLoadingExceptionWithCause() {
    final Exception cause = new Exception();
    final UncheckedExecutionException uee = new UncheckedExecutionException(cause);
    final ExecutionException ee = new ExecutionException(cause);

    LoadingCache<Object, Object> cacheUnchecked =
        CacheBuilder.newBuilder().build(bulkLoader(exceptionLoader(uee)));
    LoadingCache<Object, Object> cacheChecked =
        CacheBuilder.newBuilder().build(bulkLoader(exceptionLoader(ee)));

    try {
      cacheUnchecked.getAll(asList(new Object()));
      fail();
    } catch (ExecutionException e) {
      fail();
    } catch (UncheckedExecutionException caughtEe) {
      assertSame(uee, caughtEe.getCause());
    }

    try {
      cacheChecked.getAll(asList(new Object()));
      fail();
    } catch (ExecutionException caughtEe) {
      assertSame(ee, caughtEe.getCause());
    }
  }

  public void testConcurrentLoading() throws InterruptedException {
    testConcurrentLoading(CacheBuilder.newBuilder());
  }

  public void testConcurrentExpirationLoading() throws InterruptedException {
    testConcurrentLoading(CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS));
  }

  private static void testConcurrentLoading(CacheBuilder<Object, Object> builder)
      throws InterruptedException {
    testConcurrentLoadingDefault(builder);
    testConcurrentLoadingNull(builder);
    testConcurrentLoadingUncheckedException(builder);
    testConcurrentLoadingCheckedException(builder);
  }

  /**
   * On a successful concurrent computation, only one thread does the work, but all the threads get
   * the same result.
   */
  private static void testConcurrentLoadingDefault(CacheBuilder<Object, Object> builder)
      throws InterruptedException {

    int count = 10;
    final AtomicInteger callCount = new AtomicInteger();
    final CountDownLatch startSignal = new CountDownLatch(count + 1);
    final Object result = new Object();

    LoadingCache<String, Object> cache = builder.build(
        new CacheLoader<String, Object>() {
          @Override public Object load(String key) throws InterruptedException {
            callCount.incrementAndGet();
            startSignal.await();
            return result;
          }
        });

    List<Object> resultArray = doConcurrentGet(cache, "bar", count, startSignal);

    assertEquals(1, callCount.get());
    for (int i = 0; i < count; i++) {
      assertSame("result(" + i + ") didn't match expected", result, resultArray.get(i));
    }
  }

  /**
   * On a concurrent computation that returns null, all threads should get an
   * InvalidCacheLoadException, with the loader only called once. The result should not be cached
   * (a later request should call the loader again).
   */
  private static void testConcurrentLoadingNull(CacheBuilder<Object, Object> builder)
      throws InterruptedException {

    int count = 10;
    final AtomicInteger callCount = new AtomicInteger();
    final CountDownLatch startSignal = new CountDownLatch(count + 1);

    LoadingCache<String, String> cache = builder.build(
        new CacheLoader<String, String>() {
          @Override public String load(String key) throws InterruptedException {
            callCount.incrementAndGet();
            startSignal.await();
            return null;
          }
        });

    List<Object> result = doConcurrentGet(cache, "bar", count, startSignal);

    assertEquals(1, callCount.get());
    for (int i = 0; i < count; i++) {
      assertThat(result.get(i)).isInstanceOf(InvalidCacheLoadException.class);
    }

    // subsequent calls should call the loader again, not get the old exception
    try {
      cache.getUnchecked("bar");
      fail();
    } catch (InvalidCacheLoadException expected) {
    }
    assertEquals(2, callCount.get());
  }

  /**
   * On a concurrent computation that throws an unchecked exception, all threads should get the
   * (wrapped) exception, with the loader called only once. The result should not be cached (a later
   * request should call the loader again).
   */
  private static void testConcurrentLoadingUncheckedException(
      CacheBuilder<Object, Object> builder) throws InterruptedException {

    int count = 10;
    final AtomicInteger callCount = new AtomicInteger();
    final CountDownLatch startSignal = new CountDownLatch(count + 1);
    final RuntimeException e = new RuntimeException();

    LoadingCache<String, String> cache = builder.build(
        new CacheLoader<String, String>() {
          @Override public String load(String key) throws InterruptedException {
            callCount.incrementAndGet();
            startSignal.await();
            throw e;
          }
        });

    List<Object> result = doConcurrentGet(cache, "bar", count, startSignal);

    assertEquals(1, callCount.get());
    for (int i = 0; i < count; i++) {
      // doConcurrentGet alternates between calling getUnchecked and calling get, but an unchecked
      // exception thrown by the loader is always wrapped as an UncheckedExecutionException.
      assertThat(result.get(i)).isInstanceOf(UncheckedExecutionException.class);
      assertSame(e, ((UncheckedExecutionException) result.get(i)).getCause());
    }

    // subsequent calls should call the loader again, not get the old exception
    try {
      cache.getUnchecked("bar");
      fail();
    } catch (UncheckedExecutionException expected) {
    }
    assertEquals(2, callCount.get());
  }

  /**
   * On a concurrent computation that throws a checked exception, all threads should get the
   * (wrapped) exception, with the loader called only once. The result should not be cached (a later
   * request should call the loader again).
   */
  private static void testConcurrentLoadingCheckedException(
      CacheBuilder<Object, Object> builder) throws InterruptedException {

    int count = 10;
    final AtomicInteger callCount = new AtomicInteger();
    final CountDownLatch startSignal = new CountDownLatch(count + 1);
    final IOException e = new IOException();

    LoadingCache<String, String> cache = builder.build(
        new CacheLoader<String, String>() {
          @Override public String load(String key) throws IOException, InterruptedException {
            callCount.incrementAndGet();
            startSignal.await();
            throw e;
          }
        });

    List<Object> result = doConcurrentGet(cache, "bar", count, startSignal);

    assertEquals(1, callCount.get());
    for (int i = 0; i < count; i++) {
      // doConcurrentGet alternates between calling getUnchecked and calling get. If we call get(),
      // we should get an ExecutionException; if we call getUnchecked(), we should get an
      // UncheckedExecutionException.
      int mod = i % 3;
      if (mod == 0 || mod == 2) {
        assertThat(result.get(i)).isInstanceOf(ExecutionException.class);
        assertSame(e, ((ExecutionException) result.get(i)).getCause());
      } else {
        assertThat(result.get(i)).isInstanceOf(UncheckedExecutionException.class);
        assertSame(e, ((UncheckedExecutionException) result.get(i)).getCause());
      }
    }

    // subsequent calls should call the loader again, not get the old exception
    try {
      cache.getUnchecked("bar");
      fail();
    } catch (UncheckedExecutionException expected) {
    }
    assertEquals(2, callCount.get());
  }

  /**
   * Test-helper method that performs {@code nThreads} concurrent calls to {@code cache.get(key)}
   * or {@code cache.getUnchecked(key)}, and returns a List containing each of the results. The
   * result for any given call to {@code cache.get} or {@code cache.getUnchecked} is the value
   * returned, or the exception thrown.
   *
   * <p>As we iterate from {@code 0} to {@code nThreads}, threads with an even index will call
   * {@code getUnchecked}, and threads with an odd index will call {@code get}. If the cache throws
   * exceptions, this difference may be visible in the returned List.
   */
  private static <K> List doConcurrentGet(final LoadingCache cache, final K key,
      int nThreads, final CountDownLatch gettersStartedSignal) throws InterruptedException {

    final AtomicReferenceArray<Object> result = new AtomicReferenceArray(nThreads);
    final CountDownLatch gettersComplete = new CountDownLatch(nThreads);
    for (int i = 0; i < nThreads; i++) {
      final int index = i;
      Thread thread = new Thread(new Runnable() {
        @Override public void run() {
          gettersStartedSignal.countDown();
          Object value = null;
          try {
            int mod = index % 3;
            if (mod == 0) {
              value = cache.get(key);
            } else if (mod == 1) {
              value = cache.getUnchecked(key);
            } else {
              cache.refresh(key);
              value = cache.get(key);
            }
            result.set(index, value);
          } catch (Throwable t) {
            result.set(index, t);
          }
          gettersComplete.countDown();
        }
      });
      thread.start();
      // we want to wait until each thread is WAITING - one thread waiting inside CacheLoader.load
      // (in startSignal.await()), and the others waiting for that thread's result.
      while (thread.isAlive() && thread.getState() != Thread.State.WAITING) {
        Thread.yield();
      }
    }
    gettersStartedSignal.countDown();
    gettersComplete.await();

    List<Object> resultList = Lists.newArrayListWithExpectedSize(nThreads);
    for (int i = 0; i < nThreads; i++) {
      resultList.add(result.get(i));
    }
    return resultList;
  }

  public void testAsMapDuringLoading() throws InterruptedException, ExecutionException {
    final CountDownLatch getStartedSignal = new CountDownLatch(2);
    final CountDownLatch letGetFinishSignal = new CountDownLatch(1);
    final CountDownLatch getFinishedSignal = new CountDownLatch(2);
    final String getKey = "get";
    final String refreshKey = "refresh";
    final String suffix = "Suffix";

    CacheLoader<String, String> computeFunction = new CacheLoader() {
      @Override
      public String load(String key) throws InterruptedException {
        getStartedSignal.countDown();
        letGetFinishSignal.await();
        return key + suffix;
      }
    };

    final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .build(computeFunction);
    ConcurrentMap<String,String> map = cache.asMap();
    map.put(refreshKey, refreshKey);
    assertEquals(1, map.size());
    assertFalse(map.containsKey(getKey));
    assertSame(refreshKey, map.get(refreshKey));

    new Thread() {
      @Override
      public void run() {
        cache.getUnchecked(getKey);
        getFinishedSignal.countDown();
      }
    }.start();
    new Thread() {
      @Override
      public void run() {
        cache.refresh(refreshKey);
        getFinishedSignal.countDown();
      }
    }.start();

    getStartedSignal.await();

    // computation is in progress; asMap shouldn't have changed
    assertEquals(1, map.size());
    assertFalse(map.containsKey(getKey));
    assertSame(refreshKey, map.get(refreshKey));

    // let computation complete
    letGetFinishSignal.countDown();
    getFinishedSignal.await();
    checkNothingLogged();

    // asMap view should have been updated
    assertEquals(2, cache.size());
    assertEquals(getKey + suffix, map.get(getKey));
    assertEquals(refreshKey + suffix, map.get(refreshKey));
  }

  public void testInvalidateDuringLoading() throws InterruptedException, ExecutionException {
    // computation starts; invalidate() is called on the key being computed, computation finishes
    final CountDownLatch computationStarted = new CountDownLatch(2);
    final CountDownLatch letGetFinishSignal = new CountDownLatch(1);
    final CountDownLatch getFinishedSignal = new CountDownLatch(2);
    final String getKey = "get";
    final String refreshKey = "refresh";
    final String suffix = "Suffix";

    CacheLoader<String, String> computeFunction = new CacheLoader() {
      @Override
      public String load(String key) throws InterruptedException {
        computationStarted.countDown();
        letGetFinishSignal.await();
        return key + suffix;
      }
    };

    final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .build(computeFunction);
    ConcurrentMap<String,String> map = cache.asMap();
    map.put(refreshKey, refreshKey);

    new Thread() {
      @Override
      public void run() {
        cache.getUnchecked(getKey);
        getFinishedSignal.countDown();
      }
    }.start();
    new Thread() {
      @Override
      public void run() {
        cache.refresh(refreshKey);
        getFinishedSignal.countDown();
      }
    }.start();

    computationStarted.await();
    cache.invalidate(getKey);
    cache.invalidate(refreshKey);
    assertFalse(map.containsKey(getKey));
    assertFalse(map.containsKey(refreshKey));

    // let computation complete
    letGetFinishSignal.countDown();
    getFinishedSignal.await();
    checkNothingLogged();

    // results should be visible
    assertEquals(2, cache.size());
    assertEquals(getKey + suffix, map.get(getKey));
    assertEquals(refreshKey + suffix, map.get(refreshKey));
    assertEquals(2, cache.size());
  }

  public void testInvalidateAndReloadDuringLoading()
      throws InterruptedException, ExecutionException {
    // computation starts; clear() is called, computation finishes
    final CountDownLatch computationStarted = new CountDownLatch(2);
    final CountDownLatch letGetFinishSignal = new CountDownLatch(1);
    final CountDownLatch getFinishedSignal = new CountDownLatch(4);
    final String getKey = "get";
    final String refreshKey = "refresh";
    final String suffix = "Suffix";

    CacheLoader<String, String> computeFunction = new CacheLoader() {
      @Override
      public String load(String key) throws InterruptedException {
        computationStarted.countDown();
        letGetFinishSignal.await();
        return key + suffix;
      }
    };

    final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .build(computeFunction);
    ConcurrentMap<String,String> map = cache.asMap();
    map.put(refreshKey, refreshKey);

    new Thread() {
      @Override
      public void run() {
        cache.getUnchecked(getKey);
        getFinishedSignal.countDown();
      }
    }.start();
    new Thread() {
      @Override
      public void run() {
        cache.refresh(refreshKey);
        getFinishedSignal.countDown();
      }
    }.start();

    computationStarted.await();
    cache.invalidate(getKey);
    cache.invalidate(refreshKey);
    assertFalse(map.containsKey(getKey));
    assertFalse(map.containsKey(refreshKey));

    // start new computations
    new Thread() {
      @Override
      public void run() {
        cache.getUnchecked(getKey);
        getFinishedSignal.countDown();
      }
    }.start();
    new Thread() {
      @Override
      public void run() {
        cache.refresh(refreshKey);
        getFinishedSignal.countDown();
      }
    }.start();

    // let computation complete
    letGetFinishSignal.countDown();
    getFinishedSignal.await();
    checkNothingLogged();

    // results should be visible
    assertEquals(2, cache.size());
    assertEquals(getKey + suffix, map.get(getKey));
    assertEquals(refreshKey + suffix, map.get(refreshKey));
  }

  public void testExpandDuringLoading() throws InterruptedException {
    final int count = 3;
    final AtomicInteger callCount = new AtomicInteger();
    // tells the computing thread when to start computing
    final CountDownLatch computeSignal = new CountDownLatch(1);
    // tells the main thread when computation is pending
    final CountDownLatch secondSignal = new CountDownLatch(1);
    // tells the main thread when the second get has started
    final CountDownLatch thirdSignal = new CountDownLatch(1);
    // tells the main thread when the third get has started
    final CountDownLatch fourthSignal = new CountDownLatch(1);
    // tells the test when all gets have returned
    final CountDownLatch doneSignal = new CountDownLatch(count);

    CacheLoader<String, String> computeFunction = new CacheLoader() {
      @Override
      public String load(String key) throws InterruptedException {
        callCount.incrementAndGet();
        secondSignal.countDown();
        computeSignal.await();
        return key + "foo";
      }
    };

    final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .weakKeys()
        .build(computeFunction);

    final AtomicReferenceArray<String> result = new AtomicReferenceArray(count);

    final String key = "bar";

    // start computing thread
    new Thread() {
      @Override
      public void run() {
        result.set(0, cache.getUnchecked(key));
        doneSignal.countDown();
      }
    }.start();

    // wait for computation to start
    secondSignal.await();

    // start waiting thread
    new Thread() {
      @Override
      public void run() {
        thirdSignal.countDown();
        result.set(1, cache.getUnchecked(key));
        doneSignal.countDown();
      }
    }.start();

    // give the second get a chance to run; it is okay for this to be racy
    // as the end result should be the same either way
    thirdSignal.await();
    Thread.yield();

    // Expand!
    CacheTesting.forceExpandSegment(cache, key);

    // start another waiting thread
    new Thread() {
      @Override
      public void run() {
        fourthSignal.countDown();
        result.set(2, cache.getUnchecked(key));
        doneSignal.countDown();
      }
    }.start();

    // give the third get a chance to run; it is okay for this to be racy
    // as the end result should be the same either way
    fourthSignal.await();
    Thread.yield();

    // let computation finish
    computeSignal.countDown();
    doneSignal.await();

    assertTrue(callCount.get() == 1);
    assertEquals("barfoo", result.get(0));
    assertEquals("barfoo", result.get(1));
    assertEquals("barfoo", result.get(2));
    assertEquals("barfoo", cache.getUnchecked(key));
  }

  // Test ignored because it is extremely flaky in CI builds

  public void
      ignoreTestExpandDuringRefresh()
      throws InterruptedException, ExecutionException {
    final AtomicInteger callCount = new AtomicInteger();
    // tells the computing thread when to start computing
    final CountDownLatch computeSignal = new CountDownLatch(1);
    // tells the main thread when computation is pending
    final CountDownLatch secondSignal = new CountDownLatch(1);
    // tells the main thread when the second get has started
    final CountDownLatch thirdSignal = new CountDownLatch(1);
    // tells the main thread when the third get has started
    final CountDownLatch fourthSignal = new CountDownLatch(1);
    // tells the test when all gets have returned
    final CountDownLatch doneSignal = new CountDownLatch(3);
    final String suffix = "Suffix";

    CacheLoader<String, String> computeFunction = new CacheLoader() {
      @Override
      public String load(String key) throws InterruptedException {
        callCount.incrementAndGet();
        secondSignal.countDown();
        computeSignal.await();
        return key + suffix;
      }
    };

    final AtomicReferenceArray<String> result = new AtomicReferenceArray(2);

    final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .build(computeFunction);
    final String key = "bar";
    cache.asMap().put(key, key);

    // start computing thread
    new Thread() {
      @Override
      public void run() {
        cache.refresh(key);
        doneSignal.countDown();
      }
    }.start();

    // wait for computation to start
    secondSignal.await();
    checkNothingLogged();

    // start waiting thread
    new Thread() {
      @Override
      public void run() {
        thirdSignal.countDown();
        result.set(0, cache.getUnchecked(key));
        doneSignal.countDown();
      }
    }.start();

    // give the second get a chance to run; it is okay for this to be racy
    // as the end result should be the same either way
    thirdSignal.await();
    Thread.yield();

    // Expand!
    CacheTesting.forceExpandSegment(cache, key);

    // start another waiting thread
    new Thread() {
      @Override
      public void run() {
        fourthSignal.countDown();
        result.set(1, cache.getUnchecked(key));
        doneSignal.countDown();
      }
    }.start();

    // give the third get a chance to run; it is okay for this to be racy
    // as the end result should be the same either way
    fourthSignal.await();
    Thread.yield();

    // let computation finish
    computeSignal.countDown();
    doneSignal.await();

    assertTrue(callCount.get() == 1);
    assertEquals(key, result.get(0));
    assertEquals(key, result.get(1));
    assertEquals(key + suffix, cache.getUnchecked(key));
  }

  static <T> Callable throwing(final Exception exception) {
    return new Callable<T>() {
      @Override public T call() throws Exception {
        throw exception;
      }
    };
  }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java CacheLoadingTest.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.