@Test
  public void testBrokenEvictionVeto() throws CacheAccessException {
    TestTimeSource timeSource = new TestTimeSource();
    Expiry<Object, Object> expiry =
        Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS));
    EvictionVeto<String, byte[]> evictionVeto =
        new EvictionVeto<String, byte[]>() {

          @Override
          public boolean test(Cache.Entry<String, byte[]> entry) {
            throw new UnsupportedOperationException("Broken veto!");
          }
        };

    AbstractOffHeapStore<String, byte[]> offHeapStore =
        createAndInitStore(timeSource, expiry, evictionVeto);
    try {
      StoreEventListener<String, byte[]> mock = mock(StoreEventListener.class);
      offHeapStore.enableStoreEventNotifications(mock);

      byte[] value = getBytes(MemoryUnit.KB.toBytes(200));
      offHeapStore.put("key1", value);
      offHeapStore.put("key2", value);
      offHeapStore.put("key3", value);
      offHeapStore.put("key4", value);
      offHeapStore.put("key5", value);
      offHeapStore.put("key6", value);

      verify(mock, atLeast(1)).onEviction(anyString(), any(Store.ValueHolder.class));
    } finally {
      destroyStore(offHeapStore);
    }
  }
  @Test
  public void testExpiryAccessException() throws Exception {
    TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource,
            new Expiry<String, String>() {
              @Override
              public Duration getExpiryForCreation(String key, String value) {
                return Duration.FOREVER;
              }

              @Override
              public Duration getExpiryForAccess(String key, String value) {
                throw new RuntimeException();
              }

              @Override
              public Duration getExpiryForUpdate(String key, String oldValue, String newValue) {
                return null;
              }
            });

    offHeapStore.put("key", "value");
    assertNull(offHeapStore.get("key"));
  }
  @Test
  public void testExpiryUpdateException() throws Exception {
    final TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource,
            new Expiry<String, String>() {
              @Override
              public Duration getExpiryForCreation(String key, String value) {
                return Duration.FOREVER;
              }

              @Override
              public Duration getExpiryForAccess(String key, String value) {
                return Duration.FOREVER;
              }

              @Override
              public Duration getExpiryForUpdate(String key, String oldValue, String newValue) {
                if (timeSource.getTimeMillis() > 0) {
                  throw new RuntimeException();
                }
                return Duration.FOREVER;
              }
            });

    offHeapStore.put("key", "value");
    assertThat(offHeapStore.get("key").value(), is("value"));
    timeSource.advanceTime(1000);
    offHeapStore.put("key", "newValue");
    assertNull(offHeapStore.get("key"));
  }
  @Test
  public void testComputeIfAbsentOnExpiredEntry() throws CacheAccessException {
    TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource, Expirations.timeToIdleExpiration(new Duration(10L, TimeUnit.MILLISECONDS)));
    try {
      offHeapStore.put("key", "value");
      timeSource.advanceTime(20L);

      offHeapStore.computeIfAbsent(
          "key",
          new Function<String, String>() {
            @Override
            public String apply(String mappedKey) {
              assertThat(mappedKey, is("key"));
              return "value2";
            }
          });
      assertThat(
          getExpirationStatistic(offHeapStore)
              .count(StoreOperationOutcomes.ExpirationOutcome.SUCCESS),
          is(1L));
    } finally {
      destroyStore(offHeapStore);
    }
  }
  @Test
  public void testGetAndRemoveExpiredElementReturnsNull() throws Exception {
    TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS)));

    try {
      assertThat(offHeapStore.getAndRemove("1"), is(nullValue()));

      offHeapStore.put("1", "one");

      final AtomicReference<Store.ValueHolder<String>> invalidated =
          new AtomicReference<Store.ValueHolder<String>>();
      offHeapStore.setInvalidationListener(
          new CachingTier.InvalidationListener<String, String>() {
            @Override
            public void onInvalidation(String key, Store.ValueHolder<String> valueHolder) {
              invalidated.set(valueHolder);
            }
          });

      timeSource.advanceTime(20);
      assertThat(offHeapStore.getAndRemove("1"), is(nullValue()));
      assertThat(invalidated.get().value(), equalTo("one"));
      assertThat(
          invalidated.get().isExpired(timeSource.getTimeMillis(), TimeUnit.MILLISECONDS), is(true));
      assertThat(
          getExpirationStatistic(offHeapStore)
              .count(StoreOperationOutcomes.ExpirationOutcome.SUCCESS),
          is(1L));
    } finally {
      destroyStore(offHeapStore);
    }
  }
 @Test
 public void testFlushUpdatesAccessStats() throws CacheAccessException {
   final TestTimeSource timeSource = new TestTimeSource();
   final Expiry<Object, Object> expiry =
       Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS));
   final AbstractOffHeapStore<String, String> store = createAndInitStore(timeSource, expiry);
   try {
     final String key = "foo";
     final String value = "bar";
     store.put(key, value);
     final Store.ValueHolder<String> firstValueHolder = store.getAndFault(key);
     store.put(key, value);
     final Store.ValueHolder<String> secondValueHolder = store.getAndFault(key);
     timeSource.advanceTime(10);
     ((AbstractValueHolder) firstValueHolder)
         .accessed(timeSource.getTimeMillis(), expiry.getExpiryForAccess(key, value));
     timeSource.advanceTime(10);
     ((AbstractValueHolder) secondValueHolder)
         .accessed(timeSource.getTimeMillis(), expiry.getExpiryForAccess(key, value));
     assertThat(store.flush(key, new DelegatingValueHolder<String>(firstValueHolder)), is(false));
     assertThat(store.flush(key, new DelegatingValueHolder<String>(secondValueHolder)), is(true));
     timeSource.advanceTime(10); // this should NOT affect
     assertThat(
         store.getAndFault(key).lastAccessTime(TimeUnit.MILLISECONDS),
         is(secondValueHolder.creationTime(TimeUnit.MILLISECONDS) + 20));
   } finally {
     destroyStore(store);
   }
 }
 @Test
 public void testIteratorOnEmptyStore() throws Exception {
   TestTimeSource timeSource = new TestTimeSource();
   AbstractOffHeapStore<String, String> offHeapStore =
       createAndInitStore(
           timeSource, Expirations.timeToLiveExpiration(new Duration(10L, TimeUnit.MILLISECONDS)));
   try {
     Store.Iterator<Cache.Entry<String, Store.ValueHolder<String>>> iterator =
         offHeapStore.iterator();
     assertFalse(iterator.hasNext());
   } finally {
     destroyStore(offHeapStore);
   }
 }
  @Test
  public void testGetAndRemoveNoValue() throws Exception {
    TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(timeSource, Expirations.noExpiration());

    try {
      assertThat(offHeapStore.getAndRemove("1"), is(nullValue()));
      validateStats(
          offHeapStore, EnumSet.of(LowerCachingTierOperationsOutcome.GetAndRemoveOutcome.MISS));
    } finally {
      destroyStore(offHeapStore);
    }
  }
 @Test
 public void testFlushUpdatesHits() throws CacheAccessException {
   final TestTimeSource timeSource = new TestTimeSource();
   final AbstractOffHeapStore<String, String> store =
       createAndInitStore(timeSource, Expirations.noExpiration());
   final String key = "foo1";
   final String value = "bar1";
   store.put(key, value);
   for (int i = 0; i < 5; i++) {
     final Store.ValueHolder<String> valueHolder = store.getAndFault(key);
     timeSource.advanceTime(1);
     ((AbstractValueHolder) valueHolder)
         .accessed(timeSource.getTimeMillis(), new Duration(1L, TimeUnit.MILLISECONDS));
     assertThat(store.flush(key, new DelegatingValueHolder<String>(valueHolder)), is(true));
   }
   assertThat(store.getAndFault(key).hits(), is(5l));
 }
  @Test
  public void testIteratorSkipsExpiredEntries() throws Exception {
    TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource, Expirations.timeToLiveExpiration(new Duration(10L, TimeUnit.MILLISECONDS)));

    try {
      offHeapStore.put("key1", "value1");
      offHeapStore.put("key2", "value2");

      timeSource.advanceTime(11L);

      offHeapStore.put("key3", "value3");
      offHeapStore.put("key4", "value4");

      final List<String> expiredKeys = new ArrayList<String>();
      offHeapStore.enableStoreEventNotifications(
          new StoreEventListener<String, String>() {

            @Override
            public void onEviction(final String key, final Store.ValueHolder<String> valueHolder) {
              throw new AssertionError("This should not have happened.");
            }

            @Override
            public void onExpiration(
                final String key, final Store.ValueHolder<String> valueHolder) {
              expiredKeys.add(key);
            }
          });

      List<String> iteratedKeys = new ArrayList<String>();
      Store.Iterator<Cache.Entry<String, Store.ValueHolder<String>>> iterator =
          offHeapStore.iterator();
      while (iterator.hasNext()) {
        iteratedKeys.add(iterator.next().getKey());
      }

      assertThat(iteratedKeys, containsInAnyOrder("key3", "key4"));
      assertThat(expiredKeys, containsInAnyOrder("key1", "key2"));
    } finally {
      destroyStore(offHeapStore);
    }
  }
  @Test
  public void testIteratorWithSingleExpiredEntry() throws Exception {
    TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource, Expirations.timeToLiveExpiration(new Duration(10L, TimeUnit.MILLISECONDS)));
    try {
      offHeapStore.put("key1", "value1");

      timeSource.advanceTime(11L);

      Store.Iterator<Cache.Entry<String, Store.ValueHolder<String>>> iterator =
          offHeapStore.iterator();
      assertFalse(iterator.hasNext());
    } finally {
      destroyStore(offHeapStore);
    }
  }
  @Test
  public void testInstallMapping() throws Exception {
    final TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS)));

    try {
      assertThat(
          offHeapStore
              .installMapping(
                  "1",
                  new Function<String, Store.ValueHolder<String>>() {
                    @Override
                    public Store.ValueHolder<String> apply(String key) {
                      return new SimpleValueHolder<String>("one", timeSource.getTimeMillis(), 15);
                    }
                  })
              .value(),
          equalTo("one"));

      validateStats(
          offHeapStore, EnumSet.of(LowerCachingTierOperationsOutcome.InstallMappingOutcome.PUT));

      timeSource.advanceTime(20);

      try {
        offHeapStore.installMapping(
            "1",
            new Function<String, Store.ValueHolder<String>>() {
              @Override
              public Store.ValueHolder<String> apply(String key) {
                return new SimpleValueHolder<String>("un", timeSource.getTimeMillis(), 15);
              }
            });
        fail("expected AssertionError");
      } catch (AssertionError ae) {
        // expected
      }
    } finally {
      destroyStore(offHeapStore);
    }
  }
  @Test
  public void testGetAndFaultOnExpiredEntry() throws CacheAccessException {
    TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource, Expirations.timeToIdleExpiration(new Duration(10L, TimeUnit.MILLISECONDS)));
    try {
      offHeapStore.put("key", "value");
      timeSource.advanceTime(20L);

      Store.ValueHolder<String> valueHolder = offHeapStore.getAndFault("key");
      assertThat(valueHolder, nullValue());
      assertThat(
          getExpirationStatistic(offHeapStore)
              .count(StoreOperationOutcomes.ExpirationOutcome.SUCCESS),
          is(1L));
    } finally {
      destroyStore(offHeapStore);
    }
  }
 @Test
 public void testWriteBackOfValueHolder() throws CacheAccessException {
   TestTimeSource timeSource = new TestTimeSource();
   AbstractOffHeapStore<String, String> offHeapStore =
       createAndInitStore(
           timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS)));
   try {
     offHeapStore.put("key1", "value1");
     timeSource.advanceTime(10);
     OffHeapValueHolder<String> valueHolder =
         (OffHeapValueHolder<String>) offHeapStore.get("key1");
     assertThat(valueHolder.lastAccessTime(TimeUnit.MILLISECONDS), is(10L));
     timeSource.advanceTime(10);
     assertThat(offHeapStore.get("key1"), notNullValue());
     timeSource.advanceTime(16);
     assertThat(offHeapStore.get("key1"), nullValue());
   } finally {
     destroyStore(offHeapStore);
   }
 }
  @Test
  public void testExpiryEventFiredOnExpiredCachedEntry() throws CacheAccessException {
    TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource, Expirations.timeToIdleExpiration(new Duration(10L, TimeUnit.MILLISECONDS)));
    try {
      final List<String> expiredKeys = new ArrayList<String>();
      offHeapStore.enableStoreEventNotifications(
          new StoreEventListener<String, String>() {

            @Override
            public void onEviction(final String key, final Store.ValueHolder<String> valueHolder) {
              throw new AssertionError("This should not have happened.");
            }

            @Override
            public void onExpiration(
                final String key, final Store.ValueHolder<String> valueHolder) {
              expiredKeys.add(key);
            }
          });

      offHeapStore.put("key1", "value1");
      offHeapStore.put("key2", "value2");

      offHeapStore.get("key1"); // Bring the entry to the caching tier

      timeSource.advanceTime(11); // Expire the elements

      offHeapStore.get("key1");
      offHeapStore.get("key2");
      assertThat(expiredKeys, containsInAnyOrder("key1", "key2"));
      assertThat(
          getExpirationStatistic(offHeapStore)
              .count(StoreOperationOutcomes.ExpirationOutcome.SUCCESS),
          is(2L));
    } finally {
      destroyStore(offHeapStore);
    }
  }
  @Test
  public void testInvalidateKeyWithFunction() throws Exception {
    final TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS)));

    try {
      final AtomicReference<Store.ValueHolder<String>> invalidated =
          new AtomicReference<Store.ValueHolder<String>>();
      offHeapStore.setInvalidationListener(
          new CachingTier.InvalidationListener<String, String>() {
            @Override
            public void onInvalidation(String key, Store.ValueHolder<String> valueHolder) {
              invalidated.set(valueHolder);
            }
          });

      final AtomicBoolean functionInvoked = new AtomicBoolean(false);
      NullaryFunction<String> nullaryFunction =
          new NullaryFunction<String>() {
            @Override
            public String apply() {
              functionInvoked.set(true);
              return "";
            }
          };
      offHeapStore.invalidate("1", nullaryFunction);
      assertThat(invalidated.get(), is(nullValue()));
      assertThat(functionInvoked.get(), is(true));
      validateStats(
          offHeapStore, EnumSet.of(LowerCachingTierOperationsOutcome.InvalidateOutcome.MISS));

      functionInvoked.set(false);
      offHeapStore.put("1", "one");
      offHeapStore.invalidate("1", nullaryFunction);
      assertThat(invalidated.get().value(), equalTo("one"));
      assertThat(functionInvoked.get(), is(true));
      validateStats(
          offHeapStore,
          EnumSet.of(
              LowerCachingTierOperationsOutcome.InvalidateOutcome.MISS,
              LowerCachingTierOperationsOutcome.InvalidateOutcome.REMOVED));

      assertThat(offHeapStore.get("1"), is(nullValue()));
    } finally {
      destroyStore(offHeapStore);
    }
  }
  @Test
  public void testClear() throws Exception {
    final TestTimeSource timeSource = new TestTimeSource();
    AbstractOffHeapStore<String, String> offHeapStore =
        createAndInitStore(
            timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS)));

    try {
      offHeapStore.put("1", "one");
      offHeapStore.put("2", "two");
      offHeapStore.put("3", "three");
      offHeapStore.clear();

      assertThat(offHeapStore.get("1"), is(nullValue()));
      assertThat(offHeapStore.get("2"), is(nullValue()));
      assertThat(offHeapStore.get("3"), is(nullValue()));
    } finally {
      destroyStore(offHeapStore);
    }
  }