1   package org.apache.jcs.auxiliary.disk.indexed;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  
24  import junit.framework.TestCase;
25  
26  import org.apache.jcs.engine.CacheElement;
27  import org.apache.jcs.engine.ElementAttributes;
28  import org.apache.jcs.engine.behavior.ICacheElement;
29  import org.apache.jcs.engine.behavior.IElementAttributes;
30  import org.apache.jcs.engine.control.group.GroupAttrName;
31  import org.apache.jcs.engine.control.group.GroupId;
32  
33  /***
34   * Tests for common functionality.
35   * <p>
36   * @author Aaron Smuts
37   */
38  public class IndexDiskCacheUnitTest
39      extends TestCase
40  {
41      /***
42       * Simply verify that we can put items in the disk cache and retrieve them.
43       */
44      public void testSimplePutAndGet()
45      {
46          IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
47          cattr.setCacheName( "testSimplePutAndGet" );
48          cattr.setMaxKeySize( 1000 );
49          cattr.setDiskPath( "target/test-sandbox/IndexDiskCacheUnitTest" );
50          IndexedDiskCache disk = new IndexedDiskCache( cattr );
51  
52          disk.doRemoveAll();
53  
54          int cnt = 999;
55          for ( int i = 0; i < cnt; i++ )
56          {
57              IElementAttributes eAttr = new ElementAttributes();
58              eAttr.setIsSpool( true );
59              ICacheElement element = new CacheElement( "testSimplePutAndGet", "key:" + i, "data:" + i );
60              element.setElementAttributes( eAttr );
61              disk.doUpdate( element );
62          }
63  
64          for ( int i = 0; i < cnt; i++ )
65          {
66              ICacheElement element = disk.doGet( "key:" + i );
67              assertNotNull( "Should have recevied an element.", element );
68              assertEquals( "Element is wrong.", "data:" + i, element.getVal() );
69          }
70  
71          System.out.println( disk.getStats() );
72      }
73  
74      /***
75       * Add some items to the disk cache and then remove them one by one.
76       */
77      public void testRemoveItems()
78      {
79          IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
80          cattr.setCacheName( "testRemoveItems" );
81          cattr.setMaxKeySize( 100 );
82          cattr.setDiskPath( "target/test-sandbox/IndexDiskCacheUnitTest" );
83          IndexedDiskCache disk = new IndexedDiskCache( cattr );
84  
85          disk.doRemoveAll();
86  
87          int cnt = 25;
88          for ( int i = 0; i < cnt; i++ )
89          {
90              IElementAttributes eAttr = new ElementAttributes();
91              eAttr.setIsSpool( true );
92              ICacheElement element = new CacheElement( "testRemoveItems", "key:" + i, "data:" + i );
93              element.setElementAttributes( eAttr );
94              disk.doUpdate( element );
95          }
96  
97          // remove each
98          for ( int i = 0; i < cnt; i++ )
99          {
100             disk.remove( "key:" + i );
101             ICacheElement element = disk.doGet( "key:" + i );
102             assertNull( "Should not have recevied an element.", element );
103         }
104     }
105 
106     /***
107      * Verify that we don't override the largest item.
108      */
109     public void testRecycleBin()
110     {
111         IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
112         cattr.setCacheName( "testRemoveItems" );
113         cattr.setMaxRecycleBinSize( 2 );
114         cattr.setOptimizeAtRemoveCount( 7 );
115         cattr.setMaxKeySize( 5 );
116         cattr.setMaxPurgatorySize( 0 );
117         cattr.setDiskPath( "target/test-sandbox/BreakIndexTest" );
118         IndexedDiskCache disk = new IndexedDiskCache( cattr );
119 
120         String[] test = { "a", "bb", "ccc", "dddd", "eeeee", "ffffff", "ggggggg", "hhhhhhhhh", "iiiiiiiiii" };
121         String[] expect = { null, "bb", "ccc", null, null, "ffffff", null, "hhhhhhhhh", "iiiiiiiiii" };
122 
123         System.out.println( "------------------------- testRecycleBin " );
124 
125         for ( int i = 0; i < 6; i++ )
126         {
127             ICacheElement element = new CacheElement( "testRecycleBin", "key:" + test[i], test[i] );
128             System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
129             disk.doUpdate( element );
130         }
131 
132         for ( int i = 3; i < 5; i++ )
133         {
134             System.out.println( "About to remove " + "key:" + test[i] + " i = " + i );
135             disk.remove( "key:" + test[i] );
136         }
137 
138         // there was a bug where 7 would try to be put in the empty slot left by 4's removal, but it
139         // will not fit.
140         for ( int i = 7; i < 9; i++ )
141         {
142             ICacheElement element = new CacheElement( "testRecycleBin", "key:" + test[i], test[i] );
143             System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
144             disk.doUpdate( element );
145         }
146 
147         try
148         {
149             for ( int i = 0; i < 9; i++ )
150             {
151                 ICacheElement element = disk.get( "key:" + test[i] );
152                 if ( element != null )
153                 {
154                     System.out.println( "element = " + element.getVal() );
155                 }
156                 else
157                 {
158                     System.out.println( "null --" + "key:" + test[i] );
159                 }
160 
161                 String expectedValue = expect[i];
162                 if ( expectedValue == null )
163                 {
164                     assertNull( "Expected a null element", element );
165                 }
166                 else
167                 {
168                     assertNotNull( "The element for key [" + "key:" + test[i] + "] should not be null. i = " + i,
169                                    element );
170                     assertEquals( "Elements contents do not match expected", element.getVal(), expectedValue );
171                 }
172             }
173         }
174         catch ( Exception e )
175         {
176             e.printStackTrace();
177             fail( "Should not get an exception: " + e.toString() );
178         }
179 
180         disk.removeAll();
181     }
182 
183     /***
184      * Verify that the overlap check returns true when there are no overlaps.
185      */
186     public void testCheckForDedOverlaps_noOverlap()
187     {
188         // SETUP
189         IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
190         cattr.setCacheName( "testCheckForDedOverlaps_noOverlap" );
191         cattr.setDiskPath( "target/test-sandbox/UnitTest" );
192         IndexedDiskCache disk = new IndexedDiskCache( cattr );
193 
194         int numDescriptors = 5;
195         int pos = 0;
196         IndexedDiskElementDescriptor[] sortedDescriptors = new IndexedDiskElementDescriptor[numDescriptors];
197         for ( int i = 0; i < numDescriptors; i++ )
198         {
199             IndexedDiskElementDescriptor descriptor = new IndexedDiskElementDescriptor( pos, i * 2 );
200             pos = pos + ( i * 2 ) + IndexedDisk.RECORD_HEADER;
201             sortedDescriptors[i] = descriptor;
202         }
203 
204         // DO WORK
205         boolean result = disk.checkForDedOverlaps( sortedDescriptors );
206 
207         // VERIFY
208         assertTrue( "There should be no overlap. it should be ok", result );
209     }
210 
211     /***
212      * Verify that the overlap check returns false when there are overlaps.
213      */
214     public void testCheckForDedOverlaps_overlaps()
215     {
216         // SETUP
217         IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
218         cattr.setCacheName( "testCheckForDedOverlaps_overlaps" );
219         cattr.setDiskPath( "target/test-sandbox/UnitTest" );
220         IndexedDiskCache disk = new IndexedDiskCache( cattr );
221 
222         int numDescriptors = 5;
223         int pos = 0;
224         IndexedDiskElementDescriptor[] sortedDescriptors = new IndexedDiskElementDescriptor[numDescriptors];
225         for ( int i = 0; i < numDescriptors; i++ )
226         {
227             IndexedDiskElementDescriptor descriptor = new IndexedDiskElementDescriptor( pos, i * 2 );
228             // don't add the header + IndexedDisk.RECORD_HEADER;
229             pos = pos + ( i * 2 );
230             sortedDescriptors[i] = descriptor;
231         }
232 
233         // DO WORK
234         boolean result = disk.checkForDedOverlaps( sortedDescriptors );
235 
236         // VERIFY
237         assertFalse( "There should be overlaps. it should be not ok", result );
238     }
239 
240     /***
241      * Verify that the file size is as expected.
242      * @throws IOException
243      * @throws InterruptedException
244      */
245     public void testFileSize()
246         throws IOException, InterruptedException
247     {
248         // SETUP
249         IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
250         cattr.setCacheName( "testFileSize" );
251         cattr.setDiskPath( "target/test-sandbox/UnitTest" );
252         IndexedDiskCache disk = new IndexedDiskCache( cattr );
253 
254         int numberToInsert = 20;
255         int bytes = 24;
256         ICacheElement[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects( numberToInsert, bytes, cattr
257             .getCacheName() );
258 
259         for ( int i = 0; i < elements.length; i++ )
260         {
261             disk.doUpdate( elements[i] );
262         }
263 
264         Thread.yield();
265         Thread.sleep( 100 );
266         Thread.yield();
267 
268         long expectedSize = DiskTestObjectUtil.totalSize( elements, numberToInsert );
269         long resultSize = disk.getDataFileSize();
270 
271         System.out.println( "testFileSize stats " + disk.getStats() );
272 
273         assertEquals( "Wrong file size", expectedSize, resultSize );
274     }
275 
276     /***
277      * Verify that items are added to the recyle bin on removal.
278      * <p>
279      * @throws IOException
280      * @throws InterruptedException
281      */
282     public void testRecyleBinSize()
283         throws IOException, InterruptedException
284     {
285         // SETUP
286         int numberToInsert = 20;
287 
288         IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
289         cattr.setCacheName( "testRecyleBinSize" );
290         cattr.setDiskPath( "target/test-sandbox/UnitTest" );
291         cattr.setMaxRecycleBinSize( numberToInsert );
292         cattr.setOptimizeAtRemoveCount( numberToInsert );
293         cattr.setMaxKeySize( numberToInsert * 2 );
294         cattr.setMaxPurgatorySize( numberToInsert );
295         IndexedDiskCache disk = new IndexedDiskCache( cattr );
296 
297         int bytes = 24;
298         ICacheElement[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects( numberToInsert, bytes, cattr
299             .getCacheName() );
300 
301         for ( int i = 0; i < elements.length; i++ )
302         {
303             disk.doUpdate( elements[i] );
304         }
305 
306         Thread.yield();
307         Thread.sleep( 100 );
308         Thread.yield();
309 
310         // remove half
311         int numberToRemove = elements.length / 2;
312         for ( int i = 0; i < numberToRemove; i++ )
313         {
314             disk.doRemove( elements[i].getKey() );
315         }
316 
317         // verify that the recyle bin has the correct amount.
318         assertEquals( "The recycle bin should have the number removed.", numberToRemove, disk.getRecyleBinSize() );
319     }
320 
321     /***
322      * Verify that items of the same size use recyle bin spots. Setup the receyle bin by removing
323      * some items. Add some of the same size. Verify that the recyle count is the number added.
324      * <p>
325      * @throws IOException
326      * @throws InterruptedException
327      */
328     public void testRecyleBinUsage()
329         throws IOException, InterruptedException
330     {
331         // SETUP
332         int numberToInsert = 20;
333 
334         IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
335         cattr.setCacheName( "testRecyleBinUsage" );
336         cattr.setDiskPath( "target/test-sandbox/UnitTest" );
337         cattr.setMaxRecycleBinSize( numberToInsert );
338         cattr.setOptimizeAtRemoveCount( numberToInsert );
339         cattr.setMaxKeySize( numberToInsert * 2 );
340         cattr.setMaxPurgatorySize( numberToInsert );
341         IndexedDiskCache disk = new IndexedDiskCache( cattr );
342 
343         // we will reuse these
344         int bytes = 24;
345         ICacheElement[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects( numberToInsert, bytes, cattr
346             .getCacheName() );
347 
348         // Add some to the disk
349         for ( int i = 0; i < elements.length; i++ )
350         {
351             disk.doUpdate( elements[i] );
352         }
353 
354         Thread.yield();
355         Thread.sleep( 100 );
356         Thread.yield();
357 
358         // remove half of those added
359         int numberToRemove = elements.length / 2;
360         for ( int i = 0; i < numberToRemove; i++ )
361         {
362             disk.doRemove( elements[i].getKey() );
363         }
364 
365         // verify that the recyle bin has the correct amount.
366         assertEquals( "The recycle bin should have the number removed.", numberToRemove, disk.getRecyleBinSize() );
367 
368         // add half as many as we removed. These should all use spots in the recycle bin.
369         int numberToAdd = numberToRemove / 2;
370         for ( int i = 0; i < numberToAdd; i++ )
371         {
372             disk.doUpdate( elements[i] );
373         }
374 
375         // verify that we used the correct number of spots
376         assertEquals( "The recycle bin should have the number removed." + disk.getStats(), numberToAdd, disk
377             .getRecyleCount() );
378     }
379 
380     /***
381      * Verify that the data size is as expected after a remove and after a put that should use the
382      * spots.
383      * <p>
384      * @throws IOException
385      * @throws InterruptedException
386      */
387     public void testBytesFreeSize()
388         throws IOException, InterruptedException
389     {
390         // SETUP
391         IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
392         cattr.setCacheName( "testBytesFreeSize" );
393         cattr.setDiskPath( "target/test-sandbox/UnitTest" );
394         IndexedDiskCache disk = new IndexedDiskCache( cattr );
395 
396         int numberToInsert = 20;
397         int bytes = 24;
398         ICacheElement[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects( numberToInsert, bytes, cattr
399             .getCacheName() );
400 
401         for ( int i = 0; i < elements.length; i++ )
402         {
403             disk.doUpdate( elements[i] );
404         }
405 
406         Thread.yield();
407         Thread.sleep( 100 );
408         Thread.yield();
409 
410         // remove half of those added
411         int numberToRemove = elements.length / 2;
412         for ( int i = 0; i < numberToRemove; i++ )
413         {
414             disk.doRemove( elements[i].getKey() );
415         }
416 
417         long expectedSize = DiskTestObjectUtil.totalSize( elements, numberToRemove );
418         long resultSize = disk.getBytesFree();
419 
420         System.out.println( "testBytesFreeSize stats " + disk.getStats() );
421 
422         assertEquals( "Wrong bytes free size" + disk.getStats(), expectedSize, resultSize );
423 
424         // add half as many as we removed. These should all use spots in the recycle bin.
425         int numberToAdd = numberToRemove / 2;
426         for ( int i = 0; i < numberToAdd; i++ )
427         {
428             disk.doUpdate( elements[i] );
429         }
430 
431         long expectedSize2 = DiskTestObjectUtil.totalSize( elements, numberToAdd );
432         long resultSize2 = disk.getBytesFree();
433         assertEquals( "Wrong bytes free size" + disk.getStats(), expectedSize2, resultSize2 );
434     }
435 
436     /***
437      * Add some items to the disk cache and then remove them one by one.
438      */
439     public void testRemove_PartialKey()
440     {
441         IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
442         cattr.setCacheName( "testRemove_PartialKey" );
443         cattr.setMaxKeySize( 100 );
444         cattr.setDiskPath( "target/test-sandbox/IndexDiskCacheUnitTest" );
445         IndexedDiskCache disk = new IndexedDiskCache( cattr );
446 
447         disk.doRemoveAll();
448 
449         int cnt = 25;
450         for ( int i = 0; i < cnt; i++ )
451         {
452             IElementAttributes eAttr = new ElementAttributes();
453             eAttr.setIsSpool( true );
454             ICacheElement element = new CacheElement( "testRemove_PartialKey", i + ":key", "data:" + i );
455             element.setElementAttributes( eAttr );
456             disk.doUpdate( element );
457         }
458 
459         // verif each
460         for ( int i = 0; i < cnt; i++ )
461         {
462             ICacheElement element = disk.doGet( i + ":key" );
463             assertNotNull( "Shoulds have recevied an element.", element );
464         }
465 
466         // remove each
467         for ( int i = 0; i < cnt; i++ )
468         {
469             disk.remove( i + ":" );
470             ICacheElement element = disk.doGet( i + ":key" );
471             assertNull( "Should not have recevied an element.", element );
472         }
473     }
474 
475     /***
476      * Verify that group members are removed if we call remove with a group.
477      */
478     public void testRemove_Group()
479     {
480         // SETUP
481         IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
482         cattr.setCacheName( "testRemove_Group" );
483         cattr.setMaxKeySize( 100 );
484         cattr.setDiskPath( "target/test-sandbox/IndexDiskCacheUnitTest" );
485         IndexedDiskCache disk = new IndexedDiskCache( cattr );
486 
487         disk.doRemoveAll();
488 
489         String cacheName = "testRemove_Group_Region";
490         String groupName = "testRemove_Group";
491 
492         int cnt = 25;
493         for ( int i = 0; i < cnt; i++ )
494         {
495             GroupAttrName groupAttrName = getGroupAttrName( cacheName, groupName, i + ":key" );
496 
497             CacheElement element = new CacheElement( cacheName, groupAttrName, "data:" + i );
498 
499             IElementAttributes eAttr = new ElementAttributes();
500             eAttr.setIsSpool( true );
501             element.setElementAttributes( eAttr );
502 
503             disk.doUpdate( element );
504         }
505 
506         // verify each
507         for ( int i = 0; i < cnt; i++ )
508         {
509             GroupAttrName groupAttrName = getGroupAttrName( cacheName, groupName, i + ":key" );
510             ICacheElement element = disk.doGet( groupAttrName );
511             assertNotNull( "Should have recevied an element.", element );
512         }
513 
514         // DO WORK
515         // remove the group
516         GroupId gid = new GroupId( cacheName, groupName );
517         disk.remove( gid );
518 
519         for ( int i = 0; i < cnt; i++ )
520         {
521             GroupAttrName groupAttrName = getGroupAttrName( cacheName, groupName, i + ":key" );
522             ICacheElement element = disk.doGet( groupAttrName );
523 
524             // VERIFY
525             assertNull( "Should not have recevied an element.", element );
526         }
527 
528     }
529 
530     /***
531      * Internal method used for group functionality.
532      * <p>
533      * @param cacheName
534      * @param group
535      * @param name
536      * @return GroupAttrName
537      */
538     private GroupAttrName getGroupAttrName( String cacheName, String group, Object name )
539     {
540         GroupId gid = new GroupId( cacheName, group );
541         return new GroupAttrName( gid, name );
542     }
543 }