Author: skitching Date: Sat Mar 12 16:11:38 2005 New Revision: 157301 URL: http://svn.apache.org/viewcvs?view=rev&rev=157301 Log: Unit tests to check for expected behaviour in j2ee-like environments.
Added: jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloader.java (with props) jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloaderTestCase.java (with props) jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/MemoryTestCase.java (with props) Added: jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloader.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloader.java?view=auto&rev=157301 ============================================================================== --- jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloader.java (added) +++ jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloader.java Sat Mar 12 16:11:38 2005 @@ -0,0 +1,81 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.beanutils.converters; + +import java.io.InputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * A special classloader useful for testing j2ee-like scenarios. + * + * <p>In some tests we want to be able to emulate "container" frameworks, + * where code runs in a hierarchy of classloaders, and certain classes may + * be loaded by various classloaders in the hierarchy.</p> + * + * <p>Normally this is done by having certain jars or class-file-directories + * in the classpath of some classloaders but not others. This is quite + * difficult difficult to integrate with the build process for the unit + * tests though; compiling certain classes and having the output go into + * places that is not in the default classpath for the unit tests would be + * a major pain.</p> + * + * <p>So this class takes a sneaky alternative approach: it can grab any class + * already loaded by a parent classloader and <i>reload</i> that class via this + * classloader. The effect is exactly as if a class (or jar file) had been + * present in the classpath for a container's "shared" classloader <i>and</i> + * been present in the component-specific classpath too, without any messing + * about with the way unit test code is compiled or executed. + */ + +public class ClassReloader extends ClassLoader { + public ClassReloader(ClassLoader parent) { + super(parent); + } + + /** + * Given a class already in the classpath of a parent classloader, + * reload that class via this classloader. + */ + public Class reload(Class clazz) throws FileNotFoundException, IOException { + String className = clazz.getName(); + String classFile = className.replace('.', '/') + ".class"; + InputStream classStream = getParent().getResourceAsStream(classFile); + + if (classStream == null) { + throw new FileNotFoundException(classFile); + } + + byte[] buf = new byte[1024]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for(;;) { + int bytesRead = classStream.read(buf); + if (bytesRead == -1) + break; + baos.write(buf, 0, bytesRead); + } + classStream.close(); + + byte[] classData = baos.toByteArray(); + + // now we have the raw class data, let's turn it into a class + Class newClass = defineClass(className, classData, 0, classData.length); + resolveClass(newClass); + return newClass; + } +} Propchange: jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloader.java ------------------------------------------------------------------------------ svn:keywords = Id Added: jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloaderTestCase.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloaderTestCase.java?view=auto&rev=157301 ============================================================================== --- jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloaderTestCase.java (added) +++ jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloaderTestCase.java Sat Mar 12 16:11:38 2005 @@ -0,0 +1,73 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.beanutils.converters; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Tests for the ClassReloader utility class. + */ + +public class ClassReloaderTestCase extends TestCase { + + // ------------------------------------------------------------------------ + + public ClassReloaderTestCase(String name) { + super(name); + } + + + public static TestSuite suite() { + return new TestSuite(ClassReloaderTestCase.class); + } + + // ------------------------------------------------------------------------ + + public static class DummyClass { + } + + /** + * Test basic operation of the ClassReloader. + */ + public void testBasicOperation() throws Exception { + ClassLoader sharedLoader = this.getClass().getClassLoader(); + ClassReloader componentLoader = new ClassReloader(sharedLoader); + + Class sharedClass = DummyClass.class; + Class componentClass = componentLoader.reload(sharedClass); + + // the two Class objects contain the same bytecode, but are not equal + assertTrue(sharedClass != componentClass); + + // the two class objects have different classloaders + assertSame(sharedLoader, sharedClass.getClassLoader()); + assertSame(componentLoader, componentClass.getClassLoader()); + assertTrue(sharedLoader != componentLoader); + + // verify that objects of these two types are not assignment-compatible + Object obj1 = sharedClass.newInstance(); + Object obj2 = componentClass.newInstance(); + + assertTrue("Obj1 class incorrect", sharedClass.isInstance(obj1)); + assertFalse("Obj1 class incorrect", componentClass.isInstance(obj1)); + assertFalse("Obj2 class incorrect", sharedClass.isInstance(obj2)); + assertTrue("Obj2 class incorrect", componentClass.isInstance(obj2)); + } + +} + Propchange: jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/ClassReloaderTestCase.java ------------------------------------------------------------------------------ svn:keywords = Id Added: jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/MemoryTestCase.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/MemoryTestCase.java?view=auto&rev=157301 ============================================================================== --- jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/MemoryTestCase.java (added) +++ jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/MemoryTestCase.java Sat Mar 12 16:11:38 2005 @@ -0,0 +1,281 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.beanutils.converters; + +import java.lang.ref.WeakReference; + +import org.apache.commons.beanutils.Converter; +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.commons.beanutils.ConversionException; + +import junit.framework.TestCase; + +/** + * This class provides a number of unit tests related to classloaders and + * garbage collection, particularly in j2ee-like situations. + */ +public class MemoryTestCase extends TestCase { + + public void testWeakReference() throws Exception { + ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + ClassReloader componentLoader = new ClassReloader(origContextClassLoader); + + Thread.currentThread().setContextClassLoader(componentLoader); + Thread.currentThread().setContextClassLoader(origContextClassLoader); + + WeakReference ref = new WeakReference(componentLoader); + componentLoader = null; + + forceGarbageCollection(ref); + assertNull(ref.get()); + } finally { + // Restore context classloader that was present before this + // test started. It is expected to be the same as the system + // classloader, but we handle all cases here.. + Thread.currentThread().setContextClassLoader(origContextClassLoader); + + // and restore all the standard converters + ConvertUtils.deregister(); + } + } + + /** + * Test whether registering a standard Converter instance while + * a custom context classloader is set causes a memory leak. + * + * <p>This test emulates a j2ee container where BeanUtils has been + * loaded from a "common" lib location that is shared across all + * components running within the container. The "component" registers + * a converter object, whose class was loaded from the "common" lib + * location. The registered converter: + * <ul> + * <li>should not be visible to other components; and</li> + * <li>should not prevent the component-specific classloader from being + * garbage-collected when the container sets its reference to null. + * </ul> + * + */ + public void testComponentRegistersStandardConverter() throws Exception { + + ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + // sanity check; who's paranoid?? :-) + assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader()); + + // create a custom classloader for a "component" + // just like a container would. + ClassLoader componentLoader1 = new ClassLoader() {}; + ClassLoader componentLoader2 = new ClassLoader() {}; + + Converter origFloatConverter = ConvertUtils.lookup(Float.TYPE); + Converter floatConverter1 = new FloatConverter(); + + // Emulate the container invoking a component #1, and the component + // registering a custom converter instance whose class is + // available via the "shared" classloader. + Thread.currentThread().setContextClassLoader(componentLoader1); + { + // here we pretend we're running inside component #1 + + // When we first do a ConvertUtils operation inside a custom + // classloader, we get a completely fresh copy of the + // ConvertUtilsBean, with all-new Converter objects in it.. + assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter); + + // Now we register a custom converter (but of a standard class). + // This should only affect code that runs with exactly the + // same context classloader set. + ConvertUtils.register(floatConverter1, Float.TYPE); + assertTrue(ConvertUtils.lookup(Float.TYPE) == floatConverter1); + } + Thread.currentThread().setContextClassLoader(origContextClassLoader); + + // The converter visible outside any custom component should not + // have been altered. + assertTrue(ConvertUtils.lookup(Float.TYPE) == origFloatConverter); + + // Emulate the container invoking a component #2. + Thread.currentThread().setContextClassLoader(componentLoader2); + { + // here we pretend we're running inside component #2 + + // we should get a completely fresh ConvertUtilsBean, with + // all-new Converter objects again. + assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter); + assertFalse(ConvertUtils.lookup(Float.TYPE) == floatConverter1); + } + Thread.currentThread().setContextClassLoader(origContextClassLoader); + + // Emulate a container "undeploying" component #1. This should + // make component loader available for garbage collection (we hope) + WeakReference weakRefToComponent1 = new WeakReference(componentLoader1); + componentLoader1 = null; + + // force garbage collection and verify that the componentLoader + // has been garbage-collected + forceGarbageCollection(weakRefToComponent1); + assertNull( + "Component classloader did not release properly; memory leak present", + weakRefToComponent1.get()); + } finally { + // Restore context classloader that was present before this + // test started, so that in case of a test failure we don't stuff + // up later tests... + Thread.currentThread().setContextClassLoader(origContextClassLoader); + + // and restore all the standard converters + ConvertUtils.deregister(); + } + } + + /** + * Test whether registering a custom Converter subclass while + * a custom context classloader is set causes a memory leak. + * + * <p>This test emulates a j2ee container where BeanUtils has been + * loaded from a "common" lib location that is shared across all + * components running within the container. The "component" registers + * a converter object, whose class was loaded via the component-specific + * classloader. The registered converter: + * <ul> + * <li>should not be visible to other components; and</li> + * <li>should not prevent the component-specific classloader from being + * garbage-collected when the container sets its reference to null. + * </ul> + * + */ + public void testComponentRegistersCustomConverter() throws Exception { + + ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + // sanity check; who's paranoid?? :-) + assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader()); + + // create a custom classloader for a "component" + // just like a container would. + ClassReloader componentLoader = new ClassReloader(origContextClassLoader); + + // Load a custom Converter via component loader. This emulates what + // would happen if a user wrote their own FloatConverter subclass + // and deployed it via the component-specific classpath. + Thread.currentThread().setContextClassLoader(componentLoader); + { + // Here we pretend we're running inside the component, and that + // a class FloatConverter has been loaded from the component's + // private classpath. + Class newFloatConverterClass = componentLoader.reload(FloatConverter.class); + Object newFloatConverter = newFloatConverterClass.newInstance(); + assertTrue(newFloatConverter.getClass().getClassLoader() == componentLoader); + + // verify that this new object does implement the Converter type + // despite being loaded via a classloader different from the one + // that loaded the Converter class. + assertTrue( + "Converter loader via child does not implement parent type", + Converter.class.isInstance(newFloatConverter)); + + // this converter registration will only apply to the + // componentLoader classloader... + ConvertUtils.register((Converter)newFloatConverter, Float.TYPE); + + // After registering a custom converter, lookup should return + // it back to us. We'll try this lookup again with a different + // context-classloader set, and shouldn't see it + Converter componentConverter = ConvertUtils.lookup(Float.TYPE); + assertTrue(componentConverter.getClass().getClassLoader() == componentLoader); + + newFloatConverter = null; + } + Thread.currentThread().setContextClassLoader(origContextClassLoader); + + // Because the context classloader has been reset, we shouldn't + // see the custom registered converter here... + Converter sharedConverter = ConvertUtils.lookup(Float.TYPE); + assertFalse(sharedConverter.getClass().getClassLoader() == componentLoader); + + // and here we should see it again + Thread.currentThread().setContextClassLoader(componentLoader); + { + Converter componentConverter = ConvertUtils.lookup(Float.TYPE); + assertTrue(componentConverter.getClass().getClassLoader() == componentLoader); + } + Thread.currentThread().setContextClassLoader(origContextClassLoader); + // Emulate a container "undeploying" the component. This should + // make component loader available for garbage collection (we hope) + WeakReference weakRefToComponent = new WeakReference(componentLoader); + componentLoader = null; + + // force garbage collection and verify that the componentLoader + // has been garbage-collected + forceGarbageCollection(weakRefToComponent); + assertNull( + "Component classloader did not release properly; memory leak present", + weakRefToComponent.get()); + } finally { + // Restore context classloader that was present before this + // test started. It is expected to be the same as the system + // classloader, but we handle all cases here.. + Thread.currentThread().setContextClassLoader(origContextClassLoader); + + // and restore all the standard converters + ConvertUtils.deregister(); + } + } + + /** + * Attempt to force garbage collection of the specified target. + * + * <p>Unfortunately there is no way to force a JVM to perform + * garbage collection; all we can do is <i>hint</i> to it that + * garbage-collection would be a good idea, and to consume + * memory in order to trigger it.</p> + * + * <p>On return, target.get() will return null if the target has + * been garbage collected.</p> + * + * <p>If target.get() still returns non-null after this method has returned, + * then either there is some reference still being held to the target, or + * else we were not able to trigger garbage collection; there is no way + * to tell these scenarios apart.</p> + */ + private void forceGarbageCollection(WeakReference target) { + int bytes = 2; + + while(target.get() != null) { + System.gc(); + + // Create increasingly-large amounts of non-referenced memory + // in order to persuade the JVM to collect it. We are hoping + // here that the JVM is dumb enough to run a full gc pass over + // all data (including the target) rather than simply collecting + // this easily-reclaimable memory! + try { + byte[] b = new byte[bytes]; + bytes = bytes * 2; + } catch(OutOfMemoryError e) { + // well that sure should have forced a garbage collection + // run to occur! + break; + } + } + + // and let's do one more just to clean up any garbage we might have + // created on the last pass.. + System.gc(); + } +} Propchange: jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters/MemoryTestCase.java ------------------------------------------------------------------------------ svn:keywords = Id --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]