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: * $Id
046: */package jdbm.helper;
047:
048: import java.util.ArrayList;
049: import junit.framework.TestCase;
050:
051: /**
052: * Abstract class that provides some convenience test cases,
053: * classes and methods for any class that tests an implementation
054: * of {@link CachePolicy}.
055: * <p/>
056: * Concrete subclasses must provide {@link #createInstance(int)}. They may
057: * also need to override {@link #causeEviction(CachePolicy, int)} if the
058: * default strategy will not suffice for the implementation of being tested.
059: *
060: * @author <a href="mailto:dranatunga@users.sourceforge.net">Dilum Ranatunga</a>
061: * @version $Id: TestCachePolicy.java,v 1.1 2003/11/01 13:26:19 dranatunga Exp $
062: */
063: public abstract class TestCachePolicy extends TestCase {
064:
065: protected TestCachePolicy(String string) {
066: super (string);
067: }
068:
069: /**
070: * Factory that test methods use to create test instances. The instance
071: * should be capable of storing the specified number of <q>large objects</q>
072: * without an eviction. Large objects are created by {@link #createLargeObject()}.
073: *
074: * @param capacity the number of large objects the instance should be
075: * capable of containing.
076: * @return a non-null cache instance.
077: */
078: protected abstract CachePolicy createInstance(int capacity);
079:
080: /**
081: * Causes at least one eviction from the specified cache.
082: * <p>
083: * Note: take care when overriding this with implementations that depend
084: * on adding a temporary listener to the cache. Such a scheme can cause
085: * an infinite loop when <em>testing adding and removing listeners</em>.
086: *
087: * @param cache a cache object. It is generally safe to assume that this
088: * was created using {@link #createInstance(int)}.
089: * @param capacity the capacity used when cache was created.
090: * @throws CacheEvictionException
091: */
092: protected void causeEviction(final CachePolicy cache,
093: final int capacity) throws CacheEvictionException {
094: for (int i = 0; i < capacity; ++i) {
095: cache.put(new Object(), new Object());
096: }
097: }
098:
099: /**
100: * Tests {@link CachePolicy#addListener(CachePolicyListener)},
101: * {@link CachePolicy#removeListener(CachePolicyListener)}.
102: */
103: public void testAddRemoveListeners() {
104: try {
105: final CountingListener listener1 = new CountingListener(
106: "Listener1");
107: final CountingListener listener2_1 = new CountingListener(
108: "Listener2");
109: final CountingListener listener2_2 = new CountingListener(
110: "Listener2");
111: final int capacity = 2;
112: final CachePolicy cache = createInstance(capacity);
113:
114: { // quick check of assumptions
115: assertTrue("Listeners should be equal.", listener2_1
116: .equals(listener2_2));
117: assertTrue(
118: "Equal listeners' hashcodes should be equal.",
119: listener2_1.hashCode() == listener2_2
120: .hashCode());
121: }
122:
123: { // bad input test
124: try {
125: cache.addListener(null);
126: fail("cache.addListener(null) should throw IllegalArgumentRxception.");
127: } catch (IllegalArgumentException e) {
128: }
129: }
130:
131: { // null test
132: causeEviction(cache, capacity);
133: assertEquals(0, listener1.count());
134: assertEquals(0, listener2_1.count());
135: assertEquals(0, listener2_2.count());
136: }
137:
138: { // show that add affects cache, listener
139: cache.addListener(listener1);
140: causeEviction(cache, capacity);
141: assertTrue("listener not getting added, "
142: + "not getting eviction event "
143: + " or causeEviction not working)", listener1
144: .count() > 0);
145: }
146:
147: { // show that remove affects cache, listener
148: listener1.reset();
149: cache.removeListener(listener1);
150: causeEviction(cache, capacity);
151: assertTrue("listener not getting removed", listener1
152: .count() == 0);
153: }
154:
155: { // show that multiple listeners are used
156: listener1.reset();
157: listener2_1.reset();
158: listener2_2.reset();
159: cache.addListener(listener1);
160: cache.addListener(listener2_1);
161: cache.addListener(listener2_2);
162: causeEviction(cache, capacity);
163: assertTrue(listener1.count() > 0);
164: // note XOR: only one of the listeners should have received the event.
165: assertTrue((listener2_1.count() > 0)
166: ^ (listener2_2.count() > 0));
167: }
168:
169: {
170: // show that multiple adds of equal listeners is undone with single remove
171: cache.removeListener(listener2_1);
172: cache.removeListener(listener2_2);
173: listener2_1.reset();
174: listener2_2.reset();
175: causeEviction(cache, capacity);
176: assertTrue((listener2_1.count() == 0)
177: && (listener2_2.count() == 0));
178:
179: cache.addListener(listener2_1);
180: cache.addListener(listener2_2);
181: causeEviction(cache, capacity);
182: assertTrue((listener2_1.count() > 0)
183: ^ (listener2_2.count() > 0));
184:
185: listener2_1.reset();
186: listener2_2.reset();
187: cache.removeListener(listener2_1); // note: only one is removed.
188: causeEviction(cache, capacity);
189: assertTrue((listener2_1.count() == 0)
190: && (listener2_2.count() == 0));
191: }
192: } catch (CacheEvictionException cex) {
193: fail("Cache is throwing eviction exceptions even though none of the listeners are.");
194: }
195: }
196:
197: /**
198: * Ensures that the {@link CachePolicy} implementation propagates
199: * {@link CacheEvictionException}s back to the caller.
200: */
201: public void testEvictionExceptionPropagation()
202: throws CacheEvictionException {
203: final CachePolicyListener quietListener = new CountingListener(
204: "quiet");
205: final CachePolicyListener throwingListener = new ThrowingListener();
206: final int capacity = 1;
207: final CachePolicy cache = createInstance(capacity);
208: { // null test.
209: cache.addListener(quietListener);
210: cache.removeAll();
211: try {
212: causeEviction(cache, capacity);
213: } catch (CacheEvictionException cex) {
214: fail("Threw eviction exception when it wasn't supposed to: "
215: + cex);
216: }
217: cache.removeListener(quietListener);
218: }
219:
220: { // propagation test
221: cache.addListener(throwingListener);
222: try {
223: causeEviction(cache, capacity);
224: fail("Did not propagate expected exception.");
225: } catch (CacheEvictionException cex) {
226: }
227: cache.removeListener(throwingListener);
228: }
229: }
230:
231: protected void causeGarbageCollection() {
232: try {
233: ArrayList l = new ArrayList();
234: for (int i = 0; i < 500; ++i) {
235: l.add(createLargeObject());
236: }
237: } catch (OutOfMemoryError oome) {
238: }
239: for (int i = 0; i < 10; ++i) {
240: System.gc();
241: }
242: }
243:
244: protected Object createLargeObject() {
245: int[] a = new int[1024 * 1024]; // 1M of ints.
246: // Fill the array. This is done to prevent any sneaky VMs from
247: // saving space by lazily allocating the full array.
248: for (int i = a.length; --i >= 0;) {
249: a[i] = i;
250: }
251: return a;
252: }
253:
254: /**
255: * Listener used to test whether the event method is being invoked.
256: * Typical usage idiom is of the form:
257: * <pre>
258: * CachePolicy cache = ...;
259: * CountingListener listener = new CountingListener("mylistener");
260: * ...
261: * listener.reset();
262: * cache.addListener(listener);
263: * // do stuff with cache
264: * assertTrue(listener.count() > 0);
265: * </pre>
266: */
267: protected static final class CountingListener implements
268: CachePolicyListener {
269: private String _name;
270: private int _count = 0;
271:
272: /**
273: * Creates a counting listener with the name specified.
274: * @param name the (non-null) name of the listener.
275: */
276: CountingListener(String name) {
277: _name = new String(name); // this automatically throws NPE if name is null.
278: }
279:
280: /**
281: * Implimentation of callback method that increments count.
282: */
283: public void cacheObjectEvicted(Object obj)
284: throws CacheEvictionException {
285: _count++;
286: }
287:
288: /**
289: * Reset's this listener's count to zero.
290: */
291: void reset() {
292: _count = 0;
293: }
294:
295: /**
296: * Gets this listener's current count: the number of times the
297: * callback method's been invoked since creation/reset.
298: * @return
299: */
300: int count() {
301: return _count;
302: }
303:
304: /**
305: * Equality defined as (same type) AND (names equal).
306: */
307: public boolean equals(Object obj) {
308: if (!(obj instanceof CountingListener)) {
309: return false;
310: }
311: return _name.equals(((CountingListener) obj)._name);
312: }
313:
314: /**
315: * Defined as hashcode of name; consistent with {@link #equals(Object)}.
316: */
317: public int hashCode() {
318: return _name.hashCode();
319: }
320: }
321:
322: /**
323: * Listener used to cause a {@link CacheEvictionException}.
324: * Typical usage idiom is of the form:
325: * <pre>
326: * CachePolicy cache = ...;
327: * ThrowingListener listener = new ThrowingListener();
328: * ...
329: * // cause cache evictions without exceptions.
330: * cache.addListener(listener);
331: * try {
332: * // cause a cache eviction.
333: * fail("exception expected");
334: * } catch (CacheEvictionException e) { }
335: * </pre>
336: */
337: protected static final class ThrowingListener implements
338: CachePolicyListener {
339:
340: protected static final String MESSAGE = "Intentionally thrown for testing purposes.";
341:
342: /**
343: * Always throws a {@link CacheEvictionException} wrapping a
344: * runtime exception with the message {@link #MESSAGE}.
345: */
346: public void cacheObjectEvicted(Object obj)
347: throws CacheEvictionException {
348: throw new CacheEvictionException(new RuntimeException(
349: MESSAGE));
350: }
351: }
352: }
|