1 /* Copyright (c) 2007 Timothy Wall, All Rights Reserved 2 * 3 * The contents of this file is dual-licensed under 2 4 * alternative Open Source/Free licenses: LGPL 2.1 or later and 5 * Apache License 2.0. (starting with JNA version 4.0.0). 6 * 7 * You can freely decide which license you want to apply to 8 * the project. 9 * 10 * You may obtain a copy of the LGPL License at: 11 * 12 * http://www.gnu.org/licenses/licenses.html 13 * 14 * A copy is also included in the downloadable source code package 15 * containing JNA, in file "LGPL2.1". 16 * 17 * You may obtain a copy of the Apache License at: 18 * 19 * http://www.apache.org/licenses/ 20 * 21 * A copy is also included in the downloadable source code package 22 * containing JNA, in file "AL2.0". 23 */ 24 package com.sun.jna; 25 26 import java.io.ByteArrayInputStream; 27 import java.io.File; 28 import java.io.InputStream; 29 import java.lang.ref.Reference; 30 import java.lang.ref.WeakReference; 31 import java.net.MalformedURLException; 32 import java.net.URL; 33 import java.util.Collections; 34 35 import com.sun.jna.win32.W32APIOptions; 36 37 import junit.framework.TestCase; 38 39 public class NativeLibraryTest extends TestCase { 40 41 public static interface TestLibrary extends Library { callCount()42 int callCount(); 43 } 44 testMapSharedLibraryName()45 public void testMapSharedLibraryName() { 46 final Object[][] MAPPINGS = { 47 { Platform.MAC, "lib", ".dylib" }, 48 { Platform.LINUX, "lib", ".so" }, 49 { Platform.WINDOWS, "", ".dll" }, 50 { Platform.SOLARIS, "lib", ".so" }, 51 { Platform.FREEBSD, "lib", ".so" }, 52 { Platform.OPENBSD, "lib", ".so" }, 53 { Platform.WINDOWSCE, "", ".dll" }, 54 { Platform.AIX, "lib", ".a" }, 55 { Platform.ANDROID, "lib", ".so" }, 56 { Platform.GNU, "lib", ".so" }, 57 { Platform.KFREEBSD, "lib", ".so" }, 58 { Platform.NETBSD, "lib", ".so" }, 59 }; 60 for (int i=0;i < MAPPINGS.length;i++) { 61 int osType = ((Integer)MAPPINGS[i][0]).intValue(); 62 if (osType == Platform.getOSType()) { 63 assertEquals("Wrong shared library name mapping", 64 MAPPINGS[i][1] + "testlib" + MAPPINGS[i][2], 65 NativeLibrary.mapSharedLibraryName("testlib")); 66 } 67 } 68 } 69 testGCNativeLibrary()70 public void testGCNativeLibrary() throws Exception { 71 NativeLibrary lib = NativeLibrary.getInstance("testlib"); 72 Reference<NativeLibrary> ref = new WeakReference<NativeLibrary>(lib); 73 lib = null; 74 System.gc(); 75 long start = System.currentTimeMillis(); 76 while (ref.get() != null) { 77 Thread.sleep(10); 78 if ((System.currentTimeMillis() - start) > 5000L) 79 break; 80 } 81 assertNull("Library not GC'd", ref.get()); 82 } 83 testAvoidDuplicateLoads()84 public void testAvoidDuplicateLoads() throws Exception { 85 NativeLibrary.disposeAll(); 86 // Give the system a moment to unload the library; on OSX we 87 // occasionally get the same library handle back on subsequent dlopen 88 Thread.sleep(2); 89 90 TestLibrary lib = Native.load("testlib", TestLibrary.class); 91 assertEquals("Library should be newly loaded after explicit dispose of all native libraries", 92 1, lib.callCount()); 93 if (lib.callCount() <= 1) { 94 fail("Library should not be reloaded without dispose"); 95 } 96 } 97 testUseSingleLibraryInstance()98 public void testUseSingleLibraryInstance() { 99 TestLibrary lib = Native.load("testlib", TestLibrary.class); 100 int count = lib.callCount(); 101 TestLibrary lib2 = Native.load("testlib", TestLibrary.class); 102 int count2 = lib2.callCount(); 103 assertEquals("Interfaces should share a library instance", 104 count + 1, count2); 105 } 106 testAliasLibraryFilename()107 public void testAliasLibraryFilename() { 108 TestLibrary lib = Native.load("testlib", TestLibrary.class); 109 int count = lib.callCount(); 110 NativeLibrary nl = NativeLibrary.getInstance("testlib"); 111 TestLibrary lib2 = Native.load(nl.getFile().getName(), TestLibrary.class); 112 int count2 = lib2.callCount(); 113 assertEquals("Simple filename load not aliased", count + 1, count2); 114 } 115 testAliasLibraryFullPath()116 public void testAliasLibraryFullPath() { 117 TestLibrary lib = Native.load("testlib", TestLibrary.class); 118 int count = lib.callCount(); 119 NativeLibrary nl = NativeLibrary.getInstance("testlib"); 120 TestLibrary lib2 = Native.load(nl.getFile().getAbsolutePath(), TestLibrary.class); 121 int count2 = lib2.callCount(); 122 assertEquals("Full pathname load not aliased", count + 1, count2); 123 } 124 testAliasSimpleLibraryName()125 public void testAliasSimpleLibraryName() throws Exception { 126 NativeLibrary nl = NativeLibrary.getInstance("testlib"); 127 File file = nl.getFile(); 128 Reference<NativeLibrary> ref = new WeakReference<NativeLibrary>(nl); 129 nl = null; 130 System.gc(); 131 long start = System.currentTimeMillis(); 132 while (ref.get() != null) { 133 Thread.sleep(10); 134 if ((System.currentTimeMillis() - start) > 5000L) { 135 fail("Timed out waiting for library to be GC'd"); 136 } 137 } 138 TestLibrary lib = Native.load(file.getAbsolutePath(), TestLibrary.class); 139 int count = lib.callCount(); 140 TestLibrary lib2 = Native.load("testlib", TestLibrary.class); 141 int count2 = lib2.callCount(); 142 assertEquals("Simple library name not aliased", count + 1, count2); 143 } 144 testRejectNullFunctionName()145 public void testRejectNullFunctionName() { 146 NativeLibrary lib = NativeLibrary.getInstance("testlib"); 147 try { 148 Function f = lib.getFunction(null); 149 fail("Function must have a name: " + f); 150 } catch(NullPointerException e) { 151 // expected 152 } 153 } 154 testIncludeSymbolNameInLookupError()155 public void testIncludeSymbolNameInLookupError() { 156 NativeLibrary lib = NativeLibrary.getInstance("testlib"); 157 try { 158 lib.getGlobalVariableAddress(getName()); 159 fail("Non-existent global variable lookup should fail"); 160 } 161 catch(UnsatisfiedLinkError e) { 162 assertTrue("Expect symbol name in error message: " + e.getMessage(), e.getMessage().indexOf(getName()) != -1); 163 } 164 } 165 testFunctionHoldsLibraryReference()166 public void testFunctionHoldsLibraryReference() throws Exception { 167 NativeLibrary lib = NativeLibrary.getInstance("testlib"); 168 Reference<NativeLibrary> ref = new WeakReference<NativeLibrary>(lib); 169 Function f = lib.getFunction("callCount"); 170 lib = null; 171 System.gc(); 172 for (long start = System.currentTimeMillis(); (ref.get() != null) && ((System.currentTimeMillis() - start) < 2000L); ) { 173 Thread.sleep(10); 174 } 175 assertNotNull("Library GC'd when it should not be", ref.get()); 176 f.invokeInt(new Object[0]); 177 f = null; 178 System.gc(); 179 for (long start = System.currentTimeMillis(); (ref.get() != null) && ((System.currentTimeMillis() - start) < 5000L); ) { 180 Thread.sleep(10); 181 } 182 assertNull("Library not GC'd", ref.get()); 183 } 184 testLookupGlobalVariable()185 public void testLookupGlobalVariable() { 186 NativeLibrary lib = NativeLibrary.getInstance("testlib"); 187 Pointer global = lib.getGlobalVariableAddress("test_global"); 188 assertNotNull("Test variable not found", global); 189 final int MAGIC = 0x12345678; 190 assertEquals("Wrong value for library global variable", MAGIC, global.getInt(0)); 191 192 global.setInt(0, MAGIC+1); 193 assertEquals("Library global variable not updated", MAGIC+1, global.getInt(0)); 194 } 195 testMatchUnversionedToVersioned()196 public void testMatchUnversionedToVersioned() throws Exception { 197 File lib0 = File.createTempFile("lib", ".so.0"); 198 File dir = lib0.getParentFile(); 199 String name = lib0.getName(); 200 name = name.substring(3, name.indexOf(".so")); 201 lib0.deleteOnExit(); 202 File lib1 = new File(dir, "lib" + name + ".so.1.0"); 203 lib1.createNewFile(); 204 lib1.deleteOnExit(); 205 File lib1_1 = new File(dir, "lib" + name + ".so.1.1"); 206 lib1_1.createNewFile(); 207 lib1_1.deleteOnExit(); 208 assertEquals("Latest versioned library not found when unversioned requested for path=" + dir, 209 lib1_1.getCanonicalPath(), 210 NativeLibrary.matchLibrary(name, Collections.singletonList(dir.getCanonicalPath()))); 211 } 212 testAvoidFalseMatch()213 public void testAvoidFalseMatch() throws Exception { 214 File lib0 = File.createTempFile("lib", ".so.1"); 215 File dir = lib0.getParentFile(); 216 lib0.deleteOnExit(); 217 String name = lib0.getName(); 218 name = name.substring(3, name.indexOf(".so")); 219 File lib1 = new File(dir, "lib" + name + "-client.so.2"); 220 lib1.createNewFile(); 221 lib1.deleteOnExit(); 222 assertEquals("Library with similar prefix should be ignored for path=" + dir, 223 lib0.getCanonicalPath(), 224 NativeLibrary.matchLibrary(name, Collections.singletonList(dir.getCanonicalPath()))); 225 } 226 testParseVersion()227 public void testParseVersion() throws Exception { 228 String[] VERSIONS = { 229 "1", 230 "1.2", 231 "1.2.3", 232 "1.2.3.4",}; 233 double[] EXPECTED = { 234 1, 1.02, 1.0203, 1.020304,}; 235 for (int i = 0; i < VERSIONS.length; i++) { 236 assertEquals("Badly parsed version", EXPECTED[i], NativeLibrary.parseVersion(VERSIONS[i]), 0.0000001); 237 } 238 } 239 240 // XFAIL on android testGetProcess()241 public void testGetProcess() { 242 if (Platform.isAndroid()) { 243 fail("dlopen(NULL) segfaults on Android"); 244 } 245 NativeLibrary process = NativeLibrary.getProcess(); 246 // Access a common C library function 247 process.getFunction("printf"); 248 } 249 testLoadFoundationFramework()250 public void testLoadFoundationFramework() { 251 if (!Platform.isMac()) { 252 return; 253 } 254 assertNotNull(NativeLibrary.getInstance("Foundation")); 255 } 256 testMatchSystemFramework()257 public void testMatchSystemFramework() { 258 if (!Platform.isMac()) { 259 return; 260 } 261 262 assertEquals("Wrong framework mapping", 1, 263 NativeLibrary.matchFramework("/System/Library/Frameworks/Foundation.framework/Foundation").length); 264 assertEquals("Wrong framework mapping", "/System/Library/Frameworks/Foundation.framework/Foundation", 265 NativeLibrary.matchFramework("/System/Library/Frameworks/Foundation.framework/Foundation")[0]); 266 267 assertEquals("Wrong framework mapping", 1, 268 NativeLibrary.matchFramework("/System/Library/Frameworks/Foundation").length); 269 assertEquals("Wrong framework mapping", "/System/Library/Frameworks/Foundation.framework/Foundation", 270 NativeLibrary.matchFramework("/System/Library/Frameworks/Foundation")[0]); 271 } 272 testMatchOptionalFrameworkExists()273 public void testMatchOptionalFrameworkExists() { 274 if (!Platform.isMac()) { 275 return; 276 } 277 278 if(!new File("/System/Library/Frameworks/QuickTime.framework").exists()) { 279 return; 280 } 281 282 assertEquals("Wrong framework mapping", 1, 283 NativeLibrary.matchFramework("QuickTime").length); 284 assertEquals("Wrong framework mapping", "/System/Library/Frameworks/QuickTime.framework/QuickTime", 285 NativeLibrary.matchFramework("QuickTime")[0]); 286 287 assertEquals("Wrong framework mapping", 1, 288 NativeLibrary.matchFramework("QuickTime.framework/Versions/Current/QuickTime").length); 289 assertEquals("Wrong framework mapping", "/System/Library/Frameworks/QuickTime.framework/Versions/Current/QuickTime", 290 NativeLibrary.matchFramework("QuickTime.framework/Versions/Current/QuickTime")[0]); 291 } 292 testMatchOptionalFrameworkNotFound()293 public void testMatchOptionalFrameworkNotFound() { 294 if (!Platform.isMac()) { 295 return; 296 } 297 298 if(new File(System.getProperty("user.home") + "/Library/Frameworks/QuickTime.framework").exists()) { 299 return; 300 } 301 if(new File("/Library/Frameworks/QuickTime.framework").exists()) { 302 return; 303 } 304 if(new File("/System/Library/Frameworks/QuickTime.framework").exists()) { 305 return; 306 } 307 308 assertEquals("Wrong framework mapping", 3, 309 NativeLibrary.matchFramework("QuickTime").length); 310 assertEquals("Wrong framework mapping", System.getProperty("user.home") + "/Library/Frameworks/QuickTime.framework/QuickTime", 311 NativeLibrary.matchFramework("QuickTime")[0]); 312 assertEquals("Wrong framework mapping", "/Library/Frameworks/QuickTime.framework/QuickTime", 313 NativeLibrary.matchFramework("QuickTime")[1]); 314 assertEquals("Wrong framework mapping", "/System/Library/Frameworks/QuickTime.framework/QuickTime", 315 NativeLibrary.matchFramework("QuickTime")[2]); 316 317 assertEquals("Wrong framework mapping", 3, 318 NativeLibrary.matchFramework("QuickTime.framework/Versions/Current/QuickTime").length); 319 assertEquals("Wrong framework mapping", System.getProperty("user.home") + "/Library/Frameworks/QuickTime.framework/Versions/Current/QuickTime", 320 NativeLibrary.matchFramework("QuickTime.framework/Versions/Current/QuickTime")[0]); 321 assertEquals("Wrong framework mapping", "/Library/Frameworks/QuickTime.framework/Versions/Current/QuickTime", 322 NativeLibrary.matchFramework("QuickTime.framework/Versions/Current/QuickTime")[1]); 323 assertEquals("Wrong framework mapping", "/System/Library/Frameworks/QuickTime.framework/Versions/Current/QuickTime", 324 NativeLibrary.matchFramework("QuickTime.framework/Versions/Current/QuickTime")[2]); 325 } 326 testLoadLibraryWithOptions()327 public void testLoadLibraryWithOptions() { 328 Native.load("testlib", TestLibrary.class, Collections.singletonMap(Library.OPTION_OPEN_FLAGS, Integer.valueOf(-1))); 329 } 330 331 public interface Kernel32 { GetLastError()332 int GetLastError(); SetLastError(int code)333 void SetLastError(int code); 334 } testInterceptLastError()335 public void testInterceptLastError() { 336 if (!Platform.isWindows()) { 337 return; 338 } 339 NativeLibrary kernel32 = NativeLibrary.getInstance("kernel32", W32APIOptions.DEFAULT_OPTIONS); 340 Function get = kernel32.getFunction("GetLastError"); 341 Function set = kernel32.getFunction("SetLastError"); 342 assertEquals("SetLastError should not be customized", Function.class, set.getClass()); 343 assertTrue("GetLastError should be a Function", Function.class.isAssignableFrom(get.getClass())); 344 assertTrue("GetLastError should be a customized Function", get.getClass() != Function.class); 345 final int EXPECTED = 42; 346 set.invokeVoid(new Object[] { Integer.valueOf(EXPECTED) }); 347 assertEquals("Wrong error", EXPECTED, get.invokeInt(null)); 348 } 349 testCleanupOnLoadError()350 public void testCleanupOnLoadError() throws Exception { 351 int previousTempFileCount = Native.getTempDir().listFiles().length; 352 try { 353 NativeLibrary.getInstance("disfunct", Collections.singletonMap(Library.OPTION_CLASSLOADER, new DisfunctClassLoader())); 354 fail("Expected NativeLibrary.getInstance() to fail with an UnsatisfiedLinkError here."); 355 } catch(UnsatisfiedLinkError e) { 356 int currentTempFileCount = Native.getTempDir().listFiles().length; 357 assertEquals("Extracted native library should be cleaned up again. Number of files in temp directory:", previousTempFileCount, currentTempFileCount); 358 } 359 } 360 361 // returns unloadable "shared library" on any input 362 private class DisfunctClassLoader extends ClassLoader { 363 @Override getResource(String name)364 public URL getResource(String name) { 365 try { 366 return new URL("jar", "", name); 367 } catch(MalformedURLException e) { 368 fail("Could not even create disfunct library URL: " + e.getMessage()); 369 return null; 370 } 371 } 372 373 @Override getResourceAsStream(String name)374 public InputStream getResourceAsStream(String name) { 375 return new ByteArrayInputStream(new byte[0]); 376 } 377 } 378 main(String[] args)379 public static void main(String[] args) { 380 junit.textui.TestRunner.run(NativeLibraryTest.class); 381 } 382 } 383