001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029
030 /*
031 * Created by IntelliJ IDEA.
032 * User: Administrator
033 * Date: 28-Dec-02
034 * Time: 22:24:51
035 */
036 package org.hd.d.pg2k.test.dev;
037
038 import java.awt.Dimension;
039 import java.util.Random;
040 import java.util.concurrent.atomic.AtomicBoolean;
041
042 import junit.framework.TestCase;
043
044 import org.hd.d.pg2k.svrCore.CS8Bit;
045 import org.hd.d.pg2k.svrCore.MemoryTools;
046 import org.hd.d.pg2k.svrCore.ROByteArray;
047 import org.hd.d.pg2k.svrCore.collections.SimpleLRUMap;
048 import org.hd.d.pg2k.svrCore.collections.LRUMapAutoSizeForHitRate;
049 import org.hd.d.pg2k.svrCore.collections.SimpleProbabilisticCache;
050
051 /**Tests of the memory tools.
052 */
053 public final class MemoryToolsTest extends TestCase
054 {
055 public MemoryToolsTest(final String name)
056 {
057 super(name);
058 }
059
060 /**Tests some interactions with the GC system.
061 * At least attempts to show that the interactions are not immediately toxic.
062 */
063 public static void testBasicGCInteractions()
064 {
065 MemoryTools.preemptiveGC();
066
067 // Register a handle and have opportunity to do some incremental housekeeping too...
068 MemoryTools.registerEmergencyFreeHandle(new Runnable(){
069 public void run() { Main.getOut().println("Emergency-free handler 1 invoked..."); }
070 });
071 MemoryTools.registerRecurrentEmergencyFreeHandle(new MemoryTools.RecurrentEmergencyFreeHandle(){
072 public void run() { Main.getOut().println("Emergency-free handler 2 invoked..."); }
073 });
074
075 // Opportunity to invoke some of the memory-trimming routines.
076 MemoryTools.isMemoryStressed();
077 }
078
079 /**Simple test of runMemoryIntensiveOperation().
080 * Tests that:
081 * <ul>
082 * <li>Tasks are always run if we claim to have unlimited memory.
083 * <li>Tasks are always run if we require very limited memory.
084 * <li>Tasks are never run if we demand enormous memory and memory is
085 * not unlimited.
086 * </ul>
087 * <p>
088 * TODO: add test that we can run these concurrently without difficulty.
089 */
090 public void testRunMemoryIntensiveOperation()
091 {
092 // Our "small" memory requirement.
093 final int smallMem = 1;
094
095 // Our "huge" memory requirement.
096 final int hugeMem = Integer.MAX_VALUE;
097
098 // Small task that sets a flag when invoked.
099 final class SmallTask implements Runnable
100 {
101 public boolean done; // Initially false.
102 public void run() { done = true; }
103 }
104
105 // Run the tasks a few times to make sure that behaviour
106 // remains correct even during intensive use.
107 for(int i = 1000; --i >= 0; )
108 {
109 final SmallTask t1 = new SmallTask();
110 assertTrue("With unlimited memory, runMemoryIntensiveOperation() should return true",
111 MemoryTools.runMemoryIntensiveOperation(t1, true, hugeMem));
112 assertTrue("With unlimited memory, runMemoryIntensiveOperation() should run the task",
113 t1.done);
114
115 final SmallTask t2 = new SmallTask();
116 assertTrue("With limited memory and a huge task, runMemoryIntensiveOperation() should return false",
117 !MemoryTools.runMemoryIntensiveOperation(t2, false, hugeMem));
118 assertTrue("With limited memory and a huge task, runMemoryIntensiveOperation() should not run the task",
119 !t2.done);
120
121 final SmallTask t3 = new SmallTask();
122 assertTrue("With limited memory and a small task, runMemoryIntensiveOperation() should return true",
123 MemoryTools.runMemoryIntensiveOperation(t3, false, smallMem));
124 assertTrue("With limited memory and a small task, runMemoryIntensiveOperation() should run the task",
125 t3.done);
126 }
127 }
128
129 /**Test our intern() for various supported data types.
130 * This also tests refusal to intern various mutable data types.
131 */
132 public void testIntern()
133 {
134 // Check that MemoryTools.intern() works correctly for String.
135 final String s1 = "abc";
136 final String s2 = new String("abc");
137 assertEquals("s1.equals(s2) should be true", s1, s2);
138 assertNotSame("Should be same value but different instances.", s1, s2);
139 assertSame("intern()ing String should give same instance.", MemoryTools.intern(s1), MemoryTools.intern(s2));
140
141 // Check that MemoryTools.intern() works correctly for ROByteArray.
142 final ROByteArray roba1 = ROByteArray.compressFromString("the quick brown fox");
143 final ROByteArray roba2 = ROByteArray.compressFromString("the quick brown fox");
144 assertEquals("roba1.equals(roba2) should be true", roba1, roba2);
145 assertNotSame("Should be same value but different instances.", roba1, roba2);
146 assertSame("intern()ing ROByteArray should give same instance.", MemoryTools.intern(roba1), MemoryTools.intern(roba2));
147
148
149
150
151 // TODO: Test for Boolean and other supported types/ranges.
152
153
154
155
156 // Check that we don't attempt to intern() obvious things that we should not!
157 assertNotSame("Should not intern mutable objects (eg arrays)",
158 MemoryTools.intern(new byte[100]), MemoryTools.intern(new byte[100]));
159 assertNotSame("Should not intern mutable objects (eg Dimension)",
160 MemoryTools.intern(new Dimension()), MemoryTools.intern(new Dimension()));
161
162
163 // Simple stress-test of intern() that it does not obviously fail to free its unused values.
164 // We spend a few seconds churning as much as we can
165 // with items that we might actually intern() in real life.
166 final long stopBy = System.currentTimeMillis() + 9999;
167 final byte buf[] = new byte[16];
168 int count = 0;
169 while(System.currentTimeMillis() <= stopBy)
170 {
171 MemoryTools.intern(String.valueOf(rnd.nextLong()));
172 MemoryTools.intern(new Long(rnd.nextLong()));
173 rnd.nextBytes(buf);
174 assertSame("intern(ROByteArray) failed",
175 MemoryTools.intern(new ROByteArray(buf)),
176 MemoryTools.intern(new ROByteArray(buf)));
177 ++count;
178 }
179 System.out.println("Survived allocation and intern() of "+(4*count)+" new items.");
180 }
181
182 /**Test behaviour of SimpleLRUMapAutoSizeForHitRate. */
183 public static void testSimpleLRUMapAutoSizeForHitRate()
184 {
185 // Simply make sure that some simple reasonable values are possible.
186 LRUMapAutoSizeForHitRate.<String,String>create();
187 LRUMapAutoSizeForHitRate.<String,String>create(0.1f, 101, null);
188 LRUMapAutoSizeForHitRate.<String,String>create(0.01f, 101, null);
189 LRUMapAutoSizeForHitRate.<String,String>create(0.001f, 101, null);
190 LRUMapAutoSizeForHitRate.<String,String>create(0.1f, 101, 10001, 0.75f, null);
191 }
192
193 /**Test behaviour of SimpleProbabilisticCache. */
194 public static void testSimpleProbabilisticCache()
195 {
196 // Simply make sure that some simple reasonable values are possible.
197 final SimpleProbabilisticCache<Object,Object> smallCache = SimpleProbabilisticCache.<Object,Object>create(31, "test");
198 smallCache.trimCapacity();
199 smallCache.compact();
200
201 // Check that even for a large number of inserts into a small cache
202 // we can always get out what we just put in
203 // (as long as we lock out compaction calls).
204 for(int i = 512; --i > 0; )
205 {
206 final Integer key = Integer.valueOf(i + rnd.nextInt(13));
207 final Object value = new Integer(rnd.nextInt());
208 synchronized(smallCache)
209 {
210 smallCache.put(key, value);
211 assertEquals(value, smallCache.get(new Integer(key.intValue()))); // Ensure not ==
212 }
213 //Main.getOut().println(smallCache);
214 }
215
216 // Just make sure that basic calls don't break anything.
217 smallCache.trimCapacity();
218 smallCache.compact();
219
220 // TODO
221 }
222
223 /**Test behaviour of SimpleProbabilisticCache with CS8Bit keys. */
224 public static void testSimpleProbabilisticCacheCS8BitKeys()
225 {
226 // Simply make sure that some simple reasonable values are possible.
227 final SimpleProbabilisticCache<CS8Bit,Object> smallCache = SimpleProbabilisticCache.<CS8Bit,Object>create(31, "test");
228 smallCache.trimCapacity();
229 smallCache.compact();
230
231 // Check that even for a large number of inserts into a small cache
232 // we can always get out what we just put in
233 // (as long as we lock out compaction calls).
234 for(int i = 512; --i > 0; )
235 {
236 final CS8Bit key = new CS8Bit("key" + (i + rnd.nextInt(13)));
237 final Object value = new Integer(rnd.nextInt());
238 synchronized(smallCache)
239 {
240 smallCache.put(key, value);
241 assertEquals(value, smallCache.get(new CS8Bit(key))); // Ensure not ==
242 }
243 }
244
245 // Just make sure that basic calls don't break anything.
246 smallCache.trimCapacity();
247 smallCache.compact();
248
249 // TODO
250 }
251
252 /**Confirm correct AutoExpirable behaviour with deterministic CacheMiniMap implementation.
253 * @param m map instance to test; non-null, capacity at least 2
254 */
255 private static final void testAutoExpirableWithDeterministicCacheMiniMap(final MemoryTools.CacheMiniMap<Integer, Object> m)
256 {
257 // Putting a small number of normal entries in a SimpleLRUMap should retain them all.
258 m.clear();
259 assertTrue(m.size() == 0);
260 m.put(1, 11);
261 m.put(2, 12);
262 assertEquals(2, m.size());
263
264 // Putting even a small number of AutoExpirable entries in a SimpleLRUMap should see earlier ones evaporate.
265 m.clear();
266 assertTrue(m.size() == 0);
267 m.put(1, new MemoryTools.AutoExpirable() { @Override public boolean hasExpired() { return(true); } });
268 assertEquals("dead AutoExpirable should have evaporated", 0, m.size());
269 assertNull("dead AutoExpirable should have evaporated", m.get(1));
270 m.put(2, 22);
271 assertEquals("dead AutoExpirable should have evaporated", 1, m.size());
272 assertNull("dead AutoExpirable should have evaporated", m.get(1));
273 assertEquals("non-AutoExpirable should not have evaporated", 22, m.get(2));
274
275 // Check something that expires after insertion...
276 m.clear();
277 assertTrue(m.size() == 0);
278 final AtomicBoolean exp = new AtomicBoolean();
279 m.put(1, new MemoryTools.AutoExpirable() { @Override public boolean hasExpired() { return(exp.get()); } });
280 assertEquals("not-yet-dead AutoExpirable should not yet have evaporated", 1, m.size());
281 assertNotNull("not-yet-dead AutoExpirable should not yet have evaporated", m.get(1));
282 exp.set(true);
283 m.put(2, 22);
284 assertEquals("dead AutoExpirable should have evaporated", 1, m.size());
285 assertNull("dead AutoExpirable should have evaporated", m.get(1));
286 assertEquals("non-AutoExpirable should not have evaporated", 22, m.get(2));
287 }
288
289 /**Test various aspects of the behaviour of AutoExpirable with MemoryTools containers. */
290 public static void testAutoExpirable()
291 {
292 testAutoExpirableWithDeterministicCacheMiniMap(SimpleLRUMap.<Integer, Object>create(null));
293 testAutoExpirableWithDeterministicCacheMiniMap(LRUMapAutoSizeForHitRate.<Integer, Object>create());
294 }
295
296
297 /**Private source of OK pseudo-random numbers. */
298 private static final Random rnd = new Random();
299 }