| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // Included first as it collides with the X11 headers. | |
| #include "gtest/gtest.h" | |
| #include "flutter/shell/platform/linux/fl_binary_messenger_private.h" | |
| #include "flutter/shell/platform/linux/fl_engine_private.h" | |
| #include "flutter/shell/platform/linux/fl_method_codec_private.h" | |
| #include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" | |
| #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" | |
| #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" | |
| #include "flutter/shell/platform/linux/testing/fl_test.h" | |
| #include "flutter/shell/platform/linux/testing/mock_renderer.h" | |
| // Called when when the method call response is received in the InvokeMethod | |
| // test. | |
| static void method_response_cb(GObject* object, | |
| GAsyncResult* result, | |
| gpointer user_data) { | |
| g_autoptr(GError) error = nullptr; | |
| g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( | |
| FL_METHOD_CHANNEL(object), result, &error); | |
| EXPECT_NE(response, nullptr); | |
| EXPECT_EQ(error, nullptr); | |
| FlValue* r = fl_method_response_get_result(response, &error); | |
| EXPECT_NE(r, nullptr); | |
| EXPECT_EQ(error, nullptr); | |
| EXPECT_EQ(fl_value_get_type(r), FL_VALUE_TYPE_STRING); | |
| EXPECT_STREQ(fl_value_get_string(r), "Hello World!"); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks if invoking a method returns a value. | |
| TEST(FlMethodChannelTest, InvokeMethod) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = fl_method_channel_new( | |
| messenger, "test/standard-method", FL_METHOD_CODEC(codec)); | |
| g_autoptr(FlValue) args = fl_value_new_string("Hello World!"); | |
| fl_method_channel_invoke_method(channel, "Echo", args, nullptr, | |
| method_response_cb, loop); | |
| // Blocks here until method_response_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| // Called when when the method call response is received in the | |
| // InvokeMethodNullptrArgsMessage test. | |
| static void nullptr_args_response_cb(GObject* object, | |
| GAsyncResult* result, | |
| gpointer user_data) { | |
| g_autoptr(GError) error = nullptr; | |
| g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( | |
| FL_METHOD_CHANNEL(object), result, &error); | |
| EXPECT_NE(response, nullptr); | |
| EXPECT_EQ(error, nullptr); | |
| FlValue* r = fl_method_response_get_result(response, &error); | |
| EXPECT_NE(r, nullptr); | |
| EXPECT_EQ(error, nullptr); | |
| EXPECT_EQ(fl_value_get_type(r), FL_VALUE_TYPE_NULL); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks if a method can be invoked with nullptr for arguments. | |
| TEST(FlMethodChannelTest, InvokeMethodNullptrArgsMessage) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = fl_method_channel_new( | |
| messenger, "test/standard-method", FL_METHOD_CODEC(codec)); | |
| fl_method_channel_invoke_method(channel, "Echo", nullptr, nullptr, | |
| nullptr_args_response_cb, loop); | |
| // Blocks here until nullptr_args_response_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| // Called when when the method call response is received in the | |
| // InvokeMethodError test. | |
| static void error_response_cb(GObject* object, | |
| GAsyncResult* result, | |
| gpointer user_data) { | |
| g_autoptr(GError) error = nullptr; | |
| g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( | |
| FL_METHOD_CHANNEL(object), result, &error); | |
| EXPECT_NE(response, nullptr); | |
| EXPECT_EQ(error, nullptr); | |
| EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response)); | |
| EXPECT_STREQ( | |
| fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)), | |
| "CODE"); | |
| EXPECT_STREQ( | |
| fl_method_error_response_get_message(FL_METHOD_ERROR_RESPONSE(response)), | |
| "MESSAGE"); | |
| FlValue* details = | |
| fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(response)); | |
| EXPECT_NE(details, nullptr); | |
| EXPECT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_STRING); | |
| EXPECT_STREQ(fl_value_get_string(details), "DETAILS"); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks if an error response from a method call is handled. | |
| TEST(FlMethodChannelTest, InvokeMethodError) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = fl_method_channel_new( | |
| messenger, "test/standard-method", FL_METHOD_CODEC(codec)); | |
| g_autoptr(FlValue) args = fl_value_new_list(); | |
| fl_value_append_take(args, fl_value_new_string("CODE")); | |
| fl_value_append_take(args, fl_value_new_string("MESSAGE")); | |
| fl_value_append_take(args, fl_value_new_string("DETAILS")); | |
| fl_method_channel_invoke_method(channel, "Error", args, nullptr, | |
| error_response_cb, loop); | |
| // Blocks here until error_response_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| // Called when when the method call response is received in the | |
| // InvokeMethodNotImplemented test. | |
| static void not_implemented_response_cb(GObject* object, | |
| GAsyncResult* result, | |
| gpointer user_data) { | |
| g_autoptr(GError) error = nullptr; | |
| g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( | |
| FL_METHOD_CHANNEL(object), result, &error); | |
| EXPECT_NE(response, nullptr); | |
| EXPECT_EQ(error, nullptr); | |
| EXPECT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response)); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks if a not implemeneted response from a method call is handled. | |
| TEST(FlMethodChannelTest, InvokeMethodNotImplemented) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = fl_method_channel_new( | |
| messenger, "test/standard-method", FL_METHOD_CODEC(codec)); | |
| fl_method_channel_invoke_method(channel, "NotImplemented", nullptr, nullptr, | |
| not_implemented_response_cb, loop); | |
| // Blocks here until not_implemented_response_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| // Called when when the method call response is received in the | |
| // InvokeMethodFailure test. | |
| static void failure_response_cb(GObject* object, | |
| GAsyncResult* result, | |
| gpointer user_data) { | |
| g_autoptr(GError) error = nullptr; | |
| g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish( | |
| FL_METHOD_CHANNEL(object), result, &error); | |
| EXPECT_EQ(response, nullptr); | |
| EXPECT_NE(error, nullptr); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks if an engine failure calling a method call is handled. | |
| TEST(FlMethodChannelTest, InvokeMethodFailure) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = | |
| fl_method_channel_new(messenger, "test/failure", FL_METHOD_CODEC(codec)); | |
| fl_method_channel_invoke_method(channel, "Echo", nullptr, nullptr, | |
| failure_response_cb, loop); | |
| // Blocks here until failure_response_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| // Called when a method call is received from the engine in the | |
| // ReceiveMethodCallRespondSuccess test. | |
| static void method_call_success_cb(FlMethodChannel* channel, | |
| FlMethodCall* method_call, | |
| gpointer user_data) { | |
| EXPECT_STREQ(fl_method_call_get_name(method_call), "Foo"); | |
| EXPECT_EQ(fl_value_get_type(fl_method_call_get_args(method_call)), | |
| FL_VALUE_TYPE_STRING); | |
| EXPECT_STREQ(fl_value_get_string(fl_method_call_get_args(method_call)), | |
| "Marco!"); | |
| g_autoptr(FlValue) result = fl_value_new_string("Polo!"); | |
| g_autoptr(GError) error = nullptr; | |
| EXPECT_TRUE(fl_method_call_respond_success(method_call, result, &error)); | |
| EXPECT_EQ(error, nullptr); | |
| } | |
| // Called when a the test engine notifies us what response we sent in the | |
| // ReceiveMethodCallRespondSuccess test. | |
| static void method_call_success_response_cb( | |
| FlBinaryMessenger* messenger, | |
| const gchar* channel, | |
| GBytes* message, | |
| FlBinaryMessengerResponseHandle* response_handle, | |
| gpointer user_data) { | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(GError) error = nullptr; | |
| g_autoptr(FlMethodResponse) response = | |
| fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); | |
| EXPECT_NE(response, nullptr); | |
| EXPECT_EQ(error, nullptr); | |
| EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); | |
| FlValue* result = fl_method_success_response_get_result( | |
| FL_METHOD_SUCCESS_RESPONSE(response)); | |
| EXPECT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING); | |
| EXPECT_STREQ(fl_value_get_string(result), "Polo!"); | |
| fl_binary_messenger_send_response(messenger, response_handle, nullptr, | |
| nullptr); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks the shell able to receive and respond to method calls from the engine. | |
| TEST(FlMethodChannelTest, ReceiveMethodCallRespondSuccess) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = fl_method_channel_new( | |
| messenger, "test/standard-method", FL_METHOD_CODEC(codec)); | |
| fl_method_channel_set_method_call_handler(channel, method_call_success_cb, | |
| nullptr, nullptr); | |
| // Listen for response from the engine. | |
| fl_binary_messenger_set_message_handler_on_channel( | |
| messenger, "test/responses", method_call_success_response_cb, loop, | |
| nullptr); | |
| // Trigger the engine to make a method call. | |
| g_autoptr(FlValue) args = fl_value_new_list(); | |
| fl_value_append_take(args, fl_value_new_string("test/standard-method")); | |
| fl_value_append_take(args, fl_value_new_string("Foo")); | |
| fl_value_append_take(args, fl_value_new_string("Marco!")); | |
| fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr, | |
| nullptr, loop); | |
| // Blocks here until method_call_success_response_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| // Called when a method call is received from the engine in the | |
| // ReceiveMethodCallRespondError test. | |
| static void method_call_error_cb(FlMethodChannel* channel, | |
| FlMethodCall* method_call, | |
| gpointer user_data) { | |
| EXPECT_STREQ(fl_method_call_get_name(method_call), "Foo"); | |
| EXPECT_EQ(fl_value_get_type(fl_method_call_get_args(method_call)), | |
| FL_VALUE_TYPE_STRING); | |
| EXPECT_STREQ(fl_value_get_string(fl_method_call_get_args(method_call)), | |
| "Marco!"); | |
| g_autoptr(FlValue) details = fl_value_new_string("DETAILS"); | |
| g_autoptr(GError) error = nullptr; | |
| EXPECT_TRUE(fl_method_call_respond_error(method_call, "CODE", "MESSAGE", | |
| details, &error)); | |
| EXPECT_EQ(error, nullptr); | |
| } | |
| // Called when a the test engine notifies us what response we sent in the | |
| // ReceiveMethodCallRespondError test. | |
| static void method_call_error_response_cb( | |
| FlBinaryMessenger* messenger, | |
| const gchar* channel, | |
| GBytes* message, | |
| FlBinaryMessengerResponseHandle* response_handle, | |
| gpointer user_data) { | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(GError) error = nullptr; | |
| g_autoptr(FlMethodResponse) response = | |
| fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); | |
| EXPECT_NE(response, nullptr); | |
| EXPECT_EQ(error, nullptr); | |
| EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response)); | |
| EXPECT_STREQ( | |
| fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)), | |
| "CODE"); | |
| EXPECT_STREQ( | |
| fl_method_error_response_get_message(FL_METHOD_ERROR_RESPONSE(response)), | |
| "MESSAGE"); | |
| FlValue* details = | |
| fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(response)); | |
| EXPECT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_STRING); | |
| EXPECT_STREQ(fl_value_get_string(details), "DETAILS"); | |
| fl_binary_messenger_send_response(messenger, response_handle, nullptr, | |
| nullptr); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks the shell able to receive and respond to method calls from the engine. | |
| TEST(FlMethodChannelTest, ReceiveMethodCallRespondError) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = fl_method_channel_new( | |
| messenger, "test/standard-method", FL_METHOD_CODEC(codec)); | |
| fl_method_channel_set_method_call_handler(channel, method_call_error_cb, | |
| nullptr, nullptr); | |
| // Listen for response from the engine. | |
| fl_binary_messenger_set_message_handler_on_channel( | |
| messenger, "test/responses", method_call_error_response_cb, loop, | |
| nullptr); | |
| // Trigger the engine to make a method call. | |
| g_autoptr(FlValue) args = fl_value_new_list(); | |
| fl_value_append_take(args, fl_value_new_string("test/standard-method")); | |
| fl_value_append_take(args, fl_value_new_string("Foo")); | |
| fl_value_append_take(args, fl_value_new_string("Marco!")); | |
| fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr, | |
| nullptr, loop); | |
| // Blocks here until method_call_error_response_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| // Called when a method call is received from the engine in the | |
| // ReceiveMethodCallRespondNotImplemented test. | |
| static void method_call_not_implemented_cb(FlMethodChannel* channel, | |
| FlMethodCall* method_call, | |
| gpointer user_data) { | |
| EXPECT_STREQ(fl_method_call_get_name(method_call), "Foo"); | |
| EXPECT_EQ(fl_value_get_type(fl_method_call_get_args(method_call)), | |
| FL_VALUE_TYPE_STRING); | |
| EXPECT_STREQ(fl_value_get_string(fl_method_call_get_args(method_call)), | |
| "Marco!"); | |
| g_autoptr(GError) error = nullptr; | |
| EXPECT_TRUE(fl_method_call_respond_not_implemented(method_call, &error)); | |
| EXPECT_EQ(error, nullptr); | |
| } | |
| // Called when a the test engine notifies us what response we sent in the | |
| // ReceiveMethodCallRespondNotImplemented test. | |
| static void method_call_not_implemented_response_cb( | |
| FlBinaryMessenger* messenger, | |
| const gchar* channel, | |
| GBytes* message, | |
| FlBinaryMessengerResponseHandle* response_handle, | |
| gpointer user_data) { | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(GError) error = nullptr; | |
| g_autoptr(FlMethodResponse) response = | |
| fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); | |
| EXPECT_NE(response, nullptr); | |
| EXPECT_EQ(error, nullptr); | |
| EXPECT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response)); | |
| fl_binary_messenger_send_response(messenger, response_handle, nullptr, | |
| nullptr); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks the shell able to receive and respond to method calls from the engine. | |
| TEST(FlMethodChannelTest, ReceiveMethodCallRespondNotImplemented) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = fl_method_channel_new( | |
| messenger, "test/standard-method", FL_METHOD_CODEC(codec)); | |
| fl_method_channel_set_method_call_handler( | |
| channel, method_call_not_implemented_cb, nullptr, nullptr); | |
| // Listen for response from the engine. | |
| fl_binary_messenger_set_message_handler_on_channel( | |
| messenger, "test/responses", method_call_not_implemented_response_cb, | |
| loop, nullptr); | |
| // Trigger the engine to make a method call. | |
| g_autoptr(FlValue) args = fl_value_new_list(); | |
| fl_value_append_take(args, fl_value_new_string("test/standard-method")); | |
| fl_value_append_take(args, fl_value_new_string("Foo")); | |
| fl_value_append_take(args, fl_value_new_string("Marco!")); | |
| fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr, | |
| nullptr, loop); | |
| // Blocks here until method_call_not_implemented_response_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| // A test method codec that always generates errors on responses. | |
| G_DECLARE_FINAL_TYPE(TestMethodCodec, | |
| test_method_codec, | |
| TEST, | |
| METHOD_CODEC, | |
| FlMethodCodec) | |
| struct _TestMethodCodec { | |
| FlMethodCodec parent_instance; | |
| FlStandardMethodCodec* wrapped_codec; | |
| }; | |
| G_DEFINE_TYPE(TestMethodCodec, test_method_codec, fl_method_codec_get_type()) | |
| static void test_method_codec_dispose(GObject* object) { | |
| TestMethodCodec* self = TEST_METHOD_CODEC(object); | |
| g_clear_object(&self->wrapped_codec); | |
| G_OBJECT_CLASS(test_method_codec_parent_class)->dispose(object); | |
| } | |
| // Implements FlMethodCodec::encode_method_call. | |
| static GBytes* test_method_codec_encode_method_call(FlMethodCodec* codec, | |
| const gchar* name, | |
| FlValue* args, | |
| GError** error) { | |
| EXPECT_TRUE(TEST_IS_METHOD_CODEC(codec)); | |
| TestMethodCodec* self = TEST_METHOD_CODEC(codec); | |
| return fl_method_codec_encode_method_call( | |
| FL_METHOD_CODEC(self->wrapped_codec), name, args, error); | |
| } | |
| // Implements FlMethodCodec::decode_method_call. | |
| static gboolean test_method_codec_decode_method_call(FlMethodCodec* codec, | |
| GBytes* message, | |
| gchar** name, | |
| FlValue** args, | |
| GError** error) { | |
| EXPECT_TRUE(TEST_IS_METHOD_CODEC(codec)); | |
| TestMethodCodec* self = TEST_METHOD_CODEC(codec); | |
| return fl_method_codec_decode_method_call( | |
| FL_METHOD_CODEC(self->wrapped_codec), message, name, args, error); | |
| } | |
| // Implements FlMethodCodec::encode_success_envelope. | |
| static GBytes* test_method_codec_encode_success_envelope(FlMethodCodec* codec, | |
| FlValue* result, | |
| GError** error) { | |
| g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, | |
| "Unsupported type"); | |
| return nullptr; | |
| } | |
| // Implements FlMethodCodec::encode_error_envelope. | |
| static GBytes* test_method_codec_encode_error_envelope(FlMethodCodec* codec, | |
| const gchar* code, | |
| const gchar* message, | |
| FlValue* details, | |
| GError** error) { | |
| g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, | |
| "Unsupported type"); | |
| return nullptr; | |
| } | |
| // Implements FlMethodCodec::encode_decode_response. | |
| static FlMethodResponse* test_method_codec_decode_response(FlMethodCodec* codec, | |
| GBytes* message, | |
| GError** error) { | |
| EXPECT_TRUE(TEST_IS_METHOD_CODEC(codec)); | |
| TestMethodCodec* self = TEST_METHOD_CODEC(codec); | |
| return fl_method_codec_decode_response(FL_METHOD_CODEC(self->wrapped_codec), | |
| message, error); | |
| } | |
| static void test_method_codec_class_init(TestMethodCodecClass* klass) { | |
| G_OBJECT_CLASS(klass)->dispose = test_method_codec_dispose; | |
| FL_METHOD_CODEC_CLASS(klass)->encode_method_call = | |
| test_method_codec_encode_method_call; | |
| FL_METHOD_CODEC_CLASS(klass)->decode_method_call = | |
| test_method_codec_decode_method_call; | |
| FL_METHOD_CODEC_CLASS(klass)->encode_success_envelope = | |
| test_method_codec_encode_success_envelope; | |
| FL_METHOD_CODEC_CLASS(klass)->encode_error_envelope = | |
| test_method_codec_encode_error_envelope; | |
| FL_METHOD_CODEC_CLASS(klass)->decode_response = | |
| test_method_codec_decode_response; | |
| } | |
| static void test_method_codec_init(TestMethodCodec* self) { | |
| self->wrapped_codec = fl_standard_method_codec_new(); | |
| } | |
| TestMethodCodec* test_method_codec_new() { | |
| return TEST_METHOD_CODEC(g_object_new(test_method_codec_get_type(), nullptr)); | |
| } | |
| // Called when a method call is received from the engine in the | |
| // ReceiveMethodCallRespondSuccessError test. | |
| static void method_call_success_error_cb(FlMethodChannel* channel, | |
| FlMethodCall* method_call, | |
| gpointer user_data) { | |
| g_autoptr(FlValue) result = fl_value_new_int(42); | |
| g_autoptr(GError) response_error = nullptr; | |
| EXPECT_FALSE( | |
| fl_method_call_respond_success(method_call, result, &response_error)); | |
| EXPECT_NE(response_error, nullptr); | |
| // Respond to stop a warning occurring about not responding. | |
| fl_method_call_respond_not_implemented(method_call, nullptr); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks error correctly handled if provide an unsupported arg in a method call | |
| // response. | |
| TEST(FlMethodChannelTest, ReceiveMethodCallRespondSuccessError) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(TestMethodCodec) codec = test_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = fl_method_channel_new( | |
| messenger, "test/standard-method", FL_METHOD_CODEC(codec)); | |
| fl_method_channel_set_method_call_handler( | |
| channel, method_call_success_error_cb, loop, nullptr); | |
| // Trigger the engine to make a method call. | |
| g_autoptr(FlValue) args = fl_value_new_list(); | |
| fl_value_append_take(args, fl_value_new_string("test/standard-method")); | |
| fl_value_append_take(args, fl_value_new_string("Foo")); | |
| fl_value_append_take(args, fl_value_new_string("Marco!")); | |
| fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr, | |
| nullptr, loop); | |
| // Blocks here until method_call_success_error_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| // Called when a method call is received from the engine in the | |
| // ReceiveMethodCallRespondErrorError test. | |
| static void method_call_error_error_cb(FlMethodChannel* channel, | |
| FlMethodCall* method_call, | |
| gpointer user_data) { | |
| g_autoptr(FlValue) details = fl_value_new_int(42); | |
| g_autoptr(GError) response_error = nullptr; | |
| EXPECT_FALSE(fl_method_call_respond_error(method_call, "error", "ERROR", | |
| details, &response_error)); | |
| EXPECT_NE(response_error, nullptr); | |
| // Respond to stop a warning occurring about not responding. | |
| fl_method_call_respond_not_implemented(method_call, nullptr); | |
| g_main_loop_quit(static_cast<GMainLoop*>(user_data)); | |
| } | |
| // Checks error correctly handled if provide an unsupported arg in a method call | |
| // response. | |
| TEST(FlMethodChannelTest, ReceiveMethodCallRespondErrorError) { | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(TestMethodCodec) codec = test_method_codec_new(); | |
| g_autoptr(FlMethodChannel) channel = fl_method_channel_new( | |
| messenger, "test/standard-method", FL_METHOD_CODEC(codec)); | |
| fl_method_channel_set_method_call_handler(channel, method_call_error_error_cb, | |
| loop, nullptr); | |
| // Trigger the engine to make a method call. | |
| g_autoptr(FlValue) args = fl_value_new_list(); | |
| fl_value_append_take(args, fl_value_new_string("test/standard-method")); | |
| fl_value_append_take(args, fl_value_new_string("Foo")); | |
| fl_value_append_take(args, fl_value_new_string("Marco!")); | |
| fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr, | |
| nullptr, loop); | |
| // Blocks here until method_call_error_error_cb is called. | |
| g_main_loop_run(loop); | |
| } | |
| struct UserDataReassignMethod { | |
| GMainLoop* loop; | |
| int count; | |
| }; | |
| // This callback parses the user data as UserDataReassignMethod, | |
| // increases its `count`, and quits `loop`. | |
| static void reassign_method_cb(FlMethodChannel* channel, | |
| FlMethodCall* method_call, | |
| gpointer raw_user_data) { | |
| UserDataReassignMethod* user_data = | |
| static_cast<UserDataReassignMethod*>(raw_user_data); | |
| user_data->count += 1; | |
| g_autoptr(FlValue) result = fl_value_new_string("Polo!"); | |
| g_autoptr(GError) error = nullptr; | |
| EXPECT_TRUE(fl_method_call_respond_success(method_call, result, &error)); | |
| EXPECT_EQ(error, nullptr); | |
| g_main_loop_quit(user_data->loop); | |
| } | |
| // Make sure that the following steps will work properly: | |
| // | |
| // 1. Register a method channel. | |
| // 2. Dispose the method channel, and it's unregistered. | |
| // 3. Register a new channel with the same name. | |
| // | |
| // This is a regression test to https://github.com/flutter/flutter/issues/90817. | |
| TEST(FlMethodChannelTest, ReplaceADisposedMethodChannel) { | |
| const char* method_name = "test/standard-method"; | |
| // The loop is used to pause the main process until the callback is fully | |
| // executed. | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlValue) args = fl_value_new_list(); | |
| fl_value_append_take(args, fl_value_new_string(method_name)); | |
| fl_value_append_take(args, fl_value_new_string("FOO")); | |
| fl_value_append_take(args, fl_value_new_string("BAR")); | |
| // Register the first channel and test if it works. | |
| UserDataReassignMethod user_data1{ | |
| .loop = loop, | |
| .count = 100, | |
| }; | |
| FlMethodChannel* channel1 = | |
| fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec)); | |
| fl_method_channel_set_method_call_handler(channel1, reassign_method_cb, | |
| &user_data1, nullptr); | |
| fl_method_channel_invoke_method(channel1, "InvokeMethod", args, nullptr, | |
| nullptr, nullptr); | |
| g_main_loop_run(loop); | |
| EXPECT_EQ(user_data1.count, 101); | |
| // Dispose the first channel. | |
| g_object_unref(channel1); | |
| // Register the second channel and test if it works. | |
| UserDataReassignMethod user_data2{ | |
| .loop = loop, | |
| .count = 100, | |
| }; | |
| g_autoptr(FlMethodChannel) channel2 = | |
| fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec)); | |
| fl_method_channel_set_method_call_handler(channel2, reassign_method_cb, | |
| &user_data2, nullptr); | |
| fl_method_channel_invoke_method(channel2, "InvokeMethod", args, nullptr, | |
| nullptr, nullptr); | |
| g_main_loop_run(loop); | |
| EXPECT_EQ(user_data1.count, 101); | |
| EXPECT_EQ(user_data2.count, 101); | |
| } | |
| // Make sure that the following steps will work properly: | |
| // | |
| // 1. Register a method channel. | |
| // 2. Register the same name with a new channel. | |
| // 3. Dispose the previous method channel. | |
| // | |
| // This is a regression test to https://github.com/flutter/flutter/issues/90817. | |
| TEST(FlMethodChannelTest, DisposeAReplacedMethodChannel) { | |
| const char* method_name = "test/standard-method"; | |
| // The loop is used to pause the main process until the callback is fully | |
| // executed. | |
| g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); | |
| g_autoptr(FlEngine) engine = make_mock_engine(); | |
| FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); | |
| g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); | |
| g_autoptr(FlValue) args = fl_value_new_list(); | |
| fl_value_append_take(args, fl_value_new_string(method_name)); | |
| fl_value_append_take(args, fl_value_new_string("FOO")); | |
| fl_value_append_take(args, fl_value_new_string("BAR")); | |
| // Register the first channel and test if it works. | |
| UserDataReassignMethod user_data1{ | |
| .loop = loop, | |
| .count = 100, | |
| }; | |
| FlMethodChannel* channel1 = | |
| fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec)); | |
| fl_method_channel_set_method_call_handler(channel1, reassign_method_cb, | |
| &user_data1, nullptr); | |
| fl_method_channel_invoke_method(channel1, "InvokeMethod", args, nullptr, | |
| nullptr, nullptr); | |
| g_main_loop_run(loop); | |
| EXPECT_EQ(user_data1.count, 101); | |
| // Register a new channel to the same name. | |
| UserDataReassignMethod user_data2{ | |
| .loop = loop, | |
| .count = 100, | |
| }; | |
| g_autoptr(FlMethodChannel) channel2 = | |
| fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec)); | |
| fl_method_channel_set_method_call_handler(channel2, reassign_method_cb, | |
| &user_data2, nullptr); | |
| fl_method_channel_invoke_method(channel2, "InvokeMethod", args, nullptr, | |
| nullptr, nullptr); | |
| g_main_loop_run(loop); | |
| EXPECT_EQ(user_data1.count, 101); | |
| EXPECT_EQ(user_data2.count, 101); | |
| // Dispose the first channel. The new channel should keep working. | |
| g_object_unref(channel1); | |
| fl_method_channel_invoke_method(channel2, "InvokeMethod", args, nullptr, | |
| nullptr, nullptr); | |
| g_main_loop_run(loop); | |
| EXPECT_EQ(user_data1.count, 101); | |
| EXPECT_EQ(user_data2.count, 102); | |
| } |