/* Copyright (c) 2007-2013 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.File; import java.lang.reflect.Method; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import junit.framework.TestCase; //@SuppressWarnings("unused") public class NativeTest extends TestCase { private static final String UNICODE = "[\u0444]"; public void testLoadLibraryMethods() throws Exception { Class<?>[][] params = { { Class.class }, { Class.class, Map.class }, { String.class, Class.class }, { String.class, Class.class, Map.class } }; StringBuilder signature = new StringBuilder(Long.SIZE); for (Class<?>[] paramTypes : params) { signature.setLength(0); signature.append('('); for (Class<?> p : paramTypes) { signature.append(Native.getSignature(p)); } signature.append(')'); try { Method m = Native.class.getMethod("loadLibrary", paramTypes); Class<?> returnType = m.getReturnType(); signature.append(Native.getSignature(returnType)); assertSame("Mismatched return type for signature=" + signature, Object.class, returnType); // System.out.println("===>" + m.getName() + ": " + signature); } catch(NoSuchMethodError err) { fail("No method for signature=" + signature); } } } @SuppressWarnings("deprecation") public void testVersion() { String[] INPUTS = { "1.0", "1.0.1", "2.1.3" }; float[] EXPECTED = { 1.0f, 1.0f, 2.1f }; for (int i=0;i < INPUTS.length;i++) { assertEquals("Incorrectly parsed version", EXPECTED[i], Native.parseVersion(INPUTS[i])); } } public void testLongStringGeneration() { StringBuilder buf = new StringBuilder(); final int MAX = Platform.isWindowsCE() ? 200000 : 2000000; for (int i=0;i < MAX;i++) { buf.append('a'); } String s1 = buf.toString(); Memory m = new Memory((MAX + 1)*Native.WCHAR_SIZE); m.setWideString(0, s1); assertEquals("Missing terminator after write", 0, m.getChar(MAX*Native.WCHAR_SIZE)); String s2 = m.getWideString(0); assertEquals("Wrong string read length", s1.length(), s2.length()); assertEquals("Improper wide string read", s1, s2); } public void testCustomStringEncoding() throws Exception { final String ENCODING = System.getProperty("file.encoding"); // Keep stuff within the extended ASCII range so we work with more // limited native encodings String UNICODE = "Un \u00e9l\u00e9ment gr\u00e2ce \u00e0 l'index"; if (!UNICODE.equals(new String(UNICODE.getBytes()))) { // If the extended characters aren't encodable in the default // encoding, punt and use straight ASCII UNICODE = ""; for (char ch=1;ch < 128;ch++) { UNICODE += ch; } } final String UNICODEZ = UNICODE + "\0more stuff"; byte[] customEncoded = Native.getBytes(UNICODE, ENCODING); byte[] expected = UNICODE.getBytes(ENCODING); for (int i=0;i < Math.min(customEncoded.length, expected.length);i++) { assertEquals("Improperly encoded (" + ENCODING + ") from Java at " + i, expected[i], customEncoded[i]); } assertEquals("Wrong number of encoded characters (" + ENCODING + ")", expected.length, customEncoded.length); String result = Native.toString(customEncoded, ENCODING); assertEquals("Improperly decoded from native bytes (" + ENCODING + ")", UNICODE, result); assertEquals("Should truncate bytes at NUL terminator", UNICODE, Native.toString(UNICODEZ.getBytes(ENCODING), ENCODING)); } public void testToStringList() { List<String> expected = Arrays.asList(getClass().getPackage().getName(), getClass().getSimpleName(), "testToStringList"); StringBuilder sb = new StringBuilder(); for (String value : expected) { sb.append(value).append('\0'); } sb.append('\0'); List<String> actual = Native.toStringList(sb.toString().toCharArray()); assertEquals("Mismatched result size", expected.size(), actual.size()); for (int index = 0; index < expected.size(); index++) { String expValue = expected.get(index); String actValue = actual.get(index); assertEquals("Mismatched value at index #" + index, expValue, actValue); } } public void testDefaultStringEncoding() throws Exception { final String UNICODE = "\u0444\u043b\u0441\u0432\u0443"; final String UNICODEZ = UNICODE + "\0more stuff"; byte[] nativeEnc = Native.getBytes(UNICODE); byte[] expected = UNICODE.getBytes(Native.DEFAULT_ENCODING); for (int i=0;i < Math.min(nativeEnc.length, expected.length);i++) { assertEquals("Improperly encoded at " + i, expected[i], nativeEnc[i]); } assertEquals("Wrong number of encoded characters", expected.length, nativeEnc.length); String result = Native.toString(nativeEnc); // The native encoding might not support our test string; the result // will then be all '?' if (!result.matches("^\\?+$")) { assertEquals("Improperly decoded", UNICODE, result); } // When the native encoding doesn't support our test string, we can only // usefully compare the lengths. assertEquals("Should truncate bytes at NUL terminator", UNICODE.length(), Native.toString(UNICODEZ.getBytes(Native.DEFAULT_ENCODING)).length()); } public void testCustomizeDefaultStringEncoding() { Properties oldprops = (Properties)System.getProperties().clone(); String encoding = null; // Choose a charset that is not the default encoding so we can actually // tell we changed it. for (String charset : Charset.availableCharsets().keySet()) { if (!charset.equals(Native.DEFAULT_ENCODING)) { encoding = charset; break; } } assertNotNull("No available encodings other than the default!?", encoding); try { System.setProperty("jna.encoding", encoding); assertEquals("Default encoding should match jna.encoding setting", encoding, Native.getDefaultStringEncoding()); } finally { System.setProperties(oldprops); } } public void testSizeof() { assertEquals("Wrong bool size", 1, Native.BOOL_SIZE); } public static interface TestLib extends Library { interface VoidCallback extends Callback { void callback(); } void callVoidCallback(VoidCallback callback); } public void testSynchronizedAccess() throws Exception { final boolean[] lockHeld = { false }; final NativeLibrary nlib = NativeLibrary.getInstance("testlib", TestLib.class.getClassLoader()); final TestLib lib = Native.loadLibrary("testlib", TestLib.class); final TestLib synchlib = (TestLib)Native.synchronizedLibrary(lib); final TestLib.VoidCallback cb = new TestLib.VoidCallback() { @Override public void callback() { lockHeld[0] = Thread.holdsLock(nlib); } }; Thread t0 = new Thread() { @Override public void run() { lib.callVoidCallback(cb); } }; t0.start(); t0.join(); assertFalse("NativeLibrary lock should not be held during native call to normal library", lockHeld[0]); Thread t1 = new Thread() { @Override public void run() { synchlib.callVoidCallback(cb); } }; t1.start(); t1.join(); assertTrue("NativeLibrary lock should be held during native call to synchronized library", lockHeld[0]); } interface TestInterface extends Library { static class InnerTestClass extends Structure { interface TestCallback extends Callback { } static class InnerSubclass extends InnerTestClass implements Structure.ByReference { } @Override protected List<String> getFieldOrder() { return Collections.<String>emptyList(); } } } public void testFindInterfaceClass() throws Exception { Class<?> interfaceClass = TestInterface.class; Class<?> cls = TestInterface.InnerTestClass.class; Class<?> subClass = TestInterface.InnerTestClass.InnerSubclass.class; Class<?> callbackClass = TestInterface.InnerTestClass.TestCallback.class; assertEquals("Enclosing interface not found for class", interfaceClass, Native.findEnclosingLibraryClass(cls)); assertEquals("Enclosing interface not found for derived class", interfaceClass, Native.findEnclosingLibraryClass(subClass)); assertEquals("Enclosing interface not found for callback", interfaceClass, Native.findEnclosingLibraryClass(callbackClass)); } public interface TestInterfaceWithInstance extends Library { int TEST_ALIGNMENT = Structure.ALIGN_NONE; TypeMapper TEST_MAPPER = new DefaultTypeMapper(); String TEST_ENCODING = "test-encoding"; Map<String, Object> TEST_OPTS = new HashMap<String, Object>() { private static final long serialVersionUID = 1L; // we're not serializing it { put(OPTION_CLASSLOADER, TestInterfaceWithInstance.class.getClassLoader()); put(OPTION_TYPE_MAPPER, TEST_MAPPER); put(OPTION_STRUCTURE_ALIGNMENT, Integer.valueOf(TEST_ALIGNMENT)); put(OPTION_STRING_ENCODING, TEST_ENCODING); } }; TestInterfaceWithInstance ARBITRARY = Native.loadLibrary("testlib", TestInterfaceWithInstance.class, TEST_OPTS); abstract class TestStructure extends Structure {} } public void testOptionsInferenceFromInstanceField() { Class<?>[] classes = { TestInterfaceWithInstance.class, TestInterfaceWithInstance.TestStructure.class }; String[] desc = { "interface", "structure from interface" }; for (int i=0;i < classes.length;i++) { assertEquals("Wrong type mapper found for " + desc[i], TestInterfaceWithInstance.TEST_MAPPER, Native.getTypeMapper(classes[i])); assertEquals("Wrong alignment found for " + desc[i], TestInterfaceWithInstance.TEST_ALIGNMENT, Native.getStructureAlignment(classes[i])); assertEquals("Wrong string encoding found for " + desc[i], TestInterfaceWithInstance.TEST_ENCODING, Native.getStringEncoding(classes[i])); } } public interface TestInterfaceWithOptions extends Library { int TEST_ALIGNMENT = Structure.ALIGN_NONE; TypeMapper TEST_MAPPER = new DefaultTypeMapper(); String TEST_ENCODING = "test-encoding"; Map<String, Object> OPTIONS = new HashMap<String, Object>() { private static final long serialVersionUID = 1L; // we're not serializing it { put(OPTION_TYPE_MAPPER, TEST_MAPPER); put(OPTION_STRUCTURE_ALIGNMENT, Integer.valueOf(TEST_ALIGNMENT)); put(OPTION_STRING_ENCODING, TEST_ENCODING); } }; abstract class TestStructure extends Structure {} } public void testOptionsInferenceFromOptionsField() { Class<?>[] classes = { TestInterfaceWithOptions.class, TestInterfaceWithOptions.TestStructure.class }; for (Class<?> cls : classes) { assertEquals("Wrong type mapper found", TestInterfaceWithOptions.TEST_MAPPER, Native.getTypeMapper(cls)); assertEquals("Wrong alignment found", TestInterfaceWithOptions.TEST_ALIGNMENT, Native.getStructureAlignment(cls)); assertEquals("Wrong encoding found", TestInterfaceWithOptions.TEST_ENCODING, Native.getStringEncoding(cls)); } } public interface TestInterfaceWithTypeMapper extends Library { TypeMapper TEST_MAPPER = new DefaultTypeMapper(); TypeMapper TYPE_MAPPER = TEST_MAPPER; abstract class TestStructure extends Structure { } } public void testOptionsInferenceFromTypeMapperField() { assertEquals("Wrong type mapper found for interface which provides TYPE_MAPPER", TestInterfaceWithTypeMapper.TEST_MAPPER, Native.getTypeMapper(TestInterfaceWithTypeMapper.class)); assertEquals("Wrong type mapper found for structure from interface which provides TYPE_MAPPER", TestInterfaceWithTypeMapper.TEST_MAPPER, Native.getTypeMapper(TestInterfaceWithTypeMapper.TestStructure.class)); } public interface TestInterfaceWithAlignment extends Library { int STRUCTURE_ALIGNMENT = Structure.ALIGN_NONE; abstract class TestStructure extends Structure { } } public void testOptionsInferenceFromAlignmentField() { assertEquals("Wrong alignment found for interface which provides STRUCTURE_ALIGNMENT", Structure.ALIGN_NONE, Native.getStructureAlignment(TestInterfaceWithAlignment.class)); assertEquals("Wrong alignment found for structure from interface which provides STRUCTURE_ALIGNMENT", Structure.ALIGN_NONE, Native.getStructureAlignment(TestInterfaceWithAlignment.TestStructure.class)); } public interface TestInterfaceWithEncoding extends Library { String STRING_ENCODING = "test-encoding"; abstract class TestStructure extends Structure { } } public void testOptionsInferenceFromEncodingField() { assertEquals("Wrong encoding found for interface which provides STRING_ENCODING", TestInterfaceWithEncoding.STRING_ENCODING, Native.getStringEncoding(TestInterfaceWithEncoding.class)); assertEquals("Wrong encoding found for structure from interface which provides STRING_ENCODING", TestInterfaceWithEncoding.STRING_ENCODING, Native.getStringEncoding(TestInterfaceWithEncoding.TestStructure.class)); } public interface OptionsBase extends Library { int STRUCTURE_ALIGNMENT = Structure.ALIGN_NONE; TypeMapper TYPE_MAPPER = new DefaultTypeMapper(); class TypeMappedStructure extends Structure { public String stringField; @Override protected List <String>getFieldOrder() { return Arrays.asList("stringField"); } } } public interface OptionsSubclass extends OptionsBase, Library { TypeMapper _MAPPER = new DefaultTypeMapper(); Map<String, ?> _OPTIONS = Collections.singletonMap(Library.OPTION_TYPE_MAPPER, _MAPPER); OptionsSubclass INSTANCE = Native.loadLibrary("testlib", OptionsSubclass.class, _OPTIONS); } public void testStructureOptionsInference() { Structure s = new OptionsBase.TypeMappedStructure(); assertEquals("Wrong structure alignment for base structure", Structure.ALIGN_NONE, Native.getStructureAlignment(s.getClass())); assertEquals("Wrong type mapper for base structure", OptionsBase.TYPE_MAPPER, s.getTypeMapper()); } public void testCharArrayToString() { char[] buf = { 'a', 'b', 'c', '\0', 'd', 'e' }; assertEquals("Wrong String generated", "abc", Native.toString(buf)); } public void testByteArrayToString() { byte[] buf = { 'a', 'b', 'c', '\0', 'd', 'e' }; assertEquals("Wrong String generated", "abc", Native.toString(buf)); } public void testToByteArray() { final String VALUE = getName(); byte[] buf = Native.toByteArray(VALUE); assertEquals("Wrong byte array length", VALUE.getBytes().length+1, buf.length); assertEquals("Missing NUL terminator", (byte)0, buf[buf.length-1]); assertEquals("Wrong byte array contents", VALUE, new String(buf, 0, buf.length-1)); } public void testToByteArrayWithEncoding() throws Exception { final String ENCODING = "utf8"; final String VALUE = getName() + UNICODE; byte[] buf = Native.toByteArray(VALUE, ENCODING); assertEquals("Wrong byte array length", VALUE.getBytes(ENCODING).length+1, buf.length); assertEquals("Missing NUL terminator", (byte)0, buf[buf.length-1]); assertEquals("Wrong byte array contents", VALUE, new String(buf, 0, buf.length-1, ENCODING)); } public void testToCharArray() { final String VALUE = getName() + UNICODE; char[] buf = Native.toCharArray(VALUE); assertEquals("Wrong char array length", VALUE.length()+1, buf.length); assertEquals("Missing NUL terminator", (char)0, buf[buf.length-1]); assertEquals("Wrong char array contents: " + new String(buf), VALUE, new String(buf, 0, buf.length-1)); } private static class TestCallback implements Callback { public static final TypeMapper TYPE_MAPPER = new DefaultTypeMapper(); public void callback() { } } public void testGetTypeMapperFromCallbackInterface() throws Exception { assertEquals("Wrong type mapper for callback class", TestCallback.TYPE_MAPPER, Native.getTypeMapper(TestCallback.class)); } public void testStringReplace() { assertEquals("Bad replace", "abcdefg", Native.replace("z", "a", "zbcdefg")); assertEquals("Bad replace", "abcdefg", Native.replace("z", "g", "abcdefz")); assertEquals("Bad replace", "abcdefg", Native.replace("z", "d", "abczefg")); assertEquals("Bad replace", "abcaefa", Native.replace("z", "a", "zbczefz")); } public void testRemoveTemporaries() throws Exception { File dir = Native.getTempDir(); File tmp = new File(dir, Native.JNA_TMPLIB_PREFIX); tmp.delete(); try { assertTrue("Couldn't create temporary file " + tmp, tmp.createNewFile()); assertTrue("File isn't recognized as unpacked", Native.isUnpacked(tmp)); Native.markTemporaryFile(tmp); Native.removeTemporaryFiles(); assertFalse("Temporary file still exists", tmp.exists()); } finally { tmp.delete(); } } private static final String NUL = "\0"; public void testStringConversion() { byte[] buf = (getName() + NUL).getBytes(); assertEquals("C string improperly converted", getName(), Native.toString(buf)); } public void testStringConversionWithEncoding() throws Exception { byte[] buf = (getName() + UNICODE + NUL).getBytes("utf8"); assertEquals("Encoded C string improperly converted", getName() + UNICODE, Native.toString(buf, "utf8")); } public void testWideStringConversion() { char[] buf = (getName() + NUL).toCharArray(); assertEquals("Wide C string improperly converted", getName(), Native.toString(buf)); } public void testGetBytes() throws Exception { byte[] buf = Native.getBytes(getName() + UNICODE, "utf8"); assertEquals("Incorrect native bytes from Java String", getName() + UNICODE, new String(buf, "utf8")); } public void testGetBytesBadEncoding() throws Exception { byte[] buf = Native.getBytes(getName(), "unsupported"); assertEquals("Incorrect fallback bytes with bad encoding", getName(), new String(buf, System.getProperty("file.encoding"))); } public void testFindDirectMappedClassFailure() { try { Native.findDirectMappedClass(NativeTest.class); fail("Expect an exception if native-mapped class can't be found"); } catch(IllegalArgumentException e) { } } /** This method facilitates running tests from a single entry point outside of ant (i.e. for androide, WCE, etc.). */ public static void main(String[] args) { if (args.length == 0) { junit.textui.TestRunner.run(NativeTest.class); } else { if (args.length == 1 && "all".equals(args[0])) { args = new String[] { "com.sun.jna.NativeTest", "com.sun.jna.NativeLibraryTest", "com.sun.jna.PointerTest", "com.sun.jna.MemoryTest", "com.sun.jna.LibraryLoadTest", "com.sun.jna.ArgumentsMarshalTest", "com.sun.jna.ReturnTypesTest", "com.sun.jna.TypeMapperTest", "com.sun.jna.ByReferenceArgumentsTest", "com.sun.jna.LastErrorTest", "com.sun.jna.StructureTest",// 1 wce failure (RO fields) "com.sun.jna.StructureByValueTest", "com.sun.jna.UnionTest", "com.sun.jna.IntegerTypeTest", "com.sun.jna.VMCrashProtectionTest", "com.sun.jna.CallbacksTest", "com.sun.jna.JNAUnloadTest", "com.sun.jna.DirectTest", "com.sun.jna.DirectArgumentsMarshalTest", "com.sun.jna.DirectByReferenceArgumentsTest", "com.sun.jna.DirectTypeMapperTest", "com.sun.jna.DirectReturnTypesTest", "com.sun.jna.DirectStructureByValueTest", "com.sun.jna.DirectCallbacksTest", }; } System.out.println("Test suites: " + args.length); for (int i=0;i < args.length;i++) { System.out.println("Running tests on class " + args[i]); try { junit.textui.TestRunner.run((Class<? extends TestCase>) Class.forName(args[i])); } catch(Throwable e) { e.printStackTrace(); } } try { Thread.sleep(300000); } catch(Exception e) { } } } public void testVersionComparison() { assertTrue("Equal version", Native.isCompatibleVersion("5.1.0", "5.1.0")); assertTrue("New revision", Native.isCompatibleVersion("5.2.0", "5.2.1")); assertTrue("New minor provided, older minor expected", Native.isCompatibleVersion("5.1.0", "5.10.0")); assertFalse("Old minor provided, new minor expected", Native.isCompatibleVersion("5.10.0", "5.1.0")); assertFalse("Different major (expected < provided)", Native.isCompatibleVersion("4.0.0", "5.0.0")); assertFalse("Different major (expected > provided)", Native.isCompatibleVersion("5.0.0", "4.0.0")); } }