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        }