001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.beanutils;
019:
020: import java.util.*;
021:
022: import java.lang.ref.WeakReference;
023: import java.lang.ref.ReferenceQueue;
024:
025: import junit.framework.TestCase;
026: import junit.framework.Test;
027: import junit.framework.TestSuite;
028:
029: import org.apache.commons.logging.LogFactory;
030:
031: /**
032: * <p>
033: * Test Case for changes made during Beanutils Beanification
034: * </p>
035: *
036: * @author Robert Burrell Donkin
037: * @author Juozas Baliuka
038: * @version $Revision: 469737 $ $Date: 2006-11-01 01:16:55 +0000 (Wed, 01 Nov 2006) $
039: */
040:
041: public class BeanificationTestCase extends TestCase {
042:
043: // ---------------------------------------------------- Constants
044:
045: /** Maximum number of iterations before our test fails */
046: public static final int MAX_GC_ITERATIONS = 50;
047:
048: // ---------------------------------------------------- Instance Variables
049:
050: // ---------------------------------------------------------- Constructors
051:
052: /**
053: * Construct a new instance of this test case.
054: *
055: * @param name Name of the test case
056: */
057: public BeanificationTestCase(String name) {
058: super (name);
059: }
060:
061: // -------------------------------------------------- Overall Test Methods
062:
063: /**
064: * Set up instance variables required by this test case.
065: */
066: public void setUp() {
067:
068: ConvertUtils.deregister();
069:
070: }
071:
072: /**
073: * Return the tests included in this test suite.
074: */
075: public static Test suite() {
076: return (new TestSuite(BeanificationTestCase.class));
077: }
078:
079: /**
080: * Tear down instance variables required by this test case.
081: */
082: public void tearDown() {
083: // No action required
084: }
085:
086: // ------------------------------------------------ Individual Test Methods
087:
088: /** Test of the methodology we'll use for some of the later tests */
089: public void testMemoryTestMethodology() throws Exception {
090: // test methodology
091: // many thanks to Juozas Baliuka for suggesting this method
092: ClassLoader loader = new ClassLoader(this .getClass()
093: .getClassLoader()) {
094: };
095: WeakReference reference = new WeakReference(loader);
096: Class myClass = loader
097: .loadClass("org.apache.commons.beanutils.BetaBean");
098:
099: assertNotNull("Weak reference released early", reference.get());
100:
101: // dereference class loader and class:
102: loader = null;
103: myClass = null;
104:
105: int iterations = 0;
106: int bytz = 2;
107: while (true) {
108: System.gc();
109: if (iterations++ > MAX_GC_ITERATIONS) {
110: fail("Max iterations reached before resource released.");
111: }
112: if (reference.get() == null) {
113: break;
114:
115: } else {
116: // create garbage:
117: byte[] b = new byte[bytz];
118: bytz = bytz * 2;
119: }
120: }
121: }
122:
123: /** Tests whether classloaders and beans are released from memory by the map used by beanutils */
124: public void testMemoryLeak2() throws Exception {
125: // tests when the map used by beanutils has the right behaviour
126:
127: if (isPre14JVM()) {
128: System.out
129: .println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
130: return;
131: }
132:
133: // many thanks to Juozas Baliuka for suggesting this methodology
134: TestClassLoader loader = new TestClassLoader();
135: ReferenceQueue queue = new ReferenceQueue();
136: WeakReference loaderReference = new WeakReference(loader, queue);
137: Integer test = new Integer(1);
138:
139: WeakReference testReference = new WeakReference(test, queue);
140: //Map map = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD, true);
141: Map map = new WeakHashMap();
142: map.put(loader, test);
143:
144: assertEquals("In map", test, map.get(loader));
145: assertNotNull("Weak reference released early (1)",
146: loaderReference.get());
147: assertNotNull("Weak reference released early (2)",
148: testReference.get());
149:
150: // dereference strong references
151: loader = null;
152: test = null;
153:
154: int iterations = 0;
155: int bytz = 2;
156: while (true) {
157: System.gc();
158: if (iterations++ > MAX_GC_ITERATIONS) {
159: fail("Max iterations reached before resource released.");
160: }
161: map.isEmpty();
162:
163: if (loaderReference.get() == null
164: && testReference.get() == null) {
165: break;
166:
167: } else {
168: // create garbage:
169: byte[] b = new byte[bytz];
170: bytz = bytz * 2;
171: }
172: }
173: }
174:
175: /** Tests whether classloaders and beans are released from memory */
176: public void testMemoryLeak() throws Exception {
177: if (isPre14JVM()) {
178: System.out
179: .println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
180: return;
181: }
182:
183: // many thanks to Juozas Baliuka for suggesting this methodology
184: TestClassLoader loader = new TestClassLoader();
185: WeakReference loaderReference = new WeakReference(loader);
186: BeanUtilsBean.getInstance();
187:
188: class GetBeanUtilsBeanThread extends Thread {
189:
190: BeanUtilsBean beanUtils;
191: ConvertUtilsBean convertUtils;
192: PropertyUtilsBean propertyUtils;
193:
194: GetBeanUtilsBeanThread() {
195: }
196:
197: public void run() {
198: beanUtils = BeanUtilsBean.getInstance();
199: convertUtils = ConvertUtilsBean.getInstance();
200: propertyUtils = PropertyUtilsBean.getInstance();
201: // XXX Log keeps a reference around!
202: LogFactory.releaseAll();
203: }
204:
205: public String toString() {
206: return "GetBeanUtilsBeanThread";
207: }
208: }
209:
210: GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread();
211: WeakReference threadWeakReference = new WeakReference(thread);
212: thread.setContextClassLoader(loader);
213:
214: thread.start();
215: thread.join();
216:
217: WeakReference beanUtilsReference = new WeakReference(
218: thread.beanUtils);
219: WeakReference propertyUtilsReference = new WeakReference(
220: thread.propertyUtils);
221: WeakReference convertUtilsReference = new WeakReference(
222: thread.convertUtils);
223:
224: assertNotNull("Weak reference released early (1)",
225: loaderReference.get());
226: assertNotNull("Weak reference released early (2)",
227: beanUtilsReference.get());
228: assertNotNull("Weak reference released early (3)",
229: propertyUtilsReference.get());
230: assertNotNull("Weak reference released early (4)",
231: convertUtilsReference.get());
232:
233: // dereference strong references
234: loader = null;
235: thread.setContextClassLoader(null);
236: thread = null;
237:
238: int iterations = 0;
239: int bytz = 2;
240: while (true) {
241: BeanUtilsBean.getInstance();
242: System.gc();
243: if (iterations++ > MAX_GC_ITERATIONS) {
244: fail("Max iterations reached before resource released.");
245: }
246:
247: if (loaderReference.get() == null
248: && beanUtilsReference.get() == null
249: && propertyUtilsReference.get() == null
250: && convertUtilsReference.get() == null) {
251: break;
252:
253: } else {
254: // create garbage:
255: byte[] b = new byte[bytz];
256: bytz = bytz * 2;
257: }
258: }
259: }
260:
261: /**
262: * Tests whether difference instances are loaded by different
263: * context classloaders.
264: */
265: public void testGetByContextClassLoader() throws Exception {
266:
267: class GetBeanUtilsBeanThread extends Thread {
268:
269: private Signal signal;
270:
271: GetBeanUtilsBeanThread(Signal signal) {
272: this .signal = signal;
273: }
274:
275: public void run() {
276: signal.setSignal(2);
277: signal.setBean(BeanUtilsBean.getInstance());
278: signal.setConvertUtils(ConvertUtilsBean.getInstance());
279: signal
280: .setPropertyUtils(PropertyUtilsBean
281: .getInstance());
282: }
283:
284: public String toString() {
285: return "GetBeanUtilsBeanThread";
286: }
287: }
288:
289: Signal signal = new Signal();
290: signal.setSignal(1);
291:
292: GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(
293: signal);
294: thread.setContextClassLoader(new TestClassLoader());
295:
296: thread.start();
297: thread.join();
298:
299: assertEquals("Signal not set by test thread", 2, signal
300: .getSignal());
301: assertTrue(
302: "Different BeanUtilsBean instances per context classloader",
303: BeanUtilsBean.getInstance() != signal.getBean());
304: assertTrue(
305: "Different ConvertUtilsBean instances per context classloader",
306: ConvertUtilsBean.getInstance() != signal
307: .getConvertUtils());
308: assertTrue(
309: "Different PropertyUtilsBean instances per context classloader",
310: PropertyUtilsBean.getInstance() != signal
311: .getPropertyUtils());
312: }
313:
314: /**
315: * Tests whether difference instances are loaded by different
316: * context classloaders.
317: */
318: public void testContextClassLoaderLocal() throws Exception {
319:
320: class CCLLTesterThread extends Thread {
321:
322: private Signal signal;
323: private ContextClassLoaderLocal ccll;
324:
325: CCLLTesterThread(Signal signal, ContextClassLoaderLocal ccll) {
326: this .signal = signal;
327: this .ccll = ccll;
328: }
329:
330: public void run() {
331: ccll.set(new Integer(1789));
332: signal.setSignal(2);
333: signal.setMarkerObject(ccll.get());
334: }
335:
336: public String toString() {
337: return "CCLLTesterThread";
338: }
339: }
340:
341: ContextClassLoaderLocal ccll = new ContextClassLoaderLocal();
342: ccll.set(new Integer(1776));
343: assertEquals("Start thread sets value", new Integer(1776), ccll
344: .get());
345:
346: Signal signal = new Signal();
347: signal.setSignal(1);
348:
349: CCLLTesterThread thread = new CCLLTesterThread(signal, ccll);
350: thread.setContextClassLoader(new TestClassLoader());
351:
352: thread.start();
353: thread.join();
354:
355: assertEquals("Signal not set by test thread", 2, signal
356: .getSignal());
357: assertEquals("Second thread preserves value",
358: new Integer(1776), ccll.get());
359: assertEquals("Second thread gets value it set", new Integer(
360: 1789), signal.getMarkerObject());
361: }
362:
363: /** Tests whether calls are independent for different classloaders */
364: public void testContextClassloaderIndependence() throws Exception {
365:
366: class TestIndependenceThread extends Thread {
367: private Signal signal;
368: private PrimitiveBean bean;
369:
370: TestIndependenceThread(Signal signal, PrimitiveBean bean) {
371: this .signal = signal;
372: this .bean = bean;
373: }
374:
375: public void run() {
376: try {
377: signal.setSignal(3);
378: ConvertUtils.register(new Converter() {
379: public Object convert(Class type, Object value) {
380: return new Integer(9);
381: }
382: }, Integer.TYPE);
383: BeanUtils.setProperty(bean, "int", new Integer(1));
384: } catch (Exception e) {
385: e.printStackTrace();
386: signal.setException(e);
387: }
388: }
389:
390: public String toString() {
391: return "TestIndependenceThread";
392: }
393: }
394:
395: PrimitiveBean bean = new PrimitiveBean();
396: BeanUtils.setProperty(bean, "int", new Integer(1));
397: assertEquals("Wrong property value (1)", 1, bean.getInt());
398:
399: ConvertUtils.register(new Converter() {
400: public Object convert(Class type, Object value) {
401: return new Integer(5);
402: }
403: }, Integer.TYPE);
404: BeanUtils.setProperty(bean, "int", new Integer(1));
405: assertEquals("Wrong property value(2)", 5, bean.getInt());
406:
407: Signal signal = new Signal();
408: signal.setSignal(1);
409: TestIndependenceThread thread = new TestIndependenceThread(
410: signal, bean);
411: thread.setContextClassLoader(new TestClassLoader());
412:
413: thread.start();
414: thread.join();
415:
416: assertNull("Exception thrown by test thread:"
417: + signal.getException(), signal.getException());
418: assertEquals("Signal not set by test thread", 3, signal
419: .getSignal());
420: assertEquals("Wrong property value(3)", 9, bean.getInt());
421:
422: }
423:
424: /** Tests whether different threads can set beanutils instances correctly */
425: public void testBeanUtilsBeanSetInstance() throws Exception {
426:
427: class SetInstanceTesterThread extends Thread {
428:
429: private Signal signal;
430: private BeanUtilsBean bean;
431:
432: SetInstanceTesterThread(Signal signal, BeanUtilsBean bean) {
433: this .signal = signal;
434: this .bean = bean;
435: }
436:
437: public void run() {
438: BeanUtilsBean.setInstance(bean);
439: signal.setSignal(21);
440: signal.setBean(BeanUtilsBean.getInstance());
441: }
442:
443: public String toString() {
444: return "SetInstanceTesterThread";
445: }
446: }
447:
448: Signal signal = new Signal();
449: signal.setSignal(1);
450:
451: BeanUtilsBean beanOne = new BeanUtilsBean();
452: BeanUtilsBean beanTwo = new BeanUtilsBean();
453:
454: SetInstanceTesterThread thread = new SetInstanceTesterThread(
455: signal, beanTwo);
456: thread.setContextClassLoader(new TestClassLoader());
457:
458: BeanUtilsBean.setInstance(beanOne);
459: assertEquals("Start thread gets right instance", beanOne,
460: BeanUtilsBean.getInstance());
461:
462: thread.start();
463: thread.join();
464:
465: assertEquals("Signal not set by test thread", 21, signal
466: .getSignal());
467: assertEquals("Second thread preserves value", beanOne,
468: BeanUtilsBean.getInstance());
469: assertEquals("Second thread gets value it set", beanTwo, signal
470: .getBean());
471: }
472:
473: /** Tests whether the unset method works*/
474: public void testContextClassLoaderUnset() throws Exception {
475: BeanUtilsBean beanOne = new BeanUtilsBean();
476: ContextClassLoaderLocal ccll = new ContextClassLoaderLocal();
477: ccll.set(beanOne);
478: assertEquals("Start thread gets right instance", beanOne, ccll
479: .get());
480: ccll.unset();
481: assertTrue("Unset works", !beanOne.equals(ccll.get()));
482: }
483:
484: private boolean isPre14JVM() {
485: // some pre 1.4 JVM have buggy WeakHashMap implementations
486: // this is used to test for those JVM
487: String version = System
488: .getProperty("java.specification.version");
489: StringTokenizer tokenizer = new StringTokenizer(version, ".");
490: if (tokenizer.nextToken().equals("1")) {
491: String minorVersion = tokenizer.nextToken();
492: if (minorVersion.equals("0"))
493: return true;
494: if (minorVersion.equals("1"))
495: return true;
496: if (minorVersion.equals("2"))
497: return true;
498: if (minorVersion.equals("3"))
499: return true;
500: }
501: return false;
502: }
503:
504: // ---- Auxillary classes
505:
506: class TestClassLoader extends ClassLoader {
507: public String toString() {
508: return "TestClassLoader";
509: }
510: }
511:
512: class Signal {
513: private Exception e;
514: private int signal = 0;
515: private BeanUtilsBean bean;
516: private PropertyUtilsBean propertyUtils;
517: private ConvertUtilsBean convertUtils;
518: private Object marker;
519:
520: public Exception getException() {
521: return e;
522: }
523:
524: public void setException(Exception e) {
525: this .e = e;
526: }
527:
528: public int getSignal() {
529: return signal;
530: }
531:
532: public void setSignal(int signal) {
533: this .signal = signal;
534: }
535:
536: public Object getMarkerObject() {
537: return marker;
538: }
539:
540: public void setMarkerObject(Object marker) {
541: this .marker = marker;
542: }
543:
544: public BeanUtilsBean getBean() {
545: return bean;
546: }
547:
548: public void setBean(BeanUtilsBean bean) {
549: this .bean = bean;
550: }
551:
552: public PropertyUtilsBean getPropertyUtils() {
553: return propertyUtils;
554: }
555:
556: public void setPropertyUtils(PropertyUtilsBean propertyUtils) {
557: this .propertyUtils = propertyUtils;
558: }
559:
560: public ConvertUtilsBean getConvertUtils() {
561: return convertUtils;
562: }
563:
564: public void setConvertUtils(ConvertUtilsBean convertUtils) {
565: this.convertUtils = convertUtils;
566: }
567: }
568: }
|