diff options
author | Adam Domurad <[email protected]> | 2013-04-23 12:29:13 -0400 |
---|---|---|
committer | Adam Domurad <[email protected]> | 2013-04-23 12:29:13 -0400 |
commit | e8403ca8f62716fd3a76906b7e25cbc0d0cb5228 (patch) | |
tree | e496d8aa6fc8cb7f9a9d202c2bc9bccdaf4945da | |
parent | 3c710de15296fd7f16b144586791be134129663f (diff) |
JNLPClassLoader unit tests for file leaks
-rw-r--r-- | ChangeLog | 11 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java | 6 | ||||
-rw-r--r-- | tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java | 263 |
3 files changed, 277 insertions, 3 deletions
@@ -1,5 +1,16 @@ 2013-04-23 Adam Domurad <[email protected]> + * tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java: + New, JNLPClassLoader unit tests for (checkForMain), (getMainClassName), + (activateNativeJar), and (isInvalidJar). Checks for file descriptor + leaks. + * netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java + (isInvalidJar): Change to default visibility for testing purposes. + (checkForMain): Same. + (getMainClassName): Same. + +2013-04-23 Adam Domurad <[email protected]> + Rewrite of MethodOverloadResolver with detailed unittests. * plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java: Rewritten to reduce duplicated code, fix very subtle bugs in diff --git a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java index e790746..28b3d43 100644 --- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java +++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java @@ -556,7 +556,7 @@ public class JNLPClassLoader extends URLClassLoader { * @param jar the jar to check * @return true if file exists AND is an invalid jar, false otherwise */ - private boolean isInvalidJar(JARDesc jar){ + boolean isInvalidJar(JARDesc jar){ File cacheFile = tracker.getCacheFile(jar.getLocation()); if (cacheFile == null) return false;//File cannot be retrieved, do not claim it is an invalid jar @@ -792,7 +792,7 @@ public class JNLPClassLoader extends URLClassLoader { * @param jars Jars that are checked to see if they contain the main class * @throws LaunchException Thrown if the signed JNLP file, within the main jar, fails to be verified or does not match */ - private void checkForMain(List<JARDesc> jars) throws LaunchException { + void checkForMain(List<JARDesc> jars) throws LaunchException { // Check launch info if (mainClass == null) { @@ -878,7 +878,7 @@ public class JNLPClassLoader extends URLClassLoader { * @param location The JAR location * @return the main class name, null if there isn't one of if there was an error */ - private String getMainClassName(URL location) { + String getMainClassName(URL location) { String mainClass = null; File f = tracker.getCacheFile(location); diff --git a/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java b/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java new file mode 100644 index 0000000..6a16e2f --- /dev/null +++ b/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java @@ -0,0 +1,263 @@ +/*Copyright (C) 2013 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + */ + +package net.sourceforge.jnlp.runtime; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import net.sourceforge.jnlp.InformationDesc; +import net.sourceforge.jnlp.JARDesc; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.ResourcesDesc; +import net.sourceforge.jnlp.SecurityDesc; +import net.sourceforge.jnlp.ServerAccess; +import net.sourceforge.jnlp.Version; +import net.sourceforge.jnlp.cache.UpdatePolicy; +import net.sourceforge.jnlp.util.StreamUtils; + +import org.junit.Test; + +public class JNLPClassLoaderTest { + + /* Get the open file-descriptor count for the process. + * Note that this is specific to Unix-like operating systems. + * As well, it relies on */ + static public long getOpenFileDescriptorCount() { + MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer(); + try { + return (Long) beanServer.getAttribute( + new ObjectName("java.lang:type=OperatingSystem"), + "OpenFileDescriptorCount" + ); + } catch (Exception e) { + // Effectively disables leak tests + ServerAccess.logErrorReprint("Warning: Cannot get file descriptors for this platform!"); + return 0; + } + } + + /* Check the amount of file descriptors before and after a Runnable */ + static private void assertNoFileLeak(Runnable runnable) { + long filesOpenBefore = getOpenFileDescriptorCount(); + runnable.run(); + long filesLeaked = getOpenFileDescriptorCount() - filesOpenBefore; + assertEquals(0, filesLeaked); + } + + static private String cleanExec(File directory, String... command) throws Exception { + Process p = Runtime.getRuntime().exec(command, new String[]{}, directory); + + String stdOut = StreamUtils.readStreamAsString(p.getInputStream()); + String stdErr = StreamUtils.readStreamAsString(p.getErrorStream()); + + ServerAccess.logNoReprint("Running " + Arrays.toString(command)); + ServerAccess.logNoReprint("Standard output was: \n" + stdOut); + ServerAccess.logNoReprint("Standard error was: \n" + stdErr); + + p.getInputStream().close(); + p.getErrorStream().close(); + p.getOutputStream().close(); + + return stdOut; + + } + + /* Creates a jar in a temporary directory, with the given name & manifest contents. */ + static private File createTempJar(String jarName, String manifestContents) throws Exception { + File dir = new File(cleanExec(null /* current working dir */, "mktemp", "-d")); + cleanExec(dir, "/bin/bash", "-c", "echo '" + manifestContents + "' > Manifest.txt"); + cleanExec(dir, "jar", "-cfm", jarName, "Manifest.txt"); + return new File(dir.getAbsolutePath() + "/" + jarName); + } + + /* Creates a jar in a temporary directory, with the given name & an empty manifest. */ + static private File createTempJar(String jarName) throws Exception { + return createTempJar(jarName, ""); + } + + /* Create a JARDesc for the given URL location */ + static private JARDesc makeJarDesc(URL jarLocation) { + return new JARDesc(jarLocation, new Version("1"), null, false,false, false,false); + } + + /* A mocked dummy JNLP file with a single JAR. */ + private class MockedOneJarJNLPFile extends JNLPFile { + URL codeBase, jarLocation; + JARDesc jarDesc; + + MockedOneJarJNLPFile(File jarFile) throws MalformedURLException { + codeBase = jarFile.getParentFile().toURI().toURL(); + jarLocation = jarFile.toURI().toURL(); + jarDesc = makeJarDesc(jarLocation); + info = new ArrayList<InformationDesc>(); + } + + @Override + public ResourcesDesc getResources() { + ResourcesDesc resources = new ResourcesDesc(null, new Locale[0], new String[0], new String[0]); + resources.addResource(jarDesc); + return resources; + } + @Override + public ResourcesDesc[] getResourcesDescs(final Locale locale, final String os, final String arch) { + return new ResourcesDesc[] { getResources() }; + } + + @Override + public URL getCodeBase() { + return codeBase; + } + + @Override + public SecurityDesc getSecurity() { + return new SecurityDesc(this, SecurityDesc.SANDBOX_PERMISSIONS, null); + } + }; + + /* Note: Only does file leak testing for now. */ + @Test + public void constructorFileLeakTest() throws Exception { + final MockedOneJarJNLPFile jnlpFile = new MockedOneJarJNLPFile(createTempJar("test.jar")); + + assertNoFileLeak( new Runnable () { + @Override + public void run() { + try { + new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); + } catch (LaunchException e) { + fail(e.toString()); + } + } + }); + } + + /* Note: We should create a JNLPClassLoader with an invalid jar to test isInvalidJar with. + * However, it is tricky without it erroring-out. */ + @Test + public void isInvalidJarTest() throws Exception { + final MockedOneJarJNLPFile jnlpFile = new MockedOneJarJNLPFile(createTempJar("test.jar")); + final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); + + assertNoFileLeak( new Runnable () { + @Override + public void run() { + assertFalse(classLoader.isInvalidJar(jnlpFile.jarDesc)); + } + }); + + } + + /* Note: Only does file leak testing for now, but more testing could be added. */ + @Test + public void activateNativeFileLeakTest() throws Exception { + final MockedOneJarJNLPFile jnlpFile = new MockedOneJarJNLPFile(createTempJar("test.jar")); + final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); + + assertNoFileLeak( new Runnable () { + @Override + public void run() { + classLoader.activateNative(jnlpFile.jarDesc); + } + }); + } + + @Test + public void getMainClassNameTest() throws Exception { + /* Test with main-class */{ + final MockedOneJarJNLPFile jnlpFile = new MockedOneJarJNLPFile(createTempJar("test.jar", "Main-Class: DummyClass\n")); + final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); + + assertNoFileLeak(new Runnable() { + @Override + public void run() { + assertEquals("DummyClass", classLoader.getMainClassName(jnlpFile.jarLocation)); + } + }); + } + /* Test with-out main-class */{ + final MockedOneJarJNLPFile jnlpFile = new MockedOneJarJNLPFile(createTempJar("test.jar", "")); + final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); + + assertNoFileLeak(new Runnable() { + @Override + public void run() { + assertEquals(null, classLoader.getMainClassName(jnlpFile.jarLocation)); + } + }); + } + } + + static private <T> List<T> toList(T ... parts) { + List<T> list = new ArrayList<T>(); + for (T part : parts) { + list.add(part); + } + return list; + } + + /* Note: Although it does a basic check, this mainly checks for file-descriptor leak */ + @Test + public void checkForMainFileLeakTest() throws Exception { + final MockedOneJarJNLPFile jnlpFile = new MockedOneJarJNLPFile(createTempJar("test.jar", "")); + final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); + assertNoFileLeak(new Runnable() { + @Override + public void run() { + try { + classLoader.checkForMain(toList(jnlpFile.jarDesc)); + } catch (LaunchException e) { + fail(e.toString()); + } + } + }); + assertFalse(classLoader.hasMainJar()); + } +}
\ No newline at end of file |