commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From skitch...@apache.org
Subject svn commit: r157301 - in jakarta/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/converters: ClassReloader.java ClassReloaderTestCase.java MemoryTestCase.java
Date Sun, 13 Mar 2005 00:11:39 GMT
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: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org


Mime
View raw message