/* Copyright (c) 2007 Timothy Wall, All Rights Reserved * * The contents of this file is dual-licensed under 2 * alternative Open Source/Free licenses: LGPL 2.1 or later and * Apache License 2.0. (starting with JNA version 4.0.0). * * You can freely decide which license you want to apply to * the project. * * You may obtain a copy of the LGPL License at: * * http://www.gnu.org/licenses/licenses.html * * A copy is also included in the downloadable source code package * containing JNA, in file "LGPL2.1". * * You may obtain a copy of the Apache License at: * * http://www.apache.org/licenses/ * * A copy is also included in the downloadable source code package * containing JNA, in file "AL2.0". */ package com.sun.jna; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import com.sun.jna.win32.W32APIOptions; import junit.framework.TestCase; public class NativeLibraryTest extends TestCase { public static interface TestLibrary extends Library { int callCount(); } public void testMapSharedLibraryName() { final Object[][] MAPPINGS = { { Platform.MAC, "lib", ".dylib" }, { Platform.LINUX, "lib", ".so" }, { Platform.WINDOWS, "", ".dll" }, { Platform.SOLARIS, "lib", ".so" }, { Platform.FREEBSD, "lib", ".so" }, { Platform.OPENBSD, "lib", ".so" }, { Platform.WINDOWSCE, "", ".dll" }, { Platform.AIX, "lib", ".a" }, { Platform.ANDROID, "lib", ".so" }, { Platform.GNU, "lib", ".so" }, { Platform.KFREEBSD, "lib", ".so" }, { Platform.NETBSD, "lib", ".so" }, }; for (int i=0;i < MAPPINGS.length;i++) { int osType = ((Integer)MAPPINGS[i][0]).intValue(); if (osType == Platform.getOSType()) { assertEquals("Wrong shared library name mapping", MAPPINGS[i][1] + "testlib" + MAPPINGS[i][2], NativeLibrary.mapSharedLibraryName("testlib")); } } } public void testGCNativeLibrary() throws Exception { NativeLibrary lib = NativeLibrary.getInstance("testlib"); Reference<NativeLibrary> ref = new WeakReference<NativeLibrary>(lib); lib = null; System.gc(); long start = System.currentTimeMillis(); while (ref.get() != null) { Thread.sleep(10); if ((System.currentTimeMillis() - start) > 5000L) break; } assertNull("Library not GC'd", ref.get()); } public void testAvoidDuplicateLoads() throws Exception { NativeLibrary.disposeAll(); // Give the system a moment to unload the library; on OSX we // occasionally get the same library handle back on subsequent dlopen Thread.sleep(2); TestLibrary lib = Native.loadLibrary("testlib", TestLibrary.class); assertEquals("Library should be newly loaded after explicit dispose of all native libraries", 1, lib.callCount()); if (lib.callCount() <= 1) { fail("Library should not be reloaded without dispose"); } } public void testUseSingleLibraryInstance() { TestLibrary lib = Native.loadLibrary("testlib", TestLibrary.class); int count = lib.callCount(); TestLibrary lib2 = Native.loadLibrary("testlib", TestLibrary.class); int count2 = lib2.callCount(); assertEquals("Interfaces should share a library instance", count + 1, count2); } public void testAliasLibraryFilename() { TestLibrary lib = Native.loadLibrary("testlib", TestLibrary.class); int count = lib.callCount(); NativeLibrary nl = NativeLibrary.getInstance("testlib"); TestLibrary lib2 = Native.loadLibrary(nl.getFile().getName(), TestLibrary.class); int count2 = lib2.callCount(); assertEquals("Simple filename load not aliased", count + 1, count2); } public void testAliasLibraryFullPath() { TestLibrary lib = Native.loadLibrary("testlib", TestLibrary.class); int count = lib.callCount(); NativeLibrary nl = NativeLibrary.getInstance("testlib"); TestLibrary lib2 = Native.loadLibrary(nl.getFile().getAbsolutePath(), TestLibrary.class); int count2 = lib2.callCount(); assertEquals("Full pathname load not aliased", count + 1, count2); } public void testAliasSimpleLibraryName() throws Exception { NativeLibrary nl = NativeLibrary.getInstance("testlib"); File file = nl.getFile(); Reference<NativeLibrary> ref = new WeakReference<NativeLibrary>(nl); nl = null; System.gc(); long start = System.currentTimeMillis(); while (ref.get() != null) { Thread.sleep(10); if ((System.currentTimeMillis() - start) > 5000L) { fail("Timed out waiting for library to be GC'd"); } } TestLibrary lib = Native.loadLibrary(file.getAbsolutePath(), TestLibrary.class); int count = lib.callCount(); TestLibrary lib2 = Native.loadLibrary("testlib", TestLibrary.class); int count2 = lib2.callCount(); assertEquals("Simple library name not aliased", count + 1, count2); } public void testRejectNullFunctionName() { NativeLibrary lib = NativeLibrary.getInstance("testlib"); try { Function f = lib.getFunction(null); fail("Function must have a name: " + f); } catch(NullPointerException e) { // expected } } public void testIncludeSymbolNameInLookupError() { NativeLibrary lib = NativeLibrary.getInstance("testlib"); try { lib.getGlobalVariableAddress(getName()); fail("Non-existent global variable lookup should fail"); } catch(UnsatisfiedLinkError e) { assertTrue("Expect symbol name in error message: " + e.getMessage(), e.getMessage().indexOf(getName()) != -1); } } public void testFunctionHoldsLibraryReference() throws Exception { NativeLibrary lib = NativeLibrary.getInstance("testlib"); Reference<NativeLibrary> ref = new WeakReference<NativeLibrary>(lib); Function f = lib.getFunction("callCount"); lib = null; System.gc(); for (long start = System.currentTimeMillis(); (ref.get() != null) && ((System.currentTimeMillis() - start) < 2000L); ) { Thread.sleep(10); } assertNotNull("Library GC'd when it should not be", ref.get()); f.invokeInt(new Object[0]); f = null; System.gc(); for (long start = System.currentTimeMillis(); (ref.get() != null) && ((System.currentTimeMillis() - start) < 5000L); ) { Thread.sleep(10); } assertNull("Library not GC'd", ref.get()); } public void testLookupGlobalVariable() { NativeLibrary lib = NativeLibrary.getInstance("testlib"); Pointer global = lib.getGlobalVariableAddress("test_global"); assertNotNull("Test variable not found", global); final int MAGIC = 0x12345678; assertEquals("Wrong value for library global variable", MAGIC, global.getInt(0)); global.setInt(0, MAGIC+1); assertEquals("Library global variable not updated", MAGIC+1, global.getInt(0)); } public void testMatchUnversionedToVersioned() throws Exception { File lib0 = File.createTempFile("lib", ".so.0"); File dir = lib0.getParentFile(); String name = lib0.getName(); name = name.substring(3, name.indexOf(".so")); lib0.deleteOnExit(); File lib1 = new File(dir, "lib" + name + ".so.1.0"); lib1.createNewFile(); lib1.deleteOnExit(); File lib1_1 = new File(dir, "lib" + name + ".so.1.1"); lib1_1.createNewFile(); lib1_1.deleteOnExit(); assertEquals("Latest versioned library not found when unversioned requested for path=" + dir, lib1_1.getCanonicalPath(), NativeLibrary.matchLibrary(name, Collections.singletonList(dir.getCanonicalPath()))); } public void testAvoidFalseMatch() throws Exception { File lib0 = File.createTempFile("lib", ".so.1"); File dir = lib0.getParentFile(); lib0.deleteOnExit(); String name = lib0.getName(); name = name.substring(3, name.indexOf(".so")); File lib1 = new File(dir, "lib" + name + "-client.so.2"); lib1.createNewFile(); lib1.deleteOnExit(); assertEquals("Library with similar prefix should be ignored for path=" + dir, lib0.getCanonicalPath(), NativeLibrary.matchLibrary(name, Collections.singletonList(dir.getCanonicalPath()))); } public void testParseVersion() throws Exception { String[] VERSIONS = { "1", "1.2", "1.2.3", "1.2.3.4", }; double[] EXPECTED = { 1, 1.02, 1.0203, 1.020304, }; for (int i=0;i < VERSIONS.length;i++) { assertEquals("Badly parsed version", EXPECTED[i], NativeLibrary.parseVersion(VERSIONS[i]), 0.0000001); } } // XFAIL on android public void testGetProcess() { if (Platform.isAndroid()) { fail("dlopen(NULL) segfaults on Android"); } NativeLibrary process = NativeLibrary.getProcess(); // Access a common C library function process.getFunction("printf"); } private String expected(String f) { return new File(f).exists() ? f : null; } public void testMatchFramework() { if (!Platform.isMac()) { return; } final String[][] MAPPINGS = { // Depending on the system, /Library/Frameworks may or may not // have anything in it. { "QtCore", expected("/Library/Frameworks/QtCore.framework/QtCore") }, { "Adobe AIR", expected("/Library/Frameworks/Adobe AIR.framework/Adobe AIR") }, { "QuickTime", expected("/System/Library/Frameworks/QuickTime.framework/QuickTime") }, { "QuickTime.framework/Versions/Current/QuickTime", expected("/System/Library/Frameworks/QuickTime.framework/Versions/Current/QuickTime") }, }; for (int i=0;i < MAPPINGS.length;i++) { assertEquals("Wrong framework mapping", MAPPINGS[i][1], NativeLibrary.matchFramework(MAPPINGS[i][0])); } } public void testLoadLibraryWithOptions() { Native.loadLibrary("testlib", TestLibrary.class, Collections.singletonMap(Library.OPTION_OPEN_FLAGS, Integer.valueOf(-1))); } public interface Kernel32 { int GetLastError(); void SetLastError(int code); } public void testInterceptLastError() { if (!Platform.isWindows()) { return; } NativeLibrary kernel32 = NativeLibrary.getInstance("kernel32", W32APIOptions.DEFAULT_OPTIONS); Function get = kernel32.getFunction("GetLastError"); Function set = kernel32.getFunction("SetLastError"); assertEquals("SetLastError should not be customized", Function.class, set.getClass()); assertTrue("GetLastError should be a Function", Function.class.isAssignableFrom(get.getClass())); assertTrue("GetLastError should be a customized Function", get.getClass() != Function.class); final int EXPECTED = 42; set.invokeVoid(new Object[] { Integer.valueOf(EXPECTED) }); assertEquals("Wrong error", EXPECTED, get.invokeInt(null)); } public void testCleanupOnLoadError() throws Exception { int previousTempFileCount = Native.getTempDir().listFiles().length; try { NativeLibrary.getInstance("disfunct", Collections.singletonMap(Library.OPTION_CLASSLOADER, new DisfunctClassLoader())); fail("Expected NativeLibrary.getInstance() to fail with an UnsatisfiedLinkError here."); } catch(UnsatisfiedLinkError e) { int currentTempFileCount = Native.getTempDir().listFiles().length; assertEquals("Extracted native library should be cleaned up again. Number of files in temp directory:", previousTempFileCount, currentTempFileCount); } } // returns unloadable "shared library" on any input private class DisfunctClassLoader extends ClassLoader { @Override public URL getResource(String name) { try { return new URL("jar", "", name); } catch(MalformedURLException e) { fail("Could not even create disfunct library URL: " + e.getMessage()); return null; } } @Override public InputStream getResourceAsStream(String name) { return new ByteArrayInputStream(new byte[0]); } } public static void main(String[] args) { junit.textui.TestRunner.run(NativeLibraryTest.class); } }