1   /*
2    * RCache - A collection of simple reference-based cache implementations.
3    * Copyright (C) 2007  Rodrigo Ruiz
4    *
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
18  */
19  package net.sourceforge.rcache;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertNull;
25  import static org.junit.Assert.assertSame;
26  import static org.junit.Assert.assertTrue;
27  import static org.junit.Assert.fail;
28  
29  import java.util.ArrayList;
30  import java.util.HashSet;
31  
32  import net.sourceforge.rcache.decorator.CacheProfiler;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.junit.Test;
37  
38  /**
39   * Base class for deriving other test-cases.
40   *
41   * @author Rodrigo Ruiz
42   */
43  public abstract class BaseCacheTest {
44  
45    /**
46     * Class logger.
47     */
48    private static final Log LOG = LogFactory.getLog(BaseCacheTest.class);
49  
50    /**
51     * Creates a cache instance.
52     *
53     * @return A Cache instance
54     */
55    protected abstract BaseCache<Integer, Object> createCache();
56  
57    /**
58     * Creates a cache instance.
59     *
60     * @param capacity Initial capacity
61     * @return A Cache instance
62     */
63    protected abstract BaseCache<Integer, Object> createCache(int capacity);
64  
65    /**
66     * Creates a cache instance.
67     *
68     * @param capacity Initial capacity
69     * @param factor   Load factor
70     * @return A Cache instance
71     */
72    protected abstract BaseCache<Integer, Object> createCache(int capacity, float factor);
73  
74    /**
75     * Creates a cache instance.
76     *
77     * @param capacity Initial capacity
78     * @param factor   Load factor
79     * @param level    Concurrency level
80     * @return A Cache instance
81     */
82    protected abstract BaseCache<Integer, Object> createCache(int capacity, float factor,
83      int level);
84  
85    /**
86     * Test for {@link net.sourceforge.rcache.Cache#get(Object)}.
87     */
88    @Test public final void testGet() {
89      BaseCache<Integer, Object> cache = createCache(
90        Cache.DEFAULT_INITIAL_CAPACITY,
91        Cache.DEFAULT_LOAD_FACTOR
92      );
93      StringBuffer sb1 = new StringBuffer("1");
94      StringBuffer sb2 = new StringBuffer("2");
95  
96      cache.put(1, sb1);
97      cache.put(2, sb2);
98  
99      assertNull(cache.get(0));
100 
101     assertSame(sb1, cache.get(1));
102     assertSame(sb2, cache.get(2));
103 
104     cache.clear();
105   }
106 
107   /**
108    * Test for {@link net.sourceforge.rcache.Cache#remove(Object)}.
109    */
110   @Test public final void testRemove() {
111     BaseCache<Integer, Object> cache = createCache(1);
112     assertNull(cache.remove(1));
113 
114     assertNull(cache.put(1, "One"));
115     assertEquals("One", cache.remove(1));
116   }
117 
118   /**
119    * Tests that the cache has the correct number of items after a purge().
120    */
121   @Test public final void testPurge() {
122     BaseCache<Integer, Object> cache = createCache();
123     StringBuffer sb1 = new StringBuffer("1");
124     StringBuffer sb2 = new StringBuffer("2");
125 
126     cache.put(1, sb1);
127     cache.put(2, sb2);
128 
129     // Fire the deletion of the value for key "1"
130     sb1 = null;
131     forcePurge();
132 
133     assertEquals("Incorrect cache size;", 1, cache.size());
134 
135     cache.clear();
136   }
137 
138   /**
139    * Test for unsupported methods in {@link net.sourceforge.rcache.BaseCache}.
140    */
141   @Test public final void testUnsupported() {
142     BaseCache<Integer, Object> cache = createCache();
143     try {
144       cache.entrySet();
145       fail("Expected exception not thrown");
146     } catch (UnsupportedOperationException e) {
147       LOG.debug("Expected exception");
148     }
149     try {
150       cache.values();
151       fail("Expected exception not thrown");
152     } catch (UnsupportedOperationException e) {
153       LOG.debug("Expected exception");
154     }
155   }
156 
157   /**
158    * Tests that the following sequence:
159    * <ul>
160    *   <li>cache.put(key, x)</li>
161    *   <li>cache.remove(key)</li>
162    *   <li>cache.put(key, y)</li>
163    * </ul>
164    *
165    * Is safe. This is, after a purge(), the cache MUST still maintain
166    * the association [key, y].
167    */
168   @Test public final void testRemoveAndPut() {
169     final int[] levels = { -1, 0, 1 };
170 
171     for (int level : levels) {
172       // Test each concurrency mode
173       BaseCache<Integer, Object> cache = createCache(
174         Cache.DEFAULT_INITIAL_CAPACITY,
175         Cache.DEFAULT_LOAD_FACTOR,
176         level);
177       testRemoveAndPut(cache);
178     }
179   }
180 
181   /**
182    * Used internally by {@link #testRemoveAndPut()}.
183    *
184    * @param cache Cache instance to test
185    */
186   private void testRemoveAndPut(BaseCache<Integer, Object> cache) {
187     StringBuffer sb1 = new StringBuffer("1");
188     StringBuffer sb2 = new StringBuffer("2");
189 
190     cache.put(1, sb1);
191     // Tests that values are not overwritten
192     assertSame(sb1, cache.get(1));
193     assertSame(sb1, cache.put(1, sb2));
194     assertSame(sb1, cache.get(1));
195 
196     sb1 = null;
197     cache.remove(1);
198     cache.put(1, sb2);
199 
200     // Fire the deletion of the value for key "1"
201     forcePurge();
202 
203     assertEquals(1, cache.size());
204     assertEquals(sb2, cache.get(1));
205 
206     cache.clear();
207   }
208 
209   /**
210    * Test for some methods inherited from Object.
211    */
212   @Test public final void testObjectMethods() {
213     BaseCache<Integer, Object> cache1 = createCache();
214     BaseCache<Integer, Object> cache2 = createCache();
215 
216     // Two instances must always be different
217     assertFalse(cache1.equals(cache2));
218     assertTrue(cache1.equals(cache1));
219 
220     HashSet<Object> set = new HashSet<Object>();
221     set.add(cache1);
222     set.add(cache2);
223     assertEquals(2, set.size());
224   }
225 
226   /**
227    * Test for {@link net.sourceforge.rcache.decorator.CacheProfiler}.
228    */
229   @Test public final void testProfiler() {
230     CacheProfiler<Integer, Object> cache = new CacheProfiler<Integer, Object>(
231       createCache()
232     );
233 
234     check(new long[] { 0, 0, 0, 0 }, cache.getNums());
235     check(new double[] { 0, 0, 0, 0 }, cache.getAverages());
236     cache.put(1, "One");
237     cache.put(2, "Two");
238     check(new long[] { 0, 2, 0, 0 }, cache.getNums());
239 
240     cache.get(1);
241     cache.get(2);
242     check(new long[] { 2, 2, 0, 0 }, cache.getNums());
243 
244     cache.remove(0);
245     cache.remove(1);
246     check(new long[] { 2, 2, 2, 0 }, cache.getNums());
247 
248     cache.clear();
249     check(new long[] { 2, 2, 2, 1 }, cache.getNums());
250 
251     assertEquals(cache.getNum(Cache.Operation.GET), cache.getNums()[0]);
252     assertEquals(cache.getAverage(Cache.Operation.GET), cache.getAverages()[0], 0.0);
253 
254   }
255 
256   /**
257    * Utility method that forces caches to be purged by demanding a huge
258    * amount of memory.
259    */
260   protected final void forcePurge() {
261     ArrayList<int[]> list = new ArrayList<int[]>();
262     final int factor = 4096;
263     try {
264       while (true) {
265         list.add(new int[Integer.MAX_VALUE / factor]);
266       }
267     } catch (OutOfMemoryError e) {
268       LOG.debug("Forced an OOM for cache purging in " + list.size() + " iterations");
269     }
270   }
271 
272   /**
273    * Tests two arrays for equality.
274    *
275    * @param a1 Left array
276    * @param a2 Right array
277    */
278   protected final void check(long[] a1, long[] a2) {
279     if (a1 != a2) {
280       assertNotNull(a1);
281       assertNotNull(a2);
282       assertEquals(a1.length, a2.length);
283 
284       for (int i = 0; i < a1.length; i++) {
285         assertEquals("Item '" + i + "' differs;", a1[i], a2[i]);
286       }
287     }
288   }
289 
290   /**
291    * Tests two arrays for equality.
292    *
293    * @param a1 Left array
294    * @param a2 Right array
295    */
296   protected final void check(double[] a1, double[] a2) {
297     if (a1 != a2) {
298       assertNotNull(a1);
299       assertNotNull(a2);
300       assertEquals(a1.length, a2.length);
301 
302       for (int i = 0; i < a1.length; i++) {
303         assertEquals("Item '" + i + "' differs;", a1[i], a2[i], 0.0);
304       }
305     }
306   }
307 }