changeset 741:d6f6c5524acc

Add NativeLibraryStorageTEst
author Adam Domurad <adomurad@redhat.com>
date Mon, 03 Jun 2013 10:51:47 -0400
parents 8e6aa48abeba
children a486f1493133
files ChangeLog netx/net/sourceforge/jnlp/cache/NativeLibraryStorage.java netx/net/sourceforge/jnlp/util/StreamUtils.java tests/netx/unit/net/sourceforge/jnlp/cache/NativeLibraryStorageTest.java tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java tests/test-extensions/net/sourceforge/jnlp/util/FileTestUtils.java
diffstat 6 files changed, 368 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Jun 03 10:34:36 2013 -0400
+++ b/ChangeLog	Mon Jun 03 10:51:47 2013 -0400
@@ -1,3 +1,15 @@
+2013-06-03  Adam Domurad  <adomurad@redhat.com>
+
+	* netx/net/sourceforge/jnlp/util/StreamUtils.java
+	(copyStream): New, copies input stream to output stream
+	* tests/netx/unit/net/sourceforge/jnlp/cache/NativeLibraryStorageTest.java:
+	New, tests lookup of native libraries from folders and jars.
+	* tests/test-extensions/net/sourceforge/jnlp/util/FileTestUtils.java:
+	New, contains utilities for testing open file descriptors, creating temporary
+	directories, and creating jars.
+	* tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java:
+	Replace jar creation methods with ones from FileTestUtils. 
+
 2013-06-03  Adam Domurad  <adomurad@redhat.com>
 
 	* netx/net/sourceforge/jnlp/cache/NativeLibraryStorage.java: New,
--- a/netx/net/sourceforge/jnlp/cache/NativeLibraryStorage.java	Mon Jun 03 10:34:36 2013 -0400
+++ b/netx/net/sourceforge/jnlp/cache/NativeLibraryStorage.java	Mon Jun 03 10:51:47 2013 -0400
@@ -76,6 +76,8 @@
         return null;
     }
 
+    public static final String[] NATIVE_LIBRARY_EXTENSIONS = { ".so", ".dylib", ".jnilib", ".framework", ".dll" };
+
     /**
      * Search for and enable any native code contained in a JAR by copying the
      * native files into the filesystem. Called in the security context of the
@@ -89,8 +91,6 @@
         if (localFile == null)
             return;
 
-        String[] librarySuffixes = { ".so", ".dylib", ".jnilib", ".framework", ".dll" };
-
         try {
             JarFile jarFile = new JarFile(localFile, false);
             Enumeration<JarEntry> entries = jarFile.entries();
@@ -105,7 +105,7 @@
                 String name = new File(e.getName()).getName();
                 boolean isLibrary = false;
 
-                for (String suffix : librarySuffixes) {
+                for (String suffix : NATIVE_LIBRARY_EXTENSIONS) {
                     if (name.endsWith(suffix)) {
                         isLibrary = true;
                         break;
@@ -132,7 +132,7 @@
         }
     }
 
-    private void ensureNativeStoreDirectory() {
+    void ensureNativeStoreDirectory() {
         if (jarEntryDirectory == null) {
             jarEntryDirectory = createNativeStoreDirectory();
             addSearchDirectory(jarEntryDirectory);
--- a/netx/net/sourceforge/jnlp/util/StreamUtils.java	Mon Jun 03 10:34:36 2013 -0400
+++ b/netx/net/sourceforge/jnlp/util/StreamUtils.java	Mon Jun 03 10:51:47 2013 -0400
@@ -42,16 +42,17 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 
 public class StreamUtils {
 
-    /***
+    /**
      * Closes a stream, without throwing IOException.
      * In case of IOException, prints the stack trace to System.err.
      * 
      * @param stream the stream that will be closed
      */
