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