001    package org.apache.jcs.engine.memory.shrinking;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.io.IOException;
023    
024    import junit.framework.TestCase;
025    
026    import org.apache.jcs.engine.CacheElement;
027    import org.apache.jcs.engine.CompositeCacheAttributes;
028    import org.apache.jcs.engine.ElementAttributes;
029    import org.apache.jcs.engine.behavior.ICacheElement;
030    import org.apache.jcs.engine.control.event.ElementEventHandlerMockImpl;
031    import org.apache.jcs.engine.memory.MockMemoryCache;
032    
033    /**
034     * This tests the functionality of the shrinker thread.
035     * <p>
036     * @author Aaron Smuts
037     */
038    public class ShrinkerThreadUnitTest
039        extends TestCase
040    {
041        /** verify the check for removal
042         * <p>
043         * @throws IOException */
044        public void testCheckForRemoval_Expired() throws IOException
045        {
046            // SETUP
047            MockMemoryCache<String, String> memory = new MockMemoryCache<String, String>();
048            CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
049            cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
050            cacheAttr.setMaxSpoolPerRun( 10 );
051            memory.setCacheAttributes( cacheAttr );
052    
053            ShrinkerThread<String, String> shrinker = new ShrinkerThread<String, String>( memory );
054    
055            String key = "key";
056            String value = "value";
057    
058            ICacheElement<String, String> element = new CacheElement<String, String>( "testRegion", key, value );
059            ElementAttributes elementAttr = new ElementAttributes();
060            elementAttr.setIsEternal( false );
061            element.setElementAttributes( elementAttr );
062            element.getElementAttributes().setMaxLifeSeconds( 1 );
063    
064            long now = System.currentTimeMillis();
065            // add two seconds
066            now += 2000;
067    
068            // DO WORK
069            boolean result = shrinker.checkForRemoval( element, now );
070    
071            // VERIFY
072            assertTrue( "Item should have expired.", result );
073        }
074    
075        /** verify the check for removal
076         * <p>
077         * @throws IOException */
078        public void testCheckForRemoval_NotExpired() throws IOException
079        {
080            // SETUP
081            MockMemoryCache<String, String> memory = new MockMemoryCache<String, String>();
082            CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
083            cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
084            cacheAttr.setMaxSpoolPerRun( 10 );
085            memory.setCacheAttributes( cacheAttr );
086    
087            ShrinkerThread<String, String> shrinker = new ShrinkerThread<String, String>( memory );
088    
089            String key = "key";
090            String value = "value";
091    
092            ICacheElement<String, String> element = new CacheElement<String, String>( "testRegion", key, value );
093            ElementAttributes elementAttr = new ElementAttributes();
094            elementAttr.setIsEternal( false );
095            element.setElementAttributes( elementAttr );
096            element.getElementAttributes().setMaxLifeSeconds( 1 );
097    
098            long now = System.currentTimeMillis();
099            // subtract two seconds
100            now -= 2000;
101    
102            // DO WORK
103            boolean result = shrinker.checkForRemoval( element, now );
104    
105            // VERIFY
106            assertFalse( "Item should not have expired.", result );
107        }
108    
109        /** verify the check for removal
110         * <p>
111         * @throws IOException */
112        public void testCheckForRemoval_IdleTooLong() throws IOException
113        {
114            // SETUP
115            MockMemoryCache<String, String> memory = new MockMemoryCache<String, String>();
116            CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
117            cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
118            cacheAttr.setMaxSpoolPerRun( 10 );
119            memory.setCacheAttributes( cacheAttr );
120    
121            ShrinkerThread<String, String> shrinker = new ShrinkerThread<String, String>( memory );
122    
123            String key = "key";
124            String value = "value";
125    
126            ICacheElement<String, String> element = new CacheElement<String, String>( "testRegion", key, value );
127            ElementAttributes elementAttr = new ElementAttributes();
128            elementAttr.setIsEternal( false );
129            element.setElementAttributes( elementAttr );
130            element.getElementAttributes().setMaxLifeSeconds( 100 );
131            element.getElementAttributes().setIdleTime( 1 );
132    
133            long now = System.currentTimeMillis();
134            // add two seconds
135            now += 2000;
136    
137            // DO WORK
138            boolean result = shrinker.checkForRemoval( element, now );
139    
140            // VERIFY
141            assertTrue( "Item should have expired.", result );
142        }
143    
144        /** verify the check for removal
145         * <p>
146         * @throws IOException */
147        public void testCheckForRemoval_NotIdleTooLong() throws IOException
148        {
149            // SETUP
150            MockMemoryCache<String, String> memory = new MockMemoryCache<String, String>();
151            CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
152            cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
153            cacheAttr.setMaxSpoolPerRun( 10 );
154            memory.setCacheAttributes( cacheAttr );
155    
156            ShrinkerThread<String, String> shrinker = new ShrinkerThread<String, String>( memory );
157    
158            String key = "key";
159            String value = "value";
160    
161            ICacheElement<String, String> element = new CacheElement<String, String>( "testRegion", key, value );
162            ElementAttributes elementAttr = new ElementAttributes();
163            elementAttr.setIsEternal( false );
164            element.setElementAttributes( elementAttr );
165            element.getElementAttributes().setMaxLifeSeconds( 100 );
166            element.getElementAttributes().setIdleTime( 1 );
167    
168            long now = System.currentTimeMillis();
169            // subtract two seconds
170            now -= 2000;
171    
172            // DO WORK
173            boolean result = shrinker.checkForRemoval( element, now );
174    
175            // VERIFY
176            assertFalse( "Item should not have expired.", result );
177        }
178    
179        /**
180         * Setup cache attributes in mock. Create the shrinker with the mock. Add some elements into the
181         * mock memory cache see that they get spooled.
182         * <p>
183         * @throws Exception
184         */
185        public void testSimpleShrink()
186            throws Exception
187        {
188            // SETUP
189            MockMemoryCache<String, String> memory = new MockMemoryCache<String, String>();
190    
191            CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
192            cacheAttr.setMaxMemoryIdleTimeSeconds( 1 );
193            cacheAttr.setMaxSpoolPerRun( 10 );
194    
195            memory.setCacheAttributes( cacheAttr );
196    
197            String key = "key";
198            String value = "value";
199    
200            ICacheElement<String, String> element = new CacheElement<String, String>( "testRegion", key, value );
201    
202            ElementAttributes elementAttr = new ElementAttributes();
203            elementAttr.setIsEternal( false );
204            element.setElementAttributes( elementAttr );
205            element.getElementAttributes().setMaxLifeSeconds( 1 );
206            memory.update( element );
207    
208            ICacheElement<String, String> returnedElement1 = memory.get( key );
209            assertNotNull( "We should have received an element", returnedElement1 );
210    
211            // set this to 2 seconds ago.
212            elementAttr.lastAccessTime = System.currentTimeMillis() - 2000;
213    
214            // DO WORK
215            ShrinkerThread<String, String> shrinker = new ShrinkerThread<String, String>( memory );
216            Thread runner = new Thread( shrinker );
217            runner.run();
218    
219            Thread.sleep( 500 );
220    
221            // VERIFY
222            ICacheElement<String, String> returnedElement2 = memory.get( key );
223            assertTrue( "Waterfall should have been called.", memory.waterfallCallCount > 0 );
224            assertNull( "We not should have received an element.  It should have been spooled.", returnedElement2 );
225        }
226    
227        /**
228         * Add 10 to the memory cache. Set the spool per run limit to 3.
229         * <p>
230         * @throws Exception
231         */
232        public void testSimpleShrinkMultiple()
233            throws Exception
234        {
235            // SETUP
236            MockMemoryCache<String, String> memory = new MockMemoryCache<String, String>();
237    
238            CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
239            cacheAttr.setMaxMemoryIdleTimeSeconds( 1 );
240            cacheAttr.setMaxSpoolPerRun( 3 );
241    
242            memory.setCacheAttributes( cacheAttr );
243    
244            for ( int i = 0; i < 10; i++ )
245            {
246                String key = "key" + i;
247                String value = "value";
248    
249                ICacheElement<String, String> element = new CacheElement<String, String>( "testRegion", key, value );
250    
251                ElementAttributes elementAttr = new ElementAttributes();
252                elementAttr.setIsEternal( false );
253                element.setElementAttributes( elementAttr );
254                element.getElementAttributes().setMaxLifeSeconds( 1 );
255                memory.update( element );
256    
257                ICacheElement<String, String> returnedElement1 = memory.get( key );
258                assertNotNull( "We should have received an element", returnedElement1 );
259    
260                // set this to 2 seconds ago.
261                elementAttr.lastAccessTime = System.currentTimeMillis() - 2000;
262            }
263    
264            // DO WORK
265            ShrinkerThread<String, String> shrinker = new ShrinkerThread<String, String>( memory );
266            Thread runner = new Thread( shrinker );
267            runner.run();
268    
269            // VERIFY
270            Thread.sleep( 500 );
271            assertEquals( "Waterfall called the wrong number of times.", 3, memory.waterfallCallCount );
272            assertEquals( "Wrong number of elements remain.", 7, memory.getSize() );
273        }
274    
275        /**
276         * Add a mock event handler to the items. Verify that it gets called.
277         * <p>
278         * This is only testing the spooled background event
279         * <p>
280         * @throws Exception
281         */
282        public void testSimpleShrinkMultipleWithEventHandler()
283            throws Exception
284        {
285            // SETUP
286            MockMemoryCache<String, String> memory = new MockMemoryCache<String, String>();
287    
288            CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
289            cacheAttr.setMaxMemoryIdleTimeSeconds( 1 );
290            cacheAttr.setMaxSpoolPerRun( 3 );
291    
292            memory.setCacheAttributes( cacheAttr );
293    
294            ElementEventHandlerMockImpl handler = new ElementEventHandlerMockImpl();
295    
296            for ( int i = 0; i < 10; i++ )
297            {
298                String key = "key" + i;
299                String value = "value";
300    
301                ICacheElement<String, String> element = new CacheElement<String, String>( "testRegion", key, value );
302    
303                ElementAttributes elementAttr = new ElementAttributes();
304                elementAttr.addElementEventHandler( handler );
305                elementAttr.setIsEternal( false );
306                element.setElementAttributes( elementAttr );
307                element.getElementAttributes().setMaxLifeSeconds( 1 );
308                memory.update( element );
309    
310                ICacheElement<String, String> returnedElement1 = memory.get( key );
311                assertNotNull( "We should have received an element", returnedElement1 );
312    
313                // set this to 2 seconds ago.
314                elementAttr.lastAccessTime = System.currentTimeMillis() - 2000;
315            }
316    
317            // DO WORK
318            ShrinkerThread<String, String> shrinker = new ShrinkerThread<String, String>( memory );
319            Thread runner = new Thread( shrinker );
320            runner.run();
321    
322            // VERIFY
323            Thread.sleep( 500 );
324            assertEquals( "Waterfall called the wrong number of times.", 3, memory.waterfallCallCount );
325            // the shrinker delegates the the composite cache on the memory cache to put the
326            // event on the queue.  This make it hard to test.  TODO we need to change this to make it easier to verify.
327            //assertEquals( "Event handler ExceededIdleTimeBackground called the wrong number of times.", 3, handler.getExceededIdleTimeBackgroundCount() );
328            assertEquals( "Wrong number of elements remain.", 7, memory.getSize() );
329        }
330    }