diff --git a/deps/fbjni/BUCK b/deps/fbjni/BUCK index 58b06e3ea..2fa37b922 100644 --- a/deps/fbjni/BUCK +++ b/deps/fbjni/BUCK @@ -59,7 +59,7 @@ profilo_oss_xplat_cxx_library( profilo_oss_android_library( name = "fbjni-java", srcs = glob([ - "**/*.java", + "java/**/*.java", ]), visibility = [ "PUBLIC", diff --git a/deps/fbjni/test/AndroidManifest.xml b/deps/fbjni/test/AndroidManifest.xml new file mode 100644 index 000000000..79c56d40a --- /dev/null +++ b/deps/fbjni/test/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/deps/fbjni/test/BaseFBJniTests.java b/deps/fbjni/test/BaseFBJniTests.java new file mode 100644 index 000000000..590dc590b --- /dev/null +++ b/deps/fbjni/test/BaseFBJniTests.java @@ -0,0 +1,19 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import com.facebook.soloader.SoLoader; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.ExpectedException; + +public class BaseFBJniTests { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @BeforeClass + public static void setup() { + // Explicitly load fbjni to ensure that its JNI_OnLoad is run. + SoLoader.loadLibrary("fbjni"); + SoLoader.loadLibrary("fbjni-tests"); + } +} diff --git a/deps/fbjni/test/ByteBufferTests.java b/deps/fbjni/test/ByteBufferTests.java new file mode 100644 index 000000000..73cf6c9e3 --- /dev/null +++ b/deps/fbjni/test/ByteBufferTests.java @@ -0,0 +1,66 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import static org.fest.assertions.api.Assertions.assertThat; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import org.junit.Test; + +public class ByteBufferTests extends BaseFBJniTests { + @Test + public void testDirectByteBuffer() { + assertThat(nativeTestDirectByteBuffer()).isTrue(); + } + + public static native boolean nativeTestDirectByteBuffer(); + + @Test + public void testEmptyDirectByteBuffer() { + assertThat(nativeTestEmptyDirectByteBuffer()).isTrue(); + } + + public static native boolean nativeTestEmptyDirectByteBuffer(); + + @Test + public void testRewindBuffer() { + assertThat(nativeTestRewindBuffer()).isTrue(); + } + + public native boolean nativeTestRewindBuffer(); + + @Test + public void testAllocateDirect() { + ByteBuffer buffer = nativeAllocateDirect(5); + assertThat(buffer.isDirect()).isTrue(); + assertThat(buffer.capacity()).isEqualTo(5); + } + + public native ByteBuffer nativeAllocateDirect(int size); + + // called from native + public static void writeBytes(ByteBuffer dest, byte a, byte b, byte c, byte d) { + dest.put(a).put(b).put(c).put(d); + } + + @Test + public void testFloatBuffer() { + final int BUFFER_COUNT = 5; + final int FLOAT_SIZE = 4; + FloatBuffer buffer = + ByteBuffer.allocateDirect(BUFFER_COUNT * FLOAT_SIZE) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + buffer.put(1f); + buffer.put(2f); + buffer.put(2.5f); + buffer.put(2.75f); + buffer.put(3f); + assertThat(nativeTestFloatBuffer(buffer)).isTrue(); + } + + public native boolean nativeTestFloatBuffer(Buffer buffer); +} diff --git a/deps/fbjni/test/FBJniTests.java b/deps/fbjni/test/FBJniTests.java new file mode 100644 index 000000000..dc5e459ff --- /dev/null +++ b/deps/fbjni/test/FBJniTests.java @@ -0,0 +1,877 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +import com.facebook.jni.annotations.DoNotStrip; +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.Callable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class FBJniTests extends BaseFBJniTests { + class CustomException extends Throwable { + int mGetMessageCalls = 0; + + @Override + public String getMessage() { + return "getMessages: " + (++mGetMessageCalls); + } + } + + public interface Callbacks { + void voidFoo(); + + boolean booleanFoo(); + + byte byteFoo(); + + char charFoo(); + + short shortFoo(); + + int intFoo(); + + long longFoo(); + + float floatFoo(); + + double doubleFoo(); + + Object objectFoo(); + + String stringFoo(); + } + + public static class TestThing { + int foo; + } + + @Mock private static Callbacks mCallbacksMock; + + private int mIntFieldTest; + private String mStringFieldTest; + private TestThing mReferenceFieldTest; + private static int sIntFieldTest; + private static String sStringFieldTest; + private static TestThing sReferenceFieldTest; + + @DoNotStrip // Resolved from fbjni_tests::TestFieldAccess + int bar(double d) { + return 42; + } + + // Test case for nonvirtual function + public boolean nonVirtualMethod(boolean s) { + return s; + } + + private static void verifyAllCallbacksCalled(Callbacks mock) { + verify(mock).voidFoo(); + verify(mock).booleanFoo(); + verify(mock).byteFoo(); + verify(mock).charFoo(); + verify(mock).shortFoo(); + verify(mock).intFoo(); + verify(mock).longFoo(); + verify(mock).floatFoo(); + verify(mock).doubleFoo(); + verify(mock).objectFoo(); + verify(mock).stringFoo(); + } + + // Instead of mocking, lets call non-static functions and verify them. + public static void voidFooStatic() { + mCallbacksMock.voidFoo(); + } + + public static boolean booleanFooStatic() { + return mCallbacksMock.booleanFoo(); + } + + public static byte byteFooStatic() { + return mCallbacksMock.byteFoo(); + } + + public static char charFooStatic(char c, int s) { + return mCallbacksMock.charFoo(); + } + + public static short shortFooStatic(short s, short t) { + return mCallbacksMock.shortFoo(); + } + + public static int intFooStatic(int s) { + return mCallbacksMock.intFoo(); + } + + public static long longFooStatic() { + return mCallbacksMock.longFoo(); + } + + public static float floatFooStatic() { + return mCallbacksMock.floatFoo(); + } + + public static double doubleFooStatic() { + return mCallbacksMock.doubleFoo(); + } + + public static Object objectFooStatic() { + return mCallbacksMock.objectFoo(); + } + + public static String stringFooStatic() { + return mCallbacksMock.stringFoo(); + } + + @Test + public void resolveClass() throws ClassNotFoundException { + assertThat(nativeTestClassResolution("java/lang/Object")).isTrue(); + } + + // Some versions of Android throw ClassNotFoundException while others throw NoClassDefFoundError. + // Flatten that to always be ClassNotFoundException. + private static void wrapClassLoadingErrors(Callable code) throws Exception { + try { + code.call(); + } catch (NoClassDefFoundError ex) { + throw new ClassNotFoundException("chained NoClassDefFoundError", ex); + } + } + + @Test(expected = ClassNotFoundException.class) + public void failingToResolveClass() throws Exception { + wrapClassLoadingErrors( + new Callable() { + @Override + public Boolean call() throws Exception { + return nativeTestClassResolution("ThisClassDoesNotExist"); + } + }); + } + + private native boolean nativeTestClassResolution(String className) throws ClassNotFoundException; + + @Test + public void lazyClassResolution() throws ClassNotFoundException { + assertThat(nativeTestLazyClassResolution("java/lang/Object")).isTrue(); + } + + @Test(expected = ClassNotFoundException.class) + public void failedLazyClassResolution() throws Exception { + wrapClassLoadingErrors( + new Callable() { + @Override + public Boolean call() throws Exception { + return nativeTestLazyClassResolution("ThisClassDoesNotExist"); + } + }); + } + + private native boolean nativeTestLazyClassResolution(String className) + throws ClassNotFoundException; + + @Test + public void instanceCreation() { + assertThat(nativeCreateInstanceOf("java/lang/String")) + .isInstanceOf(String.class) + .isEqualTo("java/lang/String"); + } + + private native Object nativeCreateInstanceOf(String className); + + @Test + public void typeDescriptors() { + assertThat(nativeTestTypeDescriptors()).isTrue(); + } + + private native boolean nativeTestTypeDescriptors(); + + @Test + public void resolveVirtualMethod() throws ClassNotFoundException, NoSuchMethodException { + assertThat(nativeTestVirtualMethodResolution_I("java/lang/Object", "hashCode")).isTrue(); + } + + @Test + public void resolveVirtualMethodWithArray() throws ClassNotFoundException, NoSuchMethodException { + assertThat(nativeTestVirtualMethodResolution_arrB("java/lang/String", "getBytes")).isTrue(); + } + + @Test + public void resolveVirtualMethodWithObjectArray() + throws ClassNotFoundException, NoSuchMethodException { + assertThat(nativeTestVirtualMethodResolution_S_arrS("java/lang/String", "split")).isTrue(); + } + + @Test + public void resolveVirtualMethodWithObjectArrayArray() + throws ClassNotFoundException, NoSuchMethodException { + assertThat( + nativeTestVirtualMethodResolution_arrarrS( + "com/facebook/jni/FBJniTests", "returnMultidimensionalObjectArray")) + .isTrue(); + } + + public static String[][] returnMultidimensionalObjectArray() { + return null; + } + + @Test + public void resolveVirtualMethodWithPrimitiveArrayArray() + throws ClassNotFoundException, NoSuchMethodException { + assertThat( + nativeTestVirtualMethodResolution_arrarrI( + "com/facebook/jni/FBJniTests", "returnMultidimensionalPrimitiveArray")) + .isTrue(); + } + + public static int[][] returnMultidimensionalPrimitiveArray() { + return null; + } + + @Test(expected = NoSuchMethodError.class) + public void failingToResolveVirtualMethod() throws ClassNotFoundException, NoSuchMethodError { + nativeTestVirtualMethodResolution_I("java/lang/Object", "ThisMethodDoesNotExist"); + } + + private native boolean nativeTestVirtualMethodResolution_I(String className, String methodName) + throws ClassNotFoundException, NoSuchMethodError; + + private native boolean nativeTestVirtualMethodResolution_arrB(String className, String methodName) + throws ClassNotFoundException, NoSuchMethodError; + + private native boolean nativeTestVirtualMethodResolution_S_arrS( + String className, String methodName) throws ClassNotFoundException, NoSuchMethodError; + + private native boolean nativeTestVirtualMethodResolution_arrarrS( + String className, String methodName) throws ClassNotFoundException, NoSuchMethodError; + + private native boolean nativeTestVirtualMethodResolution_arrarrI( + String className, String methodName) throws ClassNotFoundException, NoSuchMethodError; + + @Test + public void lazyMethodResolution() throws ClassNotFoundException, NoSuchMethodError { + assertThat(nativeTestLazyVirtualMethodResolution_I("java/lang/Object", "hashCode")).isTrue(); + } + + @Test(expected = NoSuchMethodError.class) + public void failedLazyMethodResolution() throws ClassNotFoundException, NoSuchMethodError { + nativeTestLazyVirtualMethodResolution_I("java/lang/Object", "ThisMethodDoesNotExist"); + } + + private native boolean nativeTestLazyVirtualMethodResolution_I( + String className, String methodName); + + @Test + public void callbacksUsingJMethod() { + nativeTestJMethodCallbacks(mCallbacksMock); + verifyAllCallbacksCalled(mCallbacksMock); + } + + private native void nativeTestJMethodCallbacks(Callbacks callbacks); + + @Test + public void callbacksUsingJStaticMethod() { + nativeTestJStaticMethodCallbacks(); + verifyAllCallbacksCalled(mCallbacksMock); + } + + private native void nativeTestJStaticMethodCallbacks(); + + @Test + public void isAssignableFrom() { + assertThat(nativeTestIsAssignableFrom(String.class, String.class)).isTrue(); + assertThat(nativeTestIsAssignableFrom(String.class, Object.class)).isFalse(); + assertThat(nativeTestIsAssignableFrom(Object.class, String.class)).isTrue(); + assertThat(nativeTestIsAssignableFrom(ArrayList.class, Iterable.class)).isFalse(); + assertThat(nativeTestIsAssignableFrom(Iterable.class, ArrayList.class)).isTrue(); + } + + private native boolean nativeTestIsAssignableFrom(Class cls1, Class cls2); + + @Test + public void isInstanceOf() { + assertThat(nativeTestIsInstanceOf("", String.class)).isTrue(); + assertThat(nativeTestIsInstanceOf("", Object.class)).isTrue(); + assertThat(nativeTestIsInstanceOf(new Object(), String.class)).isFalse(); + assertThat(nativeTestIsInstanceOf(new ArrayList(), Iterable.class)).isTrue(); + assertThat(nativeTestIsInstanceOf(null, Iterable.class)).isTrue(); + } + + private native boolean nativeTestIsInstanceOf(Object object, Class cls); + + @Test + public void isSameObject() { + Object anObject = new Object(); + Object anotherObject = new Object(); + assertThat(nativeTestIsSameObject(anObject, anObject)).isTrue(); + assertThat(nativeTestIsSameObject(anObject, anotherObject)).isFalse(); + assertThat(nativeTestIsSameObject(null, anObject)).isFalse(); + assertThat(nativeTestIsSameObject(anObject, null)).isFalse(); + assertThat(nativeTestIsSameObject(null, null)).isTrue(); + } + + private native boolean nativeTestIsSameObject(Object a, Object b); + + @Test + public void testGetSuperClass() { + Class testClass = String.class; + Class superClass = Object.class; + Class notSuperClass = Integer.class; + + assertThat(nativeTestGetSuperclass(testClass, superClass)).isTrue(); + assertThat(nativeTestGetSuperclass(testClass, notSuperClass)).isFalse(); + } + + private native boolean nativeTestGetSuperclass(Class testClass, Class superOfTest); + + @Test + public void testWeakRefs() { + assertThat(nativeTestWeakRefs()).isTrue(); + } + + private native boolean nativeTestWeakRefs(); + + @Test + public void testAliasRefs() { + assertThat(nativeTestAlias()).isTrue(); + } + + private native boolean nativeTestAlias(); + + @Test + public void testAliasRefConversions() { + assertThat(nativeTestAliasRefConversions()).isTrue(); + } + + private native boolean nativeTestAliasRefConversions(); + + @Test + public void testNullJString() { + assertThat(nativeTestNullJString()).isTrue(); + } + + private native boolean nativeTestNullJString(); + + @Test + public void testSwap() { + assertThat(nativeTestSwap(new Object())).isTrue(); + } + + private native boolean nativeTestSwap(Object other); + + @Test + public void testEqualOperator() { + assertThat(nativeTestEqualOperator(new Object())).isTrue(); + } + + private native boolean nativeTestEqualOperator(Object other); + + @Test + public void testRelaseAlias() { + assertThat(nativeTestReleaseAlias()).isTrue(); + } + + private native boolean nativeTestReleaseAlias(); + + @Test + public void testLockingWeakReferences() { + assertThat(nativeTestLockingWeakReferences()).isTrue(); + } + + private native boolean nativeTestLockingWeakReferences(); + + @Test + public void testCreatingReferences() { + assertThat(nativeTestCreatingReferences()).isTrue(); + } + + private native boolean nativeTestCreatingReferences(); + + @Test + public void testAssignmentAndCopyConstructors() { + assertThat(nativeTestAssignmentAndCopyConstructors()).isTrue(); + } + + private native boolean nativeTestAssignmentAndCopyConstructors(); + + @Test + public void testAssignmentAndCopyCrossTypes() { + assertThat(nativeTestAssignmentAndCopyCrossTypes()).isTrue(); + } + + private native boolean nativeTestAssignmentAndCopyCrossTypes(); + + @Test + public void testNullReferences() { + assertThat(nativeTestNullReferences()).isTrue(); + } + + private native boolean nativeTestNullReferences(); + + @Test + public void testAutoAliasRefReturningVoid() { + nativeTestAutoAliasRefReturningVoid(); + } + + private native void nativeTestAutoAliasRefReturningVoid(); + + @Test + public void testFieldAccess() { + mIntFieldTest = 17; + assertThat(nativeTestFieldAccess("mIntFieldTest", mIntFieldTest, 42)).isTrue(); + assertThat(mIntFieldTest).isEqualTo(42); + } + + private native boolean nativeTestFieldAccess(String name, int oldVal, int newVal); + + @Test + public void testStringFieldAccess() { + mStringFieldTest = "initial"; + assertThat(nativeTestStringFieldAccess("mStringFieldTest", mStringFieldTest, "final")).isTrue(); + assertThat(mStringFieldTest).isEqualTo("final"); + } + + private native boolean nativeTestStringFieldAccess(String name, String oldVal, String newVal); + + @Test + public void testReferenceFieldAccess() { + for (boolean useWrapper : new boolean[] {false, true}) { + mReferenceFieldTest = new TestThing(); + TestThing newthing = new TestThing(); + + assertThat( + nativeTestReferenceFieldAccess( + "mReferenceFieldTest", mReferenceFieldTest, newthing, useWrapper)) + .isTrue(); + assertThat(mReferenceFieldTest).isEqualTo(newthing); + } + } + + private native boolean nativeTestReferenceFieldAccess( + String name, Object oldVal, Object newVal, boolean useWrapper); + + @Test + public void testStaticFieldAccess() { + sIntFieldTest = 17; + assertThat(nativeTestStaticFieldAccess("sIntFieldTest", sIntFieldTest, 42)).isTrue(); + assertThat(sIntFieldTest).isEqualTo(42); + } + + private native boolean nativeTestStaticFieldAccess(String name, int oldVal, int newVal); + + @Test + public void testStaticStringFieldAccess() { + sStringFieldTest = "initial"; + assertThat(nativeTestStaticStringFieldAccess("sStringFieldTest", sStringFieldTest, "final")) + .isTrue(); + assertThat(sStringFieldTest).isEqualTo("final"); + } + + private native boolean nativeTestStaticStringFieldAccess(String name, String oVal, String nVal); + + @Test + public void testStaticReferenceFieldAccess() { + for (boolean useWrapper : new boolean[] {false, true}) { + sReferenceFieldTest = new TestThing(); + TestThing newthing = new TestThing(); + + assertThat( + nativeTestStaticReferenceFieldAccess( + "sReferenceFieldTest", sReferenceFieldTest, newthing, useWrapper)) + .isTrue(); + assertThat(sReferenceFieldTest).isEqualTo(newthing); + } + } + + private native boolean nativeTestStaticReferenceFieldAccess( + String name, Object oldVal, Object newVal, boolean useWrapper); + + @Test + public void testNonVirtualMethod() { + assertThat(nativeTestNonVirtualMethod(true)).isTrue(); + } + + private native boolean nativeTestNonVirtualMethod(boolean s); + + @Test + public void testArrayCreation() { + String[] expectedStrings = {"one", "two", "three"}; + String[] joinedStrings = + nativeTestArrayCreation(expectedStrings[0], expectedStrings[1], expectedStrings[2]); + assertThat(joinedStrings).isEqualTo(expectedStrings); + } + + private native String[] nativeTestArrayCreation(String s0, String s1, String s2); + + @Test + public void testMultidimensionalObjectArray() { + String[] strings = {"one", "two", "three"}; + String[][] expectedStrings = {{"one", "two"}, {"three"}}; + String[][] joinedStrings = + nativeTestMultidimensionalObjectArray(strings[0], strings[1], strings[2]); + assertThat(joinedStrings).isEqualTo(expectedStrings); + } + + private native String[][] nativeTestMultidimensionalObjectArray(String s0, String s1, String s2); + + @Test + public void testMultidimensionalPrimitiveArray() { + int[] nums = {1, 2, 3}; + int[][] expectedNums = {{1, 2}, {3}}; + int[][] gotNums = nativeTestMultidimensionalPrimitiveArray(nums[0], nums[1], nums[2]); + assertThat(gotNums).isEqualTo(expectedNums); + } + + private native int[][] nativeTestMultidimensionalPrimitiveArray(int i0, int i1, int i2); + + private String[] mCapturedStringArray = null; + + @DoNotStrip + String captureStringArray(String[] input) { + mCapturedStringArray = input; + return "Stub"; + } + + @Test + public void testBuildStringArray() throws Exception { + String[] input = {"Four", "score", "and", "seven", "beers", "ago"}; + nativeTestBuildStringArray(input); + assertThat(mCapturedStringArray).isEqualTo(input); + } + + private native String nativeTestBuildStringArray(String... input); + + public Object methodResolutionWithCxxTypes(String t, long val) { + if (!"test".equals(t) || val != 3) throw new RuntimeException(); + return null; + } + + public void methodResolutionWithCxxTypesVoid(String t, long val) { + if (!"test".equals(t) || val != 3) throw new RuntimeException(); + } + + public int methodResolutionWithCxxTypesInt(String t, long val) { + if (!"test".equals(t) || val != 3) throw new RuntimeException(); + return 0; + } + + public static Object methodResolutionWithCxxTypesStatic(String t, long val) { + if (!"test".equals(t) || val != 3) throw new RuntimeException(); + return null; + } + + public static void methodResolutionWithCxxTypesVoidStatic(String t, long val) { + if (!"test".equals(t) || val != 3) throw new RuntimeException(); + } + + public static int methodResolutionWithCxxTypesIntStatic(String t, long val) { + if (!"test".equals(t) || val != 3) throw new RuntimeException(); + return 0; + } + + @Test + public void testMethodResolutionWithCxxTypes() { + testMethodResolutionWithCxxTypesNative("methodResolutionWithCxxTypes", "test", 3); + } + + private native void testMethodResolutionWithCxxTypesNative( + String callbackName, String str, long val); + + @Test(expected = CustomException.class) + public void testHandleJavaCustomException() { + testHandleJavaCustomExceptionNative(); + } + + private native void testHandleJavaCustomExceptionNative(); + + @Test + public void testHandleNullExceptionMessage() { + testHandleNullExceptionMessageNative(); + } + + private native void testHandleNullExceptionMessageNative(); + + @Test + public void testHandleNestedException() { + try { + nativeTestHandleNestedException(); + } catch (Throwable e) { + assertThat(e).isInstanceOf(ArrayIndexOutOfBoundsException.class); + e = e.getCause(); + assertThat(e).isInstanceOf(RuntimeException.class); + e = e.getCause(); + assertThat(e).isInstanceOf(CustomException.class).hasNoCause(); + } + } + + private native void nativeTestHandleNestedException(); + + @Test(expected = CppException.class) + public void testHandleNoRttiException() { + nativeTestHandleNoRttiException(); + } + + private native void nativeTestHandleNoRttiException(); + + @Test + public void testCopyConstructor() { + assertThat(nativeTestCopyConstructor()) + .isEqualTo("com.facebook.jni.FBJniTests$CustomException: getMessages: 1"); + } + + private native String nativeTestCopyConstructor(); + + @Test + public void testMoveConstructorWithEmptyWhat() { + assertThat(nativeTestMoveConstructorWithEmptyWhat()) + .isEqualTo("com.facebook.jni.FBJniTests$CustomException: getMessages: 1"); + } + + private native String nativeTestMoveConstructorWithEmptyWhat(); + + @Test + public void testMoveConstructorWithPopulatedWhat() { + assertThat(nativeTestMoveConstructorWithPopulatedWhat()) + .isEqualTo("com.facebook.jni.FBJniTests$CustomException: getMessages: 1"); + } + + private native String nativeTestMoveConstructorWithPopulatedWhat(); + + @DoNotStrip // Used in native code. + protected void customExceptionThrower() throws CustomException { + throw new CustomException(); + } + + @DoNotStrip // Used in native code. + protected void nullMessageThrower() throws NullPointerException { + // just like Preconditions.checkNotNull() does + throw new NullPointerException(); + } + + @Test + public void testHandleCppRuntimeError() { + String message = "Sample runtime error."; + thrown.expect(RuntimeException.class); + thrown.expectMessage(message); + nativeTestHandleCppRuntimeError(message); + } + + private native void nativeTestHandleCppRuntimeError(String message); + + @Test(expected = IOException.class) + public void testHandleCppIOBaseFailure() { + nativeTestHandleCppIOBaseFailure(); + } + + private native void nativeTestHandleCppIOBaseFailure(); + + @Test(expected = CppSystemErrorException.class) + public void testHandleCppSystemError() { + nativeTestHandleCppSystemError(); + } + + private native void nativeTestHandleCppSystemError(); + + @Test(expected = RuntimeException.class) + public void testInterDsoExceptionHandlingA() { + nativeTestInterDsoExceptionHandlingA(); + } + + private native void nativeTestInterDsoExceptionHandlingA(); + + @Test + public void testInterDsoExceptionHandlingB() { + assertThat(nativeTestInterDsoExceptionHandlingB()).isTrue(); + } + + private native boolean nativeTestInterDsoExceptionHandlingB(); + + @Test(expected = UnknownCppException.class) + public void testHandleCppCharPointerThrow() { + nativeTestHandleCppCharPointerThrow(); + } + + private native void nativeTestHandleCppCharPointerThrow(); + + @Test(expected = IllegalArgumentException.class) + public void testThrowJavaExceptionByName() { + nativeTestThrowJavaExceptionByName(); + } + + private native void nativeTestThrowJavaExceptionByName(); + + @Test(expected = UnknownCppException.class) + public void testHandleCppIntThrow() { + nativeTestHandleCppIntThrow(); + } + + private native void nativeTestHandleCppIntThrow(); + + @Test + public void testJThread() { + assertThat(nativeTestJThread()).isEqualTo(1); + } + + private native int nativeTestJThread(); + + @Test + public void testThreadScopeGuard() { + assertThat(nativeTestThreadScopeGuard(17)).isEqualTo(42); + } + + private native int nativeTestThreadScopeGuard(double input); + + @Test + public void testNestedThreadScopeGuard() { + assertThat(nativeTestNestedThreadScopeGuard(17)).isEqualTo(42); + } + + private native int nativeTestNestedThreadScopeGuard(double input); + + @Test + public void testClassLoadInWorker() { + assertThat(nativeTestClassLoadInWorker()).isEqualTo(1); + } + + private native int nativeTestClassLoadInWorker(); + + @Test + public void testClassLoadWorkerFastPath() { + assertThat(nativeTestClassLoadWorkerFastPath()).isEqualTo(3); + } + + private native int nativeTestClassLoadWorkerFastPath(); + + @Test + public void testToString() { + assertThat(nativeTestToString()).isTrue(); + } + + private native boolean nativeTestToString(); + + // Casting alias_ref + + @Test + public void testCorrectStaticCastAliasRef() { + // Static cast can't fail at run time. If the object isn't actually + // of that type, we just get undefined behaviour, which we can't + // check for. So we only do a positive test. + assertThat(nativeStaticCastAliasRefToString("hello")).isTrue(); + } + + @Test + public void testNullStaticCastAliasRef() { + assertThat(nativeStaticCastAliasRefToString(null)).isTrue(); + } + + private native boolean nativeStaticCastAliasRefToString(Object a); + + @Test + public void testDynamicCastAliasRefToSame() { + assertThat(nativeDynamicCastAliasRefToThrowable(new Throwable())).isTrue(); + } + + public void testDynamicCastAliasRefToBase() { + assertThat(nativeDynamicCastAliasRefToThrowable(new Exception())).isTrue(); + } + + @Test(expected = ClassCastException.class) + public void testDynamicCastAliasRefToDerived() { + nativeDynamicCastAliasRefToThrowable(new Object()); + } + + @Test(expected = ClassCastException.class) + public void testDynamicCastAliasRefToUnrelated() { + nativeDynamicCastAliasRefToThrowable(new Integer(23)); + } + + @Test + public void testNullDynamicCastAliasRef() { + assertThat(nativeDynamicCastAliasRefToThrowable(null)).isTrue(); + } + + private native boolean nativeDynamicCastAliasRefToThrowable(Object a); + + // Casting local_ref + + @Test + public void testCorrectStaticCastLocalRef() { + // Static cast can't fail at run time. If the object isn't actually + // of that type, we just get undefined behaviour, which we can't + // check for. So we only do a positive test. + assertThat(nativeStaticCastLocalRefToString("hello")).isTrue(); + } + + @Test + public void testNullStaticCastLocalRef() { + assertThat(nativeStaticCastLocalRefToString(null)).isTrue(); + } + + private native boolean nativeStaticCastLocalRefToString(Object a); + + @Test + public void testCorrectDynamicCastLocalRef() { + assertThat(nativeDynamicCastLocalRefToString("hello")).isTrue(); + } + + @Test(expected = ClassCastException.class) + public void testIncorrectDynamicCastLocalRef() { + nativeDynamicCastLocalRefToString(new Integer(23)); + } + + @Test + public void testNullDynamicCastLocalRef() { + assertThat(nativeDynamicCastLocalRefToString(null)).isTrue(); + } + + private native boolean nativeDynamicCastLocalRefToString(Object a); + + // Casting global_ref + + @Test + public void testCorrectStaticCastGlobalRef() { + // Static cast can't fail at run time. If the object isn't actually + // of that type, we just get undefined behaviour, which we can't + // check for. So we only do a positive test. + assertThat(nativeStaticCastGlobalRefToString("hello")).isTrue(); + } + + @Test + public void testNullStaticCastGlobalRef() { + assertThat(nativeStaticCastGlobalRefToString(null)).isTrue(); + } + + private native boolean nativeStaticCastGlobalRefToString(Object a); + + @Test + public void testCorrectDynamicCastGlobalRef() { + assertThat(nativeDynamicCastGlobalRefToString("hello")).isTrue(); + } + + @Test(expected = ClassCastException.class) + public void testIncorrectDynamicCastGlobalRef() { + nativeDynamicCastGlobalRefToString(new Integer(23)); + } + + @Test + public void testNullDynamicCastGlobalRef() { + assertThat(nativeDynamicCastGlobalRefToString(null)).isTrue(); + } + + private native boolean nativeDynamicCastGlobalRefToString(Object a); + + @Test + public void testCriticalNativeMethodBindsAndCanBeInvoked() { + assertThat(nativeCriticalNativeMethodBindsAndCanBeInvoked(12, 3.45f)).isTrue(); + } + + private static native boolean nativeCriticalNativeMethodBindsAndCanBeInvoked(int a, float b); +} diff --git a/deps/fbjni/test/HybridTests.java b/deps/fbjni/test/HybridTests.java new file mode 100644 index 000000000..a72dcce05 --- /dev/null +++ b/deps/fbjni/test/HybridTests.java @@ -0,0 +1,295 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import static org.fest.assertions.api.Assertions.assertThat; + +import com.facebook.jni.annotations.DoNotStrip; +import org.junit.Test; + +public class HybridTests extends BaseFBJniTests { + static class TestHybridClass { + // Hybrid classes must include a member which manages the C++ object. It + // will be initialized from C++. It must be declared exactly with this + // type and name, so JNI can find it, and initialized once in the ctor. + // The annotation is necessary to keep proguard from renaming it, or else JNI + // won't be able to find it. + @DoNotStrip private final HybridData mHybridData; + + // This is the method which creates the C++ instance and initializes + // mHybridData. Conventionally, it should be named initHybrid, and invoked + // from the constructor. This must be called only once. If the C++ + // instance is referenced before this is called, a NullPointerException + // will be thrown. + private native HybridData initHybrid(int i, String s, boolean b); + + // You can have more than one, which may be useful if the ctor is + // overloaded. This will call the default C++ ctor. + private native HybridData initHybrid(); + + // Implements factory-style initialization. You shouldn't usually + // need both styles in one class. Here we do it for testing and + // demo purposes. + private native HybridData initHybrid(String s, int i, boolean b); + + // Java ctor must invoke initHybrid(). This just passes arguments through, + // but the ctor can do whatever work it wants, as long as it calls + // initHybrid() before any native methods. + public TestHybridClass(int i, String s, boolean b) { + mHybridData = initHybrid(i, s, b); + } + + // This behaves the same as the ctor above, I just wanted a different + // signature to demonstrate factory-style initialization. + public TestHybridClass(String s, int i, boolean b) { + mHybridData = initHybrid(s, i, b); + } + + // This is the simplest case. Even if everything is default, initHybrid() + // must still be called. + public TestHybridClass() { + mHybridData = initHybrid(); + } + + // Java ctor used by C++ newObjectCxxArgs. Note this is private. + private TestHybridClass(HybridData hd) { + mHybridData = hd; + } + + public void doneUsingIt() { + mHybridData.resetNative(); + } + + // Some C++ methods. + public native void setBoth(int i, String s); + + public native int getInt(); + + public native String getString(); + + public native String getCharString(); + + public native boolean copy1(TestHybridClass other); + + public native boolean copy2(TestHybridClass other); + + public native void oops(); + + public native void setGlobal(String s); + + public native String getGlobal1(); + + public native String getGlobal2(); + + public static native TestHybridClass makeWithTwo(); + + public static native TestHybridClass makeWithThree(); + + public static native void autoconvertMany(); + } + + @Test + public void testHybridClass() { + TestHybridClass thc1 = new TestHybridClass(); + assertThat(thc1.getInt()).isEqualTo(0); + assertThat(thc1.getString()).isEqualTo(""); + + thc1.setBoth(1, "one"); + assertThat(thc1.getInt()).isEqualTo(1); + assertThat(thc1.getString()).isEqualTo("one"); + + TestHybridClass thc2 = TestHybridClass.makeWithTwo(); + assertThat(thc2.getInt()).isEqualTo(2); + assertThat(thc2.getString()).isEqualTo("two"); + + thc2.doneUsingIt(); + + thrown.expect(NullPointerException.class); + thc2.getInt(); + } + + @Test + public void testHybridAutoconversion() { + TestHybridClass thc3 = TestHybridClass.makeWithThree(); + assertThat(thc3.copy1(new TestHybridClass(3, "three", false))).isTrue(); + assertThat(thc3.getInt()).isEqualTo(3); + assertThat(thc3.getString()).isEqualTo("three"); + + TestHybridClass thc4 = new TestHybridClass(); + thc4.copy1(new TestHybridClass("four", 4, false)); + assertThat(thc4.getInt()).isEqualTo(4); + assertThat(thc4.getString()).isEqualTo("four"); + assertThat(thc4.getCharString()).isEqualTo("four"); + + TestHybridClass thc5 = new TestHybridClass(); + assertThat(thc5.copy2(new TestHybridClass(5, "five", false))).isTrue(); + assertThat(thc5.getInt()).isEqualTo(5); + assertThat(thc5.getString()).isEqualTo("five"); + } + + @Test + public void testReturnGlobalRef() { + TestHybridClass thc = new TestHybridClass(); + thc.setGlobal("global_ref"); + assertThat(thc.getGlobal1()).isEqualTo("global_ref"); + assertThat(thc.getGlobal2()).isEqualTo("global_ref"); + } + + @Test + public void testLocalLeak() { + TestHybridClass.autoconvertMany(); + } + + @Test + public void testExceptionMapping() { + TestHybridClass thc1 = new TestHybridClass(); + thrown.expect(ArrayStoreException.class); + thc1.oops(); + } + + abstract static class AbstractTestHybrid { + @DoNotStrip private final HybridData mHybridData; + + private int mAbstractNum; + + protected AbstractTestHybrid(HybridData hybridData, int an) { + mHybridData = hybridData; + mAbstractNum = an; + } + + public int abstractNum() { + return mAbstractNum; + } + + public native int nativeNum(); + + public abstract int concreteNum(); + + public abstract int sum(); + } + + static class ConcreteTestHybrid extends AbstractTestHybrid { + public ConcreteTestHybrid(int an, int nn, int cn) { + super(initHybrid(nn, cn), an); + } + + private static native HybridData initHybrid(int nn, int cn); + + // overrides can be native + @Override + public native int concreteNum(); + + // overrides can be java + @Override + public int sum() { + return nativeNum() + abstractNum() + concreteNum(); + } + } + + @Test + public void testHybridInheritance() { + AbstractTestHybrid ath = new ConcreteTestHybrid(1, 2, 3); + assertThat(ath.abstractNum()).isEqualTo(1); + assertThat(ath.nativeNum()).isEqualTo(2); + assertThat(ath.concreteNum()).isEqualTo(3); + assertThat(ath.sum()).isEqualTo(6); + } + + public static native boolean cxxTestInheritance(AbstractTestHybrid ath); + + public static native AbstractTestHybrid makeAbstractHybrid(); + + @Test + public void testHybridCxx() { + AbstractTestHybrid ath = new ConcreteTestHybrid(4, 5, 6); + assertThat(cxxTestInheritance(ath)).isTrue(); + + AbstractTestHybrid ath2 = makeAbstractHybrid(); + assertThat(ath2 instanceof ConcreteTestHybrid).isTrue(); + assertThat(ath2.abstractNum()).isEqualTo(7); + assertThat(ath2.nativeNum()).isEqualTo(8); + assertThat(ath2.concreteNum()).isEqualTo(9); + assertThat(ath2.sum()).isEqualTo(24); + } + + static class Base {} + + static class Derived extends Base { + @DoNotStrip private final HybridData mHybridData; + + private Derived(HybridData hybridData) { + mHybridData = hybridData; + } + } + + public static native boolean cxxTestDerivedJavaClass(); + + @Test + public void testDerivedJavaClassCxx() { + assertThat(cxxTestDerivedJavaClass()).isTrue(); + } + + static class TestHybridClassBase extends HybridClassBase { + protected native void initHybrid(); + + private native void initHybrid(int i); + + protected TestHybridClassBase() { + // No initHybrid() here! + // Otherwise factory construction will set native pointer twice and process will crash. + } + + public TestHybridClassBase(int i) { + initHybrid(i); + } + + // Some C++ methods. + public native void setInt(int i); + + public native int getInt(); + + public static native TestHybridClassBase makeWithThree(); + } + + static class TestHybridClassBaseDefaultCtor extends TestHybridClassBase { + public TestHybridClassBaseDefaultCtor() { + initHybrid(); + } + } + + @Test + public void testHybridBaseDefaultCtor() { + TestHybridClassBaseDefaultCtor base = new TestHybridClassBaseDefaultCtor(); + assertThat(base.getInt()).isZero(); + + base.setInt(58); + assertThat(base.getInt()).isEqualTo(58); + } + + @Test + public void testHybridBaseConstructorArgs() { + TestHybridClassBase base = new TestHybridClassBase(42); + assertThat(base.getInt()).isEqualTo(42); + } + + @Test + public void testHybridBaseFactoryConstruction() { + TestHybridClassBase base = TestHybridClassBase.makeWithThree(); + assertThat(base.getInt()).isEqualTo(3); + } + + static class Destroyable { + @DoNotStrip private final HybridData mHybridData; + + private Destroyable(HybridData hybridData) { + mHybridData = hybridData; + } + } + + public static native boolean cxxTestHybridDestruction(); + + @Test + public void testHybridDestuction() { + assertThat(cxxTestHybridDestruction()).isTrue(); + } +} diff --git a/deps/fbjni/test/IteratorTests.java b/deps/fbjni/test/IteratorTests.java new file mode 100644 index 000000000..fa1fcf4b2 --- /dev/null +++ b/deps/fbjni/test/IteratorTests.java @@ -0,0 +1,72 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import static org.fest.assertions.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; + +public class IteratorTests extends BaseFBJniTests { + @Test + public void testListIterator() { + List list = new ArrayList(); + list.add("red"); + list.add("green"); + list.add("blue"); + + assertThat(nativeTestListIterator(list)).isTrue(); + } + + private static native boolean nativeTestListIterator(List list); + + @Test + public void testMapIterator() { + Map map = new HashMap(); + map.put("one", 1); + map.put("two", 2); + map.put("four", 4); + + assertThat(nativeTestMapIterator(map)).isTrue(); + } + + private static native boolean nativeTestMapIterator(Map map); + + @Test(expected = ClassCastException.class) + public void testMapIterateWrongType() { + Map map = new HashMap(); + map.put("one", 1); + map.put("two", 2); + map.put("pi", 3.14); + + assertThat(nativeTestIterateWrongType(map)).isTrue(); + } + + private static native boolean nativeTestIterateWrongType(Map map); + + @Test + public void testMapIterateNullKey() { + Map map = new HashMap(); + map.put("one", 1); + map.put(null, -99); + map.put("four", 4); + + assertThat(nativeTestIterateNullKey(map)).isTrue(); + } + + private static native boolean nativeTestIterateNullKey(Map map); + + @Test + public void testLargeMapIteration() { + Map map = new HashMap(); + for (int i = 0; i < 3000; i++) { + map.put("" + i, "value"); + } + assertThat(nativeTestLargeMapIteration(map)).isTrue(); + } + + private static native boolean nativeTestLargeMapIteration(Map map); +} diff --git a/deps/fbjni/test/PrimitiveArrayTests.java b/deps/fbjni/test/PrimitiveArrayTests.java new file mode 100644 index 000000000..a94ffdbe8 --- /dev/null +++ b/deps/fbjni/test/PrimitiveArrayTests.java @@ -0,0 +1,593 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.fest.assertions.api.Assertions.offset; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; + +import org.junit.Ignore; +import org.junit.Test; + +public class PrimitiveArrayTests extends BaseFBJniTests { + + private static final int MAGIC = 42; + + @Test + public void testMakeArrays() { + assertThat(nativeTestMakeBooleanArray(MAGIC).length).isEqualTo(MAGIC); + assertThat(nativeTestMakeByteArray(MAGIC).length).isEqualTo(MAGIC); + assertThat(nativeTestMakeCharArray(MAGIC).length).isEqualTo(MAGIC); + assertThat(nativeTestMakeShortArray(MAGIC).length).isEqualTo(MAGIC); + assertThat(nativeTestMakeIntArray(MAGIC).length).isEqualTo(MAGIC); + assertThat(nativeTestMakeLongArray(MAGIC).length).isEqualTo(MAGIC); + assertThat(nativeTestMakeFloatArray(MAGIC).length).isEqualTo(MAGIC); + assertThat(nativeTestMakeDoubleArray(MAGIC).length).isEqualTo(MAGIC); + } + + private static native boolean[] nativeTestMakeBooleanArray(int size); + + private static native byte[] nativeTestMakeByteArray(int size); + + private static native char[] nativeTestMakeCharArray(int size); + + private static native short[] nativeTestMakeShortArray(int size); + + private static native int[] nativeTestMakeIntArray(int size); + + private static native long[] nativeTestMakeLongArray(int size); + + private static native float[] nativeTestMakeFloatArray(int size); + + private static native double[] nativeTestMakeDoubleArray(int size); + + @Test + public void testGetSetBooleanArray() { + boolean[] array = {false, true}; + + assertThat(nativeTestGetSetBooleanArray(array)).isTrue(); + assertThat(array).isEqualTo(new boolean[] {true, false}); + } + + private static native boolean nativeTestGetSetBooleanArray(boolean[] array); + + @Test + public void testPinBooleanArray() { + boolean[] array = {false, true}; + + assertThat(nativeTestPinBooleanArray(array)).isTrue(); + assertThat(array).isEqualTo(new boolean[] {true, false}); + } + + private static native boolean nativeTestPinBooleanArray(boolean[] array); + + @Test + public void testGetSetByteArray() { + byte[] array = new byte[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (byte) i; + } + + assertThat(nativeTestGetSetByteArray(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((byte) (2 * i)); + } + } + + private static native boolean nativeTestGetSetByteArray(byte[] array); + + @Test + public void testGetSetCharArray() { + char[] array = new char[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (char) i; + } + + assertThat(nativeTestGetSetCharArray(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((char) (2 * i)); + } + } + + private static native boolean nativeTestGetSetCharArray(char[] array); + + @Test + public void testGetSetShortArray() { + short[] array = new short[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (short) i; + } + + assertThat(nativeTestGetSetShortArray(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((short) (2 * i)); + } + } + + private static native boolean nativeTestGetSetShortArray(short[] array); + + @Test + public void testGetSetIntArray() { + int[] intArray = new int[MAGIC]; + for (int i = 0; i < intArray.length; ++i) { + intArray[i] = i; + } + + assertThat(nativeTestGetSetIntArray(intArray)).isTrue(); + + for (int i = 0; i < intArray.length; ++i) { + assertThat(intArray[i]).isEqualTo(2 * i); + } + } + + private static native boolean nativeTestGetSetIntArray(int[] array); + + @Test + public void testGetSetLongArray() { + long[] longArray = new long[MAGIC]; + for (int i = 0; i < longArray.length; ++i) { + longArray[i] = (long) i; + } + + assertThat(nativeTestGetSetLongArray(longArray)).isTrue(); + + for (int i = 0; i < longArray.length; ++i) { + assertThat(longArray[i]).isEqualTo(2 * i); + } + } + + private static native boolean nativeTestGetSetLongArray(long[] array); + + @Test + public void testGetSetFloatArray() { + float[] floatArray = new float[MAGIC]; + for (int i = 0; i < floatArray.length; ++i) { + floatArray[i] = i; + } + + assertThat(nativeTestGetSetFloatArray(floatArray)).isTrue(); + + for (int i = 0; i < floatArray.length; ++i) { + assertThat(floatArray[i]).isEqualTo(2 * i, offset(1e-3f)); + } + } + + private static native boolean nativeTestGetSetFloatArray(float[] array); + + @Test + public void testGetSetDoubleArray() { + double[] doubleArray = new double[MAGIC]; + for (int i = 0; i < doubleArray.length; ++i) { + doubleArray[i] = i; + } + + assertThat(nativeTestGetSetDoubleArray(doubleArray)).isTrue(); + + for (int i = 0; i < doubleArray.length; ++i) { + assertThat(doubleArray[i]).isEqualTo(2 * i, offset(1e-3)); + } + } + + private static native boolean nativeTestGetSetDoubleArray(double[] array); + + @Test + public void testPinByteArray() { + byte[] array = new byte[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (byte) i; + } + + assertThat(nativeTestPinByteArray(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((byte) (2 * i)); + } + } + + private static native boolean nativeTestPinByteArray(byte[] array); + + @Test + public void testPinCharArray() { + char[] array = new char[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (char) i; + } + + assertThat(nativeTestPinCharArray(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((char) (2 * i)); + } + } + + private static native boolean nativeTestPinCharArray(char[] array); + + @Test + public void testPinShortArray() { + short[] array = new short[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (short) i; + } + + assertThat(nativeTestPinShortArray(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((short) (2 * i)); + } + } + + private static native boolean nativeTestPinShortArray(short[] array); + + @Test + public void testPinIntArray() { + int[] intArray = new int[MAGIC]; + for (int i = 0; i < intArray.length; ++i) { + intArray[i] = i; + } + + assertThat(nativeTestPinIntArray(intArray)).isTrue(); + + for (int i = 0; i < intArray.length; ++i) { + assertThat(intArray[i]).isEqualTo(2 * i); + } + } + + private static native boolean nativeTestPinIntArray(int[] array); + + @Test + public void testPinLongArray() { + long[] longArray = new long[MAGIC]; + for (int i = 0; i < longArray.length; ++i) { + longArray[i] = (long) i; + } + + assertThat(nativeTestPinLongArray(longArray)).isTrue(); + + for (int i = 0; i < longArray.length; ++i) { + assertThat(longArray[i]).isEqualTo(2 * i); + } + } + + private static native boolean nativeTestPinLongArray(long[] array); + + @Test + public void testPinFloatArray() { + float[] floatArray = new float[MAGIC]; + for (int i = 0; i < floatArray.length; ++i) { + floatArray[i] = (long) i; + } + + assertThat(nativeTestPinFloatArray(floatArray)).isTrue(); + + for (int i = 0; i < floatArray.length; ++i) { + assertThat(floatArray[i]).isEqualTo(2 * i, offset(1e-3f)); + } + } + + private static native boolean nativeTestPinFloatArray(float[] array); + + @Test + public void testPinDoubleArray() { + double[] doubleArray = new double[MAGIC]; + for (int i = 0; i < doubleArray.length; ++i) { + doubleArray[i] = (double) i; + } + + assertThat(nativeTestPinDoubleArray(doubleArray)).isTrue(); + + for (int i = 0; i < doubleArray.length; ++i) { + assertThat(doubleArray[i]).isEqualTo(2 * i, offset(1e-3)); + } + } + + private static native boolean nativeTestPinDoubleArray(double[] array); + + @Test + public void testPinByteArrayRegion() { + byte[] array = new byte[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (byte) i; + } + + assertThat(nativeTestPinByteArrayRegion(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((byte) (2 * i)); + } + } + + private static native boolean nativeTestPinByteArrayRegion(byte[] array); + + @Test + public void testPinCharArrayRegion() { + char[] array = new char[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (char) i; + } + + assertThat(nativeTestPinCharArrayRegion(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((char) (2 * i)); + } + } + + private static native boolean nativeTestPinCharArrayRegion(char[] array); + + @Test + public void testPinShortArrayRegion() { + short[] array = new short[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (short) i; + } + + assertThat(nativeTestPinShortArrayRegion(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((short) (2 * i)); + } + } + + private static native boolean nativeTestPinShortArrayRegion(short[] array); + + @Test + public void testPinIntArrayRegion() { + int[] intArray = new int[MAGIC]; + for (int i = 0; i < intArray.length; ++i) { + intArray[i] = i; + } + + assertThat(nativeTestPinIntArrayRegion(intArray)).isTrue(); + + for (int i = 0; i < intArray.length; ++i) { + assertThat(intArray[i]).isEqualTo(2 * i); + } + } + + private static native boolean nativeTestPinIntArrayRegion(int[] array); + + @Test + public void testPinLongArrayRegion() { + long[] longArray = new long[MAGIC]; + for (int i = 0; i < longArray.length; ++i) { + longArray[i] = (long) i; + } + + assertThat(nativeTestPinLongArrayRegion(longArray)).isTrue(); + + for (int i = 0; i < longArray.length; ++i) { + assertThat(longArray[i]).isEqualTo(2 * i); + } + } + + private static native boolean nativeTestPinLongArrayRegion(long[] array); + + @Test + public void testPinFloatArrayRegion() { + float[] floatArray = new float[MAGIC]; + for (int i = 0; i < floatArray.length; ++i) { + floatArray[i] = (long) i; + } + + assertThat(nativeTestPinFloatArrayRegion(floatArray)).isTrue(); + + for (int i = 0; i < floatArray.length; ++i) { + assertThat(floatArray[i]).isEqualTo(2 * i, offset(1e-3f)); + } + } + + private static native boolean nativeTestPinFloatArrayRegion(float[] array); + + @Test + public void testPinDoubleArrayRegion() { + double[] doubleArray = new double[MAGIC]; + for (int i = 0; i < doubleArray.length; ++i) { + doubleArray[i] = (double) i; + } + + assertThat(nativeTestPinDoubleArrayRegion(doubleArray)).isTrue(); + + for (int i = 0; i < doubleArray.length; ++i) { + assertThat(doubleArray[i]).isEqualTo(2 * i, offset(1e-3)); + } + } + + private static native boolean nativeTestPinDoubleArrayRegion(double[] array); + + @Test + public void testPinByteArrayCritical() { + byte[] array = new byte[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (byte) i; + } + + assertThat(nativeTestPinByteArrayCritical(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((byte) (2 * i)); + } + } + + private static native boolean nativeTestPinByteArrayCritical(byte[] array); + + @Test + public void testPinCharArrayCritical() { + char[] array = new char[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (char) i; + } + + assertThat(nativeTestPinCharArrayCritical(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((char) (2 * i)); + } + } + + private static native boolean nativeTestPinCharArrayCritical(char[] array); + + @Test + public void testPinShortArrayCritical() { + short[] array = new short[MAGIC]; + for (int i = 0; i < array.length; ++i) { + array[i] = (short) i; + } + + assertThat(nativeTestPinShortArrayCritical(array)).isTrue(); + + for (int i = 0; i < array.length; ++i) { + assertThat(array[i]).isEqualTo((short) (2 * i)); + } + } + + private static native boolean nativeTestPinShortArrayCritical(short[] array); + + @Test + public void testPinIntArrayCritical() { + int[] intArray = new int[MAGIC]; + for (int i = 0; i < intArray.length; ++i) { + intArray[i] = i; + } + + assertThat(nativeTestPinIntArrayCritical(intArray)).isTrue(); + + for (int i = 0; i < intArray.length; ++i) { + assertThat(intArray[i]).isEqualTo(2 * i); + } + } + + private static native boolean nativeTestPinIntArrayCritical(int[] array); + + @Test + public void testPinLongArrayCritical() { + long[] longArray = new long[MAGIC]; + for (int i = 0; i < longArray.length; ++i) { + longArray[i] = (long) i; + } + + assertThat(nativeTestPinLongArrayCritical(longArray)).isTrue(); + + for (int i = 0; i < longArray.length; ++i) { + assertThat(longArray[i]).isEqualTo(2 * i); + } + } + + private static native boolean nativeTestPinLongArrayCritical(long[] array); + + @Test + public void testPinFloatArrayCritical() { + float[] floatArray = new float[MAGIC]; + for (int i = 0; i < floatArray.length; ++i) { + floatArray[i] = (long) i; + } + + assertThat(nativeTestPinFloatArrayCritical(floatArray)).isTrue(); + + for (int i = 0; i < floatArray.length; ++i) { + assertThat(floatArray[i]).isEqualTo(2 * i, offset(1e-3f)); + } + } + + private static native boolean nativeTestPinFloatArrayCritical(float[] array); + + @Test + public void testPinDoubleArrayCritical() { + double[] doubleArray = new double[MAGIC]; + for (int i = 0; i < doubleArray.length; ++i) { + doubleArray[i] = (double) i; + } + + assertThat(nativeTestPinDoubleArrayCritical(doubleArray)).isTrue(); + + for (int i = 0; i < doubleArray.length; ++i) { + assertThat(doubleArray[i]).isEqualTo(2 * i, offset(1e-3)); + } + } + + private static native boolean nativeTestPinDoubleArrayCritical(double[] array); + + @Test + public void testIndexOutOfBoundsInRegions() { + assertThat(nativeTestIndexOutOfBoundsInRegions()).isTrue(); + } + + private static native boolean nativeTestIndexOutOfBoundsInRegions(); + + @Test + public void testBooleanArrayIndexing() { + boolean[] array = {true, true, false, true, false}; + for (int ii = 0; ii < 5; ii++) { + assertThat(nativeTestBooleanArrayIndexing(array, ii)).isEqualTo(array[ii]); + } + } + + private native boolean nativeTestBooleanArrayIndexing(boolean[] array, int idx); + + @Test + public void testIntegerArrayIndexing() { + int[] array = {0, 1, 2, 3, 4}; + for (int ii = 0; ii < 5; ii++) { + assertThat(nativeTestIntegerArrayIndexing(array, ii)).isEqualTo(array[ii]); + } + } + + private native int nativeTestIntegerArrayIndexing(int[] array, int idx); + + @Test + public void testIntegerArraySize() { + int[] array = {0, 1, 2, 3, 4}; + assertThat(nativeTestIntegerArraySize(array)).isEqualTo(array.length); + } + + private native int nativeTestIntegerArraySize(int[] array); + + @Test + public void testIntegerArrayIncrement() { + int[] array = {0, 1, 2, 3, 4}; + array = nativeTestIntegerArrayIncrement(array); + for (int ii = 0; ii < 5; ii++) { + assertThat(array[ii]).isEqualTo(ii + 1); + } + } + + private native int[] nativeTestIntegerArrayIncrement(int[] array); + + @Test + public void testIntegerArrayMoveAssignment() { + int[] array = {0, 1, 2, 3, 4}; + nativeTestIntegerArrayMoveAssignment(array); + assertThat(array[0]).isEqualTo(0); + } + + private native void nativeTestIntegerArrayMoveAssignment(int[] array); + + // On ART, a large array will be placed in the large heap. Arrays here are + // non-movable and so the vm pins them in-place. A small array will be a + // movable object and thus the pinned array will be a copy. + // On Dalvik, all pinned arrays are in-place. + + @Test + @Ignore("Flakey Test. See t8845133") + public void testCopiedPinnedArray() { + int[] array = new int[100]; + assumeTrue(nativeIsPinnedArrayACopy(array)); + assertThat(nativeTestCopiedPinnedArray(array)).isTrue(); + } + + @Test + public void testNonCopiedPinnedArray() { + int[] array = new int[1000000]; + assumeFalse(nativeIsPinnedArrayACopy(array)); + assertThat(nativeTestNonCopiedPinnedArray(array)).isTrue(); + } + + private native boolean nativeIsPinnedArrayACopy(int[] array); + + private native boolean nativeTestCopiedPinnedArray(int[] array); + + private native boolean nativeTestNonCopiedPinnedArray(int[] array); +} diff --git a/deps/fbjni/test/ReadableByteChannelTests.java b/deps/fbjni/test/ReadableByteChannelTests.java new file mode 100644 index 000000000..e50eb4cca --- /dev/null +++ b/deps/fbjni/test/ReadableByteChannelTests.java @@ -0,0 +1,84 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import static org.fest.assertions.api.Assertions.assertThat; + +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.util.Arrays; +import org.junit.Test; + +public class ReadableByteChannelTests extends BaseFBJniTests { + private static final byte[] data = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + }; + + @Test + public void testSmallRead() { + byte[] testData = Arrays.copyOf(data, 8); + ReadableByteChannel channel = new TestChannel(testData); + assertThat(nativeTestSmallRead(channel, testData)).isTrue(); + } + + public static native boolean nativeTestSmallRead(ReadableByteChannel channel, byte[] data); + + @Test + public void testReadToBufferCapacity() { + ReadableByteChannel channel = new TestChannel(data); + assertThat(nativeTestReadToBufferCapacity(channel, data)).isTrue(); + } + + public static native boolean nativeTestReadToBufferCapacity( + ReadableByteChannel channel, byte[] data); + + @Test + public void testConsumeChannel() { + ReadableByteChannel channel = new TestChannel(data); + assertThat(nativeTestConsumeChannel(channel, data)).isTrue(); + } + + public static native boolean nativeTestConsumeChannel(ReadableByteChannel channel, byte[] data); + + @Test + public void testConsumeChannelIteratively() { + ReadableByteChannel channel = new TestChannel(data); + assertThat(nativeTestConsumeChannelIteratively(channel, data)).isTrue(); + } + + public static native boolean nativeTestConsumeChannelIteratively( + ReadableByteChannel channel, byte[] data); + + private static class TestChannel implements ReadableByteChannel { + private final byte[] data; + private int offset = 0; + + TestChannel(byte[] data) { + this.data = data; + } + + @Override + public int read(ByteBuffer buffer) { + if (offset >= data.length) { + return -1; + } + + int n = Math.min(buffer.remaining(), data.length - offset); + int start = offset; + offset += n; + buffer.put(Arrays.copyOfRange(data, start, offset)); + return n; + } + + @Override + public boolean isOpen() { + return true; + } + + @Override + public void close() {} + } +} diff --git a/deps/fbjni/test/jni/byte_buffer_tests.cpp b/deps/fbjni/test/jni/byte_buffer_tests.cpp new file mode 100644 index 000000000..2c27be313 --- /dev/null +++ b/deps/fbjni/test/jni/byte_buffer_tests.cpp @@ -0,0 +1,100 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include + +#include +#include + +#include "expect.h" + +using namespace facebook::jni; + +namespace { + +size_t ByteBufferCapacity(alias_ref buffer) { + static auto meth = JByteBuffer::javaClassStatic()->getMethod("capacity"); + return meth(buffer); +} + +jboolean testDirectByteBuffer(JNIEnv*, jclass) { + std::vector vec{5, 4, 3, 2, 1, 0}; + auto nbb = JByteBuffer::wrapBytes(vec.data(), vec.size()); + + EXPECT(ByteBufferCapacity(nbb) == vec.size()); + EXPECT(nbb->isDirect()); + EXPECT(nbb->getDirectSize() == vec.size()); + + auto bytes = nbb->getDirectBytes(); + + for (size_t i = 0; i < vec.size(); i++) { + EXPECT(bytes[i] == vec[i]); + } + return JNI_TRUE; +} + +jboolean testEmptyDirectByteBuffer(JNIEnv*, jclass) { + uint8_t data; + auto nbb = JByteBuffer::wrapBytes(&data, 0); + + EXPECT(ByteBufferCapacity(nbb) == 0); + EXPECT(nbb->isDirect()); + EXPECT(nbb->getDirectSize() == 0); + + return JNI_TRUE; +} + +jboolean testRewindBuffer(alias_ref self) { + std::vector vec{0, 0, 0, 0, 0, 0, 0, 0}; + auto nbb = JByteBuffer::wrapBytes(vec.data(), vec.size()); + + auto cls = self->getClass(); + auto writeBytes = + cls->getStaticMethod("writeBytes"); + + writeBytes(cls, *nbb, 0, 1, 2, 3); + nbb->rewind(); + writeBytes(cls, *nbb, 4, 5, 6, 7); + + EXPECT(vec[0] == 4); + EXPECT(vec[1] == 5); + EXPECT(vec[2] == 6); + EXPECT(vec[3] == 7); + EXPECT(vec[4] == 0); + EXPECT(vec[5] == 0); + EXPECT(vec[6] == 0); + EXPECT(vec[7] == 0); + + return JNI_TRUE; +} + +local_ref nativeAllocateDirect(alias_ref self, int size) { + return JByteBuffer::allocateDirect(size); +} + +jboolean testFloatBuffer(alias_ref self, alias_ref buffer) { + EXPECT(buffer->isDirect()); + EXPECT(buffer->getDirectCapacity() == 5); + float* raw = (float*)buffer->getDirectAddress(); + EXPECT(raw); + + EXPECT(raw[0] == 1); + EXPECT(raw[1] == 2); + EXPECT(raw[2] == 2.5); + EXPECT(raw[3] == 2.75); + EXPECT(raw[4] == 3); + + return JNI_TRUE; +} + + +} + +void RegisterByteBufferTests() { + registerNatives("com/facebook/jni/ByteBufferTests", { + makeNativeMethod("nativeTestDirectByteBuffer", testDirectByteBuffer), + makeNativeMethod("nativeTestEmptyDirectByteBuffer", testEmptyDirectByteBuffer), + makeNativeMethod("nativeTestRewindBuffer", testRewindBuffer), + makeNativeMethod("nativeAllocateDirect", nativeAllocateDirect), + makeNativeMethod("nativeTestFloatBuffer", testFloatBuffer), + }); +} diff --git a/deps/fbjni/test/jni/expect.h b/deps/fbjni/test/jni/expect.h new file mode 100644 index 000000000..da966e29f --- /dev/null +++ b/deps/fbjni/test/jni/expect.h @@ -0,0 +1,14 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include + +#define EXPECT(X) \ + do { \ + if (!(X)) { \ + FBJNI_LOGE("[%s:%d] Expectation failed: %s", __FILE__, __LINE__, #X); \ + return JNI_FALSE; \ + } \ + } while (false) + diff --git a/deps/fbjni/test/jni/fbjni_onload.cpp b/deps/fbjni/test/jni/fbjni_onload.cpp new file mode 100644 index 000000000..849ba8d12 --- /dev/null +++ b/deps/fbjni/test/jni/fbjni_onload.cpp @@ -0,0 +1,25 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include + +#include + +using namespace facebook::jni; + +void RegisterFbjniTests(); +void RegisterTestHybridClass(); +void RegisterPrimitiveArrayTests(); +void RegisterIteratorTests(); +void RegisterByteBufferTests(); +void RegisterReadableByteChannelTests(); + +jint JNI_OnLoad(JavaVM* vm, void*) { + return facebook::jni::initialize(vm, [] { + RegisterFbjniTests(); + RegisterTestHybridClass(); + RegisterPrimitiveArrayTests(); + RegisterIteratorTests(); + RegisterByteBufferTests(); + RegisterReadableByteChannelTests(); + }); +} diff --git a/deps/fbjni/test/jni/fbjni_tests.cpp b/deps/fbjni/test/jni/fbjni_tests.cpp new file mode 100644 index 000000000..f3ed115eb --- /dev/null +++ b/deps/fbjni/test/jni/fbjni_tests.cpp @@ -0,0 +1,1518 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include +#include +#include +#include +#include + +#include +#include + +#include "expect.h" +#include "no_rtti.h" + +#include + +#define EXPECT_SAME(A, B, C) EXPECT((A) == (B) && (B) == (C) && (C) == (A)) + +// A lot of the functions here are just confirming that compilation works. +#pragma GCC diagnostic ignored "-Wunused-function" + +using namespace facebook::jni; + +namespace { + +struct Callbacks : public facebook::jni::JavaClass { + constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/FBJniTests$Callbacks;"; +}; + +struct TestThing : public JavaClass { + constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/FBJniTests$TestThing;"; +}; + +// Yes, sloppy and does not handle conversions correctly but does it's job here +static std::string ToString(JNIEnv* env, jstring java_string) { + auto chars = env->GetStringUTFChars(java_string, nullptr); + if (chars == nullptr) { + throw std::runtime_error{"Couldn't get UTF chars"}; + } + + std::string string{chars}; + env->ReleaseStringUTFChars(java_string, chars); + + return string; +} + +jboolean TestClassResolution(JNIEnv* env, jobject self, jstring class_name) { + auto resolved_class = findClassLocal(ToString(env, class_name).c_str()); + return resolved_class.get() != nullptr? JNI_TRUE : JNI_FALSE; +} + +jboolean TestLazyClassResolution(JNIEnv* env, jobject self, jstring class_name) { + auto resolved_class = alias_ref{}; + resolved_class = findClassLocal(ToString(env, class_name).c_str()); + return resolved_class.get() != nullptr? JNI_TRUE : JNI_FALSE; +} + +jobject TestCreateInstanceOf(JNIEnv* env, jobject self, jstring class_name) { + auto clazz = findClassLocal(ToString(env, class_name).c_str()); + auto constructor = clazz->getConstructor(); + return clazz->newObject(constructor, class_name).release(); +} + +jboolean TestTypeDescriptors(JNIEnv* env, jobject self) { + EXPECT(jtype_traits::descriptor() == "Z"); + EXPECT(jtype_traits::descriptor() == "B"); + EXPECT(jtype_traits::descriptor() == "C"); + EXPECT(jtype_traits::descriptor() == "D"); + EXPECT(jtype_traits::descriptor() == "F"); + EXPECT(jtype_traits::descriptor() == "I"); + EXPECT(jtype_traits::descriptor() == "J"); + EXPECT(jtype_traits::descriptor() == "S"); + + EXPECT(jtype_traits::descriptor() == "Ljava/lang/String;"); + EXPECT(jtype_traits::descriptor() == "Ljava/lang/Object;"); + + EXPECT(jtype_traits::descriptor() == "[I"); + EXPECT(jtype_traits>::descriptor() == "[Ljava/lang/String;"); + EXPECT(jtype_traits>>::descriptor() + == "[[Ljava/lang/String;"); + EXPECT(jtype_traits>::descriptor() == "[[I"); + + // base_name() is meaningless for primitive types. + EXPECT(jtype_traits::base_name() == "java/lang/String"); + EXPECT(jtype_traits::base_name() == "java/lang/Object"); + + EXPECT(jtype_traits::base_name() == "[I"); + EXPECT(jtype_traits>::base_name() == "[Ljava/lang/String;"); + EXPECT(jtype_traits>>::base_name() == "[[Ljava/lang/String;"); + EXPECT(jtype_traits>::base_name() == "[[I"); + + return JNI_TRUE; +} + +jboolean TestVirtualMethodResolution_I( + JNIEnv* env, + jobject self, + jstring class_name, + jstring method_name) +{ + auto resolved_class = findClassLocal(ToString(env, class_name).c_str()); + auto resolved_method = + resolved_class->getMethod(ToString(env, method_name).c_str()); + return static_cast(resolved_method); +} + +jboolean TestVirtualMethodResolution_arrB( + JNIEnv* env, + jobject self, + jstring class_name, + jstring method_name) +{ + auto resolved_class = findClassLocal(ToString(env, class_name).c_str()); + auto resolved_method = + resolved_class->getMethod(ToString(env, method_name).c_str()); + return static_cast(resolved_method); +} + +jboolean TestVirtualMethodResolution_S_arrS( + JNIEnv* env, + jobject self, + jstring class_name, + jstring method_name) +{ + auto resolved_class = findClassLocal(ToString(env, class_name).c_str()); + auto resolved_method = + resolved_class->getMethod(jstring)>(ToString(env, method_name).c_str()); + return static_cast(resolved_method); +} + +jboolean TestVirtualMethodResolution_arrarrS( + JNIEnv* env, + jobject self, + jstring class_name, + jstring method_name) +{ + auto resolved_class = findClassLocal(ToString(env, class_name).c_str()); + auto resolved_method = + resolved_class->getStaticMethod>()>(ToString(env, method_name).c_str()); + return static_cast(resolved_method); +} + +jboolean TestVirtualMethodResolution_arrarrI( + JNIEnv* env, + jobject self, + jstring class_name, + jstring method_name) +{ + auto resolved_class = findClassLocal(ToString(env, class_name).c_str()); + auto resolved_method = + resolved_class->getStaticMethod()>(ToString(env, method_name).c_str()); + return static_cast(resolved_method); +} + +jboolean TestLazyVirtualMethodResolution_I( + JNIEnv* env, + jobject self, + jstring class_name, + jstring method_name) +{ + auto resolved_class = findClassLocal(ToString(env, class_name).c_str()); + auto resolved_method = JMethod{}; + resolved_method = resolved_class->getMethod(ToString(env, method_name).c_str()); + return static_cast(resolved_method); +} + +void TestJMethodCallbacks(JNIEnv* env, jobject self, Callbacks::javaobject callbacks) { + static const auto callbacks_class = Callbacks::javaClassStatic(); + + static const auto void_foo = callbacks_class->getMethod("voidFoo"); + void_foo(callbacks); + + static const auto boolean_foo = callbacks_class->getMethod("booleanFoo"); + boolean_foo(callbacks); + + static const auto byte_foo = callbacks_class->getMethod("byteFoo"); + byte_foo(callbacks); + + static const auto char_foo = callbacks_class->getMethod("charFoo"); + char_foo(callbacks); + + static const auto short_foo = callbacks_class->getMethod("shortFoo"); + short_foo(callbacks); + + static const auto int_foo = callbacks_class->getMethod("intFoo"); + int_foo(callbacks); + + static const auto long_foo = callbacks_class->getMethod("longFoo"); + long_foo(callbacks); + + static const auto float_foo = callbacks_class->getMethod("floatFoo"); + float_foo(callbacks); + + static const auto double_foo = callbacks_class->getMethod("doubleFoo"); + double_foo(callbacks); + + static const auto object_foo = callbacks_class->getMethod("objectFoo"); + object_foo(callbacks); + + static const auto string_foo = callbacks_class->getMethod("stringFoo"); + string_foo(callbacks); +} + +// Try to test the static functions +void TestJStaticMethodCallbacks(JNIEnv* env, jobject self) { + // static auto callbacks_class = findClassStatic(callbacks_class_name); + auto cls = findClassLocal("com/facebook/jni/FBJniTests"); + jclass jcls = env->FindClass("com/facebook/jni/FBJniTests"); + + static const auto void_foo_static = cls->getStaticMethod("voidFooStatic"); + void_foo_static(jcls); + + static const auto boolean_foo_static = cls->getStaticMethod("booleanFooStatic"); + boolean_foo_static(jcls); + + static const auto byte_foo_static = cls->getStaticMethod("byteFooStatic"); + byte_foo_static(jcls); + + static const auto char_foo_static = cls->getStaticMethod("charFooStatic"); + char_foo_static(jcls, 'c', 5); + + static const auto short_foo_static = cls->getStaticMethod("shortFooStatic"); + short_foo_static(jcls, 17, 42); + + static const auto int_foo_static = cls->getStaticMethod("intFooStatic"); + int_foo_static(jcls, 5); + + static const auto long_foo_static = cls->getStaticMethod("longFooStatic"); + long_foo_static(jcls); + + static const auto float_foo_static = cls->getStaticMethod("floatFooStatic"); + float_foo_static(jcls); + + static const auto double_foo_static = cls->getStaticMethod("doubleFooStatic"); + double_foo_static(jcls); + + static const auto object_foo_static = cls->getStaticMethod("objectFooStatic"); + object_foo_static(jcls); + + static const auto string_foo_static = cls->getStaticMethod("stringFooStatic"); + string_foo_static(jcls); +} + +jboolean TestIsAssignableFrom(JNIEnv* env, jobject self, jclass cls1, jclass cls2) { + return adopt_local(cls1)->isAssignableFrom(cls2); +} + +jboolean TestIsInstanceOf(JNIEnv* env, jobject self, jobject test_object, jclass cls) { + auto clsref = adopt_local(test_object); + return clsref->isInstanceOf(cls); +} + +jboolean TestIsSameObject(JNIEnv* env, jobject self, jobject a, jobject b) { + return isSameObject(a, b); +} + +jboolean TestGetSuperclass(JNIEnv* env, jobject self, jclass test_class, jclass super_class) { + return isSameObject(adopt_local(test_class)->getSuperclass().get(), super_class); +} + +jboolean StaticCastAliasRefToString(JNIEnv *, jobject , jobject string_as_object) { + alias_ref string_as_object_alias_ref { string_as_object }; + alias_ref string_alias_ref = static_ref_cast(string_as_object_alias_ref); + return isSameObject(string_alias_ref.get(), string_as_object_alias_ref.get()); +} + +jboolean DynamicCastAliasRefToThrowable(JNIEnv *, jobject , jobject might_actually_be_throwable) { + alias_ref might_actually_be_throwable_alias_ref { might_actually_be_throwable }; + // If the next line fails, it will throw an exception. + alias_ref throwable_alias_ref = dynamic_ref_cast(might_actually_be_throwable_alias_ref); + return isSameObject(throwable_alias_ref.get(), might_actually_be_throwable_alias_ref.get()); +} + +jboolean StaticCastLocalRefToString(JNIEnv *, jobject , jobject string_as_object) { + local_ref string_as_object_local_ref = adopt_local(string_as_object); + local_ref string_local_ref = static_ref_cast(string_as_object_local_ref); + return isSameObject(string_local_ref.get(), string_as_object_local_ref.get()); +} + +jboolean DynamicCastLocalRefToString(JNIEnv *, jobject , jobject might_actually_be_string) { + local_ref might_actually_be_string_local_ref = adopt_local(might_actually_be_string); + // If the next line fails, it will throw an exception. + local_ref string_local_ref = dynamic_ref_cast(might_actually_be_string_local_ref); + return isSameObject(string_local_ref.get(), might_actually_be_string_local_ref.get()); +} + +jboolean StaticCastGlobalRefToString(JNIEnv *, jobject , jobject string_as_object) { + global_ref string_as_object_global_ref = make_global(string_as_object); + global_ref string_global_ref = static_ref_cast(string_as_object_global_ref); + return isSameObject(string_global_ref.get(), string_as_object_global_ref.get()); +} + +jboolean DynamicCastGlobalRefToString(JNIEnv *, jobject , jobject might_actually_be_string) { + global_ref might_actually_be_string_global_ref = make_global(might_actually_be_string); + // If the next line fails, it will throw an exception. + global_ref string_global_ref = dynamic_ref_cast(might_actually_be_string_global_ref); + return isSameObject(string_global_ref.get(), might_actually_be_string_global_ref.get()); +} + +template +static void Use(Args&&... args) {} + +jboolean TestWeakRefs(JNIEnv*, jobject self) { + using facebook::jni::internal::g_reference_stats; + + g_reference_stats.reset(); + { + // Wrapping existing local that should be deleted (locals = 1) + auto local = adopt_local(self); + // Make new local (locals = 2) + auto local2 = make_local(local); + // Make weak (weaks = 1) + auto weak = make_weak(local); + // Make global (globals = 1) + auto global = weak.lockGlobal(); + // No new refs + auto banana = std::move(weak); + // No new refs + auto& binini = banana; + // Create a global of the local (keeping the local) (globals = 2) + auto dubglob = make_global(local); + // Create a weak (weaks = 2) + auto dupweak = make_weak(local); + // No new refs + swap(local, local2); + + Use(binini); + } + + FBJNI_LOGE("locals: %d", g_reference_stats.locals_deleted.load()); + FBJNI_LOGE("globals: %d", g_reference_stats.globals_deleted.load()); + FBJNI_LOGE("weaks: %d", g_reference_stats.weaks_deleted.load()); + + return (g_reference_stats.locals_deleted == 2 && + g_reference_stats.globals_deleted == 2 && + g_reference_stats.weaks_deleted == 2)? JNI_TRUE : JNI_FALSE; +} + +jboolean TestAlias(JNIEnv* env, jobject self) { + auto ref = alias_ref{self}; + return ref->isInstanceOf(findClassLocal("java/lang/Object")); +} + +jboolean testAliasRefConversions(JNIEnv*, jobject self) { + auto aLocalString = make_jstring("foo"); + alias_ref aString = aLocalString; + alias_ref anObject = aLocalString; + anObject = (jstring) nullptr; + anObject = aString; + // aString = anObject; // Shouldn't compile + + return isSameObject(aString, anObject)? JNI_TRUE : JNI_FALSE; +} + +void TestAutoAliasRefReturningVoid(facebook::jni::alias_ref self) { + // If this compiles, it succeeds. +} + +jboolean testNullJString(JNIEnv*, jobject) { + auto aNullJString = make_jstring(nullptr); + EXPECT(aNullJString.get() == (jstring) nullptr); + return JNI_TRUE; +} + +jboolean testSwap(JNIEnv*, jobject self, jobject other) { + auto selfAlias = wrap_alias(self); + auto otherAlias = wrap_alias(other); + + swap(selfAlias, otherAlias); + EXPECT(self == otherAlias); + EXPECT(other == selfAlias); + EXPECT(self != selfAlias); + EXPECT(other != otherAlias); + + auto selfLocal = make_local(self); + auto otherLocal = make_local(other); + swap(selfLocal, otherLocal); + EXPECT(self == otherLocal); + EXPECT(other == selfLocal); + EXPECT(self != selfLocal); + EXPECT(other != otherLocal); + + auto selfGlobal = make_global(self); + auto otherGlobal = make_global(other); + swap(selfGlobal, otherGlobal); + EXPECT(self == otherGlobal); + EXPECT(other == selfGlobal); + EXPECT(self != selfGlobal); + EXPECT(other != otherGlobal); + + auto selfWeak = make_weak(self); + auto otherWeak = make_weak(other); + swap(selfWeak, otherWeak); + auto selfLockedWeak = selfWeak.lockLocal(); + auto otherLockedWeak = otherWeak.lockLocal(); + EXPECT(self == otherLockedWeak); + EXPECT(other == selfLockedWeak); + EXPECT(self != selfLockedWeak); + EXPECT(other != otherLockedWeak); + + return JNI_TRUE; +} + +jboolean testEqualOperator(JNIEnv*, jobject self, jobject other) { + auto selfAlias = wrap_alias(self); + auto otherAlias = wrap_alias(other); + auto selfLocal = adopt_local(self); + auto otherLocal = adopt_local(other); + auto selfGlobal = make_global(self); + auto otherGlobal = make_global(other); + auto selfWeak = make_weak(self); + auto otherWeak = make_weak(other); + auto selfLockedWeak = selfWeak.lockLocal(); + auto otherLockedWeak = otherWeak.lockLocal(); + + EXPECT(self == selfAlias); + EXPECT(selfAlias == selfLocal); + EXPECT(selfLocal == selfGlobal); + EXPECT(selfGlobal == selfLockedWeak); + EXPECT(self != other); + EXPECT(self != otherAlias); + EXPECT(self != otherLocal); + EXPECT(self != otherGlobal); + EXPECT(self != otherLockedWeak); + EXPECT(selfAlias != nullptr); + EXPECT(!(selfAlias == nullptr)); + EXPECT(nullptr != selfLocal); + EXPECT(!(nullptr == selfGlobal)); + + return JNI_TRUE; +} + +jboolean testReleaseAlias(JNIEnv*, jobject self) { + auto local = adopt_local(self); + auto alias = local.releaseAlias(); + + EXPECT(typeid(alias) == typeid(alias_ref)); + EXPECT(isSameObject(self, alias.get())); + + return JNI_TRUE; +} + +jboolean testLockingWeakReferences(JNIEnv*, jobject self) { + auto weak = make_weak(self); + auto local = weak.lockLocal(); + auto global = weak.lockGlobal(); + + EXPECT(typeid(local) == typeid(local_ref)); + EXPECT(typeid(global) == typeid(global_ref)); + EXPECT(self == local); + EXPECT(self == global); + + return JNI_TRUE; +} + +jboolean TestFieldAccess(alias_ref self, const std::string& field_name, + jint oldval, jint newval) { + auto cls = self->getClass(); + auto fld = cls->getField(field_name.c_str()); + auto method = cls->getMethod("bar"); + + if (method(self.get(), 17) != 42) { + return JNI_FALSE; + } + + if (method(self, 17) != 42) { + return JNI_FALSE; + } + + if (self->getFieldValue(fld) != oldval) { + return JNI_FALSE; + } + + self->setFieldValue(fld, newval); + + return JNI_TRUE; +} + +jboolean TestStringFieldAccess( + JNIEnv* env, + jobject self, + jstring field_name, + jstring oldval, + jstring newval) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto fld = cls->getField(ToString(env, field_name).c_str()); + auto oldvalStr = adopt_local(oldval)->toStdString(); + + auto curvalRef = me->getFieldValue(fld); + if (curvalRef->toStdString() != oldvalStr) { + return JNI_FALSE; + } + + const alias_ref cme = me; + if (cme->getFieldValue(fld)->toStdString() != oldvalStr) { + return JNI_FALSE; + } + + me->setFieldValue(fld, newval); + + return JNI_TRUE; +} + +jboolean TestReferenceFieldAccess( + alias_ref self, + std::string const& field_name, + jobject oldval, + jobject newval, + jboolean useWrapper) { + auto cls = self->getClass(); + auto rawfld = cls->getField(field_name.c_str(), TestThing::kJavaDescriptor); + + if (self->getFieldValue(rawfld) != oldval) { + return JNI_FALSE; + } + + alias_ref const cself = self; + if (cself->getFieldValue(rawfld) != oldval) { + return JNI_FALSE; + } + + if (useWrapper) { + auto newvalRef = adopt_local(static_cast(newval)); + auto fld = cls->getField(field_name.c_str()); + self->setFieldValue(fld, newvalRef); + } else { + self->setFieldValue(rawfld, newval); + } + + return JNI_TRUE; +} + +jboolean TestStaticFieldAccess( + JNIEnv* env, + jobject self, + jstring field_name, + jint oldval, + jint newval) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto fld = cls->getStaticField(ToString(env, field_name).c_str()); + + if (cls->getStaticFieldValue(fld) != oldval) { + return JNI_FALSE; + } + cls->setStaticFieldValue(fld, newval); + return JNI_TRUE; +} + +jboolean TestStaticStringFieldAccess( + JNIEnv* env, + jobject self, + jstring field_name, + jstring oldval, + jstring newval) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto fld = cls->getStaticField(ToString(env, field_name).c_str()); + + auto curvalRef = cls->getStaticFieldValue(fld); + if (curvalRef->toStdString() != adopt_local(oldval)->toStdString()) { + return JNI_FALSE; + } + cls->setStaticFieldValue(fld, newval); + return JNI_TRUE; +} + +jboolean TestStaticReferenceFieldAccess( + alias_ref self, + std::string const& field_name, + jobject oldval, + jobject newval, + jboolean useWrapper) { + auto cls = self->getClass(); + auto rawfld = cls->getStaticField(field_name.c_str(), TestThing::kJavaDescriptor); + + auto curvalRef = cls->getStaticFieldValue(rawfld); + if (curvalRef != oldval) { + return JNI_FALSE; + } + + if (useWrapper) { + auto newvalRef = adopt_local(static_cast(newval)); + auto fld = cls->getStaticField(field_name.c_str()); + cls->setStaticFieldValue(fld, newvalRef); + } else { + cls->setStaticFieldValue(rawfld, newval); + } + + return JNI_TRUE; +} + +jboolean TestNonVirtualMethod(JNIEnv* env, jobject self, jboolean s) { + auto me = adopt_local(self); + if (!me) { + return JNI_FALSE; + } + + auto cls = me->getClass(); + if (!cls) { + return JNI_FALSE; + } + auto method = cls->getNonvirtualMethod("nonVirtualMethod"); + + jclass jcls = env->FindClass("com/facebook/jni/FBJniTests"); + + return method(self, jcls, s); +} + +jtypeArray +TestArrayCreation(JNIEnv* env, jobject self, jstring s0, jstring s1, jstring s2) { + auto array = JArrayClass::newArray(3); + array->setElement(0, s0); + array->setElement(1, s1); + array->setElement(2, s2); + return static_cast>(array.release()); +} + +jtypeArray> +TestMultidimensionalObjectArray(JNIEnv* env, jobject self, jstring s0, jstring s1, jstring s2) { + auto array = JArrayClass>::newArray(2); + auto row = JArrayClass::newArray(2); + row->setElement(0, s0); + row->setElement(1, s1); + (*array)[0] = row; + row = JArrayClass::newArray(1); + row->setElement(0, s2); + (*array)[1] = row; + return array.release(); +} + +jtypeArray +TestMultidimensionalPrimitiveArray(JNIEnv* env, jobject self, jint i0, jint i1, jint i2) { + auto array = JArrayClass::newArray(2); + auto row = JArrayInt::newArray(2); + row->setRegion(0, 1, &i0); + row->setRegion(1, 1, &i1); + (*array)[0] = row; + row = JArrayInt::newArray(1); + row->setRegion(0, 1, &i2); + (*array)[1] = row; + return array.release(); +} + +jstring TestBuildStringArray(JNIEnv* env, jobject self, jtypeArray input) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto method = cls->getMethod)>("captureStringArray"); + + auto niceInput = adopt_local_array(input); + auto length = niceInput->size(); + auto inputCopy = JArrayClass::newArray(length); + for (size_t idx = 0; idx < length; idx++) { + switch (idx % 3) { + case 0: { + // Verify that assignment from a T works. + jstring value = (jstring)env->GetObjectArrayElement(input, idx); + (*inputCopy)[idx] = value; // Assignment from actual type. + env->DeleteLocalRef(value); + break; + } + case 1: { + // Verify that direct assignment from an ElementProxy works. + (*inputCopy)[idx] = (*niceInput)[idx]; + break; + } + case 2: + default: { + // Verify that assignment from a smart reference works. + auto smartRef = adopt_local((*niceInput)[idx]); + (*inputCopy)[idx] = smartRef; + break; + } + } + } + + return method(self, inputCopy.get()).release(); +} + +template +void tryResolveMethodWithCxxTypes(std::string sig, alias_ref me, std::string methodName, Args... args) { + auto cls = me->getClass(); + auto method = cls->getMethod(methodName.c_str()); + if (!method) throw std::runtime_error("method lookup failed with signature=" + sig); + try { + method(me, args...); + } catch (std::exception&) { + throw std::runtime_error("calling method failed with signature=" + sig); + } + + auto nonVirtualMethod = cls->getNonvirtualMethod(methodName.c_str()); + if (!nonVirtualMethod) throw std::runtime_error("method lookup failed with signature=" + sig); + try { + nonVirtualMethod(me, cls.get(), args...); + } catch (std::exception&) { + throw std::runtime_error("calling method failed with signature=" + sig); + } + + auto staticMethod = cls->getStaticMethod((methodName + "Static").c_str()); + if (!staticMethod) throw std::runtime_error("static method lookup failed with signature=" + sig); + try { + staticMethod(cls, args...); + } catch (std::exception&) { + throw std::runtime_error("calling static method failed with signature=" + sig); + } +} + +// Simple utility to give us a good error message. +#define runTest(sig, ...) \ + tryResolveMethodWithCxxTypes(#sig, self, method, __VA_ARGS__); + +void TestMethodResolutionWithCxxTypes(alias_ref self, alias_ref jmethod, alias_ref str, jlong v) { + auto method = jmethod->toStdString(); + runTest(jobject(jstring, jlong), str.get(), v); + runTest(local_ref(jstring, jlong), str.get(), v); + + runTest(jobject(local_ref, jlong), make_local(str), v); + runTest(jobject(alias_ref, jlong), str, v); + + runTest(jobject(alias_ref, int64_t), str, (int64_t)v); + runTest(jobject(alias_ref, long long), str, (long long)v); + + runTest(jobject(const char*, int64_t), str->toStdString().c_str(), (int64_t)v); + + method = jmethod->toStdString() + "Void"; + runTest(void(jstring, int64_t), str.get(), v); + + method = jmethod->toStdString() + "Int"; + runTest(jint(jstring, int64_t), str.get(), v); +} + +#undef runTest + +void TestHandleJavaCustomException(JNIEnv* env, jobject self) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto method = cls->getMethod("customExceptionThrower"); + + method(self); +} + +void TestHandleNullExceptionMessage(JNIEnv* env, jobject self) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto method = cls->getMethod("nullMessageThrower"); + + try { + method(self); + } catch (const std::exception& ex) { + ex.what(); + } +} + +void TestHandleNestedException(JNIEnv* env, jobject self) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto method = cls->getMethod("customExceptionThrower"); + + try { + try { + method(self); + } catch (...) { + std::throw_with_nested(std::runtime_error("middle")); + } + } catch (...) { + std::throw_with_nested(std::out_of_range("outer")); + } +} + +void TestHandleNoRttiException(JNIEnv* env, jobject self) { + nortti::throwException(); +} + +jstring TestCopyConstructor(JNIEnv* env, jobject self) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto method = cls->getMethod("customExceptionThrower"); + + try { + method(self); + return env->NewStringUTF("method did not throw"); + } catch (JniException ex) { // No & -- we're intentionally invoking the copy constructor. + return env->NewStringUTF(ex.what()); + } +} + +jstring TestMoveConstructorWithEmptyWhat(JNIEnv* env, jobject self) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto method = cls->getMethod("customExceptionThrower"); + + try { + method(self); + return env->NewStringUTF("method did not throw"); + } catch (JniException& ex) { + auto replacement = JniException(std::move(ex)); + return env->NewStringUTF(replacement.what()); + } +} + +jstring TestMoveConstructorWithPopulatedWhat(JNIEnv* env, jobject self) { + auto me = adopt_local(self); + auto cls = me->getClass(); + auto method = cls->getMethod("customExceptionThrower"); + + try { + method(self); + return env->NewStringUTF("method did not throw"); + } catch (JniException& ex) { + ex.what(); + auto replacement = JniException(std::move(ex)); + return env->NewStringUTF(replacement.what()); + } +} + +void TestHandleCppRuntimeError(JNIEnv* env, jobject self, jstring message) { + throw std::runtime_error(ToString(env, message)); +} + +void TestHandleCppIOBaseFailure(JNIEnv* env, jobject self) { + throw std::ios_base::failure("A C++ IO base failure."); +} + +void TestHandleCppSystemError(JNIEnv* env, jobject self) { + // Throw a sample std::system_error + throw std::system_error(EFAULT, std::system_category()); +} + +void TestInterDsoExceptionHandlingA(JNIEnv* env, jobject self) { + inter_dso_exception_test_2a(); +} + +jboolean TestInterDsoExceptionHandlingB(JNIEnv* env, jobject self) { + return inter_dso_exception_test_2b(); +} + +void TestHandleCppIntThrow(JNIEnv* env, jobject self) { + throw 42; +} + +void TestHandleCppCharPointerThrow(JNIEnv* env, jobject self) { + throw "Some random message"; +} + +void TestThrowJavaExceptionByName(JNIEnv* env, jobject self) { + throwNewJavaException("java/lang/IllegalArgumentException", "bad news: %s", "it didn't work"); +} + +jint TestJThread(JNIEnv* env, jobject self) { + jint i = -1; + auto thread = JThread::create([&] { + i = 0; + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + i = 1; + }); + thread->start(); + thread->join(); + return i; +} + +// Global for simpleWorker tests. Relies on thread sync points (constructor, join) for "locking". +jint gWorkerValue; + +void simpleWorker(jobject grefSelf, jdouble input) { + auto attachGuard = ThreadScope(); // This tests the move constructor. + auto self = adopt_global(grefSelf); + // Claw up from the object to avoid classloader issues. + auto barMethod = self->getClass()->getMethod("bar"); + gWorkerValue = barMethod(self.get(), input); +} + +void nestedSimpleWorker(jobject grefSelf, jdouble input) { + ThreadScope attachGuard; // More efficient version of guard; no move constructor required. + simpleWorker(grefSelf, input); +} + +jint TestThreadScopeGuard(JNIEnv* env, jobject self, jdouble input) { + // Turn self into a global reference before passing it to a working thread. + auto grefSelf = make_global(adopt_local(self)); + auto childThread = std::thread(simpleWorker, grefSelf.release(), input); + childThread.join(); + return gWorkerValue; +} + +jint TestNestedThreadScopeGuard(JNIEnv* env, jobject self, jdouble input) { + // Turn self into a global reference before passing it to a working thread. + auto grefSelf = make_global(adopt_local(self)); + auto childThread = std::thread(nestedSimpleWorker, grefSelf.release(), input); + childThread.join(); + return gWorkerValue; +} + +void classLoadWorker() { + gWorkerValue = 0; + try { + // This should fail because we aren't attached. + Callbacks::javaClassLocal(); + gWorkerValue = -1; + return; + } catch (std::exception& e) { + // ignored + } + try { + ThreadScope::WithClassLoader([&] { + // This should now succeed. + Callbacks::javaClassLocal(); + gWorkerValue = 1; + }); + } catch (std::exception& e) { + gWorkerValue = -2; + // Catch this and log it so that we get a test failure instead of a crash. + FBJNI_LOGE("%s", e.what()); + } +} + +jint TestClassLoadInWorker(JNIEnv* env, jobject self) { + std::thread t(classLoadWorker); + t.join(); + return gWorkerValue; +} + +jint TestClassLoadWorkerFastPath(JNIEnv* env, jobject self) { + jint i = 0; + ThreadScope::WithClassLoader([&] { + // Execute on the fast path + Callbacks::javaClassLocal(); + i += 1; + }); + + std::thread t([&] { + ThreadScope::WithClassLoader([&] { + // Execute on the slow path + Callbacks::javaClassLocal(); + i += 1; + }); + }); + t.join(); + + std::thread t2([&] { + ThreadScope scope; + ThreadScope::WithClassLoader([&] { + // Execute on the slow path even though thread is already attached. + Callbacks::javaClassLocal(); + i += 1; + }); + }); + t2.join(); + + return i; +} + +void testNewObject(JNIEnv*, jobject self) { + // This is a compilation only test, verifies that all the types work out. + auto cls = findClassLocal("java/lang/String"); + auto ctr = cls->getConstructor(); + local_ref obj = cls->newObject(ctr); + auto str = obj->toStdString(); + Use(str); +} + +template +static jboolean copyAndVerify(T& orig) { + T copy{orig}; + EXPECT(orig == copy); + + return JNI_TRUE; +} + +template +static jboolean assignAndVerify(T& orig) { + T copy{}; + copy = orig; + EXPECT(orig == copy); + + return JNI_TRUE; +} + +jboolean testNullReferences(JNIEnv*, jobject) { + jobject nullobject = nullptr; + + auto local = local_ref{}; + EXPECT(!local); + + auto localWrap = adopt_local(nullobject); + EXPECT(!localWrap); + + auto localMake = make_local(local); + EXPECT(!localMake); + EXPECT_SAME(local, localWrap, localMake); + + auto global = global_ref{}; + EXPECT(!global); + + auto globalWrap = adopt_global(nullobject); + EXPECT(!globalWrap); + + auto globalMake = make_global(global); + EXPECT(!globalMake); + EXPECT_SAME(global, globalWrap, globalMake); + + weak_ref weak_global = weak_ref{}; + EXPECT(!weak_global.lockLocal()); + + weak_ref weak_globalWrap = adopt_weak_global(nullobject); + EXPECT(!weak_globalWrap.lockLocal()); + EXPECT(weak_global.lockLocal() == weak_globalWrap.lockGlobal()); + EXPECT(!make_local(adopt_local(nullobject))); + EXPECT(!make_global(nullobject)); + + return JNI_TRUE; +} + +jboolean testCreatingReferences(JNIEnv*, jobject self) { + auto a = wrap_alias(self); + auto l = adopt_local(self); + auto g = make_global(l); + auto w = make_weak(l); + + EXPECT(a == l && a == g && a == w.lockLocal()); + + auto lp = make_local(self); + auto la = make_local(a); + auto ll = make_local(l); + auto lg = make_local(g); + + EXPECT(a == lp && a == la && a == ll && a == lg); + + auto gp = make_global(self); + auto ga = make_global(a); + auto gl = make_global(l); + auto gg = make_global(g); + + EXPECT(a == gp && a == ga && a == gl && a == gg); + + return JNI_TRUE; +} + +jboolean testAssignmentAndCopyConstructors(JNIEnv*, jobject self) { + using facebook::jni::internal::g_reference_stats; + + g_reference_stats.reset(); + { + // Wrapping existing local that should be deleted (locals = 1) + auto local = adopt_local(self); + // Copy constructor (locals = 2) + EXPECT(copyAndVerify(local)); + + // Assignment (locals = 3) + EXPECT(assignAndVerify(local)); + + // Creating a new global (globals = 1) + auto global = make_global(local); + // Copy constructor (globals = 2) + EXPECT(copyAndVerify(global)); + + // Assignment (globals = 3) + EXPECT(assignAndVerify(global)); + + // Creating a new weak (weaks = 1) + auto weak = make_weak(local); + // Copy constructor (weaks = 2, globals = 5) + weak_ref weakCopy{weak}; + EXPECT(weak.lockGlobal() == weakCopy.lockGlobal()); + + // Assignment (weaks = 3, globals = 7) + weakCopy = weak; + EXPECT(weak.lockGlobal() == weakCopy.lockGlobal()); + + auto alias = alias_ref{local}; + alias_ref aliasCopy{alias}; + EXPECT(alias == aliasCopy); + + aliasCopy = alias; + EXPECT(alias == aliasCopy); + + alias = self; + alias = global; + // alias = weak; // Should not compile + } + + FBJNI_LOGE("locals: %d", g_reference_stats.locals_deleted.load()); + FBJNI_LOGE("globals: %d", g_reference_stats.globals_deleted.load()); + FBJNI_LOGE("weaks: %d", g_reference_stats.weaks_deleted.load()); + + EXPECT(g_reference_stats.locals_deleted == 3 && + g_reference_stats.globals_deleted == 7 && + g_reference_stats.weaks_deleted == 3); + + return JNI_TRUE; +} + +template