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