001: /**
002: * JDBM LICENSE v1.00
003: *
004: * Redistribution and use of this software and associated documentation
005: * ("Software"), with or without modification, are permitted provided
006: * that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain copyright
009: * statements and notices. Redistributions must also contain a
010: * copy of this document.
011: *
012: * 2. Redistributions in binary form must reproduce the
013: * above copyright notice, this list of conditions and the
014: * following disclaimer in the documentation and/or other
015: * materials provided with the distribution.
016: *
017: * 3. The name "JDBM" must not be used to endorse or promote
018: * products derived from this Software without prior written
019: * permission of Cees de Groot. For written permission,
020: * please contact cg@cdegroot.com.
021: *
022: * 4. Products derived from this Software may not be called "JDBM"
023: * nor may "JDBM" appear in their names without prior written
024: * permission of Cees de Groot.
025: *
026: * 5. Due credit should be given to the JDBM Project
027: * (http://jdbm.sourceforge.net/).
028: *
029: * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
030: * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
031: * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
032: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
033: * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
034: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
036: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
037: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
038: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
039: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
040: * OF THE POSSIBILITY OF SUCH DAMAGE.
041: *
042: * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
043: * Contributions are Copyright (C) 2000 by their associated contributors.
044: *
045: */package jdbm.helper;
046:
047: import java.lang.ref.SoftReference;
048: import junit.framework.AssertionFailedError;
049:
050: /**
051: * Unit test for {@link SoftCache}.
052: *
053: * @author <a href="mailto:dranatunga@users.sourceforge.net">Dilum Ranatunga</a>
054: * @version $Id: TestSoftCache.java,v 1.1 2003/11/01 13:29:57 dranatunga Exp $
055: */
056: public class TestSoftCache extends TestCachePolicy {
057:
058: public TestSoftCache(String name) {
059: super (name);
060: }
061:
062: /**
063: * Tests {@link SoftCache#SoftCache()} ,
064: * {@link SoftCache#SoftCache(CachePolicy)},
065: * {@link SoftCache#SoftCache(float, CachePolicy)}.
066: */
067: public void testConstructor() {
068: { // test that null parameter fails
069: try {
070: new SoftCache(null);
071: fail("constructor will null should have failed.");
072: } catch (NullPointerException npe) {
073: }
074: try {
075: new SoftCache(null);
076: fail("constructor will null should have failed.");
077: new SoftCache(0.5f, null);
078: } catch (NullPointerException npx) {
079: }
080: }
081:
082: { // test that bad load factor fails.
083: try {
084: new SoftCache(0.0f, new MRU(5));
085: fail("zero load factor should fail");
086: } catch (IllegalArgumentException iax) {
087: }
088: }
089:
090: { // test correct instantiations
091: new SoftCache();
092: new SoftCache(new MRU(5));
093: new SoftCache(0.1f, new MRU(5));
094: new SoftCache(10.0f, new MRU(5));
095: }
096: }
097:
098: /**
099: * Ensures that {@link SoftCache#put(Object, Object)} operations are
100: * delegated to the internal cache.
101: */
102: public void testPutDelegation() throws CacheEvictionException {
103: final int capacity = 5;
104: final CachePolicy internal = new MRU(capacity);
105: final CachePolicy level2 = new SoftCache(internal);
106: final Object key = new Integer(0);
107: final Object value = "EXPECTED-VALUE";
108: level2.put(key, value);
109: assertSame("L2.get() did not return expected.", value, level2
110: .get(key));
111: assertSame("L1.get() did not return expected.", value, internal
112: .get(key));
113: causeEviction(level2, capacity);
114: assertNull(internal.get(key));
115: }
116:
117: /**
118: * Shows that a soft cache can recover an evicted object, and that
119: * this recovery is conditioned on soft references not having been
120: * cleared.
121: */
122: public void testL2Recovery() throws CacheEvictionException {
123: final int capacity = 5;
124: // since soft reference clears are not predictable, we use a indicator indicator
125: final SoftReference indicator = new SoftReference(
126: createLargeObject());
127: final CachePolicy internal = new MRU(capacity);
128: final CachePolicy level2 = new SoftCache(internal);
129:
130: for (int attempts = 100; attempts-- > 0;) {
131: // keep looping until we get to complete the whole test, or until.
132: // we've tried the number of attempts we're willing to make.
133:
134: boolean heuristicRedFlag = false;
135:
136: for (int i = 0; i <= capacity; ++i) { // note <=
137: level2.put("" + i, createLargeObject());
138: }
139: // look in least recently used: '0'
140: assertNull("'0' should have been evicted from internal",
141: internal.get("0"));
142: assertNotNull("'0' should still be accessible through L2",
143: level2.get("0"));
144: // this will cause '0' to get readded to internal:
145:
146: // this construct restarts the test if indicator
147: if (null == indicator.get()) {
148: heuristicRedFlag = true;
149: }
150: causeGarbageCollection();
151: if (null != indicator.get()) {
152: heuristicRedFlag = true;
153: }
154:
155: try {
156: // since '0' was readded, least recently used is '1'
157: assertNull(
158: "'1' should have been evicted from internal",
159: internal.get("1"));
160: assertNull(
161: "Soft references should have cleared during gc",
162: level2.get("1"));
163: // getting here means test successful. no need to make
164: // any further attempts.
165: return;
166: } catch (AssertionFailedError afe) {
167: // The test failed. But if we have a heuristic red flag,
168: // we should return. Otherwise, fail the test.
169: if (!heuristicRedFlag) {
170: throw afe;
171: }
172: }
173: }
174:
175: // getting here means we were unable to test the condition.
176: fail("Could not perform soft cache test to completion.");
177: }
178:
179: /**
180: * Tests {@link SoftCache#removeAll()}
181: */
182: public void testRemoveAll() throws CacheEvictionException {
183: final int capacity = 5;
184: final CachePolicy internal = new MRU(capacity);
185: final CachePolicy level2 = new SoftCache(internal);
186:
187: // add a bunch.
188: for (int i = 0; i < (capacity + 2); ++i) {
189: level2.put("" + i, createLargeObject());
190: }
191: // show that some are still in internal
192: int count = 0;
193: for (int i = 0; i < (capacity + 2); ++i) {
194: if (null != internal.get("" + i)) {
195: count++;
196: }
197: }
198: assertTrue(count > 0);
199:
200: // show that all exist in level2.
201: for (int i = 0; i < (capacity + 2); ++i) {
202: assertNotNull(level2.get("" + i));
203: }
204:
205: // show that none exist after removeall.
206: level2.removeAll();
207: for (int i = 0; i < (capacity + 2); ++i) {
208: assertNull(internal.get("" + i));
209: assertNull(level2.get("" + i));
210: }
211: }
212:
213: /**
214: * Shows that {@link SoftCache#remove(Object)} clears objects that
215: * are <em>only</em> in L2.
216: */
217: public void testRemoveClearsL2Objects()
218: throws CacheEvictionException {
219: final int capacity = 5;
220: final CachePolicy internal = new MRU(capacity);
221: final CachePolicy level2 = new SoftCache(internal);
222:
223: { // control test: show recovery
224: for (int i = 0; i < (capacity + 2); ++i) {
225: level2.put("" + i, createLargeObject());
226: }
227: // "0", "1" evicted.
228: assertNull(internal.get("0"));
229: assertNull(internal.get("1"));
230: assertNotNull(internal.get("2"));
231: assertNotNull(level2.get("0"));
232: assertNotNull(level2.get("1"));
233: assertNotNull(level2.get("2"));
234: }
235:
236: level2.removeAll(); // this has already been tested
237:
238: { // test remove:
239: for (int i = 0; i < (capacity + 2); ++i) {
240: level2.put("" + i, createLargeObject());
241: }
242: // "0", "1" evicted, as before.
243: // Now, we remove "1" -- L2 only object, and
244: // "2" -- internal and L2 object
245: level2.remove("1");
246: level2.remove("2");
247: assertNull(internal.get("0"));
248: assertNull(internal.get("1"));
249: assertNull(internal.get("2"));
250: assertNotNull(level2.get("0"));
251: assertNull(level2.get("1"));
252: assertNull(level2.get("2"));
253: }
254: }
255:
256: public void testExceptionDuringReinstate()
257: throws CacheEvictionException {
258: final int capacity = 5;
259: final CachePolicy internal = new MRU(capacity);
260: final CachePolicy level2 = new SoftCache(internal);
261: final CachePolicyListener listener = new ThrowingListener();
262:
263: // Fill internal cache, and overflow by three: "0", "1", "2" evicted from internal
264: for (int i = 0; i < (capacity + 3); ++i) {
265: level2.put("" + i, new Object());
266: }
267:
268: { // Null test. Cause "0" to get reinserted.
269: assertNull(internal.get("0"));
270: assertNotNull(level2.get("0"));
271: assertNotNull(internal.get("0"));
272: }
273:
274: { // Test handling of exception during reinsertion
275: level2.addListener(listener);
276: assertNull(internal.get("1"));
277: assertNull(level2.get("1")); // this would have caused a reinsertion
278: assertNull(internal.get("1"));
279: }
280:
281: { // Test stability: when listener is removed, things are back to normal
282: level2.removeListener(listener);
283: assertNull(internal.get("2"));
284: assertNotNull(level2.get("2"));
285: assertNotNull(internal.get("2"));
286: }
287: }
288:
289: protected CachePolicy createInstance(final int capacity) {
290: return new SoftCache(new MRU(capacity));
291: }
292: }
|