-    public static void closeSilently (Closeable stream) {
+    public static void closeSilently(Closeable stream) {
         if (stream != null) {
             try {
                 stream.close();
@@ -61,8 +62,23 @@
         }
     }
 
+    /**
+     * Copy an input stream's contents into an output stream.
+     */
+    public static void copyStream(InputStream input, OutputStream output)
+            throws IOException {
+        byte[] buffer = new byte[1024];
+        while (true) {
+            int bytesRead = input.read(buffer);
+            if (bytesRead == -1) {
+                break;
+            }
+            output.write(buffer, 0, bytesRead);
+        }
+    }
 
-    public static String readStreamAsString(InputStream stream) throws IOException {
+    public static String readStreamAsString(InputStream stream)
+            throws IOException {
         InputStreamReader is = new InputStreamReader(stream);
         StringBuilder sb = new StringBuilder();
         BufferedReader br = new BufferedReader(is);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/netx/unit/net/sourceforge/jnlp/cache/NativeLibraryStorageTest.java	Mon Jun 03 10:51:47 2013 -0400
@@ -0,0 +1,172 @@
+/*
+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.cache;
+
+import static net.sourceforge.jnlp.util.FileTestUtils.assertNoFileLeak;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sourceforge.jnlp.Version;
+import net.sourceforge.jnlp.util.FileTestUtils;
+
+import org.junit.Test;
+
+public class NativeLibraryStorageTest {
+
+    /**************************************************************************
+     *                          Test helpers                                    *
+     **************************************************************************/
+
+    /* Associates an extension with whether it represents a native library */
+    static class FileExtension {
+        public FileExtension(String extension, boolean isNative) {
+            this.extension = extension;
+            this.isNative = isNative;
+        }
+        final String extension;
+        final boolean isNative;
+    }
+
+    static private List<FileExtension> makeExtensionsToTest() {
+        List<FileExtension> exts = new ArrayList<FileExtension>();
+        exts.add(new FileExtension(".foobar", false)); /* Dummy non-native test extension */
+        for (String ext : NativeLibraryStorage.NATIVE_LIBRARY_EXTENSIONS) {
+            exts.add(new FileExtension(ext, true));
+        }
+        return exts;
+    }
+
+    /* All the native library types we support, as well as one negative test */
+    static final List<FileExtension> extensionsToTest = makeExtensionsToTest();
+
+    /* Creates a NativeLibraryStorage object, caching the given URLs */
+    static NativeLibraryStorage nativeLibraryStorageWithCache(URL... urlsToCache) {
+        ResourceTracker tracker = new ResourceTracker();
+        for (URL urlToCache : urlsToCache) {
+            tracker.addResource(urlToCache, new Version("1.0"), null, UpdatePolicy.ALWAYS);
+        }
+
+        return new NativeLibraryStorage(tracker);
+    }
+
+    /**************************************************************************
+     *                          Test cases                                    *
+     **************************************************************************/
+
+    /* Tests searching for native libraries in jars */
+    @Test
+    public void testJarFileSearch() throws Exception {
+        /* Create a temporary directory to create jars in */
+        File tempDirectory = FileTestUtils.createTempDirectory();
+
+        for (FileExtension ext : extensionsToTest) {
+            /* Create empty file to search for */
+            String testFileName = "foobar" + ext.extension;
+            File testFile = new File(tempDirectory, testFileName);
+            FileTestUtils.createFileWithContents(testFile, "");
+
+            /* Create jar to search in */
+            File jarLocation = new File(tempDirectory, "test.jar");
+            FileTestUtils.createJarWithContents(jarLocation, testFile);
+
+            final URL tempJarUrl = jarLocation.toURI().toURL();
+            final NativeLibraryStorage storage = nativeLibraryStorageWithCache(tempJarUrl);
+
+            assertNoFileLeak( new Runnable () {
+                @Override
+                public void run() {
+                    storage.addSearchJar(tempJarUrl);
+                }
+            });
+
+            /* This check isn't critical, but ensures we do not accidentally add jars as search directories */
+            assertFalse(storage.getSearchDirectories().contains(tempJarUrl));
+
+            /* If the file we added is native, it should be found
+             * Due to an implementation detail, non-native files will not be found */
+            boolean testFileWasFound = storage.findLibrary(testFileName) != null;
+            assertEquals(ext.isNative, testFileWasFound);
+        }
+    }
+
+    /* Tests searching for native libraries in directories */
+    @Test
+    public void testDirectorySearch() throws Exception {
+        /* Create a temporary directory to search in */
+        File tempDirectory = FileTestUtils.createTempDirectory();
+
+        for (FileExtension ext : extensionsToTest) {
+            /* Create empty file in the directory */
+            String testFileName = "foobar" + ext.extension;
+            FileTestUtils.createFileWithContents(new File(tempDirectory, testFileName), "");
+
+            /* Add the directory to the search list */
+            NativeLibraryStorage storage = nativeLibraryStorageWithCache(/* None needed */);
+            storage.addSearchDirectory(tempDirectory);
+
+            /* Ensure directory is in our search list */
+            assertTrue(storage.getSearchDirectories().contains(tempDirectory));
+
+            /* The file should be found, regardless if it was native */
+            boolean testFileWasFound = storage.findLibrary(testFileName) != null;
+            assertTrue(testFileWasFound);
+        }
+    }
+
+    @Test
+    public void testCleanupTemporaryFolder() throws Exception {
+        NativeLibraryStorage storage = nativeLibraryStorageWithCache(/* None needed */);
+        storage.ensureNativeStoreDirectory();
+
+        /* The temporary native store directory should be our only search folder */
+        assertTrue(storage.getSearchDirectories().size() == 1);
+
+        File searchDirectory = storage.getSearchDirectories().get(0);
+        assertTrue(searchDirectory.exists());
+
+        /* Test that it has been deleted */
+        storage.cleanupTemporaryFolder();
+        assertFalse(searchDirectory.exists());
+    }
+}
\ No newline at end of file
--- a/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java	Mon Jun 03 10:34:36 2013 -0400
+++ b/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java	Mon Jun 03 10:51:47 2013 -0400
@@ -36,92 +36,34 @@
 
 package net.sourceforge.jnlp.runtime;
 
+import static net.sourceforge.jnlp.util.FileTestUtils.assertNoFileLeak;
 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.URL;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
 
-import javax.management.MBeanServer;
-import javax.management.ObjectName;
-
-import net.sourceforge.jnlp.JARDesc;
 import net.sourceforge.jnlp.LaunchException;
-import net.sourceforge.jnlp.ServerAccess;
-import net.sourceforge.jnlp.Version;
 import net.sourceforge.jnlp.cache.UpdatePolicy;
 import net.sourceforge.jnlp.mock.DummyJNLPFileWithJar;
-import net.sourceforge.jnlp.util.StreamUtils;
+import net.sourceforge.jnlp.util.FileTestUtils;
 
 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, "");
-    }
-
     /* Note: Only does file leak testing for now. */
     @Test
     public void constructorFileLeakTest() throws Exception {
-        final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(createTempJar("test.jar"));
+        File tempDirectory = FileTestUtils.createTempDirectory();
+        File jarLocation = new File(tempDirectory, "test.jar");
+        FileTestUtils.createJarWithContents(jarLocation /* no contents*/);
+
+        final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation);
 
         assertNoFileLeak( new Runnable () {
             @Override
@@ -139,7 +81,11 @@
      * However, it is tricky without it erroring-out. */
     @Test
     public void isInvalidJarTest() throws Exception {
-        final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(createTempJar("test.jar"));
+        File tempDirectory = FileTestUtils.createTempDirectory();
+        File jarLocation = new File(tempDirectory, "test.jar");
+        FileTestUtils.createJarWithContents(jarLocation /* no contents*/);
+
+        final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation);
         final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS);
 
         assertNoFileLeak( new Runnable () {
@@ -148,13 +94,19 @@
                     assertFalse(classLoader.isInvalidJar(jnlpFile.jarDesc));
             }
         });
-
     }
     
     @Test
     public void getMainClassNameTest() throws Exception {
-        /* Test with main-class */{
-            final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(createTempJar("test.jar", "Main-Class: DummyClass\n"));
+        File tempDirectory = FileTestUtils.createTempDirectory();
+        File jarLocation = new File(tempDirectory, "test.jar");
+
+        /* Test with main-class in manifest */ {
+            Manifest manifest = new Manifest();
+            manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, "DummyClass");
+            FileTestUtils.createJarWithContents(jarLocation, manifest);
+
+            final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation);
             final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS);
 
             assertNoFileLeak(new Runnable() {
@@ -164,8 +116,10 @@
                 }
             });
         }
-        /* Test with-out main-class */{
-            final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(createTempJar("test.jar", ""));
+        /* Test with-out any main-class specified */ {
+            FileTestUtils.createJarWithContents(jarLocation /* No contents */);
+
+            final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation);
             final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS);
 
             assertNoFileLeak(new Runnable() {
@@ -188,7 +142,11 @@
     /* Note: Although it does a basic check, this mainly checks for file-descriptor leak */
     @Test
     public void checkForMainFileLeakTest() throws Exception {
-        final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(createTempJar("test.jar", ""));
+        File tempDirectory = FileTestUtils.createTempDirectory();
+        File jarLocation = new File(tempDirectory, "test.jar");
+        FileTestUtils.createJarWithContents(jarLocation /* No contents */);
+
+        final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation);
         final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS);
         assertNoFileLeak(new Runnable() {
             @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-extensions/net/sourceforge/jnlp/util/FileTestUtils.java	Mon Jun 03 10:51:47 2013 -0400
@@ -0,0 +1,129 @@
+/*
+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.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.management.ManagementFactory;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import net.sourceforge.jnlp.ServerAccess;
+
+public class FileTestUtils {
+
+    /* Get the open file-descriptor count for the process. Note that this is
+     * specific to Unix-like operating systems. */
+    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 public void assertNoFileLeak(Runnable runnable) {
+        long filesOpenBefore = getOpenFileDescriptorCount();
+        runnable.run();
+        long filesLeaked = getOpenFileDescriptorCount() - filesOpenBefore;
+        assertEquals(0, filesLeaked);
+    }
+
+    /* Creates a file with the given contents */
+    static public void createFileWithContents(File file, String contents)
+            throws IOException {
+        PrintWriter out = new PrintWriter(file);
+        out.write(contents);
+        out.close();
+    }
+
+    /* Creates a jar in a temporary directory, with the given name & file contents */
+    static public void createJarWithContents(File jarFile, Manifest manifestContents, File... fileContents)
+            throws Exception {
+        /* Manifest quite evilly ignores all attributes if we don't specify a version! 
+         * Make sure it's set here. */
+        manifestContents.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+
+        JarOutputStream jarWriter = new JarOutputStream(new FileOutputStream(jarFile), manifestContents);
+        for (File file : fileContents) {
+            jarWriter.putNextEntry(new JarEntry(file.getName()));
+            FileInputStream fileReader = new FileInputStream(file);
+            StreamUtils.copyStream(fileReader, jarWriter);
+            fileReader.close();
+            jarWriter.closeEntry();
+        }
+        jarWriter.close();
+    }
+
+    /* Creates a jar in a temporary directory, with the given name, manifest & file contents */
+    static public void createJarWithContents(File jarFile, File... fileContents) throws Exception {
+        /* Note that we always specify a manifest, to avoid empty jars.
+         * Empty jars are not allowed by icedtea-web during the zip-file header check. */
+        createJarWithContents(jarFile, new Manifest(), fileContents);
+    }
+
+    /* Creates a temporary directory. Note that Java 7 has a method for this,
+     * but we want to remain 6-compatible. */
+    static public File createTempDirectory() throws IOException {
+        File file = File.createTempFile("temp",
+                Long.toString(System.nanoTime()));
+        file.delete();
+        if (!file.mkdir()) {
+            throw new IOException("Failed to create temporary directory '"
+                    + file + "' for test.");
+        }
+        return file;
+    }
+
+}