From 50d5a7259c7ebd98603d9f69c30820622c7fe05f Mon Sep 17 00:00:00 2001 From: Jamieson Pryor Date: Sat, 18 Nov 2023 13:28:55 -0800 Subject: [PATCH] Split runtime array tests from static array tests. Also, break out method array ref test (similar to fields) for better organisation. PiperOrigin-RevId: 583674129 --- implementation/BUILD | 62 ++- implementation/array_test.cc | 329 --------------- implementation/local_array_field_test.cc | 28 +- implementation/local_array_iteration_test.cc | 255 ++++++++++++ ...ocal_array_method_multidimensional_test.cc | 129 ++++++ implementation/local_array_method_test.cc | 377 ++++++++++++++++++ .../local_array_multidimensional_test.cc | 220 +--------- implementation/local_array_string_test.cc | 203 ++++++++++ implementation/local_array_test.cc | 256 ++---------- implementation/signature_field_test.cc | 138 +++++++ ...ature_test.cc => signature_method_test.cc} | 86 ++-- 11 files changed, 1230 insertions(+), 853 deletions(-) create mode 100644 implementation/local_array_iteration_test.cc create mode 100644 implementation/local_array_method_multidimensional_test.cc create mode 100644 implementation/local_array_method_test.cc create mode 100644 implementation/local_array_string_test.cc create mode 100644 implementation/signature_field_test.cc rename implementation/{signature_test.cc => signature_method_test.cc} (69%) diff --git a/implementation/BUILD b/implementation/BUILD index aca5dee7..836e3bda 100644 --- a/implementation/BUILD +++ b/implementation/BUILD @@ -22,6 +22,17 @@ cc_library( ], ) +cc_test( + name = "array_test", + srcs = ["array_test.cc"], + deps = [ + ":fake_test_constants", + "//:jni_bind", + "//:jni_test", + "@googletest//:gtest_main", + ], +) + cc_library( name = "array_ref", hdrs = ["array_ref.h"], @@ -45,8 +56,41 @@ cc_library( ) cc_test( - name = "array_test", - srcs = ["array_test.cc"], + name = "local_array_iteration_test", + srcs = ["local_array_iteration_test.cc"], + deps = [ + ":fake_test_constants", + "//:jni_bind", + "//:jni_test", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_method_test", + srcs = ["local_array_method_test.cc"], + deps = [ + ":fake_test_constants", + "//:jni_bind", + "//:jni_test", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_method_multidimensional_test", + srcs = ["local_array_method_multidimensional_test.cc"], + deps = [ + ":fake_test_constants", + "//:jni_bind", + "//:jni_test", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_string_test", + srcs = ["local_array_string_test.cc"], deps = [ ":fake_test_constants", "//:jni_bind", @@ -987,8 +1031,18 @@ cc_library( ) cc_test( - name = "signature_test", - srcs = ["signature_test.cc"], + name = "signature_method_test", + srcs = ["signature_method_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "signature_field_test", + srcs = ["signature_field_test.cc"], deps = [ "//:jni_bind", "//:jni_test", diff --git a/implementation/array_test.cc b/implementation/array_test.cc index b72df1d8..69b96ecb 100644 --- a/implementation/array_test.cc +++ b/implementation/array_test.cc @@ -118,334 +118,5 @@ static_assert( static_assert(FullArrayStripV(arr8) == kClass); static_assert(FullArrayStripV(arr9) == kClass); -//////////////////////////////////////////////////////////////////////////////// -// Local Materialisation Tests. -//////////////////////////////////////////////////////////////////////////////// -TEST_F(JniTest, LocalArray_ConstructsBooleanArray) { - EXPECT_CALL(*env_, NewBooleanArray(1)) - .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, DeleteLocalRef(_)); - - LocalArray boolean_array_1{1}; -} - -TEST_F(JniTest, LocalArray_ConstructsByteArray) { - EXPECT_CALL(*env_, NewByteArray(1)) - .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, DeleteLocalRef(_)); - - LocalArray byte_array_1{1}; -} - -TEST_F(JniTest, LocalArray_ConstructsIntArray) { - EXPECT_CALL(*env_, NewIntArray(1)) - .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, DeleteLocalRef(_)); - - LocalArray int_array_1{1}; -} - -//////////////////////////////////////////////////////////////////////////////// -// Implementation Tests. -//////////////////////////////////////////////////////////////////////////////// -TEST_F(JniTest, Array_HandlesSingleIntArray) { - static constexpr Class kClass{ - "ClassThatReturnsIntArrays", - Method{"IntArray", jni::Return{Array{jint{}}}, Params{}}}; - - LocalObject obj{Fake()}; - - static_assert( - !std::is_base_of_v, decltype(obj)>); - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("()[I"))); - LocalArray a = obj("IntArray"); - LocalArray b{a.Release()}; -} - -TEST_F(JniTest, Array_ConstructsFromRValue) { - LocalArray arr{jintArray{nullptr}}; - LocalArray arr2{std::move(arr)}; -} - -TEST_F(JniTest, Array_HandlesSingleObjectArrayAsReturn) { - static constexpr Class kClass{ - "ClassThatReturnsIntArrays", - Method{"ObjectArray", jni::Return{Array{kClass2}}, Params{}}}; - - LocalObject obj{Fake()}; - EXPECT_CALL(*env_, - GetMethodID(_, StrEq("ObjectArray"), StrEq("()[LkClass2;"))); - - LocalArray arr{obj("ObjectArray")}; - // arr2 { obj("ObjectArray") }; // won't compile (good). - LocalArray arr2{obj("ObjectArray")}; - LocalArray arr3{std::move(arr2)}; -} - -TEST_F(JniTest, Array_HandlesSingleArrayParam_NativeJNIType) { - static constexpr Class kClass{ - "kClass", - Method{"takesFloatArray", jni::Return{}, Params{Array{float{}}}}}; - - LocalObject obj{Fake()}; - obj("takesFloatArray", Fake()); -} - -TEST_F(JniTest, Array_HandlesSingleObjectArray) { - static constexpr Class kClass{ - "kClass", - Method{"takesObjectArray", jni::Return{}, Params{Array{kClass2}}}}; - - EXPECT_CALL( - *env_, GetMethodID(_, StrEq("takesObjectArray"), StrEq("([LkClass2;)V"))); - - LocalArray input_arg{Fake()}; - LocalObject obj{Fake()}; - obj("takesObjectArray", input_arg); -} - -TEST_F(JniTest, Array_HandlesSingleBoolAsParamWithRankfulReturnType) { - static constexpr Class kClass{ - "kClass", Method{"Foo", jni::Return{}, Params{Array{jboolean{}}}}}; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([Z)I"))); - - LocalObject obj{Fake()}; - obj("Foo", Fake()); -} - -TEST_F(JniTest, Array_HandlesSingleBoolAsParamWithVoidReturnT) { - static constexpr Class kClass{ - "kClass", Method{"Foo", jni::Return{}, Params{Array{jboolean{}}}} - - }; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([Z)V"))); - - LocalObject obj{Fake()}; - // obj("Foo", Fake()); // doesn't compile (good). - // obj("Foo", Fake()); // doesn't compile (good). - obj("Foo", Fake()); -} - -TEST_F(JniTest, Array_HandlesMultipleBoolAsParam) { - static constexpr Class kClass{ - "kClass", - Method{"Z", jni::Return{}, Params{Array{jboolean{}}}}, - Method{"ZZ", jni::Return{}, Params{Array{jboolean{}}, Array{jboolean{}}}}, - Method{"ZZZ", jni::Return{}, - Params{Array{jboolean{}}, Array{jboolean{}}, Array{jboolean{}}}}, - - }; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Z"), StrEq("([Z)V"))); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("ZZ"), StrEq("([Z[Z)V"))); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("ZZZ"), StrEq("([Z[Z[Z)V"))); - - LocalObject obj{Fake()}; - obj("Z", Fake(1)); - obj("ZZ", Fake(2), Fake(3)); - obj("ZZZ", Fake(3), Fake(4), - Fake(5)); -} - -TEST_F(JniTest, Array_HandlesComplexArrays) { - static constexpr Class kClass{ - "kClass", - Method{"Foo", jni::Return{Array{kClass2}}, - Params{Array{jint{}}, Array{jboolean{}}}}, - }; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([I[Z)[LkClass2;"))); - - LocalObject obj{Fake()}; - obj("Foo", Fake(), Fake()); -} - -TEST_F(JniTest, Array_AllowsRValuesOfLocalArrays) { - static constexpr Class kClass{ - "ClassThatTakesRValues", - Method{"Foo", jni::Return{}, Params{Array{jint{}}}}, - }; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([I)V"))); - - LocalObject obj{Fake()}; - LocalArray local_array{3}; - - obj("Foo", std::move(local_array)); -} - -TEST_F(JniTest, Array_HandlesSingle2DIntAsReturnT) { - static constexpr Class kClass{ - "kClass", Method{"I", jni::Return{Array{}}, Params{}}}; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("()[[I"))); - - LocalObject obj{Fake()}; - obj("I"); -} - -TEST_F(JniTest, Array_HandlesSingle2DIntAsParamWithRankfulReturnT) { - static constexpr Class kClass{ - "kClass", Method{"I", jni::Return{}, Params{Array{}}}}; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("([[I)I"))); - - LocalObject obj{Fake()}; - // obj("I", jintArray{nullptr}); // doesn't compile (good). - obj("I", Fake()); - obj("I", LocalArray{Fake()}); -} - -TEST_F(JniTest, Array_HandlesSingle2DClassAsReturn) { - static constexpr Class kClass{ - "kClass", - Method{"Foo", jni::Return{Array{kClass2, Rank<2>{}}}, Params{}}}; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("()[[LkClass2;"))); - - LocalObject obj{Fake()}; - obj("Foo"); -} - -TEST_F(JniTest, Array_HandlesSinglePredefinedClassAsParam) { - static constexpr Class kClass{"kClass"}; - static constexpr Class kClassThatAcceptsArrays{ - "ClassThatAcceptsArrays", - Method{"Foo", jni::Return{}, Params{Array{kClass}}}}; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass;)V"))); - - LocalObject obj{Fake()}; - LocalObject obj_to_call_on{Fake()}; - LocalArray local_array{5, obj}; - obj_to_call_on("Foo", local_array); -} - -TEST_F(JniTest, Array_HandlesSingleUndefinedClassAsParam) { - static constexpr Class kClassThatAcceptsArrays{ - "ClassThatAcceptsArrays", - // Note, Class is defined inline here. - Method{"Foo", jni::Return{}, Params{Array{Class{"kClass"}}}}}; - static constexpr Class kClass{"kClass"}; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass;)V"))); - - LocalObject obj{Fake()}; - LocalObject obj_to_call_on{Fake()}; - LocalArray local_array{5, obj}; - obj_to_call_on("Foo", local_array); -} - -//////////////////////////////////////////////////////////////////////////////// -// Methods. -//////////////////////////////////////////////////////////////////////////////// -TEST_F(JniTest, Array_LooksUpCorrectSignaturesForReturns) { - static constexpr Class kClass{ - "kClass", - Method{"BooleanArray", jni::Return{Array{jboolean{}}}, Params{}}, - Method{"ByteArray", jni::Return{Array{jbyte{}}}, Params{}}, - Method{"CharArray", jni::Return{Array{jchar{}}}, Params{}}, - Method{"ShortArray", jni::Return{Array{jshort{}}}, Params{}}, - Method{"IntArray", jni::Return{Array{jint{}}}, Params{}}, - Method{"FloatArray", jni::Return{Array{jfloat{}}}, Params{}}, - Method{"DoubleArray", jni::Return{Array{jdouble{}}}, Params{}}, - Method{"LongArray", jni::Return{Array{jlong{}}}, Params{}}, - Method{"ObjectArray", jni::Return{Array{kClass2}}, Params{}}, - }; - - LocalObject obj{jobject{nullptr}}; - 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("FloatArray"), StrEq("()[F"))); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("()[D"))); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("()[J"))); - EXPECT_CALL(*env_, - GetMethodID(_, StrEq("ObjectArray"), StrEq("()[LkClass2;"))); - obj("BooleanArray"); - obj("ByteArray"); - obj("CharArray"); - obj("ShortArray"); - obj("IntArray"); - obj("LongArray"); - obj("FloatArray"); - obj("DoubleArray"); - obj("ObjectArray"); -} - -//////////////////////////////////////////////////////////////////////////////// -// Methods: String Tests. -// These are isolated since they are special composite types. -//////////////////////////////////////////////////////////////////////////////// -TEST_F(JniTest, Array_CorrectReturnSignatureForStrings) { - static constexpr Class kClass{ - "kClass", - Method{"StringArray", jni::Return{Array{jstring{}}}, Params{}}, - }; - - LocalObject obj{Fake()}; - EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), - StrEq("()[Ljava/lang/String;"))); - LocalArray arr = obj("StringArray"); -} - -TEST_F(JniTest, Array_CorrectParamSignatureForStrings) { - static constexpr Class kClass{ - "kClass", - Method{"StringArray", jni::Return{}, Params{Array{jstring{}}}}, - }; - - LocalObject obj{Fake()}; - EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), - StrEq("([Ljava/lang/String;)V"))); - LocalArray arr{2}; - obj("StringArray", arr); -} - -//////////////////////////////////////////////////////////////////////////////// -// Multi-Dimensional arrays. -//////////////////////////////////////////////////////////////////////////////// -static constexpr Class kArrClass{ - "ArrClass", - Method{"Foo", jni::Return{Array{jint{}}}, Params{Array{}}}, - Method{"Baz", jni::Return{Array{kClass2}}, Params{Array{}}}, - Method{"Bar", jni::Return{Array{Class{"kClass3"}, Rank<2>{}}}, - Params{Array{}, Array{double{}}}}, -}; - -TEST_F(JniTest, Array_HandlesMultipleMultiDimensionalValues_1) { - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([[I)[I"))); - LocalObject obj{Fake()}; - LocalArray ret = obj("Foo", Fake()); -} - -TEST_F(JniTest, Array_HandlesMultipleMultiDimensionalValues_2) { - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("([[[I)[LkClass2;"))); - - LocalObject obj{Fake()}; - - // 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 ret = obj("Baz", jobjectArray{nullptr}); - - // TODO(b/143908983): CTAD is failing. - // LocalArray ret = obj("Baz", jobjectArray{nullptr}); - - LocalArray ret = obj("Baz", Fake()); - static_assert(std::is_same_v>); -} - -TEST_F(JniTest, Array_HandlesMultipleMultiDimensionalValues_3) { - EXPECT_CALL(*env_, - GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;"))); - - LocalObject obj{Fake()}; - obj("Bar", Fake(), Fake()); -} } // namespace diff --git a/implementation/local_array_field_test.cc b/implementation/local_array_field_test.cc index 6ead8955..f5bc02da 100644 --- a/implementation/local_array_field_test.cc +++ b/implementation/local_array_field_test.cc @@ -339,28 +339,18 @@ TEST_F(JniTest, Array_Field_Object_Test) { obj["ObjectArrayRank1"].Get().Get(2); } -//////////////////////////////////////////////////////////////////////////////// -// String Fields. -//////////////////////////////////////////////////////////////////////////////// -TEST_F(JniTest, Array_CorrectFieldSignatureForStrings) { +TEST_F(JniTest, Array_Field_HandlesLValueLocalObject) { + static constexpr Class kClass2{"kClass2"}; + static constexpr Class kClass{ - "kClass", - Field{"StringArrayRank1", Array{jstring{}}}, - Field{"StringArrayRank2", Array{jstring{}, Rank<2>{}}}, - Field{"StringArrayRank3", Array{jstring{}, Rank<3>{}}}, + "ArrayMultiTest", + Field{"Foo", Array{kClass2}}, }; - LocalObject 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 arr1 = obj["StringArrayRank1"].Get(); - LocalArray arr2 = obj["StringArrayRank2"].Get(); - LocalArray arr3 = obj["StringArrayRank3"].Get(); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[LkClass2;"))); + + LocalObject obj{Fake()}; + LocalArray{obj["Foo"].Get()}; } } // namespace diff --git a/implementation/local_array_iteration_test.cc b/implementation/local_array_iteration_test.cc new file mode 100644 index 00000000..fc1259a9 --- /dev/null +++ b/implementation/local_array_iteration_test.cc @@ -0,0 +1,255 @@ +/* + * Copyright 2023 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 +#include +#include "implementation/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptLocal; +using ::jni::ArrayView; +using ::jni::Class; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::test::Fake; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; + +//////////////////////////////////////////////////////////////////////////////// +// Rank 0. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_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 new_array{AdoptLocal{}, Fake()}; + + int i{0}; + for (jint val : new_array.Pin()) { + EXPECT_EQ(val, expected[i]); + ++i; + } + EXPECT_EQ(i, 10); +} + +TEST_F(JniTest, Array_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 new_array{AdoptLocal{}, Fake()}; + ArrayView array_view = new_array.Pin(); + EXPECT_TRUE( + std::equal(array_view.begin(), array_view.end(), expected.begin())); +} + +TEST_F(JniTest, Array_WorksWithSTLComparisonOfObjects) { + std::array expected{Fake(1), Fake(2), Fake(3)}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))); + + LocalArray new_array{Fake()}; + ArrayView array_view = new_array.Pin(); + EXPECT_TRUE( + std::equal(array_view.begin(), array_view.end(), expected.begin())); +} + +TEST_F(JniTest, Array_WorksWithSTLComparisonOfRichlyDecoratedObjects) { + std::array expected{LocalObject{AdoptLocal{}, Fake(1)}, + LocalObject{AdoptLocal{}, Fake(2)}, + LocalObject{AdoptLocal{}, Fake(3)}}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))); + + LocalArray new_array{AdoptLocal{}, Fake()}; + ArrayView array_view = new_array.Pin(); + EXPECT_TRUE( + std::equal(array_view.begin(), array_view.end(), expected.begin())); +} + +TEST_F(JniTest, Array_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())); + 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(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))) + .WillOnce(Return(Fake(4))) + .WillOnce(Return(Fake(5))); + + // All the returned intArrays are deleted. + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(3))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(4))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(5))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + + // 5 (outer array length) * 2 (pins per) = 10 + EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a)); + EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10); + + LocalArray new_array{5, Fake()}; + + int sum = 0; + for (LocalArray 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, Array_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())); + 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(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))) + .WillOnce(Return(Fake(4))) + .WillOnce(Return(Fake(5))); + + // All the returned intArrays are deleted. + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(3))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(4))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(5))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + + // 5 (outer array length) * 2 (pins per) = 10 + EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a)); + EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10); + + LocalArray new_array{5, Fake()}; + + int sum = 0; + int loop_size = new_array.Length(); + for (int i = 0; i < loop_size; ++i) { + LocalArray 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, Array_2D_Iterates_Raw_loops_of_Objects) { + EXPECT_CALL(*env_, FindClass(StrEq("[LkClass;"))); + EXPECT_CALL(*env_, NewObjectArray(5, _, Fake(100))); + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(5)); // outer + + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))) + .WillOnce(Return(Fake(4))) + .WillOnce(Return(Fake(5))); + + // All the returned objectArrays are deleted. + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(3))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(4))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(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())); + + LocalArray new_array{5, Fake(100)}; + EXPECT_EQ(jobject{new_array}, Fake()); + + int i = 1; + for (LocalArray arr : new_array.Pin()) { + EXPECT_EQ(jobject(arr), Fake(i)); + ++i; + } +} + +} // namespace diff --git a/implementation/local_array_method_multidimensional_test.cc b/implementation/local_array_method_multidimensional_test.cc new file mode 100644 index 00000000..ce792c37 --- /dev/null +++ b/implementation/local_array_method_multidimensional_test.cc @@ -0,0 +1,129 @@ +/* + * Copyright 2023 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 +#include +#include "implementation/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptGlobal; +using ::jni::Array; +using ::jni::ArrayStrip_t; +using ::jni::CDecl_t; +using ::jni::Class; +using ::jni::GlobalObject; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::Rank; +using ::jni::test::Fake; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; +static constexpr Class kClass2{"kClass2"}; +static constexpr Array arr1{jint{}}; +static constexpr Array arr2{jfloat{}}; +static constexpr Array arr3{jdouble{}}; +static constexpr Array arr4{Class{"kClass"}}; +static constexpr Array arr5{kClass}; +static constexpr Array arr6{arr1}; +static constexpr Array arr7{jint{}, Rank<2>{}}; +static constexpr Array arr8{kClass, Rank<2>{}}; +static constexpr Array arr9{kClass, Rank<3>{}}; + +//////////////////////////////////////////////////////////////////////////////// +// As Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_HandlesSingle2DIntAsReturnT) { + static constexpr Class kClass{ + "kClass", Method{"I", jni::Return{Array{}}, Params{}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("()[[I"))); + + LocalObject obj{Fake()}; + obj("I"); +} + +TEST_F(JniTest, Array_HandlesSingle2DClassAsReturn) { + static constexpr Class kClass{ + "kClass", + Method{"Foo", jni::Return{Array{kClass2, Rank<2>{}}}, Params{}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("()[[LkClass2;"))); + + LocalObject obj{Fake()}; + obj("Foo"); +} + +//////////////////////////////////////////////////////////////////////////////// +// Complex: Arrays in Params & Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_HandlesSingle2DIntAsParamWithRankfulReturnT) { + static constexpr Class kClass{ + "kClass", Method{"I", jni::Return{}, Params{Array{}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("([[I)I"))); + + LocalObject obj{Fake()}; + // obj("I", jintArray{nullptr}); // doesn't compile (good). + obj("I", Fake()); + obj("I", LocalArray{Fake()}); +} + +static constexpr Class kArrClass{ + "ArrClass", + Method{"Foo", jni::Return{Array{jint{}}}, Params{Array{}}}, + Method{"Baz", jni::Return{Array{kClass2}}, Params{Array{}}}, + Method{"Bar", jni::Return{Array{Class{"kClass3"}, Rank<2>{}}}, + Params{Array{}, Array{double{}}}}, +}; + +TEST_F(JniTest, Array_HandlesMultipleMultiDimensionalValues_1) { + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([[I)[I"))); + LocalObject obj{Fake()}; + LocalArray ret = obj("Foo", Fake()); +} + +TEST_F(JniTest, Array_HandlesMultipleMultiDimensionalValues_2) { + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("([[[I)[LkClass2;"))); + + LocalObject obj{Fake()}; + + // 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 ret = obj("Baz", jobjectArray{nullptr}); + + // TODO(b/143908983): CTAD is failing. + // LocalArray ret = obj("Baz", jobjectArray{nullptr}); + + LocalArray ret = obj("Baz", Fake()); + static_assert(std::is_same_v>); +} + +TEST_F(JniTest, Array_HandlesMultipleMultiDimensionalValues_3) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;"))); + + LocalObject obj{Fake()}; + obj("Bar", Fake(), Fake()); +} + +} // namespace diff --git a/implementation/local_array_method_test.cc b/implementation/local_array_method_test.cc new file mode 100644 index 00000000..b44c577b --- /dev/null +++ b/implementation/local_array_method_test.cc @@ -0,0 +1,377 @@ +/* + * Copyright 2023 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 +#include +#include "implementation/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptGlobal; +using ::jni::Array; +using ::jni::ArrayStrip_t; +using ::jni::CDecl_t; +using ::jni::Class; +using ::jni::FullArrayStripV; +using ::jni::GlobalObject; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::Rank; +using ::jni::Rankifier; +using ::jni::RegularToArrayTypeMap_t; +using ::jni::test::Fake; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; +static constexpr Class kClass2{"kClass2"}; +static constexpr Array arr1{jint{}}; +static constexpr Array arr2{jfloat{}}; +static constexpr Array arr3{jdouble{}}; +static constexpr Array arr4{Class{"kClass"}}; +static constexpr Array arr5{kClass}; +static constexpr Array arr6{arr1}; +static constexpr Array arr7{jint{}, Rank<2>{}}; +static constexpr Array arr8{kClass, Rank<2>{}}; +static constexpr Array arr9{kClass, Rank<3>{}}; + +//////////////////////////////////////////////////////////////////////////////// +// As Params. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayMethodTest_Params_1Z_V) { + static constexpr Class kClass{ + "kClass", Method{"Foo", jni::Return{}, Params{Array{jboolean{}}}} + + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([Z)V"))); + + LocalObject obj{Fake()}; + // obj("Foo", Fake()); // doesn't compile (good). + // obj("Foo", Fake()); // doesn't compile (good). + obj("Foo", Fake()); +} + +TEST_F(JniTest, ArrayMethodTest_Params_1I_V) { + static constexpr Class kClass{ + "ArrayMultiTest", + Method{"Foo", jni::Return{}, Params{Array{}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([I)V"))); + + LocalObject obj{jobject{nullptr}}; + obj("Foo", LocalArray{10}); +} + +TEST_F(JniTest, ArrayMethodTest_Params_1F_V) { + static constexpr Class kClass{ + "kClass", + Method{"takesFloatArray", jni::Return{}, Params{Array{float{}}}}}; + + LocalObject obj{Fake()}; + obj("takesFloatArray", Fake()); +} + +TEST_F(JniTest, ArrayMethodTest_HandlesMultipleBooleans) { + static constexpr Class kClass{ + "kClass", + Method{"Z", jni::Return{}, Params{Array{jboolean{}}}}, + Method{"ZZ", jni::Return{}, Params{Array{jboolean{}}, Array{jboolean{}}}}, + Method{"ZZZ", jni::Return{}, + Params{Array{jboolean{}}, Array{jboolean{}}, Array{jboolean{}}}}, + + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Z"), StrEq("([Z)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ZZ"), StrEq("([Z[Z)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ZZZ"), StrEq("([Z[Z[Z)V"))); + + LocalObject obj{Fake()}; + obj("Z", Fake(1)); + obj("ZZ", Fake(2), Fake(3)); + obj("ZZZ", Fake(3), Fake(4), + Fake(5)); +} + +TEST_F(JniTest, ArrayMethodTest_1Z_I) { + static constexpr Class kClass{ + "kClass", Method{"Foo", jni::Return{}, Params{Array{jboolean{}}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([Z)I"))); + + LocalObject obj{Fake()}; + obj("Foo", Fake()); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Params, Objects. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayMethodTest_1LkClass2_V) { + static constexpr Class kClass{ + "kClass", + Method{"takesObjectArray", jni::Return{}, Params{Array{kClass2}}}}; + + EXPECT_CALL( + *env_, GetMethodID(_, StrEq("takesObjectArray"), StrEq("([LkClass2;)V"))); + + LocalArray input_arg{Fake()}; + LocalObject obj{Fake()}; + obj("takesObjectArray", input_arg); +} + +TEST_F(JniTest, Array_HandlesRValueLocal) { + static constexpr Class kClass2{"kClass2"}; + + static constexpr Class kClass{ + "ArrayMultiTest", + Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), _)); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); + + LocalObject obj{jobject{nullptr}}; + obj("Foo", LocalArray{123, LocalObject{}}); +} + +TEST_F(JniTest, Array_HandlesRValueGlobal) { + static constexpr Class kClass2{"kClass2"}; + static constexpr Class kClass{ + "ArrayMultiTest", + Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), _)); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); + + LocalObject obj{jobject{nullptr}}; + obj("Foo", LocalArray{123, LocalObject{}}); +} + +TEST_F(JniTest, Array_HandlesLValueGlobalObject) { + static constexpr Class kClass2{"kClass2"}; + + static constexpr Class kClass{ + "ArrayMultiTest", + Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); + + GlobalObject obj{AdoptGlobal{}, jobject{nullptr}}; + obj("Foo", LocalArray{jobjectArray{nullptr}}); +} + +TEST_F(JniTest, ArrayMethodTest_AllowsRValuesOfLocalArrays) { + static constexpr Class kClass{ + "ClassThatTakesRValues", + Method{"Foo", jni::Return{}, Params{Array{jint{}}}}, + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([I)V"))); + + LocalObject obj{Fake()}; + LocalArray local_array{3}; + + obj("Foo", std::move(local_array)); +} + +TEST_F(JniTest, ArrayMethodTest_HandlesSinglePredefinedClassAsParam) { + static constexpr Class kClass{"kClass"}; + static constexpr Class kClassThatAcceptsArrays{ + "ClassThatAcceptsArrays", + Method{"Foo", jni::Return{}, Params{Array{kClass}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass;)V"))); + + LocalObject obj{Fake()}; + LocalObject obj_to_call_on{Fake()}; + LocalArray local_array{5, obj}; + obj_to_call_on("Foo", local_array); +} + +TEST_F(JniTest, ArrayMethodTest_HandlesSingleUndefinedClassAsParam) { + static constexpr Class kClassThatAcceptsArrays{ + "ClassThatAcceptsArrays", + // Note, Class is defined inline here. + Method{"Foo", jni::Return{}, Params{Array{Class{"kClass"}}}}}; + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass;)V"))); + + LocalObject obj{Fake()}; + LocalObject obj_to_call_on{Fake()}; + LocalArray local_array{5, obj}; + obj_to_call_on("Foo", local_array); +} + +TEST_F(JniTest, ArrayMethodTest_HandlesLValueLocalObject) { + // Made separate to guarantee signature isn't cached and RValue path is still + // exercised. Note that testing a method is all or nothing, so if a + // constructor is queried there is a test for a GetMethod with "". + static constexpr Class kClass2{"kClass2"}; + static constexpr Class kClass{ + "ArrayMultiTest", + Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); + + LocalObject obj{jobject{nullptr}}; + obj("Foo", LocalArray{123, obj}); +} + +TEST_F(JniTest, Array_DifferentiatesWithOverloads) { + static constexpr Class kClass2{"kClass2"}; + static constexpr Class kClass{ + "ArrayMultiTest", + Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), _)); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); + + LocalObject obj{jobject{nullptr}}; + obj("Foo", LocalArray{123, LocalObject{}}); +} + +TEST_F(JniTest, Array_DifferentiatesBetweenClassesWithEqualRank) { + static constexpr Class kClass2{"kClass2"}; + static constexpr Class kClass3{"kClass3"}; + static constexpr Class kClass4{"kClass4"}; + + static constexpr Class kClass{ + "kClass1", + Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, + Method{"Bar", jni::Return{}, Params{Array{kClass3}}}, + Method{"Baz", jni::Return{}, Params{Array{kClass4}}}, + Method{"Gnar", jni::Return{}, Params{Array{Class{"kClass4"}}}}, + Method{"Moo", jni::Return{}, Params{Array{Class{"kClass4"}}}}, + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), _)) + .Times(testing::AnyNumber()); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Bar"), StrEq("([LkClass3;)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("([LkClass4;)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Gnar"), StrEq("([LkClass4;)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Moo"), StrEq("([LkClass4;)V"))); + + LocalObject obj{jobject{nullptr}}; + obj("Foo", LocalArray{123, LocalObject{}}); + obj("Bar", LocalArray{123, LocalObject{}}); + // obj("Bar", LocalArray{123, LocalObject{}}); // doesn't compile. + + // These calls don't trigger new GetMethod calls. + // Note this includes calls to both forward definition and full definition. + obj("Baz", LocalArray{123, LocalObject{}}); + obj("Gnar", LocalArray{123, LocalObject{}}); + + // This call requires a new method lookup (different function name). + obj("Moo", LocalArray{123, LocalObject{}}); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayMethodTest_V_1I) { + static constexpr Class kClass{ + "ClassThatReturnsIntArrays", + Method{"IntArray", jni::Return{Array{jint{}}}, Params{}}}; + + LocalObject obj{Fake()}; + + static_assert( + !std::is_base_of_v, decltype(obj)>); + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("()[I"))); + LocalArray a = obj("IntArray"); + LocalArray b{a.Release()}; +} + +TEST_F(JniTest, ArrayMethodTest_VariedReturnTypes) { + static constexpr Class kClass{ + "kClass", + Method{"BooleanArray", jni::Return{Array{jboolean{}}}, Params{}}, + Method{"ByteArray", jni::Return{Array{jbyte{}}}, Params{}}, + Method{"CharArray", jni::Return{Array{jchar{}}}, Params{}}, + Method{"ShortArray", jni::Return{Array{jshort{}}}, Params{}}, + Method{"IntArray", jni::Return{Array{jint{}}}, Params{}}, + Method{"FloatArray", jni::Return{Array{jfloat{}}}, Params{}}, + Method{"DoubleArray", jni::Return{Array{jdouble{}}}, Params{}}, + Method{"LongArray", jni::Return{Array{jlong{}}}, Params{}}, + Method{"ObjectArray", jni::Return{Array{kClass2}}, Params{}}, + }; + + LocalObject obj{jobject{nullptr}}; + 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("FloatArray"), StrEq("()[F"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("()[D"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("()[J"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("ObjectArray"), StrEq("()[LkClass2;"))); + obj("BooleanArray"); + obj("ByteArray"); + obj("CharArray"); + obj("ShortArray"); + obj("IntArray"); + obj("LongArray"); + obj("FloatArray"); + obj("DoubleArray"); + obj("ObjectArray"); +} + +TEST_F(JniTest, ArrayMethodTest_HandlesSingleObjectArrayAsReturn) { + static constexpr Class kClass{ + "ClassThatReturnsIntArrays", + Method{"ObjectArray", jni::Return{Array{kClass2}}, Params{}}}; + + LocalObject obj{Fake()}; + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("ObjectArray"), StrEq("()[LkClass2;"))); + + LocalArray arr{obj("ObjectArray")}; + // arr2 { obj("ObjectArray") }; // won't compile (good). + LocalArray arr2{obj("ObjectArray")}; + LocalArray arr3{std::move(arr2)}; +} + +//////////////////////////////////////////////////////////////////////////////// +// Complex: As Return and Params. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayMethodTest_HandlesComplexArrays) { + static constexpr Class kClass{ + "kClass", + Method{"Foo", jni::Return{Array{kClass2}}, + Params{Array{jint{}}, Array{jboolean{}}}}, + }; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([I[Z)[LkClass2;"))); + + LocalObject obj{Fake()}; + obj("Foo", Fake(), Fake()); +} + +} // namespace diff --git a/implementation/local_array_multidimensional_test.cc b/implementation/local_array_multidimensional_test.cc index 7c386f23..45232ca5 100644 --- a/implementation/local_array_multidimensional_test.cc +++ b/implementation/local_array_multidimensional_test.cc @@ -33,7 +33,7 @@ using ::testing::StrEq; static constexpr Class kClass{"kClass"}; //////////////////////////////////////////////////////////////////////////////// -// Multi-Dimensional Construction. +// Construction. //////////////////////////////////////////////////////////////////////////////// TEST_F(JniTest, Array_BuildsFromSizeForMultiDimensionalArray_no_value) { EXPECT_CALL(*env_, NewObjectArray(10, _, Fake())); @@ -106,222 +106,4 @@ TEST_F(JniTest, Array_SetsObjectValues) { arr.Set(2, std::move(array_arg)); } -//////////////////////////////////////////////////////////////////////////////// -// Iteration. -//////////////////////////////////////////////////////////////////////////////// -TEST_F(JniTest, Array_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 new_array{AdoptLocal{}, Fake()}; - - int i{0}; - for (jint val : new_array.Pin()) { - EXPECT_EQ(val, expected[i]); - ++i; - } - EXPECT_EQ(i, 10); -} - -TEST_F(JniTest, Array_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 new_array{AdoptLocal{}, Fake()}; - ArrayView array_view = new_array.Pin(); - EXPECT_TRUE( - std::equal(array_view.begin(), array_view.end(), expected.begin())); -} - -TEST_F(JniTest, Array_WorksWithSTLComparisonOfObjects) { - std::array expected{Fake(1), Fake(2), Fake(3)}; - - EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3)); - EXPECT_CALL(*env_, GetObjectArrayElement) - .WillOnce(Return(Fake(1))) - .WillOnce(Return(Fake(2))) - .WillOnce(Return(Fake(3))); - - LocalArray new_array{Fake()}; - ArrayView array_view = new_array.Pin(); - EXPECT_TRUE( - std::equal(array_view.begin(), array_view.end(), expected.begin())); -} - -TEST_F(JniTest, Array_WorksWithSTLComparisonOfRichlyDecoratedObjects) { - std::array expected{LocalObject{AdoptLocal{}, Fake(1)}, - LocalObject{AdoptLocal{}, Fake(2)}, - LocalObject{AdoptLocal{}, Fake(3)}}; - - EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3)); - EXPECT_CALL(*env_, GetObjectArrayElement) - .WillOnce(Return(Fake(1))) - .WillOnce(Return(Fake(2))) - .WillOnce(Return(Fake(3))); - - LocalArray new_array{AdoptLocal{}, Fake()}; - ArrayView array_view = new_array.Pin(); - EXPECT_TRUE( - std::equal(array_view.begin(), array_view.end(), expected.begin())); -} - -TEST_F(JniTest, Array_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())); - 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(1))) - .WillOnce(Return(Fake(2))) - .WillOnce(Return(Fake(3))) - .WillOnce(Return(Fake(4))) - .WillOnce(Return(Fake(5))); - - // All the returned intArrays are deleted. - EXPECT_CALL(*env_, DeleteLocalRef(Fake())); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(3))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(4))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(5))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake())); - - // 5 (outer array length) * 2 (pins per) = 10 - EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a)); - EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10); - - LocalArray new_array{5, Fake()}; - - int sum = 0; - for (LocalArray 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, Array_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())); - 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(1))) - .WillOnce(Return(Fake(2))) - .WillOnce(Return(Fake(3))) - .WillOnce(Return(Fake(4))) - .WillOnce(Return(Fake(5))); - - // All the returned intArrays are deleted. - EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(3))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(4))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(5))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake())); - EXPECT_CALL(*env_, DeleteLocalRef(Fake())); - - // 5 (outer array length) * 2 (pins per) = 10 - EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a)); - EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10); - - LocalArray new_array{5, Fake()}; - - int sum = 0; - int loop_size = new_array.Length(); - for (int i = 0; i < loop_size; ++i) { - LocalArray 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, Array_2D_Iterates_Raw_loops_of_Objects) { - EXPECT_CALL(*env_, FindClass(StrEq("[LkClass;"))); - EXPECT_CALL(*env_, NewObjectArray(5, _, Fake(100))); - EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(5)); // outer - - EXPECT_CALL(*env_, GetObjectArrayElement) - .WillOnce(Return(Fake(1))) - .WillOnce(Return(Fake(2))) - .WillOnce(Return(Fake(3))) - .WillOnce(Return(Fake(4))) - .WillOnce(Return(Fake(5))); - - // All the returned objectArrays are deleted. - EXPECT_CALL(*env_, DeleteLocalRef(Fake())); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(3))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(4))); - EXPECT_CALL(*env_, DeleteLocalRef(Fake(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())); - - LocalArray new_array{5, Fake(100)}; - EXPECT_EQ(jobject{new_array}, Fake()); - - int i = 1; - for (LocalArray arr : new_array.Pin()) { - EXPECT_EQ(jobject(arr), Fake(i)); - ++i; - } -} - } // namespace diff --git a/implementation/local_array_string_test.cc b/implementation/local_array_string_test.cc new file mode 100644 index 00000000..a43dddcb --- /dev/null +++ b/implementation/local_array_string_test.cc @@ -0,0 +1,203 @@ +/* + * Copyright 2023 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 +#include +#include "implementation/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +using ::jni::Array; +using ::jni::ArrayStrip_t; +using ::jni::CDecl_t; +using ::jni::Class; +using ::jni::Field; +using ::jni::FullArrayStripV; +using ::jni::kJavaLangString; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::LocalString; +using ::jni::Method; +using ::jni::Params; +using ::jni::Rank; +using ::jni::Rankifier; +using ::jni::RegularToArrayTypeMap_t; +using ::jni::test::Fake; +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 arr_1{Fake()}; + LocalArray 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 obj{jobject{nullptr}}; + LocalArray arr{3}; + obj("StringArray", arr); +} + +//////////////////////////////////////////////////////////////////////////////// +// Caches Length. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CachesLengthExactlyOnceOnFirstRequest) { + EXPECT_CALL(*env_, GetArrayLength).Times(0); + + LocalArray obj{Fake()}; + + 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())); // FindClass + EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(0); + + LocalArray arr{3}; + arr.Set(0, LocalString{Fake()}); +} + +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())).Times(2); // FindClass + EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(0); + + LocalArray arr{ + 3, LocalObject{"Foo"}}; + arr.Set(0, LocalObject{Fake()}); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CorrectReturnSignatureForStrings) { + static constexpr Class kClass{ + "kClass", + Method{"StringArray", jni::Return{Array{jstring{}}}, Params{}}, + }; + + LocalObject obj{Fake()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), + StrEq("()[Ljava/lang/String;"))); + LocalArray arr = obj("StringArray"); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Param. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CorrectParamSignatureForStrings) { + static constexpr Class kClass{ + "kClass", + Method{"StringArray", jni::Return{}, Params{Array{jstring{}}}}, + }; + + LocalObject obj{Fake()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), + StrEq("([Ljava/lang/String;)V"))); + LocalArray 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 default_object{}; + LocalArray local_object_array{5, default_object}; +} + +/* +// TODO(b/143908983): Restore after fixing ubsan failures. +TEST_F(JniTest, Array_StringsCanBeSetOnLocalString) { + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 0, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 1, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 2, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 3, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 4, _)); + + const char* kFoo = "kFoo"; + const std::string kNar = "kNar"; + + LocalArray arr{5, LocalObject{"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 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 arr1 = obj["StringArrayRank1"].Get(); + LocalArray arr2 = obj["StringArrayRank2"].Get(); + LocalArray arr3 = obj["StringArrayRank3"].Get(); +} + +} // namespace diff --git a/implementation/local_array_test.cc b/implementation/local_array_test.cc index bb0a2de8..f6631b5c 100644 --- a/implementation/local_array_test.cc +++ b/implementation/local_array_test.cc @@ -43,6 +43,15 @@ using ::testing::StrEq; static constexpr Class kClass{"kClass"}; +//////////////////////////////////////////////////////////////////////////////// +// Convertability. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, LocalArray_IsImplicitlyConvertibleToSpanType) { + EXPECT_EQ( + static_cast(LocalArray{AdoptLocal{}, Fake()}), + Fake()); +} + //////////////////////////////////////////////////////////////////////////////// // Construction Tests. //////////////////////////////////////////////////////////////////////////////// @@ -53,12 +62,6 @@ TEST_F(JniTest, LocalArray_BuildsAndDestroys) { LocalArray int_array_1{1}; } -TEST_F(JniTest, LocalArray_IsImplicitlyConvertibleToSpanType) { - EXPECT_EQ( - static_cast(LocalArray{AdoptLocal{}, Fake()}), - Fake()); -} - TEST_F(JniTest, LocalArray_MakesNewReferenceByDefault) { EXPECT_EQ(static_cast(LocalArray{Fake()}), AsNewLocalReference(Fake())); @@ -116,16 +119,6 @@ TEST_F(JniTest, LocalArray_ConstructsObjectsForLValues) { LocalArray local_object_array{5, default_object}; } -// Same as above for jstring. -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 default_object{}; - LocalArray local_object_array{5, default_object}; -} - TEST_F(JniTest, LocalArray_ConstructsTheRightTypeForRValues) { EXPECT_CALL(*env_, NewObjectArray(5, _, _)).Times(2); @@ -135,228 +128,33 @@ TEST_F(JniTest, LocalArray_ConstructsTheRightTypeForRValues) { 5, std::move(default_object)}; } -//////////////////////////////////////////////////////////////////////////////// -// Primitive Tests. -//////////////////////////////////////////////////////////////////////////////// -TEST_F(JniTest, MethodAcceptsLocalIntArray) { - static constexpr Class kClass{ - "ArrayMultiTest", - Method{"Foo", jni::Return{}, Params{Array{}}}}; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([I)V"))); - - LocalObject obj{jobject{nullptr}}; - obj("Foo", LocalArray{10}); -} - -//////////////////////////////////////////////////////////////////////////////// -// Object Array Tests. -// -// These have been Made separate to guarantee signature isn't cached and RValue -// path is still exercised. Note that testing a method is all or nothing, so -// if a constructor is queried there is a test for a GetMethod with "". -//////////////////////////////////////////////////////////////////////////////// -TEST_F(JniTest, Array_Method_HandlesLValueLocalObject) { - static constexpr Class kClass2{"kClass2"}; - - static constexpr Class kClass{ - "ArrayMultiTest", - Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, - }; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); - - LocalObject obj{jobject{nullptr}}; - obj("Foo", LocalArray{123, obj}); -} - -TEST_F(JniTest, Array_Field_HandlesLValueLocalObject) { - static constexpr Class kClass2{"kClass2"}; - - static constexpr Class kClass{ - "ArrayMultiTest", - Field{"Foo", Array{kClass2}}, - }; - - EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[LkClass2;"))); - - LocalObject obj{Fake()}; - LocalArray{obj["Foo"].Get()}; -} - -TEST_F(JniTest, Array_HandlesLValueGlobalObject) { - static constexpr Class kClass2{"kClass2"}; +TEST_F(JniTest, LocalArray_ConstructsBooleanArray) { + EXPECT_CALL(*env_, NewBooleanArray(1)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(_)); - static constexpr Class kClass{ - "ArrayMultiTest", - Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, - }; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); - - GlobalObject obj{AdoptGlobal{}, jobject{nullptr}}; - obj("Foo", LocalArray{jobjectArray{nullptr}}); -} - -TEST_F(JniTest, Array_HandlesRValueLocal) { - static constexpr Class kClass2{"kClass2"}; - - static constexpr Class kClass{ - "ArrayMultiTest", - Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, - }; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), _)); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); - - LocalObject obj{jobject{nullptr}}; - obj("Foo", LocalArray{123, LocalObject{}}); -} - -TEST_F(JniTest, Array_HandlesRValueGlobal) { - static constexpr Class kClass2{"kClass2"}; - static constexpr Class kClass{ - "ArrayMultiTest", - Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, - }; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), _)); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); - - LocalObject obj{jobject{nullptr}}; - obj("Foo", LocalArray{123, LocalObject{}}); -} - -TEST_F(JniTest, Array_DifferentiatesBetweenClassesWithEqualRank) { - static constexpr Class kClass2{"kClass2"}; - static constexpr Class kClass3{"kClass3"}; - static constexpr Class kClass4{"kClass4"}; - - static constexpr Class kClass{ - "kClass1", - Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, - Method{"Bar", jni::Return{}, Params{Array{kClass3}}}, - Method{"Baz", jni::Return{}, Params{Array{kClass4}}}, - Method{"Gnar", jni::Return{}, Params{Array{Class{"kClass4"}}}}, - Method{"Moo", jni::Return{}, Params{Array{Class{"kClass4"}}}}, - }; - - EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), _)) - .Times(testing::AnyNumber()); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Bar"), StrEq("([LkClass3;)V"))); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("([LkClass4;)V"))); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Gnar"), StrEq("([LkClass4;)V"))); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Moo"), StrEq("([LkClass4;)V"))); - - LocalObject obj{jobject{nullptr}}; - obj("Foo", LocalArray{123, LocalObject{}}); - obj("Bar", LocalArray{123, LocalObject{}}); - // obj("Bar", LocalArray{123, LocalObject{}}); // doesn't compile. - - // These calls don't trigger new GetMethod calls. - // Note this includes calls to both forward definition and full definition. - obj("Baz", LocalArray{123, LocalObject{}}); - obj("Gnar", LocalArray{123, LocalObject{}}); - - // This call requires a new method lookup (different function name). - obj("Moo", LocalArray{123, LocalObject{}}); + LocalArray boolean_array_1{1}; } -TEST_F(JniTest, Array_DifferentiatesWithOverloads) { - static constexpr Class kClass2{"kClass2"}; - static constexpr Class kClass{ - "ArrayMultiTest", - Method{"Foo", jni::Return{}, Params{Array{kClass2}}}, - }; +TEST_F(JniTest, LocalArray_ConstructsByteArray) { + EXPECT_CALL(*env_, NewByteArray(1)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(_)); - EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), _)); - EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("([LkClass2;)V"))); - - LocalObject obj{jobject{nullptr}}; - obj("Foo", LocalArray{123, LocalObject{}}); + LocalArray byte_array_1{1}; } -TEST_F(JniTest, Array_CachesLengthExactlyOnceOnFirstRequest) { - EXPECT_CALL(*env_, GetArrayLength).Times(0); - - LocalArray obj{Fake()}; +TEST_F(JniTest, LocalArray_ConstructsIntArray) { + EXPECT_CALL(*env_, NewIntArray(1)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(_)); - EXPECT_CALL(*env_, GetArrayLength).Times(1).WillOnce(Return(5)); - EXPECT_EQ(obj.Length(), 5); - EXPECT_EQ(obj.Length(), 5); - EXPECT_EQ(obj.Length(), 5); -} - -//////////////////////////////////////////////////////////////////////////////// -// String Array Tests. -// Strings are unusual in that they have their own type (jstring) but are -// almost completely objects otherwise. -//////////////////////////////////////////////////////////////////////////////// -TEST_F(JniTest, Array_ConstructsFromAnotherStringArray) { - LocalArray arr_1{Fake()}; - LocalArray 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 obj{jobject{nullptr}}; - LocalArray arr{3}; - obj("StringArray", arr); -} - -/* -// TODO(b/143908983): Restore after fixing ubsan failures. -TEST_F(JniTest, Array_StringsCanBeSetOnLocalString) { - EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 0, _)); - EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 1, _)); - EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 2, _)); - EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 3, _)); - EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 4, _)); - - const char* kFoo = "kFoo"; - const std::string kNar = "kNar"; - - LocalArray arr{5, LocalObject{"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}); -} -*/ - -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())).Times(2); // FindClass - EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(0); - - LocalArray arr{ - 3, LocalObject{"Foo"}}; - arr.Set(0, LocalObject{Fake()}); + LocalArray int_array_1{1}; } -TEST_F(JniTest, Array_LocalStringRValuesCanBeSet) { - EXPECT_CALL(*env_, DeleteLocalRef(_)) - .Times(1); // array (object is moved from) - EXPECT_CALL(*env_, DeleteLocalRef(Fake())); // FindClass - EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(0); - - LocalArray arr{3}; - arr.Set(0, LocalString{Fake()}); +TEST_F(JniTest, Array_ConstructsFromRValue) { + LocalArray arr{jintArray{nullptr}}; + LocalArray arr2{std::move(arr)}; } } // namespace diff --git a/implementation/signature_field_test.cc b/implementation/signature_field_test.cc new file mode 100644 index 00000000..f424f5a5 --- /dev/null +++ b/implementation/signature_field_test.cc @@ -0,0 +1,138 @@ +/* + * Copyright 2022 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 +#include +#include "jni_bind.h" + +namespace { + +using ::jni::Array; +using ::jni::Class; +using ::jni::Constructor; +using ::jni::Field; +using ::jni::Id; +using ::jni::IdType; +using ::jni::JniT; +using ::jni::JniTSelector; +using ::jni::kNoIdx; +using ::jni::Method; +using ::jni::Overload; +using ::jni::Params; +using ::jni::Rank; +using ::jni::Return; +using ::jni::SelectorStaticInfo; +using ::jni::Self; +using ::jni::Signature_v; + +// clang-format off +static constexpr Class kClass1{ + "kClass1", + + // Rank 0. + Field{"f0", jboolean{}}, + Field{"f1", jbyte{}}, + Field{"f2", jchar{}}, + Field{"f3", jshort{}}, + Field{"f4", jint{}}, + Field{"f5", jfloat{}}, + Field{"f6", jdouble{}}, + Field{"f7", jlong{}}, + Field{"f8", Class{"kClass2"}}, + + // Rank 1. + Field{"f9", Array{}}, + Field{"f10", Array{}}, + Field{"f11", Array{}}, + Field{"f12", Array{}}, + Field{"f13", Array{}}, + Field{"f14", Array{}}, + Field{"f15", Array{}}, + Field{"f16", Array{}}, + Field{"f17", Array{Class{"kClass2"}}}, + + // Rank 2. + Field{"f18", Array{jboolean{}, Rank<2>{}}}, + Field{"f19", Array{jbyte{}, Rank<2>{}}}, + Field{"f20", Array{jchar{}, Rank<2>{}}}, + Field{"f21", Array{jshort{}, Rank<2>{}}}, + Field{"f22", Array{jint{}, Rank<2>{}}}, + Field{"f23", Array{jfloat{}, Rank<2>{}}}, + Field{"f24", Array{jdouble{}, Rank<2>{}}}, + Field{"f25", Array{jlong{}, Rank<2>{}}}, + Field{"f26", Array{Class{"kClass2"}, Rank<2>{}}} +}; +// clang-format on + +using JT = JniT; + +//////////////////////////////////////////////////////////////////////////////// +// Class (i.e. self). +//////////////////////////////////////////////////////////////////////////////// +using kSelf1 = Id; +using JniSelfT = JniTSelector::JniT_, -1>; +using StaticSelectorInfoSelf = SelectorStaticInfo; + +//////////////////////////////////////////////////////////////////////////////// +// Rank 0. +//////////////////////////////////////////////////////////////////////////////// +static_assert(std::string_view{"Z"} == Signature_v>); +static_assert(std::string_view{"B"} == Signature_v>); +static_assert(std::string_view{"C"} == Signature_v>); +static_assert(std::string_view{"S"} == Signature_v>); +static_assert(std::string_view{"I"} == Signature_v>); +static_assert(std::string_view{"F"} == Signature_v>); +static_assert(std::string_view{"D"} == Signature_v>); +static_assert(std::string_view{"J"} == Signature_v>); +static_assert(std::string_view{"LkClass2;"} == + Signature_v>); + +//////////////////////////////////////////////////////////////////////////////// +// Rank 1. +//////////////////////////////////////////////////////////////////////////////// +static_assert(std::string_view{"[Z"} == Signature_v>); +static_assert(std::string_view{"[B"} == Signature_v>); +static_assert(std::string_view{"[C"} == Signature_v>); +static_assert(std::string_view{"[S"} == Signature_v>); +static_assert(std::string_view{"[I"} == Signature_v>); +static_assert(std::string_view{"[F"} == Signature_v>); +static_assert(std::string_view{"[D"} == Signature_v>); +static_assert(std::string_view{"[J"} == Signature_v>); +static_assert(std::string_view{"[LkClass2;"} == + Signature_v>); + +//////////////////////////////////////////////////////////////////////////////// +// Rank 2. +//////////////////////////////////////////////////////////////////////////////// +static_assert(std::string_view{"[[Z"} == + Signature_v>); +static_assert(std::string_view{"[[B"} == + Signature_v>); +static_assert(std::string_view{"[[C"} == + Signature_v>); +static_assert(std::string_view{"[[S"} == + Signature_v>); +static_assert(std::string_view{"[[I"} == + Signature_v>); +static_assert(std::string_view{"[[F"} == + Signature_v>); +static_assert(std::string_view{"[[D"} == + Signature_v>); +static_assert(std::string_view{"[[J"} == + Signature_v>); +static_assert(std::string_view{"[[LkClass2;"} == + Signature_v>); + +} // namespace diff --git a/implementation/signature_test.cc b/implementation/signature_method_test.cc similarity index 69% rename from implementation/signature_test.cc rename to implementation/signature_method_test.cc index d472935f..1bb0a92e 100644 --- a/implementation/signature_test.cc +++ b/implementation/signature_method_test.cc @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,9 +65,7 @@ static constexpr Class kClass1{ Method{"m5", jni::Return{Self{}}, Params{}}, Method{"m6", jni::Return{}, Params{Self{}}}, Method{"m7", jni::Return{}, Params{Self{}, Self{}}}, - Method{"m8", jni::Return{Self{}}, Params{Self{}, Self{}}}, - Field{"f0", int{}}, - Field{"f1", Class{"kClass2"}}}; + Method{"m8", jni::Return{Self{}}, Params{Self{}, Self{}}}}; using JT = JniT; @@ -86,12 +84,6 @@ static_assert(std::string_view{"[[LkClass1;"} == //////////////////////////////////////////////////////////////////////////////// // Constructors. //////////////////////////////////////////////////////////////////////////////// -using kCtor0 = Id; -using kCtor1 = Id; -using kCtor2 = Id; -using kCtor3 = Id; -using kCtor4 = Id; - static_assert(std::string_view{"I"} == Signature_v>); static_assert(std::string_view{"F"} == @@ -101,11 +93,16 @@ static_assert(std::string_view{"Z"} == static_assert(std::string_view{"[I"} == Signature_v>); -static_assert(std::string_view{"()V"} == Signature_v); -static_assert(std::string_view{"(I)V"} == Signature_v); -static_assert(std::string_view{"(FZ)V"} == Signature_v); -static_assert(std::string_view{"([I)V"} == Signature_v); -static_assert(std::string_view{"([[I)V"} == Signature_v); +static_assert(std::string_view{"()V"} == + Signature_v>); +static_assert(std::string_view{"(I)V"} == + Signature_v>); +static_assert(std::string_view{"(FZ)V"} == + Signature_v>); +static_assert(std::string_view{"([I)V"} == + Signature_v>); +static_assert(std::string_view{"([[I)V"} == + Signature_v>); //////////////////////////////////////////////////////////////////////////////// // Methods (Overload sets with only one overload). @@ -134,43 +131,35 @@ static_assert(std::string_view{"F"} == static_assert(std::string_view{"Z"} == Signature_v>); -using kMethod0Overload0 = Id; -using kMethod1Overload0 = Id; -using kMethod2Overload0 = Id; -using kMethod3Overload0 = Id; - -static_assert(std::string_view{"()V"} == Signature_v); -static_assert(std::string_view{"(I)S"} == Signature_v); +static_assert(std::string_view{"()V"} == + Signature_v>); +static_assert(std::string_view{"(I)S"} == + Signature_v>); static_assert(std::string_view{"(FZ)LkClass2;"} == - Signature_v); + Signature_v>); //////////////////////////////////////////////////////////////////////////////// // Overloaded Method (smoke test, they should behave the same). //////////////////////////////////////////////////////////////////////////////// -using kMethod3Overload0 = Id; -using kMethod3Overload1 = Id; -using kMethod3Overload2 = Id; -using kMethod3Overload3 = Id; -using kMethod3Overload4 = Id; - -using kMethod4Overload0 = Id; -using kMethod4Overload1 = Id; -using kMethod4Overload2 = Id; -using kMethod4Overload3 = Id; - -static_assert(std::string_view{"()V"} == Signature_v); -static_assert(std::string_view{"(Z)I"} == Signature_v); -static_assert(std::string_view{"(SD)F"} == Signature_v); +static_assert(std::string_view{"()V"} == + Signature_v>); +static_assert(std::string_view{"(Z)I"} == + Signature_v>); +static_assert(std::string_view{"(SD)F"} == + Signature_v>); static_assert(std::string_view{"(LkClass2;)F"} == - Signature_v); + Signature_v>); static_assert(std::string_view{"()LkClass3;"} == - Signature_v); - -static_assert(std::string_view{"([I)[Z"} == Signature_v); -static_assert(std::string_view{"([[F)[[Z"} == Signature_v); -static_assert(std::string_view{"([[[S)[[[Z"} == Signature_v); + Signature_v>); + +static_assert(std::string_view{"([I)[Z"} == + Signature_v>); +static_assert(std::string_view{"([[F)[[Z"} == + Signature_v>); +static_assert(std::string_view{"([[[S)[[[Z"} == + Signature_v>); static_assert(std::string_view{"()[[LkClass2;"} == - Signature_v); + Signature_v>); //////////////////////////////////////////////////////////////////////////////// // Self Referencing Method (gives richly decorated self back). @@ -189,13 +178,4 @@ using kMethod8 = Id; static_assert(std::string_view{"(LkClass1;LkClass1;)LkClass1;"} == Signature_v.data()); -//////////////////////////////////////////////////////////////////////////////// -// Fields (Overload sets with only one overload). -//////////////////////////////////////////////////////////////////////////////// -using kField0 = Id; -using kField1 = Id; - -static_assert(std::string_view{"I"} == Signature_v); -static_assert(std::string_view{"LkClass2;"} == Signature_v); - } // namespace