/*
 * Copyright 2015 Ben Manes. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.benmanes.caffeine.cache;

import static com.github.benmanes.caffeine.cache.testing.HasRemovalNotifications.hasRemovalNotifications;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import com.github.benmanes.caffeine.cache.testing.CacheContext;
import com.github.benmanes.caffeine.cache.testing.CacheProvider;
import com.github.benmanes.caffeine.cache.testing.CacheSpec;
import com.github.benmanes.caffeine.cache.testing.CacheSpec.Population;
import com.github.benmanes.caffeine.cache.testing.CacheSpec.ReferenceType;
import com.github.benmanes.caffeine.cache.testing.CacheValidationListener;
import com.google.common.testing.GcFinalization;

/**
 * @author ben.manes@gmail.com (Ben Manes)
 */
@Listeners(CacheValidationListener.class)
@Test(groups = "slow", dataProviderClass = CacheProvider.class)
public final class ReferenceTest {
  // These are slow tests due to requiring a garbage collection cycle. Note that the G1 collector
  // should not be used due to not providing a deterministic approach (pure LRU) for when soft
  // references are discarded.

  // To run these tests in an IDE, either pass in `-Dslow` or hard code the value in CacheProvider

  @Test(dataProvider = "caches")
  @CacheSpec(keys = ReferenceType.WEAK, population = Population.EMPTY)
  public void evict_weakKeys(Cache<Integer, Integer> cache, CacheContext context) {
    context.original().clear();
    cache.put(new Random().nextInt(), 0);
    GcFinalization.awaitFullGc();
    cleanUp(cache, context, 0);
    assertThat(cache, hasRemovalNotifications(context, 1, RemovalCause.COLLECTED));
  }

  @Test(dataProvider = "caches")
  @CacheSpec(values = ReferenceType.WEAK, population = Population.FULL)
  public void evict_weakValues(Cache<Integer, Integer> cache, CacheContext context) {
    context.original().clear();
    GcFinalization.awaitFullGc();
    cleanUp(cache, context, 0);
    assertThat(cache, hasRemovalNotifications(
        context, context.initialSize(), RemovalCause.COLLECTED));
  }

  @Test(enabled = false, dataProvider = "caches")
  @CacheSpec(values = ReferenceType.SOFT, population = Population.FULL)
  public void evict_softValues(Cache<Integer, Integer> cache, CacheContext context) {
    context.clear();
    awaitSoftRefGc();
    cleanUp(cache, context, 0);
    assertThat(cache, hasRemovalNotifications(
        context, context.initialSize(), RemovalCause.COLLECTED));
  }

  static void cleanUp(Cache<Integer, Integer> cache, CacheContext context, long finalSize) {
    // As clean-up is amortized, pretend that the increment count may be as low as per entry
    for (int i = 0; i < context.population().size(); i++) {
      cache.cleanUp();
      if (cache.estimatedSize() == finalSize) {
        return; // passed
      }
    }
    GcFinalization.awaitFullGc();
    cache.cleanUp();
    assertThat(cache.estimatedSize(), is(0L));
  }

  /**
   * Tries to coerce a major GC cycle that evicts soft references, assuming that they are held
   * globally in LRU order.
   */
  static void awaitSoftRefGc() {
    byte[] garbage = new byte[1024];
    SoftReference<Object> flag = new SoftReference<>(new Object());
    List<Object> softRefs = new ArrayList<>();
    while (flag.get() != null) {
      int free = Math.abs((int) Runtime.getRuntime().freeMemory());
      int nextLength = Math.max(garbage.length, garbage.length << 2);
      garbage = new byte[Math.min(free >> 2, nextLength)];
      softRefs.add(new SoftReference<>(garbage));
    }
    softRefs.clear();
  }
}

@@ -957,16 +957,38 @@
 va.util.
+concurrent.ThreadLocal
 Random;%0A
@@ -2242,122 +2242,101 @@
    c
-ontext.original().clear();%0A    cache.put(new Random().nextInt(), 0);%0A    GcFinalization.awaitFullGc();%0A    cleanUp
+ache.put(Integer.MIN_VALUE + ThreadLocalRandom.current().nextInt(), 0);%0A    cleanUpUntilEmpty
 (cac
@@ -2338,35 +2338,32 @@
 y(cache, context
-, 0
 );%0A    assertTha
@@ -2669,49 +2669,25 @@
     
-GcFinalization.awaitFullGc();%0A    cleanUp
+cleanUpUntilEmpty
 (cac
@@ -2689,35 +2689,32 @@
 y(cache, context
-, 0
 );%0A    assertTha
@@ -3080,16 +3080,26 @@
  cleanUp
+UntilEmpty
 (cache, 
@@ -3105,19 +3105,16 @@
  context
-, 0
 );%0A    a
@@ -3247,16 +3247,26 @@
  cleanUp
+UntilEmpty
 (Cache%3CI
@@ -3313,24 +3313,8 @@
 text
-, long finalSize
 ) %7B%0A
@@ -3414,13 +3414,8 @@
     
-for (
 int 
@@ -3424,48 +3424,53 @@
 = 0;
- i %3C context.population().size(); i++) %7B
+%0A    do %7B%0A      GcFinalization.awaitFullGc();
 %0A   
@@ -3509,36 +3509,25 @@
 che.
-estimatedSize() == finalSize
+asMap().isEmpty()
 ) %7B%0A
@@ -3569,61 +3569,49 @@
    %7D
-%0A    GcFinalization.awaitFullGc();%0A    cache.cleanUp(
+ while (i++ %3C context.population().size()
 );%0A 

Try to stabilize the test case