1 /***
2 * Copyright 2003-2010 Terracotta, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package net.sf.ehcache.store;
18
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertTrue;
24
25 import java.io.IOException;
26 import java.io.Serializable;
27 import java.lang.reflect.Field;
28 import java.lang.reflect.Method;
29 import java.util.Date;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Random;
34 import java.util.concurrent.ConcurrentHashMap;
35
36 import net.sf.ehcache.AbstractCacheTest;
37 import net.sf.ehcache.Cache;
38 import net.sf.ehcache.CacheManager;
39 import net.sf.ehcache.Element;
40 import net.sf.ehcache.MemoryStoreTester;
41 import net.sf.ehcache.Statistics;
42 import net.sf.ehcache.store.compound.CompoundStore;
43 import net.sf.ehcache.store.compound.ElementSubstituteFilter;
44 import net.sf.ehcache.store.compound.factories.CapacityLimitedInMemoryFactory;
45
46 import org.junit.Before;
47 import org.junit.Test;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /***
52 * Test class for LfuMemoryStore
53 * <p/>
54 *
55 * @author <a href="ssuravarapu@users.sourceforge.net">Surya Suravarapu</a>
56 * @version $Id: LfuMemoryStoreTest.html 13146 2011-08-01 17:12:39Z oletizi $
57 */
58 public class LfuMemoryStoreTest extends MemoryStoreTester {
59
60 private static final Logger LOG = LoggerFactory.getLogger(LfuMemoryStoreTest.class.getName());
61
62 private static final Field PRIMARY_FACTORY;
63 private static final Method GET_EVICTION_TARGET;
64
65 static {
66 try {
67 PRIMARY_FACTORY = CompoundStore.class.getDeclaredField("primary");
68 PRIMARY_FACTORY.setAccessible(true);
69 GET_EVICTION_TARGET = CapacityLimitedInMemoryFactory.class.getDeclaredMethod("getEvictionTarget", Object.class, Integer.TYPE);
70 GET_EVICTION_TARGET.setAccessible(true);
71 } catch (SecurityException e) {
72 throw new RuntimeException(e);
73 } catch (NoSuchFieldException e) {
74 throw new RuntimeException(e);
75 } catch (NoSuchMethodException e) {
76 throw new RuntimeException(e);
77 }
78 }
79
80 /***
81 * setup test
82 */
83 @Override
84 @Before
85 public void setUp() throws Exception {
86 super.setUp();
87 createMemoryOnlyStore(MemoryStoreEvictionPolicy.LFU);
88 }
89
90
91 /***
92 * Check no NPE on get
93 */
94 @Override
95 @Test
96 public void testNullGet() throws IOException {
97 assertNull(store.get(null));
98 }
99
100 /***
101 * Check no NPE on remove
102 */
103 @Override
104 @Test
105 public void testNullRemove() throws IOException {
106 assertNull(store.remove(null));
107 }
108
109 /***
110 * Tests the put by reading the config file
111 */
112 @Test
113 public void testPutFromConfigZeroMemoryStore() throws Exception {
114 createMemoryStore(AbstractCacheTest.TEST_CONFIG_DIR + "ehcache-policy-test.xml", "sampleLFUCache2");
115 Element element = new Element("1", "value");
116 store.put(element);
117 assertNotNull(store.get("1"));
118 }
119
120 /***
121 * Tests the remove() method by using the parameters specified in the config file
122 */
123 @Test
124 public void testRemoveFromConfig() throws Exception {
125 createMemoryStore(AbstractCacheTest.TEST_CONFIG_DIR + "ehcache-policy-test.xml", "sampleLFUCache1");
126 removeTest();
127 }
128
129
130 /***
131 * Tests the LFU policy
132 */
133 @Test
134 public void testLfuPolicy() throws Exception {
135 createMemoryOnlyStore(MemoryStoreEvictionPolicy.LFU, 4);
136 lfuPolicyTest();
137 }
138
139 /***
140 * Tests the LFU policy by using the parameters specified in the config file
141 */
142 @Test
143 public void testLfuPolicyFromConfig() throws Exception {
144 createMemoryStore(AbstractCacheTest.TEST_CONFIG_DIR + "ehcache-policy-test.xml", "sampleLFUCache1");
145 lfuPolicyTest();
146 }
147
148
149 private void lfuPolicyTest() throws IOException, InterruptedException {
150
151 assertEquals(0, cache.getSize());
152
153
154 Element element = new Element("key1", "value1");
155 cache.put(element);
156 assertEquals(1, store.getInMemorySize());
157
158 element = new Element("key2", "value2");
159 cache.put(element);
160 assertEquals(2, store.getInMemorySize());
161
162 element = new Element("key3", "value3");
163 cache.put(element);
164 assertEquals(3, store.getInMemorySize());
165
166 element = new Element("key4", "value4");
167 cache.put(element);
168 assertEquals(4, store.getInMemorySize());
169
170
171 cache.get("key1");
172 cache.get("key1");
173 cache.get("key3");
174 cache.get("key3");
175 cache.get("key3");
176 cache.get("key4");
177
178
179 element = new Element("key5", "value5");
180 cache.put(element);
181
182 Thread.sleep(200);
183
184 assertEquals(4, store.getInMemorySize());
185
186
187 assertFalse(((CompoundStore) store).unretrievedGet("key2") instanceof Element);
188
189
190 cache.get("key5");
191 cache.get("key5");
192
193
194 element = new Element("key6", "value6");
195 cache.put(element);
196
197 Thread.sleep(200);
198
199 assertEquals(4, store.getInMemorySize());
200 assertFalse(((CompoundStore) store).unretrievedGet("key2") instanceof Element);
201 }
202
203
204 /***
205 * Multi-thread read, put and removeAll test.
206 * This checks for memory leaks
207 * using the removeAll which was the known cause of memory leaks with LruMemoryStore in JCS
208 * new sampling LFU has no leaks
209 */
210 @Override
211 @Test
212 public void testMemoryLeak() throws Exception {
213 super.testMemoryLeak();
214 }
215
216 /***
217 * Tests how random the java.util.Map iteration is by measuring the differences in iterate order.
218 * <p/>
219 * If iterate was ordered in either insert or reverse insert order the mean difference would be 1.
220 * Using Random gives a mean difference of 343.
221 * The observed value is 75, always 75 for a key set of 500 because it always iterates in the same order,
222 * just not an obvious order.
223 * <p/>
224 * Conclusion: Unable to use the iterator as a pseudorandom selector.
225 */
226 @Test
227 public void testRandomnessOfIterator() {
228 int mean = 0;
229 int absoluteDifferences = 0;
230 int lastReading = 0;
231 Map map = new ConcurrentHashMap();
232 for (int i = 1; i <= 500; i++) {
233 mean += i;
234 map.put("" + i, " ");
235 }
236 mean = mean / 500;
237 for (Iterator iterator = map.keySet().iterator(); iterator.hasNext();) {
238 String string = (String) iterator.next();
239 int thisReading = Integer.parseInt(string);
240 LOG.info("reading: " + thisReading);
241 absoluteDifferences += Math.abs(lastReading - thisReading);
242 lastReading = thisReading;
243 }
244 LOG.debug("Mean difference through iteration: " + absoluteDifferences / 500);
245
246
247 Random random = new Random();
248 while (map.size() != 0) {
249 int thisReading = random.nextInt(501);
250 Object o = map.remove("" + thisReading);
251 if (o == null) {
252 continue;
253 }
254 absoluteDifferences += Math.abs(lastReading - thisReading);
255 lastReading = thisReading;
256 }
257 LOG.info("Mean difference with random selection without replacement : " + absoluteDifferences / 500);
258 LOG.info("Mean of range 1 - 500 : " + mean);
259
260 }
261
262
263 private static final ElementSubstituteFilter<Element> IDENTITY_FILTER = new ElementSubstituteFilter<Element>() {
264 public boolean allows(Object object) {
265 return object instanceof Element;
266 }
267 };
268
269 /***
270 * Check nothing breaks and that we get the right number of samples
271 *
272 * @throws IOException
273 */
274 @Test
275 public void testSampling() throws IOException {
276 createMemoryOnlyStore(MemoryStoreEvictionPolicy.LFU, 1000);
277 List<Element> elements = null;
278 for (int i = 0; i < 10; i++) {
279 store.put(new Element("" + i, new Date()));
280 elements = ((CompoundStore) store).getRandomSample(IDENTITY_FILTER, i + 1, new Object());
281 }
282
283 for (int i = 10; i < 2000; i++) {
284 store.put(new Element("" + i, new Date()));
285 elements = ((CompoundStore) store).getRandomSample(IDENTITY_FILTER, 10, new Object());
286 assertTrue(elements.size() >= 10);
287 }
288 }
289
290
291 /***
292 * Can we deal with NonSerializable objects?
293 */
294 @Test
295 public void testNonSerializable() {
296 /***
297 * Non-serializable test class
298 */
299 class NonSerializable {
300
301 }
302 NonSerializable key = new NonSerializable();
303 store.put(new Element(key, new NonSerializable()));
304 store.get(key);
305 }
306
307
308 /***
309 * Test which reproduced an issue with flushing of an LFU store to disk on shutdown
310 */
311 @Test
312 public void testPersistLFUMemoryStore() {
313 manager.shutdown();
314 CacheManager cacheManager = new CacheManager(AbstractCacheTest.TEST_CONFIG_DIR + "ehcache-policy-test.xml");
315 Cache cache = cacheManager.getCache("test-cache");
316
317 getTestBean(cache, "test1");
318 getTestBean(cache, "test2");
319 getTestBean(cache, "test1");
320 getTestBean(cache, "test1");
321 getTestBean(cache, "test3");
322 getTestBean(cache, "test3");
323 getTestBean(cache, "test4");
324 getTestBean(cache, "test2");
325
326 Statistics stats = cache.getStatistics();
327 LOG.info(stats.toString());
328
329 cacheManager.shutdown();
330 }
331
332 private TestBean getTestBean(Cache cache, String key) {
333 Element element = cache.get(key);
334 if (element == null) {
335 element = new Element(key, new TestBean(key + "_value"));
336 cache.put(element);
337 }
338 return (TestBean) element.getValue();
339 }
340
341
342 /***
343 * A simple persistent JavaBean
344 *
345 * @author <a href="mailto:gluck@gregluck.com">Greg Luck</a>
346 * @version $Id: LfuMemoryStoreTest.html 13146 2011-08-01 17:12:39Z oletizi $
347 */
348 private final class TestBean implements Serializable {
349
350 private String string;
351
352 private TestBean() {
353
354 }
355
356 /***
357 * Constructor
358 *
359 * @param string
360 */
361 private TestBean(String string) {
362 this.string = string;
363 }
364 }
365
366
367 }
368
369
370