diff --git a/implementation/BUILD b/implementation/BUILD index 6cf4653f..32f82380 100644 --- a/implementation/BUILD +++ b/implementation/BUILD @@ -1074,7 +1074,7 @@ cc_test( deps = [ "//:jni_bind", "//:jni_test", - "//metaprogramming:concatenate", + "//metaprogramming:type_to_type_map", "@googletest//:gtest_main", ], ) diff --git a/implementation/array_test.cc b/implementation/array_test.cc index cc9858a7..025a3dd2 100644 --- a/implementation/array_test.cc +++ b/implementation/array_test.cc @@ -16,10 +16,7 @@ #include <type_traits> -#include <gmock/gmock.h> -#include <gtest/gtest.h> #include "jni_bind.h" -#include "jni_test.h" namespace { diff --git a/implementation/array_type_conversion_test.cc b/implementation/array_type_conversion_test.cc index eb1084cd..731ff168 100644 --- a/implementation/array_type_conversion_test.cc +++ b/implementation/array_type_conversion_test.cc @@ -14,8 +14,8 @@ * limitations under the License. */ -#include <gmock/gmock.h> -#include <gtest/gtest.h> +#include <type_traits> + #include "jni_bind.h" namespace { diff --git a/implementation/array_view_test.cc b/implementation/array_view_test.cc index 8f1ff7ab..66496d2f 100644 --- a/implementation/array_view_test.cc +++ b/implementation/array_view_test.cc @@ -14,6 +14,7 @@ * limitations under the License. */ #include <algorithm> +#include <array> #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -50,51 +51,43 @@ TEST_F(JniTest, ArrayView_CallsLengthProperly) { TEST_F(JniTest, ArrayView_GetsAndReleaseArrayBuffer) { EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake<jbooleanArray>()), _)) .WillOnce(::testing::Return(Fake<jboolean*>())); - EXPECT_CALL(*env_, ReleaseBooleanArrayElements( - Eq(Fake<jbooleanArray>()), - Eq(Fake<jboolean*>()), 0)); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake<jbooleanArray>()), + Eq(Fake<jboolean*>()), 0)); EXPECT_CALL(*env_, GetByteArrayElements(Eq(Fake<jbyteArray>()), _)) .WillOnce(::testing::Return(Fake<jbyte*>())); - EXPECT_CALL(*env_, - ReleaseByteArrayElements(Eq(Fake<jbyteArray>()), - Eq(Fake<jbyte*>()), 0)); + EXPECT_CALL(*env_, ReleaseByteArrayElements(Eq(Fake<jbyteArray>()), + Eq(Fake<jbyte*>()), 0)); EXPECT_CALL(*env_, GetCharArrayElements(Eq(Fake<jcharArray>()), _)) .WillOnce(::testing::Return(Fake<jchar*>())); - EXPECT_CALL(*env_, - ReleaseCharArrayElements(Eq(Fake<jcharArray>()), - Eq(Fake<jchar*>()), 0)); + EXPECT_CALL(*env_, ReleaseCharArrayElements(Eq(Fake<jcharArray>()), + Eq(Fake<jchar*>()), 0)); EXPECT_CALL(*env_, GetShortArrayElements(Eq(Fake<jshortArray>()), _)) .WillOnce(::testing::Return(Fake<jshort*>())); - EXPECT_CALL( - *env_, ReleaseShortArrayElements(Eq(Fake<jshortArray>()), - Eq(Fake<jshort*>()), 0)); + EXPECT_CALL(*env_, ReleaseShortArrayElements(Eq(Fake<jshortArray>()), + Eq(Fake<jshort*>()), 0)); EXPECT_CALL(*env_, GetIntArrayElements(Eq(Fake<jintArray>()), _)) .WillOnce(::testing::Return(Fake<jint*>())); - EXPECT_CALL(*env_, - ReleaseIntArrayElements(Eq(Fake<jintArray>()), - Eq(Fake<jint*>()), 0)); + EXPECT_CALL(*env_, ReleaseIntArrayElements(Eq(Fake<jintArray>()), + Eq(Fake<jint*>()), 0)); EXPECT_CALL(*env_, GetLongArrayElements(Eq(Fake<jlongArray>()), _)) .WillOnce(::testing::Return(Fake<jlong*>())); - EXPECT_CALL(*env_, - ReleaseLongArrayElements(Eq(Fake<jlongArray>()), - Eq(Fake<jlong*>()), 0)); + EXPECT_CALL(*env_, ReleaseLongArrayElements(Eq(Fake<jlongArray>()), + Eq(Fake<jlong*>()), 0)); EXPECT_CALL(*env_, GetFloatArrayElements(Eq(Fake<jfloatArray>()), _)) .WillOnce(::testing::Return(Fake<jfloat*>())); - EXPECT_CALL( - *env_, ReleaseFloatArrayElements(Eq(Fake<jfloatArray>()), - Eq(Fake<jfloat*>()), 0)); + EXPECT_CALL(*env_, ReleaseFloatArrayElements(Eq(Fake<jfloatArray>()), + Eq(Fake<jfloat*>()), 0)); EXPECT_CALL(*env_, GetDoubleArrayElements(Eq(Fake<jdoubleArray>()), _)) .WillOnce(::testing::Return(Fake<jdouble*>())); - EXPECT_CALL(*env_, ReleaseDoubleArrayElements( - Eq(Fake<jdoubleArray>()), - Eq(Fake<jdouble*>()), 0)); + EXPECT_CALL(*env_, ReleaseDoubleArrayElements(Eq(Fake<jdoubleArray>()), + Eq(Fake<jdouble*>()), 0)); LocalArray<jboolean> boolean_array{AdoptLocal{}, Fake<jbooleanArray>()}; LocalArray<jbyte> byte_array{AdoptLocal{}, Fake<jbyteArray>()}; @@ -118,9 +111,8 @@ TEST_F(JniTest, ArrayView_GetsAndReleaseArrayBuffer) { TEST_F(JniTest, LocalArrayView_AllowsCTAD) { EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake<jbooleanArray>()), _)) .WillOnce(::testing::Return(Fake<jboolean*>())); - EXPECT_CALL(*env_, ReleaseBooleanArrayElements( - Eq(Fake<jbooleanArray>()), - Eq(Fake<jboolean*>()), 0)); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake<jbooleanArray>()), + Eq(Fake<jboolean*>()), 0)); LocalArray<jboolean> boolean_array{AdoptLocal{}, Fake<jbooleanArray>()}; ArrayView ctad_array_view{boolean_array.Pin()}; diff --git a/implementation/class_loader_ref_test.cc b/implementation/class_loader_ref_test.cc index 3868736e..66388550 100644 --- a/implementation/class_loader_ref_test.cc +++ b/implementation/class_loader_ref_test.cc @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include <optional> #include <utility> #include <gmock/gmock.h> diff --git a/implementation/class_loader_test.cc b/implementation/class_loader_test.cc index 8ca0d6fe..29b3962c 100644 --- a/implementation/class_loader_test.cc +++ b/implementation/class_loader_test.cc @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include <gtest/gtest.h> #include "jni_bind.h" -#include "jni_test.h" namespace { diff --git a/implementation/class_test.cc b/implementation/class_test.cc index ec490a2f..44bb0fbb 100644 --- a/implementation/class_test.cc +++ b/implementation/class_test.cc @@ -14,7 +14,6 @@ * limitations under the License. */ -#include <gmock/gmock.h> #include <gtest/gtest.h> #include "jni_bind.h" #include "jni_test.h" diff --git a/implementation/constructor_test.cc b/implementation/constructor_test.cc index 4e52042d..c51bb097 100644 --- a/implementation/constructor_test.cc +++ b/implementation/constructor_test.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <gmock/gmock.h> #include <gtest/gtest.h> #include "jni_bind.h" #include "jni_test.h" diff --git a/implementation/field_ref_test.cc b/implementation/field_ref_test.cc index a6bf521a..1aa5244a 100644 --- a/implementation/field_ref_test.cc +++ b/implementation/field_ref_test.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include <optional> +#include <utility> #include <gmock/gmock.h> #include <gtest/gtest.h> diff --git a/implementation/global_object_test.cc b/implementation/global_object_test.cc index 7e19f79d..3d5af1df 100644 --- a/implementation/global_object_test.cc +++ b/implementation/global_object_test.cc @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include <optional> +#include <tuple> #include <utility> #include <gmock/gmock.h> diff --git a/implementation/id_test.cc b/implementation/id_test.cc index b5826a7f..979fb1bf 100644 --- a/implementation/id_test.cc +++ b/implementation/id_test.cc @@ -16,8 +16,6 @@ #include <string_view> -#include <gmock/gmock.h> -#include <gtest/gtest.h> #include "jni_bind.h" namespace { diff --git a/implementation/jni_type_test.cc b/implementation/jni_type_test.cc index df2d2d7f..8c1b55b4 100644 --- a/implementation/jni_type_test.cc +++ b/implementation/jni_type_test.cc @@ -15,8 +15,6 @@ */ #include <type_traits> -#include <gmock/gmock.h> -#include <gtest/gtest.h> #include "jni_bind.h" namespace { diff --git a/implementation/jvm_test.cc b/implementation/jvm_test.cc index 0cfed6fe..66008e7e 100644 --- a/implementation/jvm_test.cc +++ b/implementation/jvm_test.cc @@ -14,14 +14,8 @@ * limitations under the License. */ -#include <thread> - -#include <gmock/gmock.h> #include <gtest/gtest.h> -#include "implementation/jni_helper/jni_env.h" -#include "implementation/jni_helper/jni_helper.h" #include "jni_bind.h" -#include "jni_test.h" namespace { diff --git a/implementation/legacy/BUILD b/implementation/legacy/BUILD new file mode 100644 index 00000000..76591293 --- /dev/null +++ b/implementation/legacy/BUILD @@ -0,0 +1,245 @@ +package( + licenses = ["notice"], +) + +################################################################################ +# Array. +################################################################################ +cc_test( + name = "array_view_test", + srcs = ["array_view_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# ClassLoader. +################################################################################ +cc_test( + name = "class_loader_ref_test", + srcs = ["class_loader_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation:configuration", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "class_loader_ref_second_test", + srcs = ["class_loader_ref_second_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Field. +################################################################################ +cc_test( + name = "field_ref_test", + srcs = ["field_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# GlobalObject. +################################################################################ +cc_test( + name = "global_object_test", + srcs = ["global_object_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# LocalArray. +################################################################################ +cc_test( + name = "local_array_field_multidimensional_test", + srcs = ["local_array_field_multidimensional_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_field_test", + srcs = ["local_array_field_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_iteration_test", + srcs = ["local_array_iteration_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_multidimensional_test", + srcs = ["local_array_multidimensional_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_method_test", + srcs = ["local_array_method_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_method_multidimensional_test", + srcs = ["local_array_method_multidimensional_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_string_test", + srcs = ["local_array_string_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# LocalObject. +################################################################################ +cc_test( + name = "local_object_test", + srcs = ["local_object_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Method. +################################################################################ +cc_test( + name = "method_ref_test", + srcs = ["method_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Multi type test. +################################################################################ +cc_test( + name = "multi_type_test", + srcs = ["multi_type_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Object. +################################################################################ +cc_test( + name = "overload_ref_test", + srcs = ["overload_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Static. +################################################################################ +cc_test( + name = "static_ref_test", + srcs = ["static_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "string_ref_test", + srcs = ["string_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Self. +################################################################################ +cc_test( + name = "self_test", + srcs = ["self_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "@googletest//:gtest_main", + ], +) diff --git a/implementation/legacy/README.md b/implementation/legacy/README.md new file mode 100644 index 00000000..1677833d --- /dev/null +++ b/implementation/legacy/README.md @@ -0,0 +1,3 @@ +This directory contains a copy of the tests from implementation but using the legacy `obj.Foo("methodName", args...);` syntax. + +These tests are effectively pure duplication and may be removed at some future point. They are here to maintain coverage as JNI Bind migrates to a C++20 friendly syntax. diff --git a/implementation/legacy/array_view_test.cc b/implementation/legacy/array_view_test.cc new file mode 100644 index 00000000..7130e16f --- /dev/null +++ b/implementation/legacy/array_view_test.cc @@ -0,0 +1,447 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <algorithm> +#include <array> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::AdoptLocal; +using ::jni::ArrayView; +using ::jni::CDecl_t; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Return; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Eq; + +//////////////////////////////////////////////////////////////////////////////// +// Pin Tests. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_CallsLengthProperly) { + EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3)); + + LocalArray<jint> local_int_array{5}; + EXPECT_EQ(local_int_array.Length(), 3); +} + +TEST_F(JniTest, ArrayView_GetsAndReleaseArrayBuffer) { + EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake<jbooleanArray>()), _)) + .WillOnce(::testing::Return(Fake<jboolean*>())); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake<jbooleanArray>()), + Eq(Fake<jboolean*>()), 0)); + + EXPECT_CALL(*env_, GetByteArrayElements(Eq(Fake<jbyteArray>()), _)) + .WillOnce(::testing::Return(Fake<jbyte*>())); + EXPECT_CALL(*env_, ReleaseByteArrayElements(Eq(Fake<jbyteArray>()), + Eq(Fake<jbyte*>()), 0)); + + EXPECT_CALL(*env_, GetCharArrayElements(Eq(Fake<jcharArray>()), _)) + .WillOnce(::testing::Return(Fake<jchar*>())); + EXPECT_CALL(*env_, ReleaseCharArrayElements(Eq(Fake<jcharArray>()), + Eq(Fake<jchar*>()), 0)); + + EXPECT_CALL(*env_, GetShortArrayElements(Eq(Fake<jshortArray>()), _)) + .WillOnce(::testing::Return(Fake<jshort*>())); + EXPECT_CALL(*env_, ReleaseShortArrayElements(Eq(Fake<jshortArray>()), + Eq(Fake<jshort*>()), 0)); + + EXPECT_CALL(*env_, GetIntArrayElements(Eq(Fake<jintArray>()), _)) + .WillOnce(::testing::Return(Fake<jint*>())); + EXPECT_CALL(*env_, ReleaseIntArrayElements(Eq(Fake<jintArray>()), + Eq(Fake<jint*>()), 0)); + + EXPECT_CALL(*env_, GetLongArrayElements(Eq(Fake<jlongArray>()), _)) + .WillOnce(::testing::Return(Fake<jlong*>())); + EXPECT_CALL(*env_, ReleaseLongArrayElements(Eq(Fake<jlongArray>()), + Eq(Fake<jlong*>()), 0)); + + EXPECT_CALL(*env_, GetFloatArrayElements(Eq(Fake<jfloatArray>()), _)) + .WillOnce(::testing::Return(Fake<jfloat*>())); + EXPECT_CALL(*env_, ReleaseFloatArrayElements(Eq(Fake<jfloatArray>()), + Eq(Fake<jfloat*>()), 0)); + + EXPECT_CALL(*env_, GetDoubleArrayElements(Eq(Fake<jdoubleArray>()), _)) + .WillOnce(::testing::Return(Fake<jdouble*>())); + EXPECT_CALL(*env_, ReleaseDoubleArrayElements(Eq(Fake<jdoubleArray>()), + Eq(Fake<jdouble*>()), 0)); + + LocalArray<jboolean> boolean_array{AdoptLocal{}, Fake<jbooleanArray>()}; + LocalArray<jbyte> byte_array{AdoptLocal{}, Fake<jbyteArray>()}; + LocalArray<jchar> char_array{AdoptLocal{}, Fake<jcharArray>()}; + LocalArray<jshort> short_array{AdoptLocal{}, Fake<jshortArray>()}; + LocalArray<jint> int_array{AdoptLocal{}, Fake<jintArray>()}; + LocalArray<jlong> long_array{AdoptLocal{}, Fake<jlongArray>()}; + LocalArray<jfloat> float_array{AdoptLocal{}, Fake<jfloatArray>()}; + LocalArray<jdouble> double_array{AdoptLocal{}, Fake<jdoubleArray>()}; + + ArrayView<jboolean, 1> boolean_array_pin = {boolean_array.Pin()}; + ArrayView<jbyte, 1> byte_array_pin = {byte_array.Pin()}; + ArrayView<jint, 1> int_array_pin = {int_array.Pin()}; + ArrayView<jchar, 1> char_array_pin = {char_array.Pin()}; + ArrayView<jshort, 1> short_array_pin = {short_array.Pin()}; + ArrayView<jlong, 1> long_array_pin = {long_array.Pin()}; + ArrayView<jfloat, 1> float_array_pin = {float_array.Pin()}; + ArrayView<jdouble, 1> double_array_pin = {double_array.Pin()}; +} + +TEST_F(JniTest, LocalArrayView_AllowsCTAD) { + EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake<jbooleanArray>()), _)) + .WillOnce(::testing::Return(Fake<jboolean*>())); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake<jbooleanArray>()), + Eq(Fake<jboolean*>()), 0)); + + LocalArray<jboolean> boolean_array{AdoptLocal{}, Fake<jbooleanArray>()}; + ArrayView ctad_array_view{boolean_array.Pin()}; + + // Despite supporting construction from xvalue, move ctor is deleted (good). + // ArrayView ctad_array_view_2 {std::move(ctad_array_view)}; +} + +TEST_F(JniTest, ArrayView_ConstructsFromAnObject) { + static constexpr Class kClass{"kClass"}; + LocalArray<jobject, 1, kClass> local_obj_array{1, LocalObject<kClass>{}}; +} + +TEST_F(JniTest, ArrayView_ConstructsFromAnObjectRValueWithCTAD) { + static constexpr Class kClass{"kClass"}; + LocalArray<jobject, 1, kClass> local_obj_array{1, LocalObject<kClass>{}}; +} + +TEST_F(JniTest, ArrayView_GetsAnObject) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, GetObjectArrayElement(_, _)); + LocalArray<jobject, 1, kClass> local_obj_array{1, LocalObject<kClass>{}}; + local_obj_array.Get(0); +} + +TEST_F(JniTest, ArrayView_GetsAnObjectWithCTAD) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, GetObjectArrayElement(_, _)); + LocalArray<jobject, 1, kClass> local_obj_array{1, LocalObject<kClass>{}}; + local_obj_array.Get(0); +} + +//////////////////////////////////////////////////////////////////////////////// +// Iteration Tests: Primitives. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_BooleanIsIterable) { + std::array fake_vals{jboolean{true}, jboolean{false}, jboolean{true}}; + EXPECT_CALL(*env_, NewBooleanArray(3)) + .WillOnce(::testing::Return(Fake<jbooleanArray>())); + EXPECT_CALL(*env_, GetArrayLength(Fake<jbooleanArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetBooleanArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray<jboolean> bool_arr{3}; + ArrayView<jboolean, 1> bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_ByteIsIterable) { + std::array fake_vals{jbyte{true}, jbyte{false}, jbyte{true}}; + EXPECT_CALL(*env_, NewByteArray(3)) + .WillOnce(::testing::Return(Fake<jbyteArray>())); + EXPECT_CALL(*env_, GetArrayLength(Fake<jbyteArray>())) + .WillOnce(testing::Return(3)); + EXPECT_CALL(*env_, GetByteArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray<jbyte> bool_arr{3}; + ArrayView<jbyte, 1> bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_CharIsIterable) { + std::array fake_vals{jchar{true}, jchar{false}, jchar{true}}; + EXPECT_CALL(*env_, NewCharArray(3)) + .WillOnce(::testing::Return(Fake<jcharArray>())); + EXPECT_CALL(*env_, GetArrayLength(Fake<jcharArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetCharArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray<jchar> bool_arr{3}; + ArrayView<jchar, 1> bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_ShortIsIterable) { + std::array fake_vals{jshort{true}, jshort{false}, jshort{true}}; + EXPECT_CALL(*env_, NewShortArray(3)) + .WillOnce(::testing::Return(Fake<jshortArray>())); + EXPECT_CALL(*env_, GetArrayLength(Fake<jshortArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetShortArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray<jshort> bool_arr{3}; + ArrayView<jshort, 1> bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_IntIsIterable) { + std::array fake_vals{jint{1}, jint{2}, jint{3}}; + + EXPECT_CALL(*env_, NewIntArray(3)) + .WillOnce(::testing::Return(Fake<jintArray>())); + EXPECT_CALL(*env_, GetArrayLength(Fake<jintArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetIntArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray<jint> int_arr{3}; + ArrayView<jint, 1> int_view = int_arr.Pin(); + + EXPECT_TRUE(std::equal(int_view.begin(), int_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_LongIsIterable) { + std::array fake_vals{jlong{true}, jlong{false}, jlong{true}}; + EXPECT_CALL(*env_, NewLongArray(3)) + .WillOnce(::testing::Return(Fake<jlongArray>())); + EXPECT_CALL(*env_, GetArrayLength(Fake<jlongArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetLongArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray<jlong> bool_arr{3}; + ArrayView<jlong, 1> bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_FloatIsIterable) { + std::array fake_vals{jfloat{true}, jfloat{false}, jfloat{true}}; + + EXPECT_CALL(*env_, NewFloatArray(3)) + .WillOnce(::testing::Return(Fake<jfloatArray>())); + EXPECT_CALL(*env_, GetArrayLength(Fake<jfloatArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetFloatArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray<jfloat> bool_arr{3}; + ArrayView<jfloat, 1> bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_DoubleIsIterable) { + std::array fake_vals{jdouble{true}, jdouble{false}, jdouble{true}}; + EXPECT_CALL(*env_, NewDoubleArray(3)) + .WillOnce(::testing::Return(Fake<jdoubleArray>())); + EXPECT_CALL(*env_, GetArrayLength(Fake<jdoubleArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetDoubleArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray<jdouble> bool_arr{3}; + ArrayView<jdouble, 1> bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +//////////////////////////////////////////////////////////////////////////////// +// Iteration Tests: Objects. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_ShallowObjectsAreIterable) { + std::array fake_vals{Fake<jobject>(1), Fake<jobject>(2), Fake<jobject>(3)}; + + EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake<jobject>(1))) + .WillOnce(::testing::Return(Fake<jobject>(2))) + .WillOnce(::testing::Return(Fake<jobject>(3))); + + LocalArray<jobject> obj_arr{AdoptLocal{}, Fake<jobjectArray>()}; + ArrayView<jobject, 1> obj_view = obj_arr.Pin(); + + EXPECT_TRUE(std::equal(obj_view.begin(), obj_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_RichObjectsAreIterable) { + static constexpr Class kClass{"kClass", Method{"Foo", Return<int>{}}}; + + EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>())) + .WillOnce(testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake<jobject>(1))) + .WillOnce(::testing::Return(Fake<jobject>(2))) + .WillOnce(::testing::Return(Fake<jobject>(3))); + + LocalArray<jobject, 1, kClass> obj_arr{AdoptLocal{}, Fake<jobjectArray>()}; + auto obj_view = obj_arr.Pin(); + + // Note: GlobalObject will fail to compile here. This is good, the user + // should be forced to explicitly promote the local. + int fake_result = 123; + for (LocalObject<kClass> obj : obj_view) { + EXPECT_CALL(*env_, CallIntMethodV).WillOnce(::testing::Return(fake_result)); + EXPECT_EQ(obj("Foo"), fake_result); + fake_result++; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Iteration Tests: Rank 2 Iterations. +// +// Note: Writing through every type would be tedious, however, if these tests +// could be generalised across the universe of types it would be better. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_Rank2IntArraysAreIterable) { + std::array fake_vals{Fake<jintArray>(1), Fake<jintArray>(2), + Fake<jintArray>(3)}; + + EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 0)) + .WillOnce(::testing::Return(Fake<jintArray>(1))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 1)) + .WillOnce(::testing::Return(Fake<jintArray>(2))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 2)) + .WillOnce(::testing::Return(Fake<jintArray>(3))); + + LocalArray<jint, 2> int_arr_rank_2{AdoptLocal{}, Fake<jobjectArray>()}; + ArrayView<jint, 2> int_rank2_view = int_arr_rank_2.Pin(); + + EXPECT_TRUE(std::equal(int_rank2_view.begin(), int_rank2_view.end(), + fake_vals.begin(), fake_vals.end())); + + /* + // Also viable to write this: + // for (LocalArray<jint, 1> jint_array : int_rank2_view) { } + */ +} + +TEST_F(JniTest, ArrayView_Rank2ObjectkArraysAreIterable) { + std::array fake_vals{Fake<jobjectArray>(1), Fake<jobjectArray>(2), + Fake<jobjectArray>(3)}; + + EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 0)) + .WillOnce(::testing::Return(Fake<jobjectArray>(1))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 1)) + .WillOnce(::testing::Return(Fake<jobjectArray>(2))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 2)) + .WillOnce(::testing::Return(Fake<jobjectArray>(3))); + + LocalArray<jobject, 2> int_arr_rank_2{AdoptLocal{}, Fake<jobjectArray>()}; + ArrayView<jobject, 2> int_rank2_view = int_arr_rank_2.Pin(); + + EXPECT_TRUE(std::equal(int_rank2_view.begin(), int_rank2_view.end(), + fake_vals.begin(), fake_vals.end())); + + /* + // Also viable to write this: + // for (LocalArray<jint, 1> jint_array : int_rank2_view) { } + */ +} + +//////////////////////////////////////////////////////////////////////////////// +// Iteration Tests: Rank 3 Iterations. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_Rank3IntArraysAreIterable) { + std::array fake_vals{Fake<jobjectArray>(), Fake<jobjectArray>(), + Fake<jobjectArray>()}; + + EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 0)) + .WillOnce(::testing::Return(Fake<jobjectArray>())); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 1)) + .WillOnce(::testing::Return(Fake<jobjectArray>())); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 2)) + .WillOnce(::testing::Return(Fake<jobjectArray>())); + + LocalArray<jint, 3> int_arr_rank_3{AdoptLocal{}, Fake<jobjectArray>()}; + ArrayView<jint, 3> int_rank_3_view = int_arr_rank_3.Pin(); + + EXPECT_TRUE(std::equal(int_rank_3_view.begin(), int_rank_3_view.end(), + fake_vals.begin(), fake_vals.end())); + + // Also viable to write this: + // for (LocalArray<jint, 1> jint_array : int_rank_3_view) { } +} + +TEST_F(JniTest, ArrayView_Rank3ObjectkArraysAreIterable) { + std::array fake_vals{Fake<jobjectArray>(1), Fake<jobjectArray>(2), + Fake<jobjectArray>(3)}; + + EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>(0))) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 0)) + .WillOnce(::testing::Return(Fake<jobjectArray>(1))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 1)) + .WillOnce(::testing::Return(Fake<jobjectArray>(2))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake<jobjectArray>()), 2)) + .WillOnce(::testing::Return(Fake<jobjectArray>(3))); + + LocalArray<jobject, 3> object_arr_rank_3{AdoptLocal{}, Fake<jobjectArray>(0)}; + ArrayView<jobject, 3> object_rank_3_view = object_arr_rank_3.Pin(); + + EXPECT_TRUE(std::equal(object_rank_3_view.begin(), object_rank_3_view.end(), + fake_vals.begin(), fake_vals.end())); + + // Also viable to write this: + // for (LocalArray<jobject, 2> jobject_array : object_rank_3_view) { } +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/class_loader_ref_second_test.cc b/implementation/legacy/class_loader_ref_second_test.cc new file mode 100644 index 00000000..2641126f --- /dev/null +++ b/implementation/legacy/class_loader_ref_second_test.cc @@ -0,0 +1,103 @@ +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::ClassLoader; +using ::jni::Constructor; +using ::jni::Fake; +using ::jni::Jvm; +using ::jni::JvmRef; +using ::jni::kNullClassLoader; +using ::jni::LocalClassLoader; +using ::jni::Method; +using ::jni::Params; +using ::jni::Return; +using ::jni::SupportedClassSet; +using ::jni::test::AsGlobal; +using ::jni::test::JniTestWithNoDefaultJvmRef; +using ::testing::_; +using ::testing::InSequence; +using ::testing::StrEq; + +// This test is isolated to correctly observe querying to Class + ClassLoader +// class definitions. Ids are now cached statically against their name, so this +// results in crosstalk. +TEST_F(JniTestWithNoDefaultJvmRef, + ClassLoaderRefTest_ClassLoadersDoNotConflict) { + static constexpr Class kClass{ + "com/google/kClass", Constructor<jint>{}, + Method{"methodNoCrossTalk", Return<jint>{}, Params<jint>{}}}; + static constexpr ClassLoader kClassLoader{kNullClassLoader, + SupportedClassSet{kClass}}; + + // We will use this ClassLoader instead of the default loader to load + // Class. + static constexpr Jvm kClassLoaderJvm{kClassLoader}; + + InSequence sequence; + + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/ClassLoader"))) + .WillOnce(testing::Return(Fake<jclass>(1))); + + EXPECT_CALL(*env_, NewGlobalRef(Fake<jclass>(1))) + .WillOnce(testing::Return(AsGlobal(Fake<jclass>(1)))); + + EXPECT_CALL(*env_, + GetMethodID(AsGlobal(Fake<jclass>(1)), StrEq("loadClass"), + StrEq("(Ljava/lang/String;)Ljava/lang/Class;"))) + .WillOnce(testing::Return(Fake<jmethodID>(1))); + + EXPECT_CALL(*env_, NewStringUTF(_)) + .WillOnce(testing::Return(Fake<jstring>())); + + // We should only try to load the class once even if we create multiple + // instances. + EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(1), Fake<jmethodID>(1), _)) + .WillOnce(testing::Return(Fake<jclass>(2))); + + EXPECT_CALL(*env_, NewGlobalRef(Fake<jclass>(2))) + .WillOnce(testing::Return(AsGlobal(Fake<jclass>(2)))); + EXPECT_CALL(*env_, GetMethodID(AsGlobal(Fake<jclass>(2)), StrEq("<init>"), + StrEq("(I)V"))) + .WillOnce(testing::Return(Fake<jmethodID>(2))); + EXPECT_CALL(*env_, + NewObjectV(AsGlobal(Fake<jclass>(2)), Fake<jmethodID>(2), _)) + .WillOnce(testing::Return(Fake<jobject>(2))); + EXPECT_CALL(*env_, + NewObjectV(AsGlobal(Fake<jclass>(2)), Fake<jmethodID>(2), _)) + .WillOnce(testing::Return(Fake<jobject>(3))); + + EXPECT_CALL(*env_, + GetMethodID(AsGlobal(Fake<jclass>(2)), StrEq("methodNoCrossTalk"), + + StrEq("(I)I"))) + .WillOnce(testing::Return(Fake<jmethodID>(3))); + EXPECT_CALL(*env_, CallIntMethodV(Fake<jobject>(2), Fake<jmethodID>(3), _)) + .WillOnce(testing::Return(123)); + + // Code under test. + JvmRef<kClassLoaderJvm> jvm_ref{jvm_.get()}; + LocalClassLoader<kClassLoader, kClassLoaderJvm> class_loader{ + AdoptLocal{}, Fake<jobject>(1)}; + + auto custom_loader_object = class_loader.BuildLocalObject<kClass>(jint{1}); + + auto second_custom_loader_object = + class_loader.BuildLocalObject<kClass>(jint{2}); + + EXPECT_EQ(custom_loader_object("methodNoCrossTalk", jint{2}), 123); + + TearDown(); +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/class_loader_ref_test.cc b/implementation/legacy/class_loader_ref_test.cc new file mode 100644 index 00000000..64e73e17 --- /dev/null +++ b/implementation/legacy/class_loader_ref_test.cc @@ -0,0 +1,321 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <utility> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::AdoptGlobal; +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::ClassLoader; +using ::jni::Constructor; +using ::jni::Fake; +using ::jni::GlobalClassLoader; +using ::jni::Jvm; +using ::jni::JvmRef; +using ::jni::kDefaultJvm; +using ::jni::kNullClassLoader; +using ::jni::LocalClassLoader; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::PromoteToGlobal; +using ::jni::Return; +using ::jni::SupportedClassSet; +using ::jni::test::AsGlobal; +using ::jni::test::JniTest; +using ::jni::test::JniTestWithNoDefaultJvmRef; +using ::jni::test::kDefaultConfiguration; +using ::testing::_; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::StrEq; + +static constexpr Class kClass1{"Class1", Constructor{}, Constructor<jint>{}, + Method{"Foo", Return{Class{"Class2"}}}}; + +static constexpr Class kClass2{ + "Class2", Constructor{}, Constructor{kClass1}, + Method{"Foo", jni::Return{}, jni::Params{kClass1}}}; + +static constexpr Class kClass3{"Class3"}; +static constexpr Class kClass4{"Class4"}; + +static constexpr ClassLoader kClassLoader{kNullClassLoader, + SupportedClassSet{kClass1, kClass2}}; + +static constexpr Jvm kJvm{kClassLoader}; + +// Helper class for classloader tests that gives the standard default class +// object when using CallObjectMethodV. This is because objects built from +// class loaders are built by calling "loadClass" on the respective classloader +// instance. JniTest is strict about the number of DeleteGlobalRef calls, +// so this satisfies that requirement. +// +// Note, when using this, you must call |TearDown| to pre-empt and class +// teardown prior to expectations being set. +class JniTestForClassLoaders : public JniTestWithNoDefaultJvmRef { + void SetUp() override { + JniTestWithNoDefaultJvmRef::SetUp(); + + ON_CALL(*env_, CallObjectMethodV) + .WillByDefault(::testing::Return(Fake<jclass>())); + } +}; + +TEST_F(JniTest, LocalsAreMoveable) { + LocalClassLoader<kClassLoader, kJvm> obj_1{Fake<jobject>()}; + LocalClassLoader<kClassLoader, kJvm> obj_2{std::move(obj_1)}; +} + +TEST_F(JniTest, GlobalClassLoadersSupportAdoptionMechanics) { + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + GlobalClassLoader<kClassLoader, kJvm> obj_1{AdoptGlobal{}, Fake<jobject>()}; +} + +TEST_F(JniTest, GlobalClassLoadersSupportPromoteMechanics) { + EXPECT_CALL(*env_, DeleteLocalRef).Times(1); + GlobalClassLoader<kClassLoader, kJvm> obj_1{PromoteToGlobal{}, + Fake<jobject>()}; +} + +TEST_F(JniTest, GlobalsAreMoveable) { + GlobalClassLoader<kClassLoader, kJvm> obj_1{AdoptGlobal{}, Fake<jobject>()}; + GlobalClassLoader<kClassLoader, kJvm> obj_2{std::move(obj_1)}; +} + +//////////////////////////////////////////////////////////////////////////////// +// Default JVM, non-default classloader (No ID teardown on JVM destruction). +// +// Because these tests use |jni::kDefaultJvm|, the global refs bound to class +// and method IDs won't be freed (they are static across the process). As a +// hack, skip testing for them by calling clearing expected globals. +// +// An alternate (more effective) emulation would be to have these tests run +// as independent processes which would reflect the static nature of the memory. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, LocalObject_SupportsPassingAnObjectWithAClassLoader) { + JvmRef<kDefaultJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + + // LocalObject<kClass2, kClassLoader> a{}; // doesn't compile (good). + LocalObject<kClass1, kClassLoader> a{Fake<jobject>()}; + LocalObject<kClass2> b{}; + b("Foo", a); + + default_globals_made_that_should_be_released_.clear(); +} + +TEST_F(JniTestForClassLoaders, + ClassLoaderRefTest_ConstructorsAcceptClassLoadedObjects) { + JvmRef<kDefaultJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader<kClassLoader> local_class_loader{Fake<jobject>()}; + auto a = local_class_loader.BuildLocalObject<kClass1>(); + LocalObject<kClass2> b{a}; + b("Foo", a); + + default_globals_made_that_should_be_released_.clear(); + TearDown(); +} + +TEST_F(JniTestForClassLoaders, + lassLoaderRefTest_ConstructorsAcceptGlobalClassLoadedObjects) { + JvmRef<kDefaultJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader<kClassLoader> local_class_loader{Fake<jobject>()}; + auto a = local_class_loader.BuildGlobalObject<kClass1>(); + LocalObject<kClass2> b{a}; + b("Foo", a); + + default_globals_made_that_should_be_released_.clear(); + TearDown(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Non standard JVM, non-default classloader (ID teardown on JVM destruction). +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTestForClassLoaders, + ClassLoaderRefTest_DefaultLoadedObjectBuildsWithClassLoadedObject) { + JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + LocalClassLoader<kClassLoader, kJvm> local_class_loader{AdoptLocal{}, + Fake<jobject>()}; + LocalObject<kClass1, kClassLoader> a = + local_class_loader.BuildLocalObject<kClass1>(); + LocalObject<kClass2> b{a}; + b("Foo", a); + + TearDown(); +} + +TEST_F(JniTestForClassLoaders, ClassLoaderRefTest_ConstructsFromRValue) { + JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()}; + LocalObject<kClass1, kClassLoader, kJvm> b{ + local_class_loader.BuildLocalObject<kClass1>()}; + + LocalObject<kClass2, kClassLoader, kJvm> c{b("Foo")}; + + TearDown(); +} + +TEST_F(JniTestForClassLoaders, + ClassLoaderRefTest_ClassLoadedObjectBuildsWithDefaultLoadedObject) { + JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()}; + + LocalObject<kClass1> a{}; + LocalObject<kClass2, kClassLoader, kJvm> b = + local_class_loader.BuildLocalObject<kClass2>(a); + b("Foo", a); + + TearDown(); +} + +TEST_F( + JniTestForClassLoaders, + ClassLoaderRefTest_LocalClassLoaderWithSingleClassAndConstructorCompiles) { + JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()}; + auto a = local_class_loader.BuildLocalObject<kClass1>(12345); + // local_class_loader.BuildLocalObject<kClass1>(); doesn't compile (good) + + TearDown(); +} + +TEST_F(JniTestWithNoDefaultJvmRef, + ClassLoaderRefTest_LocalClassLoaderWithMultipleClassesCompiles) { + ON_CALL(*env_, CallObjectMethodV) + .WillByDefault(::testing::Return(Fake<jclass>())); + + static constexpr ClassLoader kClassLoader{ + kNullClassLoader, SupportedClassSet{kClass1, kClass2, kClass3, kClass4}}; + static constexpr Jvm kJvm{kClassLoader}; + JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()}; + + LocalObject a{local_class_loader.BuildLocalObject<kClass1>()}; + auto b = local_class_loader.BuildLocalObject<kClass2>(); + auto c = local_class_loader.BuildLocalObject<kClass3>(); + auto d = local_class_loader.BuildLocalObject<kClass4>(); + + TearDown(); +} + +TEST_F(JniTestWithNoDefaultJvmRef, + DefaultLoadedObjectAcceptsClassLoadedObject) { + ON_CALL(*env_, CallObjectMethodV(testing::_, testing::_, testing::_)) + .WillByDefault(::testing::Return(Fake<jclass>())); + + JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()}; + + LocalObject<kClass1, kClassLoader, kJvm> a = + local_class_loader.BuildLocalObject<kClass1>(); + LocalObject<kClass2> b{}; + b("Foo", a); + + TearDown(); +} + +TEST_F(JniTestWithNoDefaultJvmRef, + ClassLoaderRefTest_DefaultLoadedClassCompiles) { + ON_CALL(*env_, CallObjectMethodV(testing::_, testing::_, testing::_)) + .WillByDefault(::testing::Return(Fake<jclass>())); + JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()}; + + LocalObject a{local_class_loader.BuildLocalObject<kClass1>()}; + TearDown(); +} + +TEST_F(JniTestWithNoDefaultJvmRef, + ClassLoaderRefTest_ClassesOfDifferentClassLoadersAreUnique) { + static constexpr Class class_under_test{ + "com/google/ARCore", + Method{"Foo", jni::Return{}}, + }; + static constexpr ClassLoader class_loader{ + kNullClassLoader, SupportedClassSet{class_under_test}}; + + static constexpr jni::Jvm atypical_jvm_definition{class_loader}; + + InSequence seq; + + // The java/lang/Class and java/lang/ClassLoader will always be from the + // default loader, and they only need to be cached once. + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/Class"))) + .WillOnce(testing::Return(Fake<jclass>(2))); + + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/ClassLoader"))) + .WillOnce(testing::Return(Fake<jclass>(3))); + + EXPECT_CALL(*env_, GetObjectClass(Fake<jobject>(1))) + .WillOnce(testing::Return(Fake<jclass>(1))); + + EXPECT_CALL(*env_, + GetMethodID(AsGlobal(Fake<jclass>(2)), StrEq("getClassLoader"), + StrEq("()Ljava/lang/ClassLoader;"))) + .WillOnce(testing::Return(Fake<jmethodID>(2))); + + EXPECT_CALL(*env_, CallObjectMethodV(Fake<jclass>(1), Fake<jmethodID>(2), _)) + .WillOnce(testing::Return(Fake<jobject>(3))); + + EXPECT_CALL(*env_, + GetMethodID(Eq(AsGlobal(Fake<jclass>(3))), StrEq("loadClass"), + StrEq("(Ljava/lang/String;)Ljava/lang/Class;"))) + .WillOnce(testing::Return(Fake<jmethodID>(1))); + + // Note: While "/" is the mandatory delimiter for describing the class in its + // definition, load class uses "." delineation. Strangely, when calling + // Classloader.loadClass on Android both '.' and '/'work, but on x86 Java (and + // presumably other JVM implementations), only the "." is accepted. + EXPECT_CALL(*env_, NewStringUTF(StrEq("com.google.ARCore"))) + .WillOnce(testing::Return(Fake<jstring>())); + + EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(3), Fake<jmethodID>(1), _)) + .WillOnce(testing::Return(Fake<jobject>(2))); + + // Make sure we try to get the method with the loaded class, not the direct + // object class. + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("()V"))) + .WillOnce(testing::Return(Fake<jmethodID>(1))); + + // Code under test. + jni::JvmRef<atypical_jvm_definition> jvm_ref{jvm_.get(), + kDefaultConfiguration}; + jni::LocalObject<class_under_test, class_loader, atypical_jvm_definition> + obj1{AdoptLocal{}, Fake<jobject>(1)}; + obj1("Foo"); + + this->TearDown(); +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/field_ref_test.cc b/implementation/legacy/field_ref_test.cc new file mode 100644 index 00000000..70521495 --- /dev/null +++ b/implementation/legacy/field_ref_test.cc @@ -0,0 +1,174 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utility> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::GlobalObject; +using ::jni::LocalObject; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +// clang-format off +static constexpr Class java_class_under_test{ + "com/google/TestClass", + Field{"booleanField", jboolean{}}, + Field{"byteField", jbyte{}}, + Field{"charField", jchar{}}, + Field{"shortField", jshort{}}, + Field{"intField", jint{}}, + Field{"longField", jlong{}}, + Field{"floatField", jfloat{}}, + Field{"doubleField", jdouble{}} +}; +// clang-format on + +TEST_F(JniTest, Field_SimpleGetAndSet) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("SomeField"), StrEq("I"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetIntField(_, Fake<jfieldID>())).WillOnce(Return(999)); + EXPECT_CALL(*env_, SetIntField(_, Fake<jfieldID>(), 123)); + + static constexpr Class java_class_under_test{"com/google/TestClass", + Field("SomeField", jint{})}; + + GlobalObject<java_class_under_test> obj{}; + EXPECT_EQ(999, obj["SomeField"].Get()); + obj["SomeField"].Set(123); +} + +TEST_F(JniTest, Field_BooleanField) { + EXPECT_CALL(*env_, GetBooleanField); + EXPECT_CALL(*env_, SetBooleanField); + + LocalObject<java_class_under_test> obj{}; + obj["booleanField"].Get(); + obj["booleanField"].Set(true); +} + +TEST_F(JniTest, Field_ByteField) { + EXPECT_CALL(*env_, GetByteField); + EXPECT_CALL(*env_, SetByteField); + + LocalObject<java_class_under_test> obj{}; + obj["byteField"].Get(); + obj["byteField"].Set(jbyte{123}); +} + +TEST_F(JniTest, Field_CharField) { + EXPECT_CALL(*env_, GetCharField); + EXPECT_CALL(*env_, SetCharField); + + LocalObject<java_class_under_test> obj{}; + obj["charField"].Get(); + obj["charField"].Set(jchar{'a'}); +} + +TEST_F(JniTest, Field_ShortField) { + EXPECT_CALL(*env_, GetShortField); + EXPECT_CALL(*env_, SetShortField); + + LocalObject<java_class_under_test> obj{}; + obj["shortField"].Get(); + obj["shortField"].Set(jshort{123}); +} + +TEST_F(JniTest, Field_intField) { + EXPECT_CALL(*env_, GetIntField); + EXPECT_CALL(*env_, SetIntField); + + LocalObject<java_class_under_test> obj{}; + obj["intField"].Get(); + obj["intField"].Set(123); +} + +TEST_F(JniTest, Field_longField) { + EXPECT_CALL(*env_, GetLongField); + EXPECT_CALL(*env_, SetLongField); + + LocalObject<java_class_under_test> obj{}; + obj["longField"].Get(); + obj["longField"].Set(123); +} + +TEST_F(JniTest, Field_floatField) { + LocalObject<java_class_under_test> obj{}; + EXPECT_CALL(*env_, GetFloatField); + EXPECT_CALL(*env_, SetFloatField); + obj["floatField"].Get(); + obj["floatField"].Set(123.f); +} + +TEST_F(JniTest, Field_doubleField) { + LocalObject<java_class_under_test> obj{}; + EXPECT_CALL(*env_, GetDoubleField); + EXPECT_CALL(*env_, SetDoubleField); + obj["doubleField"].Get(); + obj["doubleField"].Set(123.); +} + +TEST_F(JniTest, Field_ObjectGet) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("classField"), StrEq("LkClass2;"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(_, Fake<jfieldID>())) + .WillOnce(Return(Fake<jobject>())); + + static constexpr Class kClass{"com/google/TestClass", + Field{"classField", Class{"kClass2"}}}; + static constexpr Class kClass2{"kClass2"}; + + LocalObject<kClass> obj{}; + LocalObject<kClass2> obj2 = obj["classField"].Get(); +} + +TEST_F(JniTest, Field_ObjectSet) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("classField"), StrEq("LkClass2;"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, SetObjectField(_, Fake<jfieldID>(), Fake<jobject>())) + .Times(4); + + static constexpr Class kClass2{"kClass2"}; + static constexpr Class kClass{ + "com/google/TestClass", + // Field{"classField", Class{"kClass2"}}}; // also works + Field{"classField", kClass2}}; + + LocalObject<kClass> obj{}; + LocalObject<kClass2> some_obj{AdoptLocal{}, Fake<jobject>()}; + obj["classField"].Set(Fake<jobject>()); + obj["classField"].Set(LocalObject<kClass2>{AdoptLocal{}, Fake<jobject>()}); + obj["classField"].Set(some_obj); + obj["classField"].Set(std::move(some_obj)); +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/global_object_test.cc b/implementation/legacy/global_object_test.cc new file mode 100644 index 00000000..60b42f54 --- /dev/null +++ b/implementation/legacy/global_object_test.cc @@ -0,0 +1,289 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <tuple> +#include <utility> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::AdoptGlobal; +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::Constructor; +using ::jni::Fake; +using ::jni::Field; +using ::jni::GlobalObject; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::NewRef; +using ::jni::Params; +using ::jni::PromoteToGlobal; +using ::jni::test::AsGlobal; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +TEST_F(JniTest, GlobalObject_AllowsNullPtrT) { + static constexpr Class kClass{"kClass"}; + EXPECT_CALL(*env_, NewLocalRef).Times(0); + EXPECT_CALL(*env_, NewGlobalRef).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + EXPECT_CALL(*env_, DeleteGlobalRef).Times(0); + + GlobalObject<kClass> obj{nullptr}; + EXPECT_EQ(jobject{obj}, nullptr); +} + +TEST_F(JniTest, GlobalObject_CallsNewAndDeleteOnNewObject) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, NewObjectV).WillOnce(Return(Fake<jobject>())); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>()))); + + GlobalObject<kClass> global_object{}; + + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>())); +} + +TEST_F(JniTest, GlobalObject_ConstructsFromNonStandardConstructor) { + static constexpr Class kClass{ + "kClass", + Constructor{jfloat{}, jfloat{}}, + }; + + EXPECT_CALL(*env_, NewObjectV).WillOnce(Return(Fake<jobject>())); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>()))); + + GlobalObject<kClass> global_object{1.f, 2.f}; + + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>())); +} + +TEST_F(JniTest, GlobalObject_ComparesAgainstOtherGlobalObjects) { + static constexpr Class kClass{ + "kClass", + Constructor{jfloat{}, jfloat{}}, + }; + static constexpr Class kClass2{ + "kClass2", + }; + GlobalObject<kClass> val_1{AdoptGlobal{}, Fake<jobject>(1)}; + GlobalObject<kClass2> val_2{AdoptGlobal{}, Fake<jobject>(2)}; + + EXPECT_TRUE(val_1 == val_1); + EXPECT_FALSE(val_1 == val_2); + EXPECT_TRUE(val_1 != val_2); + EXPECT_TRUE(val_2 == val_2); + EXPECT_TRUE(val_2 != val_1); + EXPECT_FALSE(val_1 == val_2); +} + +TEST_F(JniTest, GlobalObject_DoesNotDeleteAnyLocalsForAdoptedGlobalJobject) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, NewObjectV).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>())); + + GlobalObject<kClass> global_object{AdoptGlobal{}, Fake<jobject>()}; + + EXPECT_EQ(jobject{global_object}, Fake<jobject>()); +} + +TEST_F(JniTest, GlobalObject_PromotesJobjectsOnConstruction) { + EXPECT_CALL(*env_, NewObjectV).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>()))); + + static constexpr Class kClass{"kClass"}; + GlobalObject<kClass> global_object{PromoteToGlobal{}, Fake<jobject>()}; + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>())); +} + +TEST_F(JniTest, GlobalObject_PromotesDecoratedLocals) { + EXPECT_CALL(*env_, NewObjectV).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>()))); + + static constexpr Class kClass{"kClass"}; + LocalObject<kClass> local_obj{AdoptLocal{}, Fake<jobject>()}; + // GlobalObject<kClass> global_object{local_obj}; // doesn't compile (good). + GlobalObject<kClass> global_object{std::move(local_obj)}; + + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>())); +} + +// Identical to above but Local constructed in place. +TEST_F(JniTest, GlobalObject_PromotesDecoratedLocalsFromXValue) { + EXPECT_CALL(*env_, NewObjectV).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>()))); + + static constexpr Class kClass{"kClass"}; + GlobalObject<kClass> global_object{ + LocalObject<kClass>{AdoptLocal{}, Fake<jobject>()}}; + + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>())); +} + +TEST_F(JniTest, GlobalObject_CallsOnlyDeleteOnWrapCtor) { + EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>())); + + static constexpr Class kClass{"com/google/CallsOnlyDeleteOnWrapCtor"}; + GlobalObject<kClass> global_object{AdoptGlobal{}, Fake<jobject>()}; + + EXPECT_NE(jobject{global_object}, nullptr); +} + +TEST_F(JniTest, GlobalObject_CallsNewGlobalRefOnCopy) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, NewGlobalRef(Fake<jobject>(1))) + .WillOnce(::testing::Return(Fake<jobject>(2))); + EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(1))).Times(0); + + GlobalObject<kClass> global_object{NewRef{}, Fake<jobject>(1)}; + EXPECT_EQ(jobject{global_object}, Fake<jobject>(2)); +} + +TEST_F(JniTest, GlobalObject_CallsDeleteOnceAfterAMoveConstruction) { + EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>())); + + static constexpr Class kClass{ + "com/google/CallsDeleteOnceAfterAMoveConstruction"}; + GlobalObject<kClass> global_object_1{AdoptGlobal{}, Fake<jobject>()}; + EXPECT_EQ(jobject{global_object_1}, Fake<jobject>()); + GlobalObject<kClass> global_object_2{std::move(global_object_1)}; + + EXPECT_EQ(jobject{global_object_1}, nullptr); // NOLINT + EXPECT_EQ(jobject{global_object_2}, Fake<jobject>()); +} + +TEST_F(JniTest, GlobalObject_FunctionsProperlyInSTLContainer) { + EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>(1))); + EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>(2))); + + static constexpr Class kClass{ + "com/google/CallsDeleteOnceAfterAMoveConstruction"}; + GlobalObject<kClass> global_object_1{AdoptGlobal{}, Fake<jobject>(1)}; + GlobalObject<kClass> global_object_2{AdoptGlobal{}, Fake<jobject>(2)}; + std::tuple t{std::move(global_object_1), std::move(global_object_2)}; +} + +TEST_F(JniTest, GlobalObject_ValuesWorkAfterMoveConstructor) { + EXPECT_CALL(*env_, CallIntMethodV).Times(3); + EXPECT_CALL(*env_, SetIntField).Times(4); + + static constexpr Class kClass{ + "com/google/ValuesWorkAfterMoveConstructor", + Method{"Foo", jni::Return<jint>{}, Params<jint>{}}, + Field{"BarField", jint{}}}; + GlobalObject<kClass> global_object_1{AdoptGlobal{}, Fake<jobject>(1)}; + global_object_1("Foo", 1); + global_object_1("Foo", 2); + global_object_1["BarField"].Set(1); + + GlobalObject<kClass> global_object_2{std::move(global_object_1)}; + global_object_2("Foo", 3); + global_object_2["BarField"].Set(2); + global_object_2["BarField"].Set(3); + global_object_2["BarField"].Set(4); + + GlobalObject<kClass> global_object_3{AdoptGlobal{}, Fake<jobject>(1)}; +} + +TEST_F(JniTest, GlobalObject_ObjectReturnsInstanceMethods) { + InSequence seq; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("<init>"), StrEq("()V"))) + .WillOnce(Return(Fake<jmethodID>(1))); + EXPECT_CALL(*env_, NewObjectV(_, Fake<jmethodID>(1), _)) + .WillOnce(Return(Fake<jobject>())); + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)I"))) + .WillOnce(Return(Fake<jmethodID>(2))); + EXPECT_CALL(*env_, CallIntMethodV(_, _, _)); + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(F)V"))) + .WillOnce(Return(Fake<jmethodID>(2))); + EXPECT_CALL(*env_, CallVoidMethodV(_, _, _)); + + EXPECT_CALL(*env_, + GetMethodID(_, + StrEq("AMethodWithAReallyLongNameThatWouldPossiblyBeH" + "ardForTemplatesToHandle"), + StrEq("(IFIFD)D"))); + EXPECT_CALL(*env_, CallDoubleMethodV(_, _, _)); + + static constexpr Class java_class_under_test{ + "com/google/ObjectReturnsInstanceMethods", + Method{"Foo", jni::Return<jint>{}, Params<jint>{}}, + Method{"Baz", jni::Return<void>{}, Params<jfloat>{}}, + Method{"AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplates" + "ToHandle", + jni::Return<jdouble>{}, + Params<jint, jfloat, jint, jfloat, jdouble>{}}}; + + GlobalObject<java_class_under_test> global_object{}; + global_object("Foo", 1); + global_object("Baz", 2.f); + global_object( + "AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplatesToHandle", + int{}, float{}, int{}, float{}, double{}); +} + +TEST_F(JniTest, GlobalObject_ReleasesGlobalsForAlternateConstructors) { + static constexpr Class java_class_under_test{ + "ReleasesGlobalsForAlternateConstructors", jni::Constructor<int>{}}; + GlobalObject<java_class_under_test> g1{1}; + GlobalObject<java_class_under_test> g2{2}; + GlobalObject<java_class_under_test> g3{3}; + EXPECT_CALL(*env_, DeleteGlobalRef(_)).Times(3); +} + +TEST_F(JniTest, GlobalObject_SupportsPassingAPrvalue) { + static constexpr Class kTestClass1{"TestClass1"}; + static constexpr Class kTestClass2{ + "TestClass2", Method{"Foo", jni::Return{}, jni::Params{kTestClass1}}}; + + GlobalObject<kTestClass1> a{}; + GlobalObject<kTestClass2> b{}; + b("Foo", std::move(a)); +} + +TEST_F(JniTest, GlobalObjects_PromoteRValuesFromEmittedLValues) { + static constexpr Class kClass1{"TestClass1"}; + static constexpr Class kClass2{ + "TestClass2", Method{"Foo", jni::Return{kClass1}, jni::Params{}}}; + + LocalObject<kClass2> b{}; + GlobalObject<kClass1> a{b("Foo")}; + + a = b("Foo"); +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/local_array_field_multidimensional_test.cc b/implementation/legacy/local_array_field_multidimensional_test.cc new file mode 100644 index 00000000..cba7e3a8 --- /dev/null +++ b/implementation/legacy/local_array_field_multidimensional_test.cc @@ -0,0 +1,156 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::AdoptLocal; +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::LocalObject; +using ::jni::Rank; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; + +static constexpr Class kRank2{ + "kFieldClass", + Field{"BooleanArray", Array{jboolean{}, Rank<2>{}}}, + Field{"ByteArray", Array{jbyte{}, Rank<2>{}}}, + Field{"CharArray", Array{jchar{}, Rank<2>{}}}, + Field{"ShortArray", Array{jshort{}, Rank<2>{}}}, + Field{"IntArray", Array{jint{}, Rank<2>{}}}, + Field{"FloatArray", Array{jfloat{}, Rank<2>{}}}, + Field{"DoubleArray", Array{jdouble{}, Rank<2>{}}}, + Field{"LongArray", Array{jlong{}, Rank<2>{}}}, + Field{"ObjectArray", Array{kClass, Rank<2>{}}}, +}; + +TEST_F(JniTest, LocalArrayField_Rank2) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[[Z"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[[B"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[[C"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[[S"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[[I"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[[F"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[[D"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[[J"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ObjectArray"), StrEq("[[LkClass;"))); + + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillOnce(::testing::Return(Fake<jobjectArray>(1))) + .WillOnce(::testing::Return(Fake<jobjectArray>(2))) + .WillOnce(::testing::Return(Fake<jobjectArray>(3))) + .WillOnce(::testing::Return(Fake<jobjectArray>(4))) + .WillOnce(::testing::Return(Fake<jobjectArray>(5))) + .WillOnce(::testing::Return(Fake<jobjectArray>(6))) + .WillOnce(::testing::Return(Fake<jobjectArray>(7))) + .WillOnce(::testing::Return(Fake<jobjectArray>(8))) + .WillOnce(::testing::Return(Fake<jobjectArray>(9))); + + LocalObject<kRank2> obj{AdoptLocal{}, Fake<jobject>()}; + + EXPECT_EQ(static_cast<jobjectArray>(obj["BooleanArray"].Get()), + Fake<jobjectArray>(1)); + EXPECT_EQ(static_cast<jobjectArray>(obj["ByteArray"].Get()), + Fake<jobjectArray>(2)); + EXPECT_EQ(static_cast<jobjectArray>(obj["CharArray"].Get()), + Fake<jobjectArray>(3)); + EXPECT_EQ(static_cast<jobjectArray>(obj["ShortArray"].Get()), + Fake<jobjectArray>(4)); + EXPECT_EQ(static_cast<jobjectArray>(obj["IntArray"].Get()), + Fake<jobjectArray>(5)); + EXPECT_EQ(static_cast<jobjectArray>(obj["FloatArray"].Get()), + Fake<jobjectArray>(6)); + EXPECT_EQ(static_cast<jobjectArray>(obj["DoubleArray"].Get()), + Fake<jobjectArray>(7)); + EXPECT_EQ(static_cast<jobjectArray>(obj["LongArray"].Get()), + Fake<jobjectArray>(8)); + EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArray"].Get()), + Fake<jobjectArray>(9)); +} + +static constexpr Class kRank3{ + "kFieldClass", + Field{"BooleanArray", Array{jboolean{}, Rank<3>{}}}, + Field{"ByteArray", Array{jbyte{}, Rank<3>{}}}, + Field{"CharArray", Array{jchar{}, Rank<3>{}}}, + Field{"ShortArray", Array{jshort{}, Rank<3>{}}}, + Field{"IntArray", Array{jint{}, Rank<3>{}}}, + Field{"FloatArray", Array{jfloat{}, Rank<3>{}}}, + Field{"DoubleArray", Array{jdouble{}, Rank<3>{}}}, + Field{"LongArray", Array{jlong{}, Rank<3>{}}}, + Field{"ObjectArray", Array{kClass, Rank<3>{}}}, +}; + +TEST_F(JniTest, LocalArrayField_Rank3) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[[[Z"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[[[B"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[[[C"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[[[S"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[[[I"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[[[F"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[[[D"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[[[J"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ObjectArray"), StrEq("[[[LkClass;"))); + + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillOnce(::testing::Return(Fake<jobjectArray>(1))) + .WillOnce(::testing::Return(Fake<jobjectArray>(2))) + .WillOnce(::testing::Return(Fake<jobjectArray>(3))) + .WillOnce(::testing::Return(Fake<jobjectArray>(4))) + .WillOnce(::testing::Return(Fake<jobjectArray>(5))) + .WillOnce(::testing::Return(Fake<jobjectArray>(6))) + .WillOnce(::testing::Return(Fake<jobjectArray>(7))) + .WillOnce(::testing::Return(Fake<jobjectArray>(8))) + .WillOnce(::testing::Return(Fake<jobjectArray>(9))); + + LocalObject<kRank3> obj{AdoptLocal{}, Fake<jobject>()}; + + EXPECT_EQ(static_cast<jobjectArray>(obj["BooleanArray"].Get()), + Fake<jobjectArray>(1)); + EXPECT_EQ(static_cast<jobjectArray>(obj["ByteArray"].Get()), + Fake<jobjectArray>(2)); + EXPECT_EQ(static_cast<jobjectArray>(obj["CharArray"].Get()), + Fake<jobjectArray>(3)); + EXPECT_EQ(static_cast<jobjectArray>(obj["ShortArray"].Get()), + Fake<jobjectArray>(4)); + EXPECT_EQ(static_cast<jobjectArray>(obj["IntArray"].Get()), + Fake<jobjectArray>(5)); + EXPECT_EQ(static_cast<jobjectArray>(obj["FloatArray"].Get()), + Fake<jobjectArray>(6)); + EXPECT_EQ(static_cast<jobjectArray>(obj["DoubleArray"].Get()), + Fake<jobjectArray>(7)); + EXPECT_EQ(static_cast<jobjectArray>(obj["LongArray"].Get()), + Fake<jobjectArray>(8)); + EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArray"].Get()), + Fake<jobjectArray>(9)); +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/local_array_field_test.cc b/implementation/legacy/local_array_field_test.cc new file mode 100644 index 00000000..7bcfc97a --- /dev/null +++ b/implementation/legacy/local_array_field_test.cc @@ -0,0 +1,484 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::AdoptLocal; +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Rank; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::InSequence; +using ::testing::StrEq; + +static constexpr Class kClass2{"kClass2"}; + +//////////////////////////////////////////////////////////////////////////////// +// Fields: Smoke Tests. +//////////////////////////////////////////////////////////////////////////////// +static constexpr Class kFieldClass{ + "kFieldClass", + Field{"BooleanArray", Array{jboolean{}}}, + Field{"ByteArray", Array{jbyte{}}}, + Field{"CharArray", Array{jchar{}}}, + Field{"ShortArray", Array{jshort{}}}, + Field{"IntArray", Array{jint{}}}, + Field{"FloatArray", Array{jfloat{}}}, + Field{"DoubleArray", Array{jdouble{}}}, + Field{"LongArray", Array{jlong{}}}, + Field{"ObjectArrayRank1", Array{kClass2}}, + Field{"ObjectArrayRank2", Array{kClass2, Rank<2>{}}}, + Field{"ObjectArrayRank3", Array{kClass2, Rank<3>{}}}, +}; + +TEST_F(JniTest, ArrayField_Object_Iteration_Rank_1) { + EXPECT_CALL(*env_, GetObjectField) + .WillOnce(::testing::Return(Fake<jobjectArray>())); + EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake<jobject>(100))) + .WillOnce(::testing::Return(Fake<jobject>(101))) + .WillOnce(::testing::Return(Fake<jobject>(102))); + + int i = 100; + LocalObject<kFieldClass> obj{Fake<jobject>()}; + for (LocalObject<kClass2> a : obj["ObjectArrayRank1"].Get().Pin()) { + EXPECT_EQ(static_cast<jobject>(a), Fake<jobject>(i)); + i++; + } +} + +TEST_F(JniTest, ArrayField_Object_Iteration_Rank_2) { + EXPECT_CALL(*env_, GetObjectField) + .WillOnce(::testing::Return(Fake<jobjectArray>())); + EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake<jobject>(100))) + .WillOnce(::testing::Return(Fake<jobject>(101))) + .WillOnce(::testing::Return(Fake<jobject>(102))); + + int i = 100; + LocalObject<kFieldClass> obj{Fake<jobject>(1)}; + for (LocalArray<jobject, 1, kClass2> a : + obj["ObjectArrayRank2"].Get().Pin()) { + EXPECT_EQ(static_cast<jobject>(a), Fake<jobject>(i)); + i++; + } +} + +TEST_F(JniTest, Array_FieldTests) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[Z"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[B"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[C"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[S"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[I"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[F"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[D"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[J"))); + EXPECT_CALL(*env_, + GetFieldID(_, StrEq("ObjectArrayRank1"), StrEq("[LkClass2;"))); + EXPECT_CALL(*env_, + GetFieldID(_, StrEq("ObjectArrayRank2"), StrEq("[[LkClass2;"))); + EXPECT_CALL(*env_, + GetFieldID(_, StrEq("ObjectArrayRank3"), StrEq("[[[LkClass2;"))); + + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillOnce(::testing::Return(Fake<jbooleanArray>())) + .WillOnce(::testing::Return(Fake<jbyteArray>())) + .WillOnce(::testing::Return(Fake<jcharArray>())) + .WillOnce(::testing::Return(Fake<jshortArray>())) + .WillOnce(::testing::Return(Fake<jintArray>())) + .WillOnce(::testing::Return(Fake<jfloatArray>())) + .WillOnce(::testing::Return(Fake<jdoubleArray>())) + .WillOnce(::testing::Return(Fake<jlongArray>())) + .WillOnce(::testing::Return(Fake<jobjectArray>(1))) + .WillOnce(::testing::Return(Fake<jobjectArray>(2))) + .WillOnce(::testing::Return(Fake<jobjectArray>(3))); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + + EXPECT_EQ(static_cast<jbooleanArray>(obj["BooleanArray"].Get()), + Fake<jbooleanArray>()); + EXPECT_EQ(static_cast<jbyteArray>(obj["ByteArray"].Get()), + Fake<jbyteArray>()); + EXPECT_EQ(static_cast<jcharArray>(obj["CharArray"].Get()), + Fake<jcharArray>()); + EXPECT_EQ(static_cast<jshortArray>(obj["ShortArray"].Get()), + Fake<jshortArray>()); + EXPECT_EQ(static_cast<jintArray>(obj["IntArray"].Get()), Fake<jintArray>()); + EXPECT_EQ(static_cast<jfloatArray>(obj["FloatArray"].Get()), + Fake<jfloatArray>()); + EXPECT_EQ(static_cast<jdoubleArray>(obj["DoubleArray"].Get()), + Fake<jdoubleArray>()); + EXPECT_EQ(static_cast<jlongArray>(obj["LongArray"].Get()), + Fake<jlongArray>()); + EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArrayRank1"].Get()), + Fake<jobjectArray>(1)); + EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArrayRank2"].Get()), + Fake<jobjectArray>(2)); + EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArrayRank3"].Get()), + Fake<jobjectArray>(3)); +} + +TEST_F(JniTest, Array_Field_Boolean_Test) { + std::unique_ptr<jboolean> fake_storage_ptr(new jboolean()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[Z"))) + .WillRepeatedly(::testing::Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillRepeatedly(::testing::Return(Fake<jbooleanArray>())); + EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(), + Fake<jbooleanArray>())) + .Times(4); + EXPECT_CALL(*env_, GetBooleanArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Fake<jbooleanArray>(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseBooleanArrayElements(Fake<jbooleanArray>(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + LocalArray<jboolean> arr{obj["BooleanArray"].Get()}; + LocalArray<jboolean> arr2{AdoptLocal{}, Fake<jbooleanArray>()}; + obj["BooleanArray"].Set(Fake<jbooleanArray>()); + obj["BooleanArray"].Set( + LocalArray<jboolean>{AdoptLocal{}, Fake<jbooleanArray>()}); + obj["BooleanArray"].Set(arr2); + obj["BooleanArray"].Set(obj["BooleanArray"].Get()); + + EXPECT_EQ(obj["BooleanArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["BooleanArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Byte_Test) { + std::unique_ptr<jbyte> fake_storage_ptr(new jbyte()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[B"))) + .WillRepeatedly(::testing::Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillRepeatedly(::testing::Return(Fake<jbyteArray>())); + EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(), + Fake<jbyteArray>())) + .Times(4); + EXPECT_CALL(*env_, GetByteArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseByteArrayElements(Fake<jbyteArray>(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseByteArrayElements(Fake<jbyteArray>(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + LocalArray<jbyte> arr{obj["ByteArray"].Get()}; + LocalArray<jbyte> arr2{AdoptLocal{}, Fake<jbyteArray>()}; + obj["ByteArray"].Set(Fake<jbyteArray>()); + obj["ByteArray"].Set(LocalArray<jbyte>{AdoptLocal{}, Fake<jbyteArray>()}); + obj["ByteArray"].Set(arr2); + obj["ByteArray"].Set(obj["ByteArray"].Get()); + EXPECT_EQ(obj["ByteArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["ByteArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Char_Test) { + std::unique_ptr<jchar> fake_storage_ptr(new jchar()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[C"))) + .WillRepeatedly(::testing::Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillRepeatedly(::testing::Return(Fake<jcharArray>())); + EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(), + Fake<jcharArray>())) + .Times(4); + EXPECT_CALL(*env_, GetCharArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseCharArrayElements(Fake<jcharArray>(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseCharArrayElements(Fake<jcharArray>(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + LocalArray<jchar> arr{obj["CharArray"].Get()}; + LocalArray<jchar> arr2{AdoptLocal{}, Fake<jcharArray>()}; + obj["CharArray"].Set(Fake<jcharArray>()); + obj["CharArray"].Set(LocalArray<jchar>{AdoptLocal{}, Fake<jcharArray>()}); + obj["CharArray"].Set(arr2); + obj["CharArray"].Set(obj["CharArray"].Get()); + EXPECT_EQ(obj["CharArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["CharArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Short_Test) { + std::unique_ptr<jshort> fake_storage_ptr(new jshort()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[S"))) + .WillRepeatedly(::testing::Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillRepeatedly(::testing::Return(Fake<jshortArray>())); + EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(), + Fake<jshortArray>())) + .Times(4); + EXPECT_CALL(*env_, GetShortArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseShortArrayElements(Fake<jshortArray>(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseShortArrayElements(Fake<jshortArray>(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + LocalArray<jshort> arr{obj["ShortArray"].Get()}; + LocalArray<jshort> arr2{AdoptLocal{}, Fake<jshortArray>()}; + obj["ShortArray"].Set(Fake<jshortArray>()); + obj["ShortArray"].Set(LocalArray<jshort>{AdoptLocal{}, Fake<jshortArray>()}); + obj["ShortArray"].Set(arr2); + obj["ShortArray"].Set(obj["ShortArray"].Get()); + EXPECT_EQ(obj["ShortArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["ShortArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Int_Test) { + std::unique_ptr<jint> fake_storage_ptr(new jint()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[I"))) + .WillRepeatedly(::testing::Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillRepeatedly(::testing::Return(Fake<jintArray>())); + EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(), + Fake<jintArray>())) + .Times(4); + EXPECT_CALL(*env_, GetIntArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseIntArrayElements(Fake<jintArray>(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, ReleaseIntArrayElements( + Fake<jintArray>(), fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + LocalArray<jint> arr{obj["IntArray"].Get()}; + LocalArray<jint> arr2{AdoptLocal{}, Fake<jintArray>()}; + obj["IntArray"].Set(Fake<jintArray>()); + obj["IntArray"].Set(LocalArray<jint>{AdoptLocal{}, Fake<jintArray>()}); + obj["IntArray"].Set(arr2); + obj["IntArray"].Set(obj["IntArray"].Get()); + EXPECT_EQ(obj["IntArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["IntArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Float_Test) { + std::unique_ptr<jfloat> fake_storage_ptr(new jfloat()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[F"))) + .WillRepeatedly(::testing::Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillRepeatedly(::testing::Return(Fake<jfloatArray>())); + EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(), + Fake<jfloatArray>())) + .Times(4); + EXPECT_CALL(*env_, GetFloatArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseFloatArrayElements(Fake<jfloatArray>(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseFloatArrayElements(Fake<jfloatArray>(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + LocalArray<jfloat> arr{obj["FloatArray"].Get()}; + LocalArray<jfloat> arr2{AdoptLocal{}, Fake<jfloatArray>()}; + obj["FloatArray"].Set(Fake<jfloatArray>()); + obj["FloatArray"].Set(LocalArray<jfloat>{AdoptLocal{}, Fake<jfloatArray>()}); + obj["FloatArray"].Set(arr2); + obj["FloatArray"].Set(obj["FloatArray"].Get()); + EXPECT_EQ(obj["FloatArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["FloatArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Double_Test) { + std::unique_ptr<jdouble> fake_storage_ptr(new jdouble()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[D"))) + .WillRepeatedly(::testing::Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillRepeatedly(::testing::Return(Fake<jdoubleArray>())); + EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(), + Fake<jdoubleArray>())) + .Times(4); + EXPECT_CALL(*env_, GetDoubleArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseDoubleArrayElements(Fake<jdoubleArray>(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseDoubleArrayElements(Fake<jdoubleArray>(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + LocalArray<jdouble> arr{obj["DoubleArray"].Get()}; + LocalArray<jdouble> arr2{AdoptLocal{}, Fake<jdoubleArray>()}; + obj["DoubleArray"].Set(Fake<jdoubleArray>()); + obj["DoubleArray"].Set( + LocalArray<jdouble>{AdoptLocal{}, Fake<jdoubleArray>()}); + obj["DoubleArray"].Set(arr2); + obj["DoubleArray"].Set(obj["DoubleArray"].Get()); + EXPECT_EQ(obj["DoubleArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["DoubleArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Long_Test) { + std::unique_ptr<jlong> fake_storage_ptr(new jlong()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[J"))) + .WillRepeatedly(::testing::Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillRepeatedly(::testing::Return(Fake<jlongArray>())); + EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(), + Fake<jlongArray>())) + .Times(4); + EXPECT_CALL(*env_, GetLongArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseLongArrayElements(Fake<jlongArray>(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseLongArrayElements(Fake<jlongArray>(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + LocalArray<jlong> arr{obj["LongArray"].Get()}; + LocalArray<jlong> arr2{AdoptLocal{}, Fake<jlongArray>()}; + obj["LongArray"].Set(Fake<jlongArray>()); + obj["LongArray"].Set(LocalArray<jlong>{AdoptLocal{}, Fake<jlongArray>()}); + obj["LongArray"].Set(arr2); + obj["LongArray"].Set(obj["LongArray"].Get()); + EXPECT_EQ(obj["LongArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["LongArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Object_Test) { + EXPECT_CALL(*env_, + GetFieldID(_, StrEq("ObjectArrayRank1"), StrEq("[LkClass2;"))) + .WillOnce(::testing::Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>())) + .WillRepeatedly(::testing::Return(Fake<jobjectArray>())); + EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(), + Fake<jobjectArray>())) + .Times(4); + EXPECT_CALL(*env_, GetObjectArrayElement(Fake<jobjectArray>(), 2)); + + LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()}; + LocalArray<jobject, 1, kClass2> arr2{AdoptLocal{}, Fake<jobjectArray>()}; + LocalArray<jobject, 1, kClass2> arr{obj["ObjectArrayRank1"].Get()}; + obj["ObjectArrayRank1"].Set(Fake<jobjectArray>()); + obj["ObjectArrayRank1"].Set( + LocalArray<jobject, 1, kClass2>{AdoptLocal{}, Fake<jobjectArray>()}); + obj["ObjectArrayRank1"].Set(arr2); + obj["ObjectArrayRank1"].Set(obj["ObjectArrayRank1"].Get()); + obj["ObjectArrayRank1"].Get().Get(2); +} + +TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_1) { + static constexpr Class kClass2{"kClass2"}; + + static constexpr Class kClass{ + "ArrayMultiTest", + Field{"Foo", Array{kClass2}}, + }; + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[LkClass2;"))); + + LocalObject<kClass> obj{Fake<jobject>()}; + LocalArray<jobject, 1, kClass2>{obj["Foo"].Get()}; +} + +TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2) { + static constexpr Class kClass2{"kClass2"}; + + static constexpr Class kClass{ + "kClass", + Field{"Foo", Array{kClass2, Rank<2>{}}}, + }; + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[[LkClass2;"))); + + LocalObject<kClass> obj{Fake<jobject>()}; + LocalArray<jobject, 2, kClass2> arr_from_field{obj["Foo"].Get()}; +} + +TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2_Iteration) { + static constexpr Class kClass2{"kClass2"}; + + static constexpr Class kClass{ + "kClass", + Field{"Foo", Array{kClass2, Rank<2>{}}}, + }; + + InSequence seq; + + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[[LkClass2;"))); + EXPECT_CALL(*env_, GetObjectField) + .WillRepeatedly(::testing::Return(Fake<jobjectArray>())); + EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3)); + + // A temporary local array is materialised in the field access. + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>())); + + // Internal elements looped over. + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake<jobject>(100))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(100))); + + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake<jobject>(101))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(101))); + + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake<jobject>(102))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(102))); + + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobjectArray>()))); + + // Object with queried array field. + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(1))); + + LocalObject<kClass> obj{AdoptLocal{}, Fake<jobject>(1)}; + int i = 100; + for (LocalArray<jobject> obj : obj["Foo"].Get().Pin()) { + EXPECT_EQ(static_cast<jobject>(obj), Fake<jobject>(i)); + i++; + } +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/local_array_iteration_test.cc b/implementation/legacy/local_array_iteration_test.cc new file mode 100644 index 00000000..81637ced --- /dev/null +++ b/implementation/legacy/local_array_iteration_test.cc @@ -0,0 +1,266 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <algorithm> +#include <array> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::AdoptLocal; +using ::jni::ArrayView; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; + +//////////////////////////////////////////////////////////////////////////////// +// Rank 0. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, IteratesOver1DRange) { + std::array expected{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(10)); + EXPECT_CALL(*env_, GetIntArrayElements) + .WillRepeatedly(Return(expected.data())); + + LocalArray<jint, 1> new_array{AdoptLocal{}, Fake<jintArray>()}; + + int i{0}; + for (jint val : new_array.Pin()) { + EXPECT_EQ(val, expected[i]); + ++i; + } + EXPECT_EQ(i, 10); +} + +TEST_F(JniTest, WorksWithSTLComparison) { + std::array expected{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(10)); + EXPECT_CALL(*env_, GetIntArrayElements) + .WillRepeatedly(Return(expected.data())); + + LocalArray<jint, 1> new_array{AdoptLocal{}, Fake<jintArray>()}; + ArrayView array_view = new_array.Pin(); + EXPECT_TRUE( + std::equal(array_view.begin(), array_view.end(), expected.begin())); +} + +TEST_F(JniTest, WorksWithSTLComparisonOfObjects) { + std::array expected{Fake<jobject>(1), Fake<jobject>(2), Fake<jobject>(3)}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake<jobject>(1))) + .WillOnce(Return(Fake<jobject>(2))) + .WillOnce(Return(Fake<jobject>(3))); + + LocalArray<jobject, 1, kClass> new_array{Fake<jobjectArray>()}; + ArrayView array_view = new_array.Pin(); + EXPECT_TRUE( + std::equal(array_view.begin(), array_view.end(), expected.begin())); +} + +TEST_F(JniTest, WorksWithSTLComparisonOfRichlyDecoratedObjects) { + std::array expected{LocalObject<kClass>{AdoptLocal{}, Fake<jobject>(1)}, + LocalObject<kClass>{AdoptLocal{}, Fake<jobject>(2)}, + LocalObject<kClass>{AdoptLocal{}, Fake<jobject>(3)}}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake<jobject>(1))) + .WillOnce(Return(Fake<jobject>(2))) + .WillOnce(Return(Fake<jobject>(3))); + + LocalArray<jobject, 1, kClass> new_array{AdoptLocal{}, Fake<jobjectArray>()}; + ArrayView array_view = new_array.Pin(); + EXPECT_TRUE( + std::equal(array_view.begin(), array_view.end(), expected.begin())); +} + +TEST_F(JniTest, 2D_Iterates) { + int a[5] = {1, 2, 3, 4, 5}; + std::array expected{1, 2, 3, 4, 5}; + + EXPECT_CALL(*env_, FindClass(StrEq("[I"))); + EXPECT_CALL(*env_, NewObjectArray(5, _, Fake<jobjectArray>())); + EXPECT_CALL(*env_, GetArrayLength) + .WillOnce(Return(5)) // outer + .WillOnce(Return(1)) // inner: 1, running sum: 1 + .WillOnce(Return(2)) // inner: 1, running sum: 3 + .WillOnce(Return(3)) // inner: 1, running sum: 6 + .WillOnce(Return(4)) // inner: 1, running sum: 10 + .WillOnce(Return(5)); // inner: 1, running sum: 15 + + // 5: Once per outer array element. + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake<jintArray>(1))) + .WillOnce(Return(Fake<jintArray>(2))) + .WillOnce(Return(Fake<jintArray>(3))) + .WillOnce(Return(Fake<jintArray>(4))) + .WillOnce(Return(Fake<jintArray>(5))); + + // All the returned intArrays are deleted. + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(3))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(4))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(5))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>())); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobjectArray>()))); + + // 5 (outer array length) * 2 (pins per) = 10 + EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a)); + EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10); + + LocalArray<jint, 2> new_array{5, Fake<jobjectArray>()}; + + int sum = 0; + for (LocalArray<jint> arr_1d : new_array.Pin()) { + // Note: Each `Pin` below triggers a separate `GetIntArrayElements`. + { + auto pinn = arr_1d.Pin(); + EXPECT_TRUE(std::equal(pinn.begin(), pinn.end(), expected.begin())); + } + + // Note: GetArrayLength is not called again (it's cached). + { + for (jint val : arr_1d.Pin()) { + sum += val; + } + } + } + + // 1 + 3 + 6 + 10 + 15 = 35 + EXPECT_EQ(sum, 35); +} + +// Identical to above except with raw loops. +TEST_F(JniTest, 2D_Iterates_Raw_loops) { + int a[5] = {1, 2, 3, 4, 5}; + std::array expected{1, 2, 3, 4, 5}; + + EXPECT_CALL(*env_, FindClass(StrEq("[I"))); + EXPECT_CALL(*env_, NewObjectArray(5, _, Fake<jobjectArray>())); + EXPECT_CALL(*env_, GetArrayLength) + .WillOnce(Return(5)) // outer + .WillOnce(Return(1)) // inner: 1, running sum: 1 + .WillOnce(Return(2)) // inner: 1, running sum: 3 + .WillOnce(Return(3)) // inner: 1, running sum: 6 + .WillOnce(Return(4)) // inner: 1, running sum: 10 + .WillOnce(Return(5)); // inner: 1, running sum: 15 + + // 5: Once per outer array element. + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake<jintArray>(1))) + .WillOnce(Return(Fake<jintArray>(2))) + .WillOnce(Return(Fake<jintArray>(3))) + .WillOnce(Return(Fake<jintArray>(4))) + .WillOnce(Return(Fake<jintArray>(5))); + + // All the returned intArrays are deleted. + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(3))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(4))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(5))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>())); + + // 5 (outer array length) * 2 (pins per) = 10 + EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a)); + EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10); + + LocalArray<jint, 2> new_array{5, Fake<jobjectArray>()}; + + int sum = 0; + int loop_size = new_array.Length(); + for (int i = 0; i < loop_size; ++i) { + LocalArray<jint, 1> arr_1d{new_array.Get(i)}; + + // Note: Each `Pin` below triggers a separate `GetIntArrayElements`. + { + auto pinn = arr_1d.Pin(); + EXPECT_TRUE(std::equal(pinn.begin(), pinn.end(), expected.begin())); + } + + // Note: GetArrayLength is not called again (it's cached). + { + for (jint val : arr_1d.Pin()) { + sum += val; + } + } + } + + // 1 + 3 + 6 + 10 + 15 = 35 + EXPECT_EQ(sum, 35); +} + +// Identical to above except with object loops. +TEST_F(JniTest, 2D_Iterates_Raw_loops_of_Objects) { + EXPECT_CALL(*env_, FindClass(StrEq("[LkClass;"))); + EXPECT_CALL(*env_, NewObjectArray(5, _, Fake<jobjectArray>(100))); + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(5)); // outer + + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake<jobjectArray>(1))) + .WillOnce(Return(Fake<jobjectArray>(2))) + .WillOnce(Return(Fake<jobjectArray>(3))) + .WillOnce(Return(Fake<jobjectArray>(4))) + .WillOnce(Return(Fake<jobjectArray>(5))); + + // All the returned objectArrays are deleted. + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(3))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(4))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(5))); + + // Note: This is just 0 (default), not 100. 100 is the sample (i.e. template) + // object, no arg is the default that is created. + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>())); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobjectArray>()))); + + LocalArray<jobject, 2, kClass> new_array{5, Fake<jobjectArray>(100)}; + EXPECT_EQ(jobject{new_array}, Fake<jobjectArray>()); + + int i = 1; + for (LocalArray<jobject, 1, kClass> arr : new_array.Pin()) { + EXPECT_EQ(jobject(arr), Fake<jobjectArray>(i)); + ++i; + } +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/local_array_method_multidimensional_test.cc b/implementation/legacy/local_array_method_multidimensional_test.cc new file mode 100644 index 00000000..7428b006 --- /dev/null +++ b/implementation/legacy/local_array_method_multidimensional_test.cc @@ -0,0 +1,123 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <type_traits> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::Rank; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass2{"kClass2"}; + +//////////////////////////////////////////////////////////////////////////////// +// As Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, V_2I) { + static constexpr Class kClass{"kClass", + Method{"I", jni::Return{Array<jint, 2>{}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("()[[I"))); + + LocalObject<kClass> obj{Fake<jobject>()}; + obj("I"); +} + +TEST_F(JniTest, V_2LkClass) { + static constexpr Class kClass{ + "kClass", Method{"Foo", jni::Return{Array{kClass2, Rank<2>{}}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("()[[LkClass2;"))); + + LocalObject<kClass> obj{Fake<jobject>()}; + obj("Foo"); +} + +//////////////////////////////////////////////////////////////////////////////// +// Complex: Arrays in Params & Return. +//////////////////////////////////////////////////////////////////////////////// +static constexpr Class kArrClass{ + "ArrClass", + Method{"Foo", jni::Return{Array{jint{}}}, Params{Array<jint, 2>{}}}, + Method{"Baz", jni::Return{Array{kClass2}}, Params{Array<jint, 3>{}}}, + Method{"Bar", jni::Return{Array{Class{"kClass3"}, Rank<2>{}}}, + Params{Array<jint, 3>{}, Array{double{}}}}, +}; + +TEST_F(JniTest, 3I1D_2LkClass) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;"))); + + LocalObject<kArrClass> obj{Fake<jobject>()}; + obj("Bar", Fake<jobjectArray>(), Fake<jdoubleArray>()); +} + +TEST_F(JniTest, 2I_I) { + static constexpr Class kClass{ + "kClass", Method{"I", jni::Return<int>{}, Params{Array<jint, 2>{}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("([[I)I"))); + + LocalObject<kClass> obj{Fake<jobject>()}; + // obj("I", jintArray{nullptr}); // doesn't compile (good). + obj("I", Fake<jobjectArray>()); + obj("I", LocalArray<jint, 2>{Fake<jobjectArray>()}); +} + +TEST_F(JniTest, 3I_1LkClass) { + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("([[[I)[LkClass2;"))); + + LocalObject<kArrClass> obj{Fake<jobject>()}; + + // The compiler complains of unused variable here. This would be worth digging + // into to understand the underlying cause (i.e. only the following fails). + // LocalArray<jobject, 1, kClass2> ret = obj("Baz", jobjectArray{nullptr}); + + // TODO(b/143908983): CTAD is failing. + // LocalArray ret = obj("Baz", jobjectArray{nullptr}); + + LocalArray<jobject, 1, kClass2> ret = obj("Baz", Fake<jobjectArray>()); + static_assert(std::is_same_v<decltype(ret), LocalArray<jobject, 1, kClass2>>); +} + +TEST_F(JniTest, 3I1D_1LkClass) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;"))); + + LocalObject<kArrClass> obj{Fake<jobject>()}; + obj("Bar", Fake<jobjectArray>(), Fake<jdoubleArray>()); +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/local_array_method_test.cc b/implementation/legacy/local_array_method_test.cc new file mode 100644 index 00000000..b5c81f48 --- /dev/null +++ b/implementation/legacy/local_array_method_test.cc @@ -0,0 +1,232 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::Return; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass2{"kClass2"}; + +//////////////////////////////////////////////////////////////////////////////// +// As Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ReturnSmokeTest) { + static constexpr Class kClass{ + "kClass", + Method{"BooleanArray", Return{Array{jboolean{}}}}, + Method{"ByteArray", Return{Array{jbyte{}}}}, + Method{"CharArray", Return{Array{jchar{}}}}, + Method{"ShortArray", Return{Array{jshort{}}}}, + Method{"IntArray", Return{Array{jint{}}}}, + Method{"FloatArray", Return{Array{jfloat{}}}}, + Method{"DoubleArray", Return{Array{jdouble{}}}}, + Method{"LongArray", Return{Array{jlong{}}}}, + Method{"ObjectArray", Return{Array{kClass2}}}, + }; + + EXPECT_CALL(*env_, CallObjectMethodV) + .WillOnce(testing::Return(Fake<jbooleanArray>())) + .WillOnce(testing::Return(Fake<jbyteArray>())) + .WillOnce(testing::Return(Fake<jcharArray>())) + .WillOnce(testing::Return(Fake<jshortArray>())) + .WillOnce(testing::Return(Fake<jintArray>())) + .WillOnce(testing::Return(Fake<jlongArray>())) + .WillOnce(testing::Return(Fake<jfloatArray>())) + .WillOnce(testing::Return(Fake<jdoubleArray>())) + .WillOnce(testing::Return(Fake<jobjectArray>())); + + LocalObject<kClass> obj{Fake<jobject>()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("BooleanArray"), StrEq("()[Z"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ByteArray"), StrEq("()[B"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("CharArray"), StrEq("()[C"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ShortArray"), StrEq("()[S"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("()[I"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("()[J"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("FloatArray"), StrEq("()[F"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("()[D"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("ObjectArray"), StrEq("()[LkClass2;"))); + + LocalArray<jboolean> bool_array{obj("BooleanArray")}; + EXPECT_EQ((static_cast<jbooleanArray>(bool_array)), (Fake<jbooleanArray>())); + + LocalArray<jbyte> byte_array{obj("ByteArray")}; + EXPECT_EQ((static_cast<jbyteArray>(byte_array)), (Fake<jbyteArray>())); + + LocalArray<jchar> char_array{obj("CharArray")}; + EXPECT_EQ((static_cast<jcharArray>(char_array)), (Fake<jcharArray>())); + + LocalArray<jshort> short_array{obj("ShortArray")}; + EXPECT_EQ((static_cast<jshortArray>(short_array)), (Fake<jshortArray>())); + + LocalArray<jint> int_array{obj("IntArray")}; + EXPECT_EQ((static_cast<jintArray>(int_array)), (Fake<jintArray>())); + + LocalArray<jlong> long_array{obj("LongArray")}; + EXPECT_EQ((static_cast<jlongArray>(long_array)), (Fake<jlongArray>())); + + LocalArray<jfloat> float_array{obj("FloatArray")}; + EXPECT_EQ((static_cast<jfloatArray>(float_array)), (Fake<jfloatArray>())); + + LocalArray<jdouble> double_array{obj("DoubleArray")}; + EXPECT_EQ((static_cast<jdoubleArray>(double_array)), (Fake<jdoubleArray>())); + + LocalArray<jobject, 1, kClass2> object_array{obj("ObjectArray")}; + EXPECT_EQ((static_cast<jobjectArray>(object_array)), (Fake<jobjectArray>())); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Params. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ParamsSmokeTest) { + static constexpr Class kClass{ + "kClass", + Method{"BooleanArray", Return{}, Params{Array{jboolean{}}}}, + Method{"ByteArray", Return{}, Params{Array{jbyte{}}}}, + Method{"CharArray", Return{}, Params{Array{jchar{}}}}, + Method{"ShortArray", Return{}, Params{Array{jshort{}}}}, + Method{"IntArray", Return{}, Params{Array{jint{}}}}, + Method{"FloatArray", Return{}, Params{Array{jfloat{}}}}, + Method{"DoubleArray", Return{}, Params{Array{jdouble{}}}}, + Method{"LongArray", Return{}, Params{Array{jlong{}}}}, + Method{"ObjectArray", Return{}, Params{Array{kClass2}}}, + }; + + LocalObject<kClass> obj{Fake<jobject>()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("BooleanArray"), StrEq("([Z)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ByteArray"), StrEq("([B)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("CharArray"), StrEq("([C)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ShortArray"), StrEq("([S)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("([I)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("FloatArray"), StrEq("([F)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("([D)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("([J)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("ObjectArray"), StrEq("([LkClass2;)V"))); + + obj("BooleanArray", LocalArray<jboolean>{Fake<jbooleanArray>()}); + obj("ByteArray", LocalArray<jbyte>{Fake<jbyteArray>()}); + obj("CharArray", LocalArray<jchar>{Fake<jcharArray>()}); + obj("ShortArray", LocalArray<jshort>{Fake<jshortArray>()}); + obj("IntArray", LocalArray<jint>{Fake<jintArray>()}); + obj("FloatArray", LocalArray<jfloat>{Fake<jfloatArray>()}); + obj("DoubleArray", LocalArray<jdouble>{Fake<jdoubleArray>()}); + obj("LongArray", LocalArray<jlong>{Fake<jlongArray>()}); + obj("ObjectArray", LocalArray<jobject, 1, kClass2>{Fake<jobjectArray>()}); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Complex. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ComplexSmokeTest) { + static constexpr Class kClass{ + "kClass", + Method{"BooleanArray", Return{Array{jboolean{}}}, + Params{Array{jboolean{}}}}, + Method{"ByteArray", Return{Array{jbyte{}}}, Params{Array{jbyte{}}}}, + Method{"CharArray", Return{Array{jchar{}}}, Params{Array{jchar{}}}}, + Method{"ShortArray", Return{Array{jshort{}}}, Params{Array{jshort{}}}}, + Method{"IntArray", Return{Array{jint{}}}, Params{Array{jint{}}}}, + Method{"FloatArray", Return{Array{jfloat{}}}, Params{Array{jfloat{}}}}, + Method{"DoubleArray", Return{Array{jdouble{}}}, Params{Array{jdouble{}}}}, + Method{"LongArray", Return{Array{jlong{}}}, Params{Array{jlong{}}}}, + Method{"ObjectArray", Return{Array{kClass2}}, Params{Array{kClass2}}}, + }; + + EXPECT_CALL(*env_, CallObjectMethodV) + .WillOnce(testing::Return(Fake<jbooleanArray>())) + .WillOnce(testing::Return(Fake<jbyteArray>())) + .WillOnce(testing::Return(Fake<jcharArray>())) + .WillOnce(testing::Return(Fake<jshortArray>())) + .WillOnce(testing::Return(Fake<jintArray>())) + .WillOnce(testing::Return(Fake<jfloatArray>())) + .WillOnce(testing::Return(Fake<jdoubleArray>())) + .WillOnce(testing::Return(Fake<jlongArray>())) + .WillOnce(testing::Return(Fake<jobjectArray>())); + + LocalObject<kClass> obj{Fake<jobject>()}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("BooleanArray"), StrEq("([Z)[Z"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ByteArray"), StrEq("([B)[B"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("CharArray"), StrEq("([C)[C"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ShortArray"), StrEq("([S)[S"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("([I)[I"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("FloatArray"), StrEq("([F)[F"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("([D)[D"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("([J)[J"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ObjectArray"), + StrEq("([LkClass2;)[LkClass2;"))); + + LocalArray<jboolean> bool_array{ + obj("BooleanArray", LocalArray<jboolean>{Fake<jbooleanArray>()})}; + EXPECT_EQ((static_cast<jbooleanArray>(bool_array)), (Fake<jbooleanArray>())); + + LocalArray<jbyte> byte_array{ + obj("ByteArray", LocalArray<jbyte>{Fake<jbyteArray>()})}; + EXPECT_EQ((static_cast<jbyteArray>(byte_array)), (Fake<jbyteArray>())); + + LocalArray<jchar> char_array{ + obj("CharArray", LocalArray<jchar>{Fake<jcharArray>()})}; + EXPECT_EQ((static_cast<jcharArray>(char_array)), (Fake<jcharArray>())); + + LocalArray<jshort> short_array{ + obj("ShortArray", LocalArray<jshort>{Fake<jshortArray>()})}; + EXPECT_EQ((static_cast<jshortArray>(short_array)), (Fake<jshortArray>())); + + LocalArray<jint> int_array{ + obj("IntArray", LocalArray<jint>{Fake<jintArray>()})}; + EXPECT_EQ((static_cast<jintArray>(int_array)), (Fake<jintArray>())); + + LocalArray<jfloat> float_array{ + obj("FloatArray", LocalArray<jfloat>{Fake<jfloatArray>()})}; + EXPECT_EQ((static_cast<jfloatArray>(float_array)), (Fake<jfloatArray>())); + + LocalArray<jdouble> double_array{ + obj("DoubleArray", LocalArray<jdouble>{Fake<jdoubleArray>()})}; + EXPECT_EQ((static_cast<jdoubleArray>(double_array)), (Fake<jdoubleArray>())); + + LocalArray<jlong> long_array{ + obj("LongArray", LocalArray<jlong>{Fake<jlongArray>()})}; + EXPECT_EQ((static_cast<jlongArray>(long_array)), (Fake<jlongArray>())); + + LocalArray<jobject, 1, kClass2> object_array{obj( + "ObjectArray", LocalArray<jobject, 1, kClass2>{Fake<jobjectArray>()})}; + EXPECT_EQ((static_cast<jobjectArray>(object_array)), (Fake<jobjectArray>())); +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/local_array_multidimensional_test.cc b/implementation/legacy/local_array_multidimensional_test.cc new file mode 100644 index 00000000..160b016a --- /dev/null +++ b/implementation/legacy/local_array_multidimensional_test.cc @@ -0,0 +1,116 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstddef> +#include <utility> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +#if __clang__ + +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; + +//////////////////////////////////////////////////////////////////////////////// +// Construction. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, BuildsFromSizeForMultiDimensional_no_value) { + EXPECT_CALL(*env_, NewObjectArray(10, _, Fake<jintArray>())); + + LocalArray<jint, 2>{std::size_t{10}, Fake<jintArray>()}; +} + +TEST_F(JniTest, BuildsFromSizeForMultiDimensional_primitive_xref) { + EXPECT_CALL(*env_, FindClass(StrEq("[I"))); + EXPECT_CALL(*env_, NewObjectArray(10, _, Fake<jintArray>())); + + LocalArray<jint, 2>{std::size_t{10}, + LocalArray<jint>{AdoptLocal{}, Fake<jintArray>()}}; +} + +TEST_F(JniTest, BuildsFromSizeForMultiDimensional_primitive_lvalue) { + EXPECT_CALL(*env_, NewObjectArray(10, _, Fake<jintArray>())); + + LocalArray<jint> arr{AdoptLocal{}, Fake<jintArray>()}; + LocalArray<jint, 2>{std::size_t{10}, arr}; +} + +//////////////////////////////////////////////////////////////////////////////// +// Getters. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, GetsIntValues) { + EXPECT_CALL(*env_, GetObjectArrayElement(Fake<jobjectArray>(), 0)) + .WillOnce(::testing::Return(Fake<jintArray>(0))); + EXPECT_CALL(*env_, GetObjectArrayElement(Fake<jobjectArray>(), 1)) + .WillOnce(::testing::Return(Fake<jintArray>(1))); + EXPECT_CALL(*env_, GetObjectArrayElement(Fake<jobjectArray>(), 2)) + .WillOnce(::testing::Return(Fake<jintArray>(2))); + LocalArray<jint, 2> arr{std::size_t{10}, Fake<jobjectArray>()}; + + EXPECT_EQ((static_cast<jintArray>(arr.Get(0))), (Fake<jintArray>(0))); + EXPECT_EQ((static_cast<jintArray>(arr.Get(1))), (Fake<jintArray>(1))); + EXPECT_EQ((static_cast<jintArray>(arr.Get(2))), (Fake<jintArray>(2))); +} + +//////////////////////////////////////////////////////////////////////////////// +// Setters. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, SetsIntValues) { + EXPECT_CALL( + *env_, SetObjectArrayElement(Fake<jobjectArray>(), 0, Fake<jintArray>())); + EXPECT_CALL( + *env_, SetObjectArrayElement(Fake<jobjectArray>(), 1, Fake<jintArray>())); + EXPECT_CALL( + *env_, SetObjectArrayElement(Fake<jobjectArray>(), 2, Fake<jintArray>())); + + LocalArray<jint, 1> array_arg{AdoptLocal{}, Fake<jintArray>()}; + LocalArray<jint, 2> arr{std::size_t{10}, Fake<jobjectArray>()}; + arr.Set(0, array_arg); + arr.Set(1, array_arg); + arr.Set(2, std::move(array_arg)); +} + +TEST_F(JniTest, SetsObjectValues) { + EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(1), 0, + Fake<jobjectArray>(2))); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(1), 1, + Fake<jobjectArray>(2))); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(1), 2, + Fake<jobjectArray>(2))); + + LocalArray<jobject, 1, kClass> array_arg{AdoptLocal{}, Fake<jobjectArray>(2)}; + LocalArray<jint, 2> arr{AdoptLocal{}, Fake<jobjectArray>(1)}; + arr.Set(0, array_arg); + arr.Set(1, array_arg); + arr.Set(2, std::move(array_arg)); +} + +#endif // __clang__ + +} // namespace diff --git a/implementation/legacy/local_array_string_test.cc b/implementation/legacy/local_array_string_test.cc new file mode 100644 index 00000000..2f2ff8c9 --- /dev/null +++ b/implementation/legacy/local_array_string_test.cc @@ -0,0 +1,208 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> +#include <utility> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +#if __clang__ + +using ::jni::Array; +using ::jni::ArrayStrip_t; +using ::jni::CDecl_t; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::kJavaLangString; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::LocalString; +using ::jni::Method; +using ::jni::Params; +using ::jni::Rank; +using ::jni::RegularToArrayTypeMap_t; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +namespace { + +static constexpr Class kClass{"kClass"}; + +//////////////////////////////////////////////////////////////////////////////// +// Strings are unusual in that they have their own type (jstring) but are +// almost completely objects otherwise. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Construction / Materialisation. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_ConstructsFromAnotherStringArray) { + LocalArray<jstring> arr_1{Fake<jobjectArray>()}; + LocalArray<jstring> arr_2{std::move(arr_1)}; +} + +TEST_F(JniTest, Array_CorrectSignatureForStringParams) { + EXPECT_CALL(*env_, FindClass(StrEq("ClassThatReturnsArrays"))); + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/String"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), + StrEq("([Ljava/lang/String;)V"))); + + static constexpr Class kClass{ + "ClassThatReturnsArrays", + Method{"StringArray", jni::Return{}, jni::Params{Array{jstring{}}}}, + }; + + LocalObject<kClass> obj{jobject{nullptr}}; + LocalArray<jstring> arr{3}; + obj("StringArray", arr); +} + +//////////////////////////////////////////////////////////////////////////////// +// Caches Length. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CachesLengthExactlyOnceOnFirstRequest) { + EXPECT_CALL(*env_, GetArrayLength).Times(0); + + LocalArray<jint> obj{Fake<jintArray>()}; + + EXPECT_CALL(*env_, GetArrayLength).Times(1).WillOnce(Return(5)); + EXPECT_EQ(obj.Length(), 5); + EXPECT_EQ(obj.Length(), 5); + EXPECT_EQ(obj.Length(), 5); +} + +//////////////////////////////////////////////////////////////////////////////// +// Setters. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_LocalStringRValuesCanBeSet) { + EXPECT_CALL(*env_, DeleteLocalRef(_)) + .Times(1); // array (object is moved from) + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())); // FindClass + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>())).Times(0); + + LocalArray<jstring> arr{3}; + arr.Set(0, LocalString{Fake<jstring>()}); +} + +TEST_F(JniTest, Array_LocalVanillaObjectRValuesCanBeSet) { + // Unfortunately this is getting cached separately by `LocalArray`. + // In the future, this should drop to 1. + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/String"))).Times(2); + + EXPECT_CALL(*env_, DeleteLocalRef(_)).Times(2); // array, in place obj + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())).Times(2); // FindClass + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>())).Times(0); + + LocalArray<jobject, 1, kJavaLangString> arr{ + 3, LocalObject<kJavaLangString>{"Foo"}}; + arr.Set(0, LocalObject<kJavaLangString>{Fake<jstring>()}); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CorrectReturnSignatureForStrings) { + static constexpr Class kClass{ + "kClass", + Method{"StringArray", jni::Return{Array{jstring{}}}}, + }; + + LocalObject<kClass> obj{Fake<jobject>()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), + StrEq("()[Ljava/lang/String;"))); + LocalArray<jstring> arr = obj("StringArray"); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Param. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CorrectParamSignatureForStrings) { + static constexpr Class kClass{ + "kClass", + Method{"StringArray", jni::Return{}, Params{Array{jstring{}}}}, + }; + + LocalObject<kClass> obj{Fake<jobject>()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), + StrEq("([Ljava/lang/String;)V"))); + LocalArray<jstring> arr{2}; + obj("StringArray", arr); +} + +TEST_F(JniTest, LocalStringArray_ConstructsObjectsForLValues) { + // Unlike POD, objects are constructed with a size, a jclass, and an init + // object. This makes for a slightly different API then other objects. + EXPECT_CALL(*env_, NewObjectArray); + + LocalObject<kClass> default_object{}; + LocalArray<jobject, 1, kClass> local_object_array{5, default_object}; +} + +/* +// TODO(b/143908983): Restore after fixing ubsan failures. +TEST_F(JniTest, Array_StringsCanBeSetOnLocalString) { + EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 0, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 1, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 2, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 3, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 4, _)); + + const char* kFoo = "kFoo"; + const std::string kNar = "kNar"; + + LocalArray<jstring> arr{5, LocalObject<kJavaLangString>{"Foo"}}; + arr.Set(0, "Bar"); + arr.Set(1, std::string{"Baz"}); + arr.Set(2, std::string_view{"Bar"}); + arr.Set(3, std::string_view{kFoo}); + arr.Set(4, std::string_view{kNar}); +} +*/ + +//////////////////////////////////////////////////////////////////////////////// +// String Fields. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CorrectFieldSignatureForStrings) { + static constexpr Class kClass{ + "kClass", + Field{"StringArrayRank1", Array{jstring{}}}, + Field{"StringArrayRank2", Array{jstring{}, Rank<2>{}}}, + Field{"StringArrayRank3", Array{jstring{}, Rank<3>{}}}, + }; + + LocalObject<kClass> obj{jobject{nullptr}}; + EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank1"), + StrEq("[Ljava/lang/String;"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank2"), + StrEq("[[Ljava/lang/String;"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank3"), + StrEq("[[[Ljava/lang/String;"))); + + LocalArray<jstring> arr1 = obj["StringArrayRank1"].Get(); + LocalArray<jstring, 2> arr2 = obj["StringArrayRank2"].Get(); + LocalArray<jstring, 3> arr3 = obj["StringArrayRank3"].Get(); +} + +} // namespace + +#endif // __clang__ diff --git a/implementation/legacy/local_object_test.cc b/implementation/legacy/local_object_test.cc new file mode 100644 index 00000000..beee5b26 --- /dev/null +++ b/implementation/legacy/local_object_test.cc @@ -0,0 +1,331 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <array> +#include <tuple> +#include <utility> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +#if __clang__ + +namespace { + +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::NewRef; +using ::jni::Params; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::InSequence; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; +static constexpr Class kClass2{"kClass2"}; + +TEST_F(JniTest, LocalObject_AllowsNullPtrT) { + EXPECT_CALL(*env_, NewLocalRef).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + + LocalObject<kClass> obj{nullptr}; + EXPECT_EQ(jobject{obj}, nullptr); +} + +TEST_F(JniTest, LocalObject_DoesntTryToDeleteNull) { + EXPECT_CALL(*env_, NewLocalRef).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + + LocalObject<kClass> obj{jobject{nullptr}}; + EXPECT_EQ(jobject{obj}, nullptr); +} + +TEST_F(JniTest, LocalObject_CallsNewAndDeleteOnNewObject) { + EXPECT_CALL(*env_, NewObjectV).WillOnce(testing::Return(Fake<jobject>())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>())).Times(1); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())).Times(1); + + LocalObject<kClass> obj{}; + EXPECT_EQ(jobject{obj}, Fake<jobject>()); +} + +TEST_F(JniTest, LocalObject_CallsOnlyDeleteOnWrapCtor) { + EXPECT_CALL(*env_, NewLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>()))) + .Times(1); + + LocalObject<kClass> obj{Fake<jobject>()}; + EXPECT_NE(jobject{obj}, nullptr); +} + +TEST_F(JniTest, LocalObject_CallsNewLocalRefByDefault) { + EXPECT_CALL(*env_, NewLocalRef).WillOnce(::testing::Return(Fake<jobject>(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(2))); + + LocalObject<kClass> obj{Fake<jobject>(1)}; + EXPECT_EQ(jobject{obj}, Fake<jobject>(2)); +} + +TEST_F(JniTest, LocalObject_CallsNewLocalRefOnCopy) { + EXPECT_CALL(*env_, NewLocalRef).WillOnce(::testing::Return(Fake<jobject>(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(2))); + + LocalObject<kClass> obj{NewRef{}, Fake<jobject>(1)}; + EXPECT_EQ(jobject{obj}, Fake<jobject>(2)); +} + +TEST_F(JniTest, LocalObject_ObjectReturnsInstanceMethods) { + // This test doesn't use the default JniTest helpers to be a little more + // explicit about exactly what calls must be made in what order. + static constexpr Class kClass{ + "com/google/AnotherClass", + Method{"Foo", jni::Return<jint>{}, Params<jint>{}}, + Method{"Baz", jni::Return<void>{}, Params<jfloat>{}}, + Method{"AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplates" + "ToHandle", + jni::Return<jdouble>{}, + Params<jint, jfloat, jint, jfloat, jdouble>{}}}; + + InSequence seq; + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())).Times(1); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("<init>"), StrEq("()V"))) + .WillOnce(testing::Return(Fake<jmethodID>(1))); + EXPECT_CALL(*env_, NewObjectV(_, Fake<jmethodID>(1), _)) + .WillOnce(testing::Return(Fake<jobject>())); + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)I"))) + .WillOnce(testing::Return(Fake<jmethodID>(2))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(F)V"))) + .WillOnce(testing::Return(Fake<jmethodID>(2))); + EXPECT_CALL(*env_, + GetMethodID(_, + StrEq("AMethodWithAReallyLongNameThatWouldPossiblyBeH" + "ardForTemplatesToHandle"), + StrEq("(IFIFD)D"))) + .WillOnce(testing::Return(Fake<jmethodID>(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>())).Times(1); + + LocalObject<kClass> obj{}; + obj("Foo", 12345); + obj("Baz", 12345.f); + obj("AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplatesToHandle", + 12345, 12345.f, 12345, 12345.f, jdouble{12345}); +} + +TEST_F(JniTest, LocalObject_CallsDeleteOnceAfterAMoveConstruction) { + EXPECT_CALL(*env_, NewLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>()))) + .Times(1); + + LocalObject<kClass> obj_1{Fake<jobject>()}; + + EXPECT_NE(jobject{obj_1}, nullptr); + + LocalObject<kClass> obj_2{std::move(obj_1)}; + + EXPECT_NE(jobject{obj_2}, nullptr); +} + +TEST_F(JniTest, LocalObject_FunctionsProperlyInSTLContainer) { + EXPECT_CALL(*env_, NewLocalRef).Times(2); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>(1)))); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>(2)))); + + LocalObject<kClass> obj_1{Fake<jobject>(1)}; + LocalObject<kClass> obj_2{Fake<jobject>(2)}; + std::tuple t{std::move(obj_1), std::move(obj_2)}; +} + +TEST_F(JniTest, LocalObject_ValuesWorkAfterMoveConstructor) { + static constexpr Class kClass{ + "com/google/ValuesWorkAfterMoveConstructor", + Method{"Foo", jni::Return<jint>{}, Params<jint>{}}, + Field{"BarField", jint{}}}; + + EXPECT_CALL(*env_, CallIntMethodV).Times(3); + EXPECT_CALL(*env_, SetIntField).Times(4); + + LocalObject<kClass> obj_1{Fake<jobject>()}; + obj_1("Foo", 1); + obj_1("Foo", 2); + obj_1["BarField"].Set(1); + + LocalObject<kClass> obj_2{std::move(obj_1)}; + obj_2("Foo", 3); + obj_2["BarField"].Set(2); + obj_2["BarField"].Set(3); + obj_2["BarField"].Set(4); +} + +TEST_F(JniTest, LocalObject_ReleasesLocalsForAlternateConstructors) { + static constexpr Class kClass{"ReleasesLocalsForAlternateConstructors", + jni::Constructor<int>{}}; + LocalObject<kClass> g1{1}; + LocalObject<kClass> g2{2}; + LocalObject<kClass> g3{3}; + EXPECT_CALL(*env_, DeleteLocalRef(_)).Times(3); +} + +TEST_F(JniTest, LocalObject_ComparesAgainstOtherLocalObjects) { + LocalObject<kClass> val_1{Fake<jobject>(1)}; + LocalObject<kClass2> val_2{Fake<jobject>(2)}; + + EXPECT_TRUE(val_1 == val_1); + EXPECT_FALSE(val_1 == val_2); + EXPECT_TRUE(val_1 != val_2); + EXPECT_TRUE(val_2 == val_2); + EXPECT_TRUE(val_2 != val_1); + EXPECT_FALSE(val_1 == val_2); +} + +TEST_F(JniTest, LocalObject_ComparesAgainstjobjects) { + static constexpr Class kClass{"kClass1"}; + LocalObject<kClass> val_1{Fake<jobject>()}; + + EXPECT_TRUE(val_1 == AsNewLocalReference(Fake<jobject>())); + EXPECT_TRUE(AsNewLocalReference(Fake<jobject>()) == val_1); + + EXPECT_FALSE(val_1 != AsNewLocalReference(Fake<jobject>())); + EXPECT_FALSE(AsNewLocalReference(Fake<jobject>()) != val_1); +} + +struct A { + LocalObject<kClass> val_1; + LocalObject<kClass2> val_2; + + A(LocalObject<kClass>&& val_1, LocalObject<kClass2>&& val_2) + : val_1(std::move(val_1)), val_2(std::move(val_2)) {} + + A(A&& rhs) : val_1(std::move(rhs.val_1)), val_2(std::move(rhs.val_2)) {} +}; + +constexpr bool operator==(const A& lhs, const A& rhs) { + return lhs.val_1 == rhs.val_1 && lhs.val_2 == rhs.val_2; +} + +constexpr bool operator!=(const A& lhs, const A& rhs) { + return lhs.val_1 != rhs.val_1 || lhs.val_2 != rhs.val_2; +} + +TEST_F(JniTest, LocalObject_ComparesAgainstOtherLocalObjects_InContainers) { + A val_1{LocalObject<kClass>{Fake<jobject>(1)}, {Fake<jobject>(2)}}; + A val_2{LocalObject<kClass2>{Fake<jobject>(1)}, {Fake<jobject>(3)}}; + + EXPECT_FALSE(val_1 == val_2); + EXPECT_TRUE(val_1 != val_2); + + EXPECT_EQ((std::array{A{LocalObject<kClass>(Fake<jobject>(1)), + LocalObject<kClass2>(Fake<jobject>(2))}}), + (std::array{A{LocalObject<kClass>(Fake<jobject>(1)), + LocalObject<kClass2>(Fake<jobject>(2))}})); + + EXPECT_TRUE((std::array{A{LocalObject<kClass>(Fake<jobject>(1)), + LocalObject<kClass2>(Fake<jobject>(2))}} != + (std::array{A{LocalObject<kClass>(Fake<jobject>(1)), + LocalObject<kClass2>(Fake<jobject>(3))}}))); +} + +TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnLvalue) { + static constexpr Class kClass2{ + "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}}; + + LocalObject<kClass> a{}; + LocalObject<kClass2> b{}; + b("Foo", a); +} + +TEST_F(JniTest, LocalObject_SupportsReturningAClass) { + static constexpr Class kClass{ + "Class1", Method{"Foo", jni::Return{kClass2}, jni::Params{}}}; + + LocalObject<kClass> a{}; + a("Foo"); +} + +TEST_F(JniTest, LocalObject_SupportsReturningAString) { + static constexpr Class kClass{ + "Class1", Method{"Foo", jni::Return<jstring>{}, jni::Params{}}}; + + LocalObject<kClass> a{}; + a("Foo"); +} + +jobject ReturnOutputOfMethod() { + static constexpr Class kClass2{"Class2", Method{"Foo", jni::Return{kClass}}}; + + return LocalObject<kClass2>{}("Foo").Release(); +} + +TEST_F(JniTest, LocalObject_CompilesWhenReturnReleasing) { + ReturnOutputOfMethod(); +} + +TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnPrvalue) { + static constexpr Class kClass2{ + "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}}; + + LocalObject<kClass> a{}; + LocalObject<kClass2> b{}; + b("Foo", std::move(a)); +} + +TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnXvalue) { + static constexpr Class kClass2{ + "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}}; + + LocalObject<kClass2> b{}; + b("Foo", LocalObject<kClass>{}); +} + +TEST_F(JniTest, LocalObject_MovesInContainerStruct) { + struct A { + const LocalObject<kClass> val; + + A(LocalObject<kClass>&& in) : val(std::move(in)) {} + }; + + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>()))); + + A a{LocalObject<kClass>{Fake<jobject>()}}; +} + +TEST_F(JniTest, LocalObject_DoesntCrossTalkOverClassMethodIds) { + static constexpr Class kClass{ + "kClass", Method{"Foo", jni::Return{}, jni::Params<int>{}}}; + + static constexpr Class kClass2{ + "kClass2", Method{"Foo", jni::Return{}, jni::Params<int>{}}}; + + EXPECT_CALL(*env_, GetMethodID(_, _, StrEq("(I)V"))).Times(2); + + LocalObject<kClass> obj_1{Fake<jobject>(1)}; + LocalObject<kClass2> obj_2{Fake<jobject>(2)}; + + // These are different method IDs (they are different classes). + obj_1("Foo", 1); + obj_2("Foo", 1); +} + +} // namespace + +#endif // __clang__ \ No newline at end of file diff --git a/implementation/legacy/method_ref_test.cc b/implementation/legacy/method_ref_test.cc new file mode 100644 index 00000000..c5a972fd --- /dev/null +++ b/implementation/legacy/method_ref_test.cc @@ -0,0 +1,305 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstddef> +#include <utility> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +#if __clang__ + +namespace { + +using ::jni::AdoptGlobal; +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::GlobalObject; +using ::jni::Id; +using ::jni::IdType; +using ::jni::JniT; +using ::jni::kDefaultClassLoader; +using ::jni::kNoIdx; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::OverloadRef; +using ::jni::Params; +using ::jni::Rank; +using ::jni::Return; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::StrEq; + +template <const auto& class_loader_v, const auto& class_v, size_t I> +using MethodRefT_t = OverloadRef<Id<JniT<jobject, class_v, class_loader_v>, + IdType::OVERLOAD, I, 0, kNoIdx, 0>, + IdType::OVERLOAD_PARAM>; + +TEST_F(JniTest, MethodRef_DoesntStaticCrossTalkWithTagUse) { + static constexpr Method m{"FooV", Return<void>{}, Params{jint{}}}; + static constexpr Class kSomeClass{"someClass", m}; + + MethodRefT_t<kDefaultClassLoader, kSomeClass, 0>::Invoke( + Fake<jclass>(), Fake<jobject>(), 123); +} + +TEST_F(JniTest, MethodRef_CallsGetMethodCorrectlyForSingleMethod) { + static constexpr Method m1{"FooV", Return<void>{}}; + static constexpr Class c{"SimpleClass", m1}; + + InSequence seq; + EXPECT_CALL(*env_, + GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"), StrEq("()V"))) + .WillOnce(testing::Return(Fake<jmethodID>())); + EXPECT_CALL(*env_, CallVoidMethodV(Fake<jobject>(), Fake<jmethodID>(), _)); + + MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(), + Fake<jobject>()); +} + +TEST_F(JniTest, MethodRef_ReturnWithObject) { + static constexpr Class c2{"someClass2"}; + static constexpr Method m1{"FooV", Return{c2}}; + static constexpr Class c{"someClass", m1}; + + InSequence seq; + EXPECT_CALL(*env_, GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"), + StrEq("()LsomeClass2;"))) + .WillOnce(testing::Return(Fake<jmethodID>())); + EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(), Fake<jmethodID>(), _)); + + MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(), + Fake<jobject>()); +} + +TEST_F(JniTest, MethodRef_ReturnWithRank1Object) { + static constexpr Class c2{"someClass2"}; + static constexpr Method m1{"FooV", Return{Array{c2}}}; + static constexpr Class c{"someClass", m1}; + + InSequence seq; + EXPECT_CALL(*env_, GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"), + StrEq("()[LsomeClass2;"))) + .WillOnce(testing::Return(Fake<jmethodID>())); + EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(), Fake<jmethodID>(), _)); + + MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(), + Fake<jobject>()); +} + +TEST_F(JniTest, MethodRef_ReturnWithRank2Object) { + static constexpr Class c2{"someClass2"}; + static constexpr Method m1{"FooV", Return{Array{c2, Rank<2>{}}}, Params<>{}}; + static constexpr Class c{"someClass", m1}; + + InSequence seq; + EXPECT_CALL(*env_, GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"), + StrEq("()[[LsomeClass2;"))) + .WillOnce(testing::Return(Fake<jmethodID>())); + EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(), Fake<jmethodID>(), _)); + + MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(), + Fake<jobject>()); +} + +TEST_F(JniTest, MethodRef_ReturnWithNoParams) { + static constexpr Method m1{"FooV", Return<void>{}}; + static constexpr Method m2{"BarI", Return<jint>{}}; + static constexpr Method m3{"BazF", Return<jfloat>{}}; + static constexpr Class c{"someClass", m1, m2, m3}; + + InSequence seq; + EXPECT_CALL(*env_, + GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"), StrEq("()V"))) + .WillOnce(testing::Return(Fake<jmethodID>(1))); + EXPECT_CALL(*env_, CallVoidMethodV(Fake<jobject>(), Fake<jmethodID>(1), _)); + + EXPECT_CALL(*env_, + GetMethodID(Eq(Fake<jclass>()), StrEq("BarI"), StrEq("()I"))) + .WillOnce(testing::Return(Fake<jmethodID>(2))); + EXPECT_CALL(*env_, CallIntMethodV(Fake<jobject>(), Fake<jmethodID>(2), _)); + + EXPECT_CALL(*env_, + GetMethodID(Eq(Fake<jclass>()), StrEq("BazF"), StrEq("()F"))) + .WillOnce(testing::Return(Fake<jmethodID>(3))); + EXPECT_CALL(*env_, CallFloatMethodV(Fake<jobject>(), Fake<jmethodID>(3), _)); + + MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(), + Fake<jobject>()); + MethodRefT_t<kDefaultClassLoader, c, 1>::Invoke(Fake<jclass>(), + Fake<jobject>()); + MethodRefT_t<kDefaultClassLoader, c, 2>::Invoke(Fake<jclass>(), + Fake<jobject>()); +} + +TEST_F(JniTest, MethodRef_SingleParam) { + constexpr Method m1{"SomeFunc1", Return<void>{}, Params<jint>{}}; + constexpr Method m2{"SomeFunc2", Return<jint>{}, Params<jfloat>{}}; + constexpr Method m3{"SomeFunc3", Return<jfloat>{}, Params<jfloat>{}}; + static constexpr Class c{"someClass", m1, m2, m3}; + + InSequence seq; + EXPECT_CALL( + *env_, GetMethodID(Eq(Fake<jclass>()), StrEq("SomeFunc1"), StrEq("(I)V"))) + .WillOnce(testing::Return(Fake<jmethodID>(1))); + // There is no clear way to test variable vaargs type arguments using Gmock, + // but at least we can test the correct method is called. + EXPECT_CALL(*env_, CallVoidMethodV(Fake<jobject>(), Fake<jmethodID>(1), _)); + + EXPECT_CALL( + *env_, GetMethodID(Eq(Fake<jclass>()), StrEq("SomeFunc2"), StrEq("(F)I"))) + .WillOnce(testing::Return(Fake<jmethodID>(2))); + EXPECT_CALL(*env_, CallIntMethodV(Fake<jobject>(), Fake<jmethodID>(2), _)); + + EXPECT_CALL( + *env_, GetMethodID(Eq(Fake<jclass>()), StrEq("SomeFunc3"), StrEq("(F)F"))) + .WillOnce(testing::Return(Fake<jmethodID>(3))); + EXPECT_CALL(*env_, CallFloatMethodV(Fake<jobject>(), Fake<jmethodID>(3), _)); + + MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(), + Fake<jobject>(), 1); + MethodRefT_t<kDefaultClassLoader, c, 1>::Invoke(Fake<jclass>(), + Fake<jobject>(), 1.234f); + MethodRefT_t<kDefaultClassLoader, c, 2>::Invoke(Fake<jclass>(), + Fake<jobject>(), 5.6789f); +} + +TEST_F(JniTest, MethodRef_ReturnsObjects) { + static constexpr Class c1{"Bazz"}; + static constexpr Class kClass{ + "com/google/ReturnsObjects", + Method{"Foo", Return{c1}, Params<jint>{}}, + }; + + // Note, class refs are not released, so Times() != 2. + EXPECT_CALL(*env_, NewObjectV).WillOnce(testing::Return(Fake<jobject>())); + + GlobalObject<kClass> global_object{}; + LocalObject<c1> new_obj{global_object("Foo", 5)}; +} + +TEST_F(JniTest, MethodRef_PassesObjects) { + static constexpr Class c1{"com/google/Bazz"}; + static constexpr Class kClass{ + "com/google/PassesObjects", + Method{"Foo", Return<jint>{}, Params{c1}}, + }; + + LocalObject<c1> local_object{Fake<jobject>()}; + GlobalObject<kClass> global_object{AdoptGlobal{}, Fake<jobject>(100)}; + + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Lcom/google/Bazz;)I"))); + + global_object("Foo", local_object); +} + +TEST_F(JniTest, MethodRef_PassesAndReturnsMultipleObjects) { + static constexpr Class c1{"Class1"}; + static constexpr Class c2{"Class2"}; + static constexpr Class c3{"Class3"}; + static constexpr Class c4{"Class4"}; + + static constexpr Class class_under_test{ + "com/google/PassesAndReturnsMultipleObjects", + Method{"Foo", Return{c1}, Params{c1, c2, c3, c4}}, + }; + + LocalObject<c1> obj1{Fake<jobject>(1)}; + LocalObject<c2> obj2{Fake<jobject>(2)}; + LocalObject<c3> obj3{Fake<jobject>(3)}; + LocalObject<c4> obj4{Fake<jobject>(4)}; + LocalObject<class_under_test> object_under_test{Fake<jobject>(5)}; + + LocalObject<c1> obj5{object_under_test("Foo", obj1, obj2, obj3, obj4)}; +} + +TEST_F(JniTest, MethodRef_SupportsForwardDefines) { + static constexpr Class kClass1{ + "kClass1", + Method{"m1", Return<void>{}, Params{Class{"kClass1"}}}, + Method{"m2", Return<void>{}, Params{Class{"kClass2"}}}, + Method{"m3", Return{Class{"kClass1"}}}, + Method{"m4", Return{Class{"kClass2"}}}, + }; + + static constexpr Class kClass2{ + "kClass2", + Method{"m1", Return<void>{}, Params{Class{"kClass1"}}}, + Method{"m2", Return<void>{}, Params{Class{"kClass2"}}}, + Method{"m3", Return{Class{"kClass1"}}}, + Method{"m4", Return{Class{"kClass2"}}}, + }; + + LocalObject<kClass1> c1_obj1{Fake<jobject>(1)}; + LocalObject<kClass1> c1_obj2{Fake<jobject>(2)}; + + LocalObject<kClass2> c2_obj1{Fake<jobject>(3)}; + LocalObject<kClass2> c2_obj2{Fake<jobject>(4)}; + + c1_obj1("m1", c1_obj1); + c1_obj1("m2", c2_obj1); + c1_obj1("m1", c1_obj1("m3")); + c1_obj1("m2", c1_obj1("m4")); + + c2_obj1("m1", c1_obj1); + c2_obj1("m2", c2_obj2); + c2_obj1("m2", std::move(std::move(c2_obj2))); + + c1_obj1("m2", std::move(c2_obj1)); + + // c2_obj1("m1", c1_obj1); // illegal! triggers warnings (post move read). + // c2_obj1("m2", c2_obj2); // illegal! triggers warnings (post move read). + // c2_obj1("m2", std::move(c2_obj2)); // illegal! triggers warnings (post + // move read). +} + +TEST_F(JniTest, MethodRef_SupportsStrings) { + static constexpr Class class_under_test{ + "com/google/SupportsStrings", + Method{"Foo", Return<void>{}, Params<jstring>{}}, + Method{"Bar", Return<void>{}, Params<jstring, jstring>{}}, + Method{"Baz", Return<jstring>{}}, + }; + + LocalObject<class_under_test> obj1{Fake<jobject>()}; + obj1("Foo", "This is a method."); + obj1("Bar", "This is a method.", "It takes strings"); + obj1("Baz"); +} + +TEST_F(JniTest, MethodRef_SupportsArrays) { + static constexpr Class kClass{"kClass"}; + static constexpr Class class_under_test{ + "com/google/SupportsArrays", + Method{"Foo", Return<void>{}, Params{Array{kClass}}}, + Method{"Bar", Return<void>{}, Params<int>{}}}; + + LocalArray<jobject, 1, kClass> local_array{nullptr}; + LocalObject<class_under_test> obj1{Fake<jobject>()}; + obj1("Foo", local_array); +} + +} // namespace + +#endif // __clang__ \ No newline at end of file diff --git a/implementation/legacy/multi_type_test.cc b/implementation/legacy/multi_type_test.cc new file mode 100644 index 00000000..77de5065 --- /dev/null +++ b/implementation/legacy/multi_type_test.cc @@ -0,0 +1,118 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +#if __clang__ + +namespace { + +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::Method; +using ::jni::Params; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Eq; +using ::testing::Return; +using ::testing::StrEq; + +TEST_F(JniTest, MultiTypeTest_SimpleSmokeTestForSingleObject) { + static constexpr Class object{ + "ARCore", + Method{"Foo", jni::Return<jint>{}, Params<jint, jfloat>{}}, + Method{"Bar", jni::Return{jint{}}}, + Method{"Baz", jni::Return<void>{}, Params<jfloat>{}}, + Field{"SomeField", jint{}}, + }; + + EXPECT_CALL(*env_, FindClass(StrEq("ARCore"))) + .WillOnce(Return(Fake<jclass>(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>(1))); + EXPECT_CALL(*env_, NewGlobalRef(Eq(Fake<jclass>(1)))) + .WillOnce(Return(Fake<jclass>(2))); + EXPECT_CALL(*env_, + GetMethodID(Fake<jclass>(2), StrEq("<init>"), StrEq("()V"))) + .WillOnce(Return(Fake<jmethodID>())); + + EXPECT_CALL(*env_, NewObjectV(Fake<jclass>(2), Fake<jmethodID>(), _)) + .WillOnce(Return(Fake<jobject>(1))); + EXPECT_CALL(*env_, NewGlobalRef(Fake<jobject>(1))) + .WillOnce(Return(Fake<jobject>(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(1))).Times(1); + + EXPECT_CALL(*env_, GetMethodID(Fake<jclass>(2), StrEq("Foo"), StrEq("(IF)I"))) + .WillOnce(Return(Fake<jmethodID>())); + EXPECT_CALL(*env_, GetMethodID(Fake<jclass>(2), StrEq("Bar"), StrEq("()I"))) + .WillOnce(Return(Fake<jmethodID>())); + EXPECT_CALL(*env_, GetMethodID(Fake<jclass>(2), StrEq("Baz"), StrEq("(F)V"))) + .WillOnce(Return(Fake<jmethodID>())); + EXPECT_CALL(*env_, + GetFieldID(Fake<jclass>(2), StrEq("SomeField"), StrEq("I"))) + .WillOnce(Return(Fake<jfieldID>())); + + EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>(2))).Times(1); + EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jclass>(2))).Times(1); + + jni::GlobalObject<object> obj{}; + obj("Foo", 1, 2.f); + obj("Baz", 1.f); + obj("Baz", 1.f); + obj("Baz", 2.f); + obj("Baz", 3.f); + obj("Bar"); + obj["SomeField"].Get(); +} + +TEST_F(JniTest, MultiTypeTest_MethodsOfSameNameButDifferentClassAreUnique) { + EXPECT_CALL(*env_, GetMethodID).Times(2); + + static constexpr Class c1{ + "com/google/ARCore", + Method{"Foo", jni::Return<jint>{}, Params<jint>{}}, + }; + static constexpr Class c2{ + "com/google/VRCore", + Method{"Foo", jni::Return<jint>{}, Params<jint>{}}, + }; + + jni::LocalObject<c1> obj1{Fake<jobject>(1)}; + jni::LocalObject<c2> obj2{Fake<jobject>(2)}; + obj1("Foo", 12345); + obj2("Foo", 12345); + + // All of these calls ought not query for a method ID again. + obj1("Foo", 12345); + obj1("Foo", 12345); + obj1("Foo", 12345); + obj1("Foo", 12345); + obj2("Foo", 12345); + obj2("Foo", 12345); + obj2("Foo", 12345); + + jni::LocalObject<c1> obj3{Fake<jobject>(3)}; + obj3("Foo", 12345); + obj3("Foo", 12345); +} + +} // namespace + +#endif // __clang__ \ No newline at end of file diff --git a/implementation/legacy/overload_ref_test.cc b/implementation/legacy/overload_ref_test.cc new file mode 100644 index 00000000..bc40c349 --- /dev/null +++ b/implementation/legacy/overload_ref_test.cc @@ -0,0 +1,130 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "jni_bind.h" +#include "jni_test.h" + +#if __clang__ + +namespace { + +using ::jni::Class; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Overload; +using ::jni::Params; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +TEST_F(JniTest, MethodRef_AsksForCorrectMethods1) { + static constexpr Class kClass{ + "com/google/SupportsStrings", + Method{"Foo", jni::Return<void>{}, Params<jstring>{}}, + }; + LocalObject<kClass> obj{}; + + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); + + obj("Foo", "test"); +} + +TEST_F(JniTest, MethodRef_AsksForCorrectMethods2) { + static constexpr Class kClass{ + "com/google/SupportsStrings", + Method{"Foo", Overload{jni::Return<void>{}, Params{jint{}}}, + Overload{jni::Return<void>{}, Params{jstring{}}}}}; + LocalObject<kClass> obj{}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); + + obj("Foo", 1); + // obj("Foo", 2.f); doesn't compile (good). + obj("Foo", "test"); +} + +TEST_F(JniTest, MethodRef_AsksForCorrectMethods3) { + static constexpr Class kClass{ + "com/google/SupportsStrings", + Method{ + "Foo", + Overload{jni::Return<void>{}, Params{jint{}}}, + Overload{jni::Return<void>{}, Params{jstring{}}}, + Overload{jni::Return<void>{}, Params{jstring{}, jstring{}}}, + }}; + LocalObject<kClass> obj{}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), + StrEq("(Ljava/lang/String;Ljava/lang/String;)V"))); + + obj("Foo", 1); + // obj("Foo", 2.f); // doesn't compile (good). + obj("Foo", "test"); + obj("Foo", "test1", "test2"); + obj("Foo", "this_doesnt", "trigger_method_lookup"); +} + +TEST_F(JniTest, MethodRef_AsksForCorrectMethodsWhenMultiplePresent4) { + static constexpr Class kClass{ + "com/google/SupportsStrings", + Method{ + "Foo", + Overload{jni::Return<void>{}, Params{jint{}}}, + Overload{jni::Return<void>{}, Params{jstring{}}}, + Overload{jni::Return<void>{}, Params{jstring{}, jstring{}}}, + }, + Method{ + "Baz", + Overload{jni::Return<void>{}, Params{jint{}}}, + Overload{jni::Return<void>{}, Params{jstring{}, jstring{}}}, + Overload{jni::Return<void>{}, Params{jfloat{}, jfloat{}}}, + }}; + LocalObject<kClass> obj{}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), + StrEq("(Ljava/lang/String;Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(I)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Baz"), + StrEq("(Ljava/lang/String;Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(FF)V"))); + + obj("Foo", 1); + // obj("Foo", 2.f); // doesn't compile (good). + obj("Foo", "test"); + obj("Foo", "test1", "test2"); + obj("Foo", "this_doesnt", "trigger_method_lookup"); + obj("Baz", 1); + obj("Baz", "test3", "test4"); + obj("Baz", 1234.f, 5678.f); +} + +} // namespace + +#endif // __clang__ \ No newline at end of file diff --git a/implementation/legacy/self_test.cc b/implementation/legacy/self_test.cc new file mode 100644 index 00000000..cd422cff --- /dev/null +++ b/implementation/legacy/self_test.cc @@ -0,0 +1,72 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "jni_bind.h" +#include "jni_test.h" + +#if __clang__ + +namespace { + +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::Return; +using ::jni::Self; +using ::jni::test::AsGlobal; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::StrEq; + +static constexpr Class kClass{"Builder", Method{"build", Return{Self{}}}, + Method{"takesBuilder", Return{}, Params{Self{}}}}; + +TEST_F(JniTest, SelfCanBeUsedAsAReturnValue) { + EXPECT_CALL(*env_, FindClass(StrEq("Builder"))) + .WillOnce(::testing::Return(Fake<jclass>())); + EXPECT_CALL(*env_, GetMethodID(AsGlobal(Fake<jclass>()), StrEq("build"), + StrEq("()LBuilder;"))); + + LocalObject<kClass> obj{Fake<jobject>()}; + obj("build"); +} + +TEST_F(JniTest, SelfCanBeUsedAsAReturnValueAndMaintainsRichDecoration) { + EXPECT_CALL(*env_, GetMethodID).Times(AnyNumber()); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("build"), StrEq("()LBuilder;"))); + + LocalObject<kClass> obj{Fake<jobject>()}; + obj("build")("build")("build"); +} + +TEST_F(JniTest, SelfCanBeUsedAsParam) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("takesBuilder"), StrEq("(LBuilder;)V"))); + + LocalObject<kClass> obj_1{Fake<jobject>(1)}; + LocalObject<kClass> obj_2{Fake<jobject>(2)}; + + obj_1("takesBuilder", obj_2); +} + +} // namespace + +#endif // __clang__ \ No newline at end of file diff --git a/implementation/legacy/static_ref_test.cc b/implementation/legacy/static_ref_test.cc new file mode 100644 index 00000000..54e02fc6 --- /dev/null +++ b/implementation/legacy/static_ref_test.cc @@ -0,0 +1,377 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +#if __clang__ + +namespace { + +using ::jni::AdoptLocal; +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::LocalObject; +using ::jni::LocalString; +using ::jni::Method; +using ::jni::Params; +using ::jni::Rank; +using ::jni::Self; +using ::jni::Static; +using ::jni::StaticRef; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +//////////////////////////////////////////////////////////////////////////////// +// Fields. +//////////////////////////////////////////////////////////////////////////////// + +static constexpr Class kClass2{"kClass2"}; + +// clang-format off +static constexpr Class kClass{ + "kClass", + Static { + Field{"booleanField", jboolean{}}, + Field{"byteField", jbyte{}}, + Field{"charField", jchar{}}, + Field{"shortField", jshort{}}, + Field{"intField", jint{}}, + Field{"longField", jlong{}}, + Field{"floatField", jfloat{}}, + Field{"doubleField", jdouble{}}, + Field{"stringField", jstring{}}, + Field{"classField", Class{"kClass2"}}, + Field{"selfField", Self{}} + }, +}; +// clang-format on + +TEST_F(JniTest, StaticBooleanField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("booleanField"), StrEq("Z"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticBooleanField(_, Fake<jfieldID>())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticBooleanField(_, Fake<jfieldID>(), true)); + + LocalObject<kClass> obj; + EXPECT_TRUE(StaticRef<kClass>{}["booleanField"].Get()); + StaticRef<kClass>{}["booleanField"].Set(true); +} + +TEST_F(JniTest, StaticByteField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("byteField"), StrEq("B"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticByteField(_, Fake<jfieldID>())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticByteField(_, Fake<jfieldID>(), true)); + + LocalObject<kClass> obj; + EXPECT_TRUE(StaticRef<kClass>{}["byteField"].Get()); + StaticRef<kClass>{}["byteField"].Set(true); +} + +TEST_F(JniTest, StaticCharField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("charField"), StrEq("C"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticCharField(_, Fake<jfieldID>())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticCharField(_, Fake<jfieldID>(), true)); + + LocalObject<kClass> obj; + EXPECT_TRUE(StaticRef<kClass>{}["charField"].Get()); + StaticRef<kClass>{}["charField"].Set(true); +} + +TEST_F(JniTest, StaticShortField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("shortField"), StrEq("S"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticShortField(_, Fake<jfieldID>())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticShortField(_, Fake<jfieldID>(), true)); + + LocalObject<kClass> obj; + EXPECT_TRUE(StaticRef<kClass>{}["shortField"].Get()); + StaticRef<kClass>{}["shortField"].Set(true); +} + +TEST_F(JniTest, StaticIntField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("intField"), StrEq("I"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticIntField(_, Fake<jfieldID>())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticIntField(_, Fake<jfieldID>(), true)); + + LocalObject<kClass> obj; + EXPECT_TRUE(StaticRef<kClass>{}["intField"].Get()); + StaticRef<kClass>{}["intField"].Set(true); +} + +TEST_F(JniTest, StaticLongField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("longField"), StrEq("J"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticLongField(_, Fake<jfieldID>())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticLongField(_, Fake<jfieldID>(), true)); + + LocalObject<kClass> obj; + EXPECT_TRUE(StaticRef<kClass>{}["longField"].Get()); + StaticRef<kClass>{}["longField"].Set(true); +} + +TEST_F(JniTest, StaticFloatField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("floatField"), StrEq("F"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticFloatField(_, Fake<jfieldID>())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticFloatField(_, Fake<jfieldID>(), true)); + + LocalObject<kClass> obj; + EXPECT_TRUE(StaticRef<kClass>{}["floatField"].Get()); + StaticRef<kClass>{}["floatField"].Set(true); +} + +TEST_F(JniTest, StaticDoubleField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("doubleField"), StrEq("D"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticDoubleField(_, Fake<jfieldID>())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticDoubleField(_, Fake<jfieldID>(), true)); + + LocalObject<kClass> obj; + EXPECT_TRUE(StaticRef<kClass>{}["doubleField"].Get()); + StaticRef<kClass>{}["doubleField"].Set(true); +} + +TEST_F(JniTest, StaticField_ObjectGet) { + EXPECT_CALL(*env_, + GetStaticFieldID(_, StrEq("classField"), StrEq("LkClass2;"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticObjectField(_, Fake<jfieldID>())) + .WillOnce(Return(Fake<jobject>())); + + jni::LocalObject<kClass2> obj = StaticRef<kClass>{}["classField"].Get(); +} + +TEST_F(JniTest, StaticField_StringSet) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("stringField"), + StrEq("Ljava/lang/String;"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, + SetStaticObjectField(_, Fake<jfieldID>(), Fake<jstring>())); + + StaticRef<kClass>{}["stringField"].Set( + LocalString{AdoptLocal{}, Fake<jstring>()}); +} + +TEST_F(JniTest, StaticField_ObjectSet) { + EXPECT_CALL(*env_, + GetStaticFieldID(_, StrEq("classField"), StrEq("LkClass2;"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, + SetStaticObjectField(_, Fake<jfieldID>(), Fake<jobject>())); + + StaticRef<kClass>{}["classField"].Set( + LocalObject<kClass2>{AdoptLocal{}, Fake<jobject>()}); +} + +TEST_F(JniTest, StaticField_SelfGet) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("selfField"), StrEq("LkClass;"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, GetStaticObjectField(_, Fake<jfieldID>())) + .WillOnce(Return(Fake<jobject>())); + + jni::LocalObject<kClass> obj = StaticRef<kClass>{}["selfField"].Get(); +} + +TEST_F(JniTest, StaticField_SelfSet) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("selfField"), StrEq("LkClass;"))) + .WillOnce(Return(Fake<jfieldID>())); + EXPECT_CALL(*env_, + SetStaticObjectField(_, Fake<jfieldID>(), Fake<jobject>())); + + StaticRef<kClass>{}["selfField"].Set( + LocalObject<kClass>{AdoptLocal{}, Fake<jobject>()}); +} + +//////////////////////////////////////////////////////////////////////////////// +// Static Methods. +//////////////////////////////////////////////////////////////////////////////// + +// clang-format off +static constexpr Class kMethodClass{ + "kMethodClass", + Static { + Method{"booleanMethod", ::jni::Return{jboolean{}}}, + Method{"byteMethod", ::jni::Return{jbyte{}}}, + Method{"charMethod", ::jni::Return{jchar{}}}, + Method{"shortMethod", ::jni::Return{jshort{}}}, + Method{"intMethod", ::jni::Return{jint{}}}, + Method{"longMethod", ::jni::Return{jlong{}}}, + Method{"floatMethod", ::jni::Return{jfloat{}}}, + Method{"doubleMethod", ::jni::Return{jdouble{}}}, + Method{"stringMethod", ::jni::Return{jstring{}}}, + Method{"objectMethod", ::jni::Return{Class{"kClass2"}}}, + Method{"rank1ArrayMethod", ::jni::Return{Array{Class{"kClass2"}}}}, + Method{"rank2ArrayMethod", ::jni::Return{Array{Class{"kClass2"}, Rank<2>{}}}}, + Method{"selfMethod", ::jni::Return{Self{}}}, + + Method{"simpleFunc", ::jni::Return{int{}}, Params{jfloat{}}}, + Method{"complexFunc", ::jni::Return{float{}}, + Params{ + Array{Class{"kClass2"}, Rank<2>{}}, int{}, float{}, Class{"kClass3"}, + } + } + }, +}; +// clang-format on + +TEST_F(JniTest, StaticExerciseAllReturns) { + EXPECT_CALL(*env_, + GetStaticMethodID(_, StrEq("booleanMethod"), StrEq("()Z"))); + EXPECT_CALL(*env_, CallStaticBooleanMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("byteMethod"), StrEq("()B"))); + EXPECT_CALL(*env_, CallStaticByteMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("charMethod"), StrEq("()C"))); + EXPECT_CALL(*env_, CallStaticCharMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("shortMethod"), StrEq("()S"))); + EXPECT_CALL(*env_, CallStaticShortMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("intMethod"), StrEq("()I"))); + EXPECT_CALL(*env_, CallStaticIntMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("longMethod"), StrEq("()J"))); + EXPECT_CALL(*env_, CallStaticLongMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("floatMethod"), StrEq("()F"))); + EXPECT_CALL(*env_, CallStaticFloatMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("doubleMethod"), StrEq("()D"))); + EXPECT_CALL(*env_, CallStaticDoubleMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("stringMethod"), + StrEq("()Ljava/lang/String;"))); + + EXPECT_CALL( + *env_, GetStaticMethodID(_, StrEq("objectMethod"), StrEq("()LkClass2;"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank1ArrayMethod"), + StrEq("()[LkClass2;"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank2ArrayMethod"), + StrEq("()[[LkClass2;"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("selfMethod"), + StrEq("()LkMethodClass;"))); + + EXPECT_CALL(*env_, CallStaticObjectMethodV).Times(5); + + StaticRef<kMethodClass>{}("booleanMethod"); + StaticRef<kMethodClass>{}("byteMethod"); + StaticRef<kMethodClass>{}("charMethod"); + StaticRef<kMethodClass>{}("shortMethod"); + StaticRef<kMethodClass>{}("intMethod"); + StaticRef<kMethodClass>{}("longMethod"); + StaticRef<kMethodClass>{}("floatMethod"); + StaticRef<kMethodClass>{}("doubleMethod"); + StaticRef<kMethodClass>{}("stringMethod"); + + StaticRef<kMethodClass>{}("objectMethod"); + StaticRef<kMethodClass>{}("rank1ArrayMethod"); + StaticRef<kMethodClass>{}("rank2ArrayMethod"); + + LocalObject<kClass> self_ret = StaticRef<kMethodClass>{}("selfMethod"); +} + +// clang-format off +static constexpr Class kMethodClassSingleParam{ + "kMethodClassSingleParam", + Static { + Method{"booleanMethod", ::jni::Return<void>{}, Params<jboolean>{}}, + Method{"byteMethod", ::jni::Return<void>{}, Params<jbyte>{}}, + Method{"charMethod", ::jni::Return<void>{}, Params<jchar>{}}, + Method{"shortMethod", ::jni::Return<void>{}, Params<jshort>{}}, + Method{"intMethod", ::jni::Return<void>{}, Params<jint>{}}, + Method{"longMethod", ::jni::Return<void>{}, Params<jlong>{}}, + Method{"floatMethod", ::jni::Return<void>{}, Params<jfloat>{}}, + Method{"doubleMethod", ::jni::Return<void>{}, Params<jdouble>{}}, + Method{"stringMethod", ::jni::Return<void>{}, Params<jstring>{}}, + Method{"objectMethod", ::jni::Return<void>{}, Params{Class{"kClass2"}}}, + Method{"rank1ArrayMethod", ::jni::Return<void>{}, Params{Array{Class{"kClass2"}}}}, + Method{"rank2ArrayMethod", ::jni::Return<void>{}, Params{Array{Class{"kClass2"}, Rank<2>{}}}}, + Method{"selfMethod", ::jni::Return<void>{}, Params{Self{}}} + }, +}; +// clang-format on + +TEST_F(JniTest, StaticExerciseAllTypesThroughSingleParam) { + EXPECT_CALL(*env_, + GetStaticMethodID(_, StrEq("booleanMethod"), StrEq("(Z)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("byteMethod"), StrEq("(B)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("charMethod"), StrEq("(C)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("shortMethod"), StrEq("(S)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("intMethod"), StrEq("(I)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("longMethod"), StrEq("(J)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("floatMethod"), StrEq("(F)V"))); + EXPECT_CALL(*env_, + GetStaticMethodID(_, StrEq("doubleMethod"), StrEq("(D)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("stringMethod"), + StrEq("(Ljava/lang/String;)V"))); + + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("objectMethod"), + StrEq("(LkClass2;)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank1ArrayMethod"), + StrEq("([LkClass2;)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank2ArrayMethod"), + StrEq("([[LkClass2;)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("selfMethod"), + StrEq("(LkMethodClassSingleParam;)V"))); + + EXPECT_CALL(*env_, CallStaticVoidMethodV).Times(13); + + StaticRef<kMethodClassSingleParam>{}("booleanMethod", jboolean{true}); + StaticRef<kMethodClassSingleParam>{}("byteMethod", jbyte{1}); + StaticRef<kMethodClassSingleParam>{}("charMethod", jchar{'a'}); + StaticRef<kMethodClassSingleParam>{}("shortMethod", jshort{1}); + StaticRef<kMethodClassSingleParam>{}("intMethod", jint{123}); + StaticRef<kMethodClassSingleParam>{}("longMethod", jlong{456}); + StaticRef<kMethodClassSingleParam>{}("floatMethod", jfloat{789.f}); + StaticRef<kMethodClassSingleParam>{}("doubleMethod", jdouble{101.}); + StaticRef<kMethodClassSingleParam>{}("stringMethod", "test"); + + // It would be more complete to exercise all types here. + StaticRef<kMethodClassSingleParam>{}("objectMethod", Fake<jobject>()); + StaticRef<kMethodClassSingleParam>{}("rank1ArrayMethod", + Fake<jobjectArray>()); + StaticRef<kMethodClassSingleParam>{}("rank2ArrayMethod", + Fake<jobjectArray>()); + StaticRef<kMethodClassSingleParam>{}("selfMethod", Fake<jobject>()); +} + +TEST_F(JniTest, StaticExerciseComplexSetOfParams) { + // The primary difference for statics are how they handle their returns. + // Coverage is already fairly extensive for params. + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("simpleFunc"), StrEq("(F)I"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("complexFunc"), + StrEq("([[LkClass2;IFLkClass3;)F"))); + + StaticRef<kMethodClass>{}("simpleFunc", 123.f); + StaticRef<kMethodClass>{}("complexFunc", jobjectArray{nullptr}, 123, 456.f, + jobject{nullptr}); +} + +} // namespace + +#endif // __clang__ \ No newline at end of file diff --git a/implementation/legacy/string_ref_test.cc b/implementation/legacy/string_ref_test.cc new file mode 100644 index 00000000..23136d92 --- /dev/null +++ b/implementation/legacy/string_ref_test.cc @@ -0,0 +1,242 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> +#include <string_view> +#include <utility> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +#if __clang__ + +namespace { + +using ::jni::AdoptGlobal; +using ::jni::Fake; +using ::jni::GlobalObject; +using ::jni::GlobalString; +using ::jni::kJavaLangString; +using ::jni::LocalObject; +using ::jni::LocalString; +using ::jni::NewRef; +using ::jni::UtfStringView; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +const char* char_ptr = "TestString"; + +static constexpr jni::Class kClass{ + "Class", + jni::Method{"Foo", jni::Return<jstring>{}, jni::Params{}}, + jni::Method{"TakesStrParam", jni::Return<void>{}, jni::Params<jstring>{}}, +}; + +//////////////////////////////////////////////////////////////////////////////// +// Local String Tests. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, LocalString_NullPtrT) { LocalString str{nullptr}; } + +TEST_F(JniTest, LocalString_IsImplicitlyConvertible) { + LocalString str{Fake<jstring>()}; + EXPECT_EQ(static_cast<jstring>(str), AsNewLocalReference(Fake<jstring>())); +} + +TEST_F(JniTest, LocalString_NullWorks) { + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + LocalString str{jstring{nullptr}}; +} + +TEST_F(JniTest, LocalString_ConstructsFromObject) { + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>()))); + LocalObject<kJavaLangString> undecorated_object{Fake<jobject>()}; + LocalString decorated_object{std::move(undecorated_object)}; +} + +TEST_F(JniTest, LocalString_CopiesFromObject) { + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>()))); + EXPECT_CALL(*env_, NewLocalRef(Fake<jobject>())); + + LocalString decorated_object{NewRef{}, Fake<jobject>()}; + + EXPECT_EQ(jstring{decorated_object}, AsNewLocalReference(Fake<jobject>())); +} + +TEST_F(JniTest, LocalString_CopiesFromJString) { + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>(2))); + EXPECT_CALL(*env_, NewLocalRef(Fake<jstring>(1))) + .WillOnce(::testing::Return(Fake<jstring>(2))); + + LocalString decorated_object{NewRef{}, Fake<jstring>(1)}; + + EXPECT_EQ(jstring{decorated_object}, Fake<jstring>(2)); +} + +TEST_F(JniTest, LocalString_ConstructsFromOutputOfMethod) { + LocalObject<kClass> obj{}; + LocalString str{obj("Foo")}; +} + +TEST_F(JniTest, LocalString_ConstructsFromByteArray) { + EXPECT_CALL(*env_, GetMethodID(_, StrEq("<init>"), StrEq("([B)V"))); + LocalString str{Fake<jbyteArray>()}; +} + +TEST_F(JniTest, LocalString_CreatesFromCharPtr) { + LocalString str{"TestString"}; +} + +TEST_F(JniTest, LocalString_CreatesFromStringView) { + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString"))) + .WillOnce(Return(Fake<jstring>())); + + // jclass for temp String class reference. + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())); + // The variable str (which is itself an object). + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>())); + // TODO(b/143908983): Currently strings leak one local during proxying. + // Temporary xref created during construction. + // EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>())); + + LocalString str{std::string_view{char_ptr}}; +} + +TEST_F(JniTest, LocalString_CreatesFromString) { + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString"))) + .WillOnce(Return(Fake<jstring>())); + + // jclass for temp String class reference. + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())); + // The variable str (which is itself an object). + EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>())); + // TODO(b/143908983): Currently strings leak one local during proxying. + // Temporary xref created during construction. + // EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>())); + LocalString str{std::string{"TestString"}}; +} + +TEST_F(JniTest, LocalString_CreatesFromCharPtrForGlobals) { + GlobalString str{"TestString"}; +} + +TEST_F(JniTest, LocalString_PinsAndUnpinsMemoryForLocals) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("<init>"), StrEq("(Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestLocalString"))); + EXPECT_CALL(*env_, GetStringUTFChars(_, nullptr)).WillOnce(Return(char_ptr)); + EXPECT_CALL(*env_, ReleaseStringUTFChars(_, char_ptr)); + + LocalString str{"TestLocalString"}; + UtfStringView utf_string_view = str.Pin(); + EXPECT_EQ(utf_string_view.ToString().data(), char_ptr); +} + +TEST_F(JniTest, LocalString_AllowsLValueLocalString) { + LocalObject<kClass> obj{}; + LocalString local_string{"abcde"}; + obj("TakesStrParam", local_string); +} + +TEST_F(JniTest, LocalString_AllowsRValueLocalString) { + LocalObject<kClass> obj{}; + obj("TakesStrParam", LocalString{"abcde"}); +} + +//////////////////////////////////////////////////////////////////////////////// +// Global String Tests. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, GlobalString_NullWorks) { + GlobalString str{AdoptGlobal{}, jstring{nullptr}}; +} + +TEST_F(JniTest, GlobalString_ConstructsFromObject) { + EXPECT_CALL(*env_, DeleteGlobalRef).Times(1); + GlobalObject<kJavaLangString> undecorated_object{AdoptGlobal{}, + Fake<jobject>()}; + GlobalString decorated_object{std::move(undecorated_object)}; +} + +TEST_F(JniTest, GlobalString_GlobalsReleaseWithGlobalMechanism) { + EXPECT_CALL(*env_, DeleteGlobalRef); + GlobalString str{AdoptGlobal{}, Fake<jstring>()}; +} + +TEST_F(JniTest, GlobalString_ConstructsFromOutputOfMethod) { + LocalObject<kClass> obj{}; + GlobalString str{obj("Foo")}; +} + +TEST_F(JniTest, GlobalString_ConstructsFromByteArray) { + EXPECT_CALL(*env_, GetMethodID(_, StrEq("<init>"), StrEq("([B)V"))); + GlobalString str{Fake<jbyteArray>()}; +} + +TEST_F(JniTest, GlobalString_CreatesFromCharPtr) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("<init>"), StrEq("(Ljava/lang/String;)V"))); + GlobalString str{"TestString"}; +} + +TEST_F(JniTest, GlobalString_CreatesFromStringView) { + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString"))) + .WillOnce(Return(Fake<jstring>())); + GlobalString str{std::string_view{char_ptr}}; +} + +TEST_F(JniTest, GlobalString_CreatesFromString) { + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString"))) + .WillOnce(Return(Fake<jstring>())); + GlobalString str{std::string{"TestString"}}; +} + +TEST_F(JniTest, GlobalString_CreatesFromCharPtrForGlobals) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("<init>"), StrEq("(Ljava/lang/String;)V"))); + GlobalString str{"TestString"}; +} + +TEST_F(JniTest, GlobalString_PinsAndUnpinsMemoryForLocals) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("<init>"), StrEq("(Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestGlobalString"))); + EXPECT_CALL(*env_, GetStringUTFChars(_, nullptr)).WillOnce(Return(char_ptr)); + EXPECT_CALL(*env_, ReleaseStringUTFChars(_, char_ptr)); + + GlobalString str{"TestGlobalString"}; + UtfStringView utf_string_view = str.Pin(); + EXPECT_EQ(utf_string_view.ToString().data(), char_ptr); +} + +TEST_F(JniTest, GlobalString_AllowsLValueGlobalString) { + LocalObject<kClass> obj{}; + GlobalString global_string{"abcde"}; + obj("TakesStrParam", global_string); +} + +TEST_F(JniTest, GlobalString_AllowsRValueGlobalString) { + LocalObject<kClass> obj{}; + obj("TakesStrParam", GlobalString{"abcde"}); +} + +} // namespace + +#endif // __clang__ \ No newline at end of file diff --git a/implementation/proxy_test.cc b/implementation/proxy_test.cc index f82ed8b6..50652843 100644 --- a/implementation/proxy_test.cc +++ b/implementation/proxy_test.cc @@ -23,6 +23,7 @@ #include <gtest/gtest.h> #include "jni_bind.h" #include "jni_test.h" +#include "metaprogramming/type_to_type_map.h" using ::jni::AsDecl_t; using ::jni::Class; diff --git a/implementation/string_ref_test.cc b/implementation/string_ref_test.cc index 722def8f..08156c61 100644 --- a/implementation/string_ref_test.cc +++ b/implementation/string_ref_test.cc @@ -14,6 +14,11 @@ * limitations under the License. */ +#include <string> +#include <string_view> +#include <utility> + +#include <gmock/gmock.h> #include <gtest/gtest.h> #include "implementation/jni_helper/fake_test_constants.h" #include "jni_bind.h"