From b268b43ac6fdbc4f3a2ed1429b99ace424906090 Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Tue, 24 Apr 2018 01:37:19 +0900 Subject: Migrate libmojo repository into libchrome, part 2. This CL moves following files. - .gitignore - Android.bp is merged into libchrome's Android.bp. - base/android/* - build/* except build_config.h which is exactly same with libchrome's. - ipc/* - mojo/* except mojo/public/tools/bindings/generators/__init__.py which is unused and not in chrome repository. - soong/* into libchrome_tools/ - third_party/{catapult,jinja2,markupsafe,ply}/* - ui/gfx/{geometry,range}/mojo/* Then, update several paths/build rules to be adapted. Bug: 73606903 Test: Built locally. Ran on DUT. Change-Id: I2a532a42aa68dcb215dbd71d8673192311509726 --- mojo/public/cpp/bindings/tests/BUILD.gn | 146 +++ .../tests/associated_interface_unittest.cc | 1189 +++++++++++++++++++ .../bindings/tests/bind_task_runner_unittest.cc | 395 +++++++ .../bindings/tests/binding_callback_unittest.cc | 338 ++++++ .../cpp/bindings/tests/binding_set_unittest.cc | 416 +++++++ mojo/public/cpp/bindings/tests/binding_unittest.cc | 611 ++++++++++ .../public/cpp/bindings/tests/bindings_perftest.cc | 286 +++++ mojo/public/cpp/bindings/tests/blink_typemaps.gni | 8 + mojo/public/cpp/bindings/tests/buffer_unittest.cc | 93 ++ .../cpp/bindings/tests/chromium_typemaps.gni | 9 + .../cpp/bindings/tests/connector_unittest.cc | 599 ++++++++++ .../public/cpp/bindings/tests/constant_unittest.cc | 60 + .../cpp/bindings/tests/container_test_util.cc | 52 + .../cpp/bindings/tests/container_test_util.h | 55 + .../cpp/bindings/tests/data_view_unittest.cc | 303 +++++ mojo/public/cpp/bindings/tests/e2e_perftest.cc | 204 ++++ mojo/public/cpp/bindings/tests/equals_unittest.cc | 122 ++ .../cpp/bindings/tests/handle_passing_unittest.cc | 356 ++++++ mojo/public/cpp/bindings/tests/hash_unittest.cc | 35 + .../cpp/bindings/tests/interface_ptr_unittest.cc | 937 +++++++++++++++ mojo/public/cpp/bindings/tests/map_unittest.cc | 46 + mojo/public/cpp/bindings/tests/message_queue.cc | 39 + mojo/public/cpp/bindings/tests/message_queue.h | 44 + .../cpp/bindings/tests/mojo_test_blink_export.h | 29 + mojo/public/cpp/bindings/tests/mojo_test_export.h | 29 + .../bindings/tests/multiplex_router_unittest.cc | 314 +++++ mojo/public/cpp/bindings/tests/pickle_unittest.cc | 403 +++++++ .../cpp/bindings/tests/pickled_types_blink.cc | 67 ++ .../cpp/bindings/tests/pickled_types_blink.h | 88 ++ .../cpp/bindings/tests/pickled_types_chromium.cc | 69 ++ .../cpp/bindings/tests/pickled_types_chromium.h | 81 ++ mojo/public/cpp/bindings/tests/rect_blink.h | 83 ++ mojo/public/cpp/bindings/tests/rect_blink.typemap | 18 + mojo/public/cpp/bindings/tests/rect_blink_traits.h | 35 + mojo/public/cpp/bindings/tests/rect_chromium.h | 87 ++ .../cpp/bindings/tests/rect_chromium.typemap | 18 + .../cpp/bindings/tests/rect_chromium_traits.h | 34 + .../bindings/tests/report_bad_message_unittest.cc | 194 +++ .../bindings/tests/request_response_unittest.cc | 157 +++ mojo/public/cpp/bindings/tests/router_test_util.cc | 111 ++ mojo/public/cpp/bindings/tests/router_test_util.h | 92 ++ .../cpp/bindings/tests/sample_service_unittest.cc | 362 ++++++ .../tests/serialization_warning_unittest.cc | 251 ++++ mojo/public/cpp/bindings/tests/shared_rect.h | 43 + .../public/cpp/bindings/tests/shared_rect_traits.h | 33 + .../cpp/bindings/tests/struct_traits_unittest.cc | 553 +++++++++ mojo/public/cpp/bindings/tests/struct_unittest.cc | 526 +++++++++ .../cpp/bindings/tests/struct_with_traits.typemap | 26 + .../cpp/bindings/tests/struct_with_traits_impl.cc | 36 + .../cpp/bindings/tests/struct_with_traits_impl.h | 168 +++ .../tests/struct_with_traits_impl_traits.cc | 137 +++ .../tests/struct_with_traits_impl_traits.h | 196 +++ .../cpp/bindings/tests/sync_method_unittest.cc | 831 +++++++++++++ .../bindings/tests/test_native_types_blink.typemap | 17 + .../tests/test_native_types_chromium.typemap | 17 + .../cpp/bindings/tests/type_conversion_unittest.cc | 161 +++ mojo/public/cpp/bindings/tests/union_unittest.cc | 1246 ++++++++++++++++++++ .../bindings/tests/validation_context_unittest.cc | 297 +++++ .../bindings/tests/validation_test_input_parser.cc | 412 +++++++ .../bindings/tests/validation_test_input_parser.h | 121 ++ .../cpp/bindings/tests/validation_unittest.cc | 498 ++++++++ mojo/public/cpp/bindings/tests/variant_test_util.h | 32 + .../cpp/bindings/tests/versioning_apptest.cc | 123 ++ .../cpp/bindings/tests/versioning_test_service.cc | 127 ++ .../public/cpp/bindings/tests/wtf_hash_unittest.cc | 60 + mojo/public/cpp/bindings/tests/wtf_map_unittest.cc | 41 + .../cpp/bindings/tests/wtf_types_unittest.cc | 245 ++++ 67 files changed, 14811 insertions(+) create mode 100644 mojo/public/cpp/bindings/tests/BUILD.gn create mode 100644 mojo/public/cpp/bindings/tests/associated_interface_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/binding_callback_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/binding_set_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/binding_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/bindings_perftest.cc create mode 100644 mojo/public/cpp/bindings/tests/blink_typemaps.gni create mode 100644 mojo/public/cpp/bindings/tests/buffer_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/chromium_typemaps.gni create mode 100644 mojo/public/cpp/bindings/tests/connector_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/constant_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/container_test_util.cc create mode 100644 mojo/public/cpp/bindings/tests/container_test_util.h create mode 100644 mojo/public/cpp/bindings/tests/data_view_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/e2e_perftest.cc create mode 100644 mojo/public/cpp/bindings/tests/equals_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/handle_passing_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/hash_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/map_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/message_queue.cc create mode 100644 mojo/public/cpp/bindings/tests/message_queue.h create mode 100644 mojo/public/cpp/bindings/tests/mojo_test_blink_export.h create mode 100644 mojo/public/cpp/bindings/tests/mojo_test_export.h create mode 100644 mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/pickle_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/pickled_types_blink.cc create mode 100644 mojo/public/cpp/bindings/tests/pickled_types_blink.h create mode 100644 mojo/public/cpp/bindings/tests/pickled_types_chromium.cc create mode 100644 mojo/public/cpp/bindings/tests/pickled_types_chromium.h create mode 100644 mojo/public/cpp/bindings/tests/rect_blink.h create mode 100644 mojo/public/cpp/bindings/tests/rect_blink.typemap create mode 100644 mojo/public/cpp/bindings/tests/rect_blink_traits.h create mode 100644 mojo/public/cpp/bindings/tests/rect_chromium.h create mode 100644 mojo/public/cpp/bindings/tests/rect_chromium.typemap create mode 100644 mojo/public/cpp/bindings/tests/rect_chromium_traits.h create mode 100644 mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/request_response_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/router_test_util.cc create mode 100644 mojo/public/cpp/bindings/tests/router_test_util.h create mode 100644 mojo/public/cpp/bindings/tests/sample_service_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/shared_rect.h create mode 100644 mojo/public/cpp/bindings/tests/shared_rect_traits.h create mode 100644 mojo/public/cpp/bindings/tests/struct_traits_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/struct_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits.typemap create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits_impl.h create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h create mode 100644 mojo/public/cpp/bindings/tests/sync_method_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/test_native_types_blink.typemap create mode 100644 mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap create mode 100644 mojo/public/cpp/bindings/tests/type_conversion_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/union_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/validation_context_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/validation_test_input_parser.cc create mode 100644 mojo/public/cpp/bindings/tests/validation_test_input_parser.h create mode 100644 mojo/public/cpp/bindings/tests/validation_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/variant_test_util.h create mode 100644 mojo/public/cpp/bindings/tests/versioning_apptest.cc create mode 100644 mojo/public/cpp/bindings/tests/versioning_test_service.cc create mode 100644 mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/wtf_map_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/wtf_types_unittest.cc (limited to 'mojo/public/cpp/bindings/tests') diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn new file mode 100644 index 0000000000..668ca6da90 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/BUILD.gn @@ -0,0 +1,146 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("tests") { + testonly = true + + sources = [ + "associated_interface_unittest.cc", + "bind_task_runner_unittest.cc", + "binding_callback_unittest.cc", + "binding_set_unittest.cc", + "binding_unittest.cc", + "buffer_unittest.cc", + "connector_unittest.cc", + "constant_unittest.cc", + "container_test_util.cc", + "container_test_util.h", + "data_view_unittest.cc", + "equals_unittest.cc", + "handle_passing_unittest.cc", + "hash_unittest.cc", + "interface_ptr_unittest.cc", + "map_unittest.cc", + "message_queue.cc", + "message_queue.h", + "multiplex_router_unittest.cc", + "report_bad_message_unittest.cc", + "request_response_unittest.cc", + "router_test_util.cc", + "router_test_util.h", + "sample_service_unittest.cc", + "serialization_warning_unittest.cc", + "struct_unittest.cc", + "sync_method_unittest.cc", + "type_conversion_unittest.cc", + "union_unittest.cc", + "validation_context_unittest.cc", + "validation_unittest.cc", + "variant_test_util.h", + ] + + deps = [ + ":mojo_public_bindings_test_utils", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + "//mojo/public/cpp/test_support:test_utils", + "//mojo/public/interfaces/bindings/tests:test_associated_interfaces", + "//mojo/public/interfaces/bindings/tests:test_export_component", + "//mojo/public/interfaces/bindings/tests:test_export_component2", + "//mojo/public/interfaces/bindings/tests:test_exported_import", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/interfaces/bindings/tests:test_struct_traits_interfaces", + "//testing/gtest", + ] + + data = [ + "//mojo/public/interfaces/bindings/tests/data/validation/", + ] + + if (is_ios) { + assert_no_deps = [ "//third_party/WebKit/*" ] + } else { + sources += [ + "pickle_unittest.cc", + "struct_traits_unittest.cc", + ] + + deps += [ "//mojo/public/interfaces/bindings/tests:test_interfaces_blink" ] + } +} + +if (!is_ios) { + source_set("for_blink_tests") { + testonly = true + + sources = [ + "container_test_util.cc", + "container_test_util.h", + "variant_test_util.h", + "wtf_hash_unittest.cc", + "wtf_map_unittest.cc", + "wtf_types_unittest.cc", + ] + + deps = [ + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + "//mojo/public/interfaces/bindings/tests:test_export_blink_component", + "//mojo/public/interfaces/bindings/tests:test_exported_import_blink", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/interfaces/bindings/tests:test_interfaces_blink", + "//mojo/public/interfaces/bindings/tests:test_wtf_types", + "//mojo/public/interfaces/bindings/tests:test_wtf_types_blink", + "//testing/gtest", + ] + } +} + +source_set("struct_with_traits_impl") { + sources = [ + "struct_with_traits_impl.cc", + "struct_with_traits_impl.h", + ] + + deps = [ + "//base", + "//mojo/public/cpp/system:system", + ] +} + +source_set("perftests") { + testonly = true + + sources = [ + "bindings_perftest.cc", + ] + + if (!is_ios) { + sources += [ "e2e_perftest.cc" ] + } + + deps = [ + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + "//mojo/public/cpp/test_support:test_utils", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//testing/gtest", + ] +} + +source_set("mojo_public_bindings_test_utils") { + sources = [ + "validation_test_input_parser.cc", + "validation_test_input_parser.h", + ] + + deps = [ + "//mojo/public/c/system", + ] +} diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc new file mode 100644 index 0000000000..be225e4761 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc @@ -0,0 +1,1189 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using mojo::internal::MultiplexRouter; + +class IntegerSenderImpl : public IntegerSender { + public: + explicit IntegerSenderImpl(AssociatedInterfaceRequest request) + : binding_(this, std::move(request)) {} + + ~IntegerSenderImpl() override {} + + void set_notify_send_method_called( + const base::Callback& callback) { + notify_send_method_called_ = callback; + } + + void Echo(int32_t value, const EchoCallback& callback) override { + callback.Run(value); + } + void Send(int32_t value) override { notify_send_method_called_.Run(value); } + + AssociatedBinding* binding() { return &binding_; } + + void set_connection_error_handler(const base::Closure& handler) { + binding_.set_connection_error_handler(handler); + } + + private: + AssociatedBinding binding_; + base::Callback notify_send_method_called_; +}; + +class IntegerSenderConnectionImpl : public IntegerSenderConnection { + public: + explicit IntegerSenderConnectionImpl( + InterfaceRequest request) + : binding_(this, std::move(request)) {} + + ~IntegerSenderConnectionImpl() override {} + + void GetSender(AssociatedInterfaceRequest sender) override { + IntegerSenderImpl* sender_impl = new IntegerSenderImpl(std::move(sender)); + sender_impl->set_connection_error_handler( + base::Bind(&DeleteSender, sender_impl)); + } + + void AsyncGetSender(const AsyncGetSenderCallback& callback) override { + IntegerSenderAssociatedPtrInfo ptr_info; + auto request = MakeRequest(&ptr_info); + GetSender(std::move(request)); + callback.Run(std::move(ptr_info)); + } + + Binding* binding() { return &binding_; } + + private: + static void DeleteSender(IntegerSenderImpl* sender) { delete sender; } + + Binding binding_; +}; + +class AssociatedInterfaceTest : public testing::Test { + public: + AssociatedInterfaceTest() {} + ~AssociatedInterfaceTest() override { base::RunLoop().RunUntilIdle(); } + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + template + AssociatedInterfacePtrInfo EmulatePassingAssociatedPtrInfo( + AssociatedInterfacePtrInfo ptr_info, + scoped_refptr source, + scoped_refptr target) { + ScopedInterfaceEndpointHandle handle = ptr_info.PassHandle(); + CHECK(handle.pending_association()); + auto id = source->AssociateInterface(std::move(handle)); + return AssociatedInterfacePtrInfo(target->CreateLocalEndpointHandle(id), + ptr_info.version()); + } + + void CreateRouterPair(scoped_refptr* router0, + scoped_refptr* router1) { + MessagePipe pipe; + *router0 = new MultiplexRouter(std::move(pipe.handle0), + MultiplexRouter::MULTI_INTERFACE, true, + base::ThreadTaskRunnerHandle::Get()); + *router1 = new MultiplexRouter(std::move(pipe.handle1), + MultiplexRouter::MULTI_INTERFACE, false, + base::ThreadTaskRunnerHandle::Get()); + } + + void CreateIntegerSenderWithExistingRouters( + scoped_refptr router0, + IntegerSenderAssociatedPtrInfo* ptr_info0, + scoped_refptr router1, + IntegerSenderAssociatedRequest* request1) { + *request1 = MakeRequest(ptr_info0); + *ptr_info0 = EmulatePassingAssociatedPtrInfo(std::move(*ptr_info0), router1, + router0); + } + + void CreateIntegerSender(IntegerSenderAssociatedPtrInfo* ptr_info, + IntegerSenderAssociatedRequest* request) { + scoped_refptr router0; + scoped_refptr router1; + CreateRouterPair(&router0, &router1); + CreateIntegerSenderWithExistingRouters(router1, ptr_info, router0, request); + } + + // Okay to call from any thread. + void QuitRunLoop(base::RunLoop* run_loop) { + if (loop_.task_runner()->BelongsToCurrentThread()) { + run_loop->Quit(); + } else { + loop_.task_runner()->PostTask( + FROM_HERE, + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(run_loop))); + } + } + + private: + base::MessageLoop loop_; +}; + +void DoSetFlagAndRunClosure(bool* flag, const base::Closure& closure) { + *flag = true; + closure.Run(); +} + +void DoExpectValueSetFlagAndRunClosure(int32_t expected_value, + bool* flag, + const base::Closure& closure, + int32_t value) { + EXPECT_EQ(expected_value, value); + DoSetFlagAndRunClosure(flag, closure); +} + +base::Closure SetFlagAndRunClosure(bool* flag, const base::Closure& closure) { + return base::Bind(&DoSetFlagAndRunClosure, flag, closure); +} + +base::Callback ExpectValueSetFlagAndRunClosure( + int32_t expected_value, + bool* flag, + const base::Closure& closure) { + return base::Bind( + &DoExpectValueSetFlagAndRunClosure, expected_value, flag, closure); +} + +void Fail() { + FAIL() << "Unexpected connection error"; +} + +TEST_F(AssociatedInterfaceTest, InterfacesAtBothEnds) { + // Bind to the same pipe two associated interfaces, whose implementation lives + // at different ends. Test that the two don't interfere with each other. + + scoped_refptr router0; + scoped_refptr router1; + CreateRouterPair(&router0, &router1); + + AssociatedInterfaceRequest request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSenderWithExistingRouters(router1, &ptr_info, router0, &request); + + IntegerSenderImpl impl0(std::move(request)); + AssociatedInterfacePtr ptr0; + ptr0.Bind(std::move(ptr_info)); + + CreateIntegerSenderWithExistingRouters(router0, &ptr_info, router1, &request); + + IntegerSenderImpl impl1(std::move(request)); + AssociatedInterfacePtr ptr1; + ptr1.Bind(std::move(ptr_info)); + + base::RunLoop run_loop, run_loop2; + bool ptr0_callback_run = false; + ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(123, &ptr0_callback_run, + run_loop.QuitClosure())); + + bool ptr1_callback_run = false; + ptr1->Echo(456, ExpectValueSetFlagAndRunClosure(456, &ptr1_callback_run, + run_loop2.QuitClosure())); + + run_loop.Run(); + run_loop2.Run(); + EXPECT_TRUE(ptr0_callback_run); + EXPECT_TRUE(ptr1_callback_run); + + bool ptr0_error_callback_run = false; + base::RunLoop run_loop3; + ptr0.set_connection_error_handler( + SetFlagAndRunClosure(&ptr0_error_callback_run, run_loop3.QuitClosure())); + + impl0.binding()->Close(); + run_loop3.Run(); + EXPECT_TRUE(ptr0_error_callback_run); + + bool impl1_error_callback_run = false; + base::RunLoop run_loop4; + impl1.binding()->set_connection_error_handler( + SetFlagAndRunClosure(&impl1_error_callback_run, run_loop4.QuitClosure())); + + ptr1.reset(); + run_loop4.Run(); + EXPECT_TRUE(impl1_error_callback_run); +} + +class TestSender { + public: + TestSender() + : sender_thread_("TestSender"), + next_sender_(nullptr), + max_value_to_send_(-1) { + sender_thread_.Start(); + } + + // The following three methods are called on the corresponding sender thread. + void SetUp(IntegerSenderAssociatedPtrInfo ptr_info, + TestSender* next_sender, + int32_t max_value_to_send) { + CHECK(sender_thread_.task_runner()->BelongsToCurrentThread()); + + ptr_.Bind(std::move(ptr_info)); + next_sender_ = next_sender ? next_sender : this; + max_value_to_send_ = max_value_to_send; + } + + void Send(int32_t value) { + CHECK(sender_thread_.task_runner()->BelongsToCurrentThread()); + + if (value > max_value_to_send_) + return; + + ptr_->Send(value); + + next_sender_->sender_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestSender::Send, base::Unretained(next_sender_), ++value)); + } + + void TearDown() { + CHECK(sender_thread_.task_runner()->BelongsToCurrentThread()); + + ptr_.reset(); + } + + base::Thread* sender_thread() { return &sender_thread_; } + + private: + base::Thread sender_thread_; + TestSender* next_sender_; + int32_t max_value_to_send_; + + AssociatedInterfacePtr ptr_; +}; + +class TestReceiver { + public: + TestReceiver() : receiver_thread_("TestReceiver"), expected_calls_(0) { + receiver_thread_.Start(); + } + + void SetUp(AssociatedInterfaceRequest request0, + AssociatedInterfaceRequest request1, + size_t expected_calls, + const base::Closure& notify_finish) { + CHECK(receiver_thread_.task_runner()->BelongsToCurrentThread()); + + impl0_.reset(new IntegerSenderImpl(std::move(request0))); + impl0_->set_notify_send_method_called( + base::Bind(&TestReceiver::SendMethodCalled, base::Unretained(this))); + impl1_.reset(new IntegerSenderImpl(std::move(request1))); + impl1_->set_notify_send_method_called( + base::Bind(&TestReceiver::SendMethodCalled, base::Unretained(this))); + + expected_calls_ = expected_calls; + notify_finish_ = notify_finish; + } + + void TearDown() { + CHECK(receiver_thread_.task_runner()->BelongsToCurrentThread()); + + impl0_.reset(); + impl1_.reset(); + } + + base::Thread* receiver_thread() { return &receiver_thread_; } + const std::vector& values() const { return values_; } + + private: + void SendMethodCalled(int32_t value) { + values_.push_back(value); + + if (values_.size() >= expected_calls_) + notify_finish_.Run(); + } + + base::Thread receiver_thread_; + size_t expected_calls_; + + std::unique_ptr impl0_; + std::unique_ptr impl1_; + + std::vector values_; + + base::Closure notify_finish_; +}; + +class NotificationCounter { + public: + NotificationCounter(size_t total_count, const base::Closure& notify_finish) + : total_count_(total_count), + current_count_(0), + notify_finish_(notify_finish) {} + + ~NotificationCounter() {} + + // Okay to call from any thread. + void OnGotNotification() { + bool finshed = false; + { + base::AutoLock locker(lock_); + CHECK_LT(current_count_, total_count_); + current_count_++; + finshed = current_count_ == total_count_; + } + + if (finshed) + notify_finish_.Run(); + } + + private: + base::Lock lock_; + const size_t total_count_; + size_t current_count_; + base::Closure notify_finish_; +}; + +TEST_F(AssociatedInterfaceTest, MultiThreadAccess) { + // Set up four associated interfaces on a message pipe. Use the inteface + // pointers on four threads in parallel; run the interface implementations on + // two threads. Test that multi-threaded access works. + + const int32_t kMaxValue = 1000; + MessagePipe pipe; + scoped_refptr router0; + scoped_refptr router1; + CreateRouterPair(&router0, &router1); + + AssociatedInterfaceRequest requests[4]; + IntegerSenderAssociatedPtrInfo ptr_infos[4]; + for (size_t i = 0; i < 4; ++i) { + CreateIntegerSenderWithExistingRouters(router1, &ptr_infos[i], router0, + &requests[i]); + } + + TestSender senders[4]; + for (size_t i = 0; i < 4; ++i) { + senders[i].sender_thread()->task_runner()->PostTask( + FROM_HERE, base::Bind(&TestSender::SetUp, base::Unretained(&senders[i]), + base::Passed(&ptr_infos[i]), nullptr, + kMaxValue * (i + 1) / 4)); + } + + base::RunLoop run_loop; + TestReceiver receivers[2]; + NotificationCounter counter( + 2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + for (size_t i = 0; i < 2; ++i) { + receivers[i].receiver_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestReceiver::SetUp, base::Unretained(&receivers[i]), + base::Passed(&requests[2 * i]), + base::Passed(&requests[2 * i + 1]), + static_cast(kMaxValue / 2), + base::Bind(&NotificationCounter::OnGotNotification, + base::Unretained(&counter)))); + } + + for (size_t i = 0; i < 4; ++i) { + senders[i].sender_thread()->task_runner()->PostTask( + FROM_HERE, base::Bind(&TestSender::Send, base::Unretained(&senders[i]), + kMaxValue * i / 4 + 1)); + } + + run_loop.Run(); + + for (size_t i = 0; i < 4; ++i) { + base::RunLoop run_loop; + senders[i].sender_thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])), + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + run_loop.Run(); + } + + for (size_t i = 0; i < 2; ++i) { + base::RunLoop run_loop; + receivers[i].receiver_thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])), + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + run_loop.Run(); + } + + EXPECT_EQ(static_cast(kMaxValue / 2), receivers[0].values().size()); + EXPECT_EQ(static_cast(kMaxValue / 2), receivers[1].values().size()); + + std::vector all_values; + all_values.insert(all_values.end(), receivers[0].values().begin(), + receivers[0].values().end()); + all_values.insert(all_values.end(), receivers[1].values().begin(), + receivers[1].values().end()); + + std::sort(all_values.begin(), all_values.end()); + for (size_t i = 0; i < all_values.size(); ++i) + ASSERT_EQ(static_cast(i + 1), all_values[i]); +} + +TEST_F(AssociatedInterfaceTest, FIFO) { + // Set up four associated interfaces on a message pipe. Use the inteface + // pointers on four threads; run the interface implementations on two threads. + // Take turns to make calls using the four pointers. Test that FIFO-ness is + // preserved. + + const int32_t kMaxValue = 100; + MessagePipe pipe; + scoped_refptr router0; + scoped_refptr router1; + CreateRouterPair(&router0, &router1); + + AssociatedInterfaceRequest requests[4]; + IntegerSenderAssociatedPtrInfo ptr_infos[4]; + for (size_t i = 0; i < 4; ++i) { + CreateIntegerSenderWithExistingRouters(router1, &ptr_infos[i], router0, + &requests[i]); + } + + TestSender senders[4]; + for (size_t i = 0; i < 4; ++i) { + senders[i].sender_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestSender::SetUp, base::Unretained(&senders[i]), + base::Passed(&ptr_infos[i]), + base::Unretained(&senders[(i + 1) % 4]), kMaxValue)); + } + + base::RunLoop run_loop; + TestReceiver receivers[2]; + NotificationCounter counter( + 2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + for (size_t i = 0; i < 2; ++i) { + receivers[i].receiver_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestReceiver::SetUp, base::Unretained(&receivers[i]), + base::Passed(&requests[2 * i]), + base::Passed(&requests[2 * i + 1]), + static_cast(kMaxValue / 2), + base::Bind(&NotificationCounter::OnGotNotification, + base::Unretained(&counter)))); + } + + senders[0].sender_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestSender::Send, base::Unretained(&senders[0]), 1)); + + run_loop.Run(); + + for (size_t i = 0; i < 4; ++i) { + base::RunLoop run_loop; + senders[i].sender_thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])), + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + run_loop.Run(); + } + + for (size_t i = 0; i < 2; ++i) { + base::RunLoop run_loop; + receivers[i].receiver_thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])), + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + run_loop.Run(); + } + + EXPECT_EQ(static_cast(kMaxValue / 2), receivers[0].values().size()); + EXPECT_EQ(static_cast(kMaxValue / 2), receivers[1].values().size()); + + for (size_t i = 0; i < 2; ++i) { + for (size_t j = 1; j < receivers[i].values().size(); ++j) + EXPECT_LT(receivers[i].values()[j - 1], receivers[i].values()[j]); + } +} + +void CaptureInt32(int32_t* storage, + const base::Closure& closure, + int32_t value) { + *storage = value; + closure.Run(); +} + +void CaptureSenderPtrInfo(IntegerSenderAssociatedPtr* storage, + const base::Closure& closure, + IntegerSenderAssociatedPtrInfo info) { + storage->Bind(std::move(info)); + closure.Run(); +} + +TEST_F(AssociatedInterfaceTest, PassAssociatedInterfaces) { + IntegerSenderConnectionPtr connection_ptr; + IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr)); + + IntegerSenderAssociatedPtr sender0; + connection_ptr->GetSender(MakeRequest(&sender0)); + + int32_t echoed_value = 0; + base::RunLoop run_loop; + sender0->Echo(123, base::Bind(&CaptureInt32, &echoed_value, + run_loop.QuitClosure())); + run_loop.Run(); + EXPECT_EQ(123, echoed_value); + + IntegerSenderAssociatedPtr sender1; + base::RunLoop run_loop2; + connection_ptr->AsyncGetSender( + base::Bind(&CaptureSenderPtrInfo, &sender1, run_loop2.QuitClosure())); + run_loop2.Run(); + EXPECT_TRUE(sender1); + + base::RunLoop run_loop3; + sender1->Echo(456, base::Bind(&CaptureInt32, &echoed_value, + run_loop3.QuitClosure())); + run_loop3.Run(); + EXPECT_EQ(456, echoed_value); +} + +TEST_F(AssociatedInterfaceTest, BindingWaitAndPauseWhenNoAssociatedInterfaces) { + IntegerSenderConnectionPtr connection_ptr; + IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr)); + + IntegerSenderAssociatedPtr sender0; + connection_ptr->GetSender(MakeRequest(&sender0)); + + EXPECT_FALSE(connection.binding()->HasAssociatedInterfaces()); + // There are no associated interfaces running on the pipe yet. It is okay to + // pause. + connection.binding()->PauseIncomingMethodCallProcessing(); + connection.binding()->ResumeIncomingMethodCallProcessing(); + + // There are no associated interfaces running on the pipe yet. It is okay to + // wait. + EXPECT_TRUE(connection.binding()->WaitForIncomingMethodCall()); + + // The previous wait has dispatched the GetSender request message, therefore + // an associated interface has been set up on the pipe. It is not allowed to + // wait or pause. + EXPECT_TRUE(connection.binding()->HasAssociatedInterfaces()); +} + +class PingServiceImpl : public PingService { + public: + explicit PingServiceImpl(PingServiceAssociatedRequest request) + : binding_(this, std::move(request)) {} + ~PingServiceImpl() override {} + + AssociatedBinding& binding() { return binding_; } + + void set_ping_handler(const base::Closure& handler) { + ping_handler_ = handler; + } + + // PingService: + void Ping(const PingCallback& callback) override { + if (!ping_handler_.is_null()) + ping_handler_.Run(); + callback.Run(); + } + + private: + AssociatedBinding binding_; + base::Closure ping_handler_; +}; + +class PingProviderImpl : public AssociatedPingProvider { + public: + explicit PingProviderImpl(AssociatedPingProviderRequest request) + : binding_(this, std::move(request)) {} + ~PingProviderImpl() override {} + + // AssociatedPingProvider: + void GetPing(PingServiceAssociatedRequest request) override { + ping_services_.emplace_back(new PingServiceImpl(std::move(request))); + + if (expected_bindings_count_ > 0 && + ping_services_.size() == expected_bindings_count_ && + !quit_waiting_.is_null()) { + expected_bindings_count_ = 0; + base::ResetAndReturn(&quit_waiting_).Run(); + } + } + + std::vector>& ping_services() { + return ping_services_; + } + + void WaitForBindings(size_t count) { + DCHECK(quit_waiting_.is_null()); + + expected_bindings_count_ = count; + base::RunLoop loop; + quit_waiting_ = loop.QuitClosure(); + loop.Run(); + } + + private: + Binding binding_; + std::vector> ping_services_; + size_t expected_bindings_count_ = 0; + base::Closure quit_waiting_; +}; + +class CallbackFilter : public MessageReceiver { + public: + explicit CallbackFilter(const base::Closure& callback) + : callback_(callback) {} + ~CallbackFilter() override {} + + static std::unique_ptr Wrap(const base::Closure& callback) { + return base::MakeUnique(callback); + } + + // MessageReceiver: + bool Accept(Message* message) override { + callback_.Run(); + return true; + } + + private: + const base::Closure callback_; +}; + +// Verifies that filters work as expected on associated bindings, i.e. that +// they're notified in order, before dispatch; and that each associated +// binding in a group operates with its own set of filters. +TEST_F(AssociatedInterfaceTest, BindingWithFilters) { + AssociatedPingProviderPtr provider; + PingProviderImpl provider_impl(MakeRequest(&provider)); + + PingServiceAssociatedPtr ping_a, ping_b; + provider->GetPing(MakeRequest(&ping_a)); + provider->GetPing(MakeRequest(&ping_b)); + provider_impl.WaitForBindings(2); + + ASSERT_EQ(2u, provider_impl.ping_services().size()); + PingServiceImpl& ping_a_impl = *provider_impl.ping_services()[0]; + PingServiceImpl& ping_b_impl = *provider_impl.ping_services()[1]; + + int a_status, b_status; + auto handler_helper = [] (int* a_status, int* b_status, int expected_a_status, + int new_a_status, int expected_b_status, + int new_b_status) { + EXPECT_EQ(expected_a_status, *a_status); + EXPECT_EQ(expected_b_status, *b_status); + *a_status = new_a_status; + *b_status = new_b_status; + }; + auto create_handler = [&] (int expected_a_status, int new_a_status, + int expected_b_status, int new_b_status) { + return base::Bind(handler_helper, &a_status, &b_status, expected_a_status, + new_a_status, expected_b_status, new_b_status); + }; + + ping_a_impl.binding().AddFilter( + CallbackFilter::Wrap(create_handler(0, 1, 0, 0))); + ping_a_impl.binding().AddFilter( + CallbackFilter::Wrap(create_handler(1, 2, 0, 0))); + ping_a_impl.set_ping_handler(create_handler(2, 3, 0, 0)); + + ping_b_impl.binding().AddFilter( + CallbackFilter::Wrap(create_handler(3, 3, 0, 1))); + ping_b_impl.binding().AddFilter( + CallbackFilter::Wrap(create_handler(3, 3, 1, 2))); + ping_b_impl.set_ping_handler(create_handler(3, 3, 2, 3)); + + for (int i = 0; i < 10; ++i) { + a_status = 0; + b_status = 0; + + { + base::RunLoop loop; + ping_a->Ping(loop.QuitClosure()); + loop.Run(); + } + + EXPECT_EQ(3, a_status); + EXPECT_EQ(0, b_status); + + { + base::RunLoop loop; + ping_b->Ping(loop.QuitClosure()); + loop.Run(); + } + + EXPECT_EQ(3, a_status); + EXPECT_EQ(3, b_status); + } +} + +TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTesting) { + AssociatedInterfaceRequest request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + IntegerSenderImpl impl0(std::move(request)); + AssociatedInterfacePtr ptr0; + ptr0.Bind(std::move(ptr_info)); + ptr0.set_connection_error_handler(base::Bind(&Fail)); + + bool ptr0_callback_run = false; + ptr0->Echo(123, ExpectValueSetFlagAndRunClosure( + 123, &ptr0_callback_run, base::Bind(&base::DoNothing))); + ptr0.FlushForTesting(); + EXPECT_TRUE(ptr0_callback_run); +} + +void SetBool(bool* value) { + *value = true; +} + +template +void SetBoolWithUnusedParameter(bool* value, T unused) { + *value = true; +} + +TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTestingWithClosedPeer) { + AssociatedInterfaceRequest request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + AssociatedInterfacePtr ptr0; + ptr0.Bind(std::move(ptr_info)); + bool called = false; + ptr0.set_connection_error_handler(base::Bind(&SetBool, &called)); + request = nullptr; + + ptr0.FlushForTesting(); + EXPECT_TRUE(called); + ptr0.FlushForTesting(); +} + +TEST_F(AssociatedInterfaceTest, AssociatedBindingFlushForTesting) { + AssociatedInterfaceRequest request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + IntegerSenderImpl impl0(std::move(request)); + impl0.set_connection_error_handler(base::Bind(&Fail)); + AssociatedInterfacePtr ptr0; + ptr0.Bind(std::move(ptr_info)); + + bool ptr0_callback_run = false; + ptr0->Echo(123, ExpectValueSetFlagAndRunClosure( + 123, &ptr0_callback_run, base::Bind(&base::DoNothing))); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + impl0.binding()->FlushForTesting(); + impl0.binding()->FlushForTesting(); + EXPECT_TRUE(ptr0_callback_run); +} + +TEST_F(AssociatedInterfaceTest, + AssociatedBindingFlushForTestingWithClosedPeer) { + scoped_refptr router0; + scoped_refptr router1; + CreateRouterPair(&router0, &router1); + + AssociatedInterfaceRequest request; + { + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSenderWithExistingRouters(router1, &ptr_info, router0, + &request); + } + + IntegerSenderImpl impl(std::move(request)); + bool called = false; + impl.set_connection_error_handler(base::Bind(&SetBool, &called)); + impl.binding()->FlushForTesting(); + EXPECT_TRUE(called); + impl.binding()->FlushForTesting(); +} + +TEST_F(AssociatedInterfaceTest, BindingFlushForTesting) { + IntegerSenderConnectionPtr ptr; + IntegerSenderConnectionImpl impl(MakeRequest(&ptr)); + bool called = false; + ptr->AsyncGetSender(base::Bind( + &SetBoolWithUnusedParameter, &called)); + EXPECT_FALSE(called); + impl.binding()->set_connection_error_handler(base::Bind(&Fail)); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + impl.binding()->FlushForTesting(); + impl.binding()->FlushForTesting(); + EXPECT_TRUE(called); +} + +TEST_F(AssociatedInterfaceTest, BindingFlushForTestingWithClosedPeer) { + IntegerSenderConnectionPtr ptr; + IntegerSenderConnectionImpl impl(MakeRequest(&ptr)); + bool called = false; + impl.binding()->set_connection_error_handler(base::Bind(&SetBool, &called)); + ptr.reset(); + EXPECT_FALSE(called); + impl.binding()->FlushForTesting(); + EXPECT_TRUE(called); + impl.binding()->FlushForTesting(); +} + +TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTesting) { + IntegerSenderConnectionPtr ptr; + auto binding = + MakeStrongBinding(base::MakeUnique( + IntegerSenderConnectionRequest{}), + MakeRequest(&ptr)); + bool called = false; + IntegerSenderAssociatedPtr sender_ptr; + ptr->GetSender(MakeRequest(&sender_ptr)); + sender_ptr->Echo(1, base::Bind(&SetBoolWithUnusedParameter, &called)); + EXPECT_FALSE(called); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + ASSERT_TRUE(binding); + binding->FlushForTesting(); + ASSERT_TRUE(binding); + binding->FlushForTesting(); + EXPECT_TRUE(called); +} + +TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTestingWithClosedPeer) { + IntegerSenderConnectionPtr ptr; + bool called = false; + auto binding = + MakeStrongBinding(base::MakeUnique( + IntegerSenderConnectionRequest{}), + MakeRequest(&ptr)); + binding->set_connection_error_handler(base::Bind(&SetBool, &called)); + ptr.reset(); + EXPECT_FALSE(called); + ASSERT_TRUE(binding); + binding->FlushForTesting(); + EXPECT_TRUE(called); + ASSERT_FALSE(binding); +} + +TEST_F(AssociatedInterfaceTest, PtrFlushForTesting) { + IntegerSenderConnectionPtr ptr; + IntegerSenderConnectionImpl impl(MakeRequest(&ptr)); + bool called = false; + ptr.set_connection_error_handler(base::Bind(&Fail)); + ptr->AsyncGetSender(base::Bind( + &SetBoolWithUnusedParameter, &called)); + EXPECT_FALSE(called); + ptr.FlushForTesting(); + EXPECT_TRUE(called); +} + +TEST_F(AssociatedInterfaceTest, PtrFlushForTestingWithClosedPeer) { + IntegerSenderConnectionPtr ptr; + MakeRequest(&ptr); + bool called = false; + ptr.set_connection_error_handler(base::Bind(&SetBool, &called)); + EXPECT_FALSE(called); + ptr.FlushForTesting(); + EXPECT_TRUE(called); + ptr.FlushForTesting(); +} + +TEST_F(AssociatedInterfaceTest, AssociatedBindingConnectionErrorWithReason) { + AssociatedInterfaceRequest request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + IntegerSenderImpl impl(std::move(request)); + AssociatedInterfacePtr ptr; + ptr.Bind(std::move(ptr_info)); + + base::RunLoop run_loop; + impl.binding()->set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(123u, custom_reason); + EXPECT_EQ("farewell", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(123u, "farewell"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, + PendingAssociatedBindingConnectionErrorWithReason) { + // Test that AssociatedBinding is notified with connection error when the + // interface hasn't associated with a message pipe and the peer is closed. + + IntegerSenderAssociatedPtr ptr; + IntegerSenderImpl impl(MakeRequest(&ptr)); + + base::RunLoop run_loop; + impl.binding()->set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(123u, custom_reason); + EXPECT_EQ("farewell", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(123u, "farewell"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, AssociatedPtrConnectionErrorWithReason) { + AssociatedInterfaceRequest request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + IntegerSenderImpl impl(std::move(request)); + AssociatedInterfacePtr ptr; + ptr.Bind(std::move(ptr_info)); + + base::RunLoop run_loop; + ptr.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(456u, custom_reason); + EXPECT_EQ("farewell", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + impl.binding()->CloseWithReason(456u, "farewell"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, PendingAssociatedPtrConnectionErrorWithReason) { + // Test that AssociatedInterfacePtr is notified with connection error when the + // interface hasn't associated with a message pipe and the peer is closed. + + IntegerSenderAssociatedPtr ptr; + auto request = MakeRequest(&ptr); + + base::RunLoop run_loop; + ptr.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(456u, custom_reason); + EXPECT_EQ("farewell", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + request.ResetWithReason(456u, "farewell"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, AssociatedRequestResetWithReason) { + AssociatedInterfaceRequest request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + AssociatedInterfacePtr ptr; + ptr.Bind(std::move(ptr_info)); + + base::RunLoop run_loop; + ptr.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(789u, custom_reason); + EXPECT_EQ("long time no see", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + request.ResetWithReason(789u, "long time no see"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, ThreadSafeAssociatedInterfacePtr) { + IntegerSenderConnectionPtr connection_ptr; + IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr)); + + IntegerSenderAssociatedPtr sender; + connection_ptr->GetSender(MakeRequest(&sender)); + + scoped_refptr thread_safe_sender = + ThreadSafeIntegerSenderAssociatedPtr::Create(std::move(sender)); + + { + // Test the thread safe pointer can be used from the interface ptr thread. + int32_t echoed_value = 0; + base::RunLoop run_loop; + (*thread_safe_sender) + ->Echo(123, base::Bind(&CaptureInt32, &echoed_value, + run_loop.QuitClosure())); + run_loop.Run(); + EXPECT_EQ(123, echoed_value); + } + + // Test the thread safe pointer can be used from another thread. + base::RunLoop run_loop; + base::Thread other_thread("service test thread"); + other_thread.Start(); + + auto run_method = base::Bind( + [](const scoped_refptr& main_task_runner, + const base::Closure& quit_closure, + const scoped_refptr& + thread_safe_sender) { + auto done_callback = base::Bind( + [](const scoped_refptr& main_task_runner, + const base::Closure& quit_closure, + base::PlatformThreadId thread_id, int32_t result) { + EXPECT_EQ(123, result); + // Validate the callback is invoked on the calling thread. + EXPECT_EQ(thread_id, base::PlatformThread::CurrentId()); + // Notify the run_loop to quit. + main_task_runner->PostTask(FROM_HERE, quit_closure); + }); + (*thread_safe_sender) + ->Echo(123, + base::Bind(done_callback, main_task_runner, quit_closure, + base::PlatformThread::CurrentId())); + }, + base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(), + thread_safe_sender); + other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method); + + // Block until the method callback is called on the background thread. + run_loop.Run(); +} + +struct ForwarderTestContext { + IntegerSenderConnectionPtr connection_ptr; + std::unique_ptr interface_impl; + IntegerSenderAssociatedRequest sender_request; +}; + +TEST_F(AssociatedInterfaceTest, + ThreadSafeAssociatedInterfacePtrWithTaskRunner) { + // Start the thread from where we'll bind the interface pointer. + base::Thread other_thread("service test thread"); + other_thread.Start(); + const scoped_refptr& other_thread_task_runner = + other_thread.message_loop()->task_runner(); + + ForwarderTestContext* context = new ForwarderTestContext(); + IntegerSenderAssociatedPtrInfo sender_info; + base::WaitableEvent sender_info_bound_event( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + auto setup = [](base::WaitableEvent* sender_info_bound_event, + IntegerSenderAssociatedPtrInfo* sender_info, + ForwarderTestContext* context) { + context->interface_impl = base::MakeUnique( + MakeRequest(&context->connection_ptr)); + + auto sender_request = MakeRequest(sender_info); + context->connection_ptr->GetSender(std::move(sender_request)); + + // Unblock the main thread as soon as |sender_info| is set. + sender_info_bound_event->Signal(); + }; + other_thread_task_runner->PostTask( + FROM_HERE, + base::Bind(setup, &sender_info_bound_event, &sender_info, context)); + sender_info_bound_event.Wait(); + + // Create a ThreadSafeAssociatedPtr that binds on the background thread and is + // associated with |connection_ptr| there. + scoped_refptr thread_safe_ptr = + ThreadSafeIntegerSenderAssociatedPtr::Create(std::move(sender_info), + other_thread_task_runner); + + // Issue a call on the thread-safe ptr immediately. Note that this may happen + // before the interface is bound on the background thread, and that must be + // OK. + { + auto echo_callback = + base::Bind([](const base::Closure& quit_closure, int32_t result) { + EXPECT_EQ(123, result); + quit_closure.Run(); + }); + base::RunLoop run_loop; + (*thread_safe_ptr) + ->Echo(123, base::Bind(echo_callback, run_loop.QuitClosure())); + + // Block until the method callback is called. + run_loop.Run(); + } + + other_thread_task_runner->DeleteSoon(FROM_HERE, context); + + // Reset the pointer now so the InterfacePtr associated resources can be + // deleted before the background thread's message loop is invalidated. + thread_safe_ptr = nullptr; +} + +class DiscardingAssociatedPingProviderProvider + : public AssociatedPingProviderProvider { + public: + void GetPingProvider( + AssociatedPingProviderAssociatedRequest request) override {} +}; + +TEST_F(AssociatedInterfaceTest, CloseWithoutBindingAssociatedRequest) { + DiscardingAssociatedPingProviderProvider ping_provider_provider; + mojo::Binding binding( + &ping_provider_provider); + auto provider_provider = binding.CreateInterfacePtrAndBind(); + AssociatedPingProviderAssociatedPtr provider; + provider_provider->GetPingProvider(mojo::MakeRequest(&provider)); + PingServiceAssociatedPtr ping; + provider->GetPing(mojo::MakeRequest(&ping)); + base::RunLoop run_loop; + ping.set_connection_error_handler(run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, GetIsolatedInterface) { + IntegerSenderAssociatedPtr sender; + GetIsolatedInterface(MakeRequest(&sender).PassHandle()); + sender->Send(42); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc new file mode 100644 index 0000000000..569eb518c6 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc @@ -0,0 +1,395 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class TestTaskRunner : public base::SingleThreadTaskRunner { + public: + TestTaskRunner() + : thread_id_(base::PlatformThread::CurrentRef()), + quit_called_(false), + task_ready_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED) {} + + bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here, + base::OnceClosure task, + base::TimeDelta delay) override { + NOTREACHED(); + return false; + } + + bool PostDelayedTask(const tracked_objects::Location& from_here, + base::OnceClosure task, + base::TimeDelta delay) override { + { + base::AutoLock locker(lock_); + tasks_.push(std::move(task)); + } + task_ready_.Signal(); + return true; + } + bool RunsTasksOnCurrentThread() const override { + return base::PlatformThread::CurrentRef() == thread_id_; + } + + // Only quits when Quit() is called. + void Run() { + DCHECK(RunsTasksOnCurrentThread()); + quit_called_ = false; + + while (true) { + { + base::AutoLock locker(lock_); + while (!tasks_.empty()) { + auto task = std::move(tasks_.front()); + tasks_.pop(); + + { + base::AutoUnlock unlocker(lock_); + std::move(task).Run(); + if (quit_called_) + return; + } + } + } + task_ready_.Wait(); + } + } + + void Quit() { + DCHECK(RunsTasksOnCurrentThread()); + quit_called_ = true; + } + + // Waits until one task is ready and runs it. + void RunOneTask() { + DCHECK(RunsTasksOnCurrentThread()); + + while (true) { + { + base::AutoLock locker(lock_); + if (!tasks_.empty()) { + auto task = std::move(tasks_.front()); + tasks_.pop(); + + { + base::AutoUnlock unlocker(lock_); + std::move(task).Run(); + return; + } + } + } + task_ready_.Wait(); + } + } + + private: + ~TestTaskRunner() override {} + + const base::PlatformThreadRef thread_id_; + bool quit_called_; + base::WaitableEvent task_ready_; + + // Protect |tasks_|. + base::Lock lock_; + std::queue tasks_; + + DISALLOW_COPY_AND_ASSIGN(TestTaskRunner); +}; + +template +class IntegerSenderImpl : public IntegerSender { + public: + IntegerSenderImpl(RequestType request, + scoped_refptr runner) + : binding_(this, std::move(request), std::move(runner)) {} + + ~IntegerSenderImpl() override {} + + using EchoHandler = base::Callback; + + void set_echo_handler(const EchoHandler& handler) { echo_handler_ = handler; } + + void Echo(int32_t value, const EchoCallback& callback) override { + if (echo_handler_.is_null()) + callback.Run(value); + else + echo_handler_.Run(value, callback); + } + void Send(int32_t value) override { NOTREACHED(); } + + BindingType* binding() { return &binding_; } + + private: + BindingType binding_; + EchoHandler echo_handler_; +}; + +class IntegerSenderConnectionImpl : public IntegerSenderConnection { + public: + using SenderType = IntegerSenderImpl, + IntegerSenderAssociatedRequest>; + + explicit IntegerSenderConnectionImpl( + IntegerSenderConnectionRequest request, + scoped_refptr runner, + scoped_refptr sender_runner) + : binding_(this, std::move(request), std::move(runner)), + sender_runner_(std::move(sender_runner)) {} + + ~IntegerSenderConnectionImpl() override {} + + void set_get_sender_notification(const base::Closure& notification) { + get_sender_notification_ = notification; + } + void GetSender(IntegerSenderAssociatedRequest sender) override { + sender_impl_.reset(new SenderType(std::move(sender), sender_runner_)); + get_sender_notification_.Run(); + } + + void AsyncGetSender(const AsyncGetSenderCallback& callback) override { + NOTREACHED(); + } + + Binding* binding() { return &binding_; } + + SenderType* sender_impl() { return sender_impl_.get(); } + + private: + Binding binding_; + std::unique_ptr sender_impl_; + scoped_refptr sender_runner_; + base::Closure get_sender_notification_; +}; + +class BindTaskRunnerTest : public testing::Test { + protected: + void SetUp() override { + binding_task_runner_ = scoped_refptr(new TestTaskRunner); + ptr_task_runner_ = scoped_refptr(new TestTaskRunner); + + auto request = MakeRequest(&ptr_, ptr_task_runner_); + impl_.reset(new ImplType(std::move(request), binding_task_runner_)); + } + + base::MessageLoop loop_; + scoped_refptr binding_task_runner_; + scoped_refptr ptr_task_runner_; + + IntegerSenderPtr ptr_; + using ImplType = + IntegerSenderImpl, IntegerSenderRequest>; + std::unique_ptr impl_; +}; + +class AssociatedBindTaskRunnerTest : public testing::Test { + protected: + void SetUp() override { + connection_binding_task_runner_ = + scoped_refptr(new TestTaskRunner); + connection_ptr_task_runner_ = + scoped_refptr(new TestTaskRunner); + sender_binding_task_runner_ = + scoped_refptr(new TestTaskRunner); + sender_ptr_task_runner_ = scoped_refptr(new TestTaskRunner); + + auto connection_request = + MakeRequest(&connection_ptr_, connection_ptr_task_runner_); + connection_impl_.reset(new IntegerSenderConnectionImpl( + std::move(connection_request), connection_binding_task_runner_, + sender_binding_task_runner_)); + + connection_impl_->set_get_sender_notification( + base::Bind(&AssociatedBindTaskRunnerTest::QuitTaskRunner, + base::Unretained(this))); + + connection_ptr_->GetSender( + MakeRequest(&sender_ptr_, sender_ptr_task_runner_)); + connection_binding_task_runner_->Run(); + } + + void QuitTaskRunner() { + connection_binding_task_runner_->Quit(); + } + + base::MessageLoop loop_; + scoped_refptr connection_binding_task_runner_; + scoped_refptr connection_ptr_task_runner_; + scoped_refptr sender_binding_task_runner_; + scoped_refptr sender_ptr_task_runner_; + + IntegerSenderConnectionPtr connection_ptr_; + std::unique_ptr connection_impl_; + IntegerSenderAssociatedPtr sender_ptr_; +}; + +void DoSetFlagAndQuitTaskRunner(bool* flag, + scoped_refptr task_runner) { + *flag = true; + if (task_runner) + task_runner->Quit(); +} + +void DoExpectValueSetFlagAndQuitTaskRunner( + int32_t expected_value, + bool* flag, + scoped_refptr task_runner, + int32_t value) { + EXPECT_EQ(expected_value, value); + DoSetFlagAndQuitTaskRunner(flag, task_runner); +} + +void DoExpectValueSetFlagForwardValueAndQuitTaskRunner( + int32_t expected_value, + bool* flag, + scoped_refptr task_runner, + int32_t value, + const IntegerSender::EchoCallback& callback) { + EXPECT_EQ(expected_value, value); + *flag = true; + callback.Run(value); + task_runner->Quit(); +} + +base::Closure SetFlagAndQuitTaskRunner( + bool* flag, + scoped_refptr task_runner) { + return base::Bind(&DoSetFlagAndQuitTaskRunner, flag, task_runner); +} + +base::Callback ExpectValueSetFlagAndQuitTaskRunner( + int32_t expected_value, + bool* flag, + scoped_refptr task_runner) { + return base::Bind(&DoExpectValueSetFlagAndQuitTaskRunner, expected_value, + flag, task_runner); +} + +TEST_F(BindTaskRunnerTest, MethodCall) { + bool echo_called = false; + impl_->set_echo_handler( + base::Bind(&DoExpectValueSetFlagForwardValueAndQuitTaskRunner, + 1024, &echo_called, binding_task_runner_)); + bool echo_replied = false; + ptr_->Echo(1024, ExpectValueSetFlagAndQuitTaskRunner(1024, &echo_replied, + ptr_task_runner_)); + binding_task_runner_->Run(); + EXPECT_TRUE(echo_called); + ptr_task_runner_->Run(); + EXPECT_TRUE(echo_replied); +} + +TEST_F(BindTaskRunnerTest, BindingConnectionError) { + bool connection_error_called = false; + impl_->binding()->set_connection_error_handler( + SetFlagAndQuitTaskRunner(&connection_error_called, binding_task_runner_)); + ptr_.reset(); + binding_task_runner_->Run(); + EXPECT_TRUE(connection_error_called); +} + +TEST_F(BindTaskRunnerTest, PtrConnectionError) { + bool connection_error_called = false; + ptr_.set_connection_error_handler( + SetFlagAndQuitTaskRunner(&connection_error_called, ptr_task_runner_)); + impl_->binding()->Close(); + ptr_task_runner_->Run(); + EXPECT_TRUE(connection_error_called); +} + +void ExpectValueSetFlagAndForward(int32_t expected_value, + bool* flag, + int32_t value, + const IntegerSender::EchoCallback& callback) { + EXPECT_EQ(expected_value, value); + *flag = true; + callback.Run(value); +} + +TEST_F(AssociatedBindTaskRunnerTest, MethodCall) { + bool echo_called = false; + connection_impl_->sender_impl()->set_echo_handler( + base::Bind(&ExpectValueSetFlagAndForward, 1024, &echo_called)); + + bool echo_replied = false; + sender_ptr_->Echo( + 1024, ExpectValueSetFlagAndQuitTaskRunner(1024, &echo_replied, nullptr)); + + // The Echo request first arrives at the master endpoint's task runner, and + // then is forwarded to the associated endpoint's task runner. + connection_binding_task_runner_->RunOneTask(); + sender_binding_task_runner_->RunOneTask(); + EXPECT_TRUE(echo_called); + + // Similarly, the Echo response arrives at the master endpoint's task runner + // and then is forwarded to the associated endpoint's task runner. + connection_ptr_task_runner_->RunOneTask(); + sender_ptr_task_runner_->RunOneTask(); + EXPECT_TRUE(echo_replied); +} + +TEST_F(AssociatedBindTaskRunnerTest, BindingConnectionError) { + bool sender_impl_error = false; + connection_impl_->sender_impl()->binding()->set_connection_error_handler( + SetFlagAndQuitTaskRunner(&sender_impl_error, + sender_binding_task_runner_)); + bool connection_impl_error = false; + connection_impl_->binding()->set_connection_error_handler( + SetFlagAndQuitTaskRunner(&connection_impl_error, + connection_binding_task_runner_)); + bool sender_ptr_error = false; + sender_ptr_.set_connection_error_handler( + SetFlagAndQuitTaskRunner(&sender_ptr_error, sender_ptr_task_runner_)); + connection_ptr_.reset(); + sender_ptr_task_runner_->Run(); + EXPECT_TRUE(sender_ptr_error); + connection_binding_task_runner_->Run(); + EXPECT_TRUE(connection_impl_error); + sender_binding_task_runner_->Run(); + EXPECT_TRUE(sender_impl_error); +} + +TEST_F(AssociatedBindTaskRunnerTest, PtrConnectionError) { + bool sender_impl_error = false; + connection_impl_->sender_impl()->binding()->set_connection_error_handler( + SetFlagAndQuitTaskRunner(&sender_impl_error, + sender_binding_task_runner_)); + bool connection_ptr_error = false; + connection_ptr_.set_connection_error_handler( + SetFlagAndQuitTaskRunner(&connection_ptr_error, + connection_ptr_task_runner_)); + bool sender_ptr_error = false; + sender_ptr_.set_connection_error_handler( + SetFlagAndQuitTaskRunner(&sender_ptr_error, sender_ptr_task_runner_)); + connection_impl_->binding()->Close(); + sender_binding_task_runner_->Run(); + EXPECT_TRUE(sender_impl_error); + connection_ptr_task_runner_->Run(); + EXPECT_TRUE(connection_ptr_error); + sender_ptr_task_runner_->Run(); + EXPECT_TRUE(sender_ptr_error); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc b/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc new file mode 100644 index 0000000000..43122ceb4f --- /dev/null +++ b/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc @@ -0,0 +1,338 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/gtest_util.h" +#include "build/build_config.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// The tests in this file are designed to test the interaction between a +// Callback and its associated Binding. If a Callback is deleted before +// being used we DCHECK fail--unless the associated Binding has already +// been closed or deleted. This contract must be explained to the Mojo +// application developer. For example it is the developer's responsibility to +// ensure that the Binding is destroyed before an unused Callback is destroyed. +// +/////////////////////////////////////////////////////////////////////////////// + +namespace mojo { +namespace test { +namespace { + +void SaveValue(int32_t* storage, const base::Closure& closure, int32_t value) { + *storage = value; + if (!closure.is_null()) + closure.Run(); +} + +base::Callback BindValueSaver(int32_t* last_value_seen, + const base::Closure& closure) { + return base::Bind(&SaveValue, last_value_seen, closure); +} + +// An implementation of sample::Provider used on the server side. +// It only implements one of the methods: EchoInt(). +// All it does is save the values and Callbacks it sees. +class InterfaceImpl : public sample::Provider { + public: + InterfaceImpl() + : last_server_value_seen_(0), + callback_saved_(new EchoIntCallback) {} + + ~InterfaceImpl() override { + if (callback_saved_) { + delete callback_saved_; + } + } + + // Run's the callback previously saved from the last invocation + // of |EchoInt()|. + bool RunCallback() { + if (callback_saved_) { + callback_saved_->Run(last_server_value_seen_); + return true; + } + return false; + } + + // Delete's the previously saved callback. + void DeleteCallback() { + delete callback_saved_; + callback_saved_ = nullptr; + } + + // sample::Provider implementation + + // Saves its two input values in member variables and does nothing else. + void EchoInt(int32_t x, const EchoIntCallback& callback) override { + last_server_value_seen_ = x; + *callback_saved_ = callback; + if (!closure_.is_null()) { + closure_.Run(); + closure_.Reset(); + } + } + + void EchoString(const std::string& a, + const EchoStringCallback& callback) override { + CHECK(false) << "Not implemented."; + } + + void EchoStrings(const std::string& a, + const std::string& b, + const EchoStringsCallback& callback) override { + CHECK(false) << "Not implemented."; + } + + void EchoMessagePipeHandle( + ScopedMessagePipeHandle a, + const EchoMessagePipeHandleCallback& callback) override { + CHECK(false) << "Not implemented."; + } + + void EchoEnum(sample::Enum a, const EchoEnumCallback& callback) override { + CHECK(false) << "Not implemented."; + } + + void resetLastServerValueSeen() { last_server_value_seen_ = 0; } + + int32_t last_server_value_seen() const { return last_server_value_seen_; } + + void set_closure(const base::Closure& closure) { closure_ = closure; } + + private: + int32_t last_server_value_seen_; + EchoIntCallback* callback_saved_; + base::Closure closure_; +}; + +class BindingCallbackTest : public testing::Test { + public: + BindingCallbackTest() {} + ~BindingCallbackTest() override {} + + protected: + int32_t last_client_callback_value_seen_; + sample::ProviderPtr interface_ptr_; + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +// Tests that the InterfacePtr and the Binding can communicate with each +// other normally. +TEST_F(BindingCallbackTest, Basic) { + // Create the ServerImpl and the Binding. + InterfaceImpl server_impl; + Binding binding(&server_impl, MakeRequest(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop, run_loop2; + server_impl.set_closure(run_loop.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, + run_loop2.QuitClosure())); + run_loop.Run(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now run the Callback. + server_impl.RunCallback(); + run_loop2.Run(); + + // Check that the client has now seen the correct value. + EXPECT_EQ(7, last_client_callback_value_seen_); + + // Initialize the test values again. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method again. + base::RunLoop run_loop3, run_loop4; + server_impl.set_closure(run_loop3.QuitClosure()); + interface_ptr_->EchoInt( + 13, + BindValueSaver(&last_client_callback_value_seen_, + run_loop4.QuitClosure())); + run_loop3.Run(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(13, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now run the Callback again. + server_impl.RunCallback(); + run_loop4.Run(); + + // Check that the client has now seen the correct value again. + EXPECT_EQ(13, last_client_callback_value_seen_); +} + +// Tests that running the Callback after the Binding has been deleted +// results in a clean failure. +TEST_F(BindingCallbackTest, DeleteBindingThenRunCallback) { + // Create the ServerImpl. + InterfaceImpl server_impl; + base::RunLoop run_loop; + { + // Create the binding in an inner scope so it can be deleted first. + Binding binding(&server_impl, + MakeRequest(&interface_ptr_)); + interface_ptr_.set_connection_error_handler(run_loop.QuitClosure()); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop2; + server_impl.set_closure(run_loop2.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop2.Run(); + } + // The binding has now been destroyed and the pipe is closed. + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now try to run the Callback. This should do nothing since the pipe + // is closed. + EXPECT_TRUE(server_impl.RunCallback()); + PumpMessages(); + + // Check that the client has still not seen the correct value. + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Attempt to invoke the method again and confirm that an error was + // encountered. + interface_ptr_->EchoInt( + 13, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop.Run(); + EXPECT_TRUE(interface_ptr_.encountered_error()); +} + +// Tests that deleting a Callback without running it after the corresponding +// binding has already been deleted does not result in a crash. +TEST_F(BindingCallbackTest, DeleteBindingThenDeleteCallback) { + // Create the ServerImpl. + InterfaceImpl server_impl; + { + // Create the binding in an inner scope so it can be deleted first. + Binding binding(&server_impl, + MakeRequest(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop; + server_impl.set_closure(run_loop.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop.Run(); + } + // The binding has now been destroyed and the pipe is closed. + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Delete the callback without running it. This should not + // cause a problem because the insfrastructure can detect that the + // binding has already been destroyed and the pipe is closed. + server_impl.DeleteCallback(); +} + +// Tests that closing a Binding allows us to delete a callback +// without running it without encountering a crash. +TEST_F(BindingCallbackTest, CloseBindingBeforeDeletingCallback) { + // Create the ServerImpl and the Binding. + InterfaceImpl server_impl; + Binding binding(&server_impl, MakeRequest(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop; + server_impl.set_closure(run_loop.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop.Run(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now close the Binding. + binding.Close(); + + // Delete the callback without running it. This should not + // cause a crash because the insfrastructure can detect that the + // binding has already been closed. + server_impl.DeleteCallback(); + + // Check that the client has still not seen the correct value. + EXPECT_EQ(0, last_client_callback_value_seen_); +} + +// Tests that deleting a Callback without using it before the +// Binding has been destroyed or closed results in a DCHECK. +TEST_F(BindingCallbackTest, DeleteCallbackBeforeBindingDeathTest) { + // Create the ServerImpl and the Binding. + InterfaceImpl server_impl; + Binding binding(&server_impl, MakeRequest(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop; + server_impl.set_closure(run_loop.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop.Run(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + EXPECT_DCHECK_DEATH(server_impl.DeleteCallback()); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/binding_set_unittest.cc b/mojo/public/cpp/bindings/tests/binding_set_unittest.cc new file mode 100644 index 0000000000..07acfbebe0 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/binding_set_unittest.cc @@ -0,0 +1,416 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/associated_binding_set.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/strong_binding_set.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class BindingSetTest : public testing::Test { + public: + BindingSetTest() {} + ~BindingSetTest() override {} + + base::MessageLoop& loop() { return loop_; } + + private: + base::MessageLoop loop_; + + DISALLOW_COPY_AND_ASSIGN(BindingSetTest); +}; + +template +void ExpectContextHelper(BindingSetType* binding_set, + ContextType expected_context) { + EXPECT_EQ(expected_context, binding_set->dispatch_context()); +} + +template +base::Closure ExpectContext(BindingSetType* binding_set, + ContextType expected_context) { + return base::Bind( + &ExpectContextHelper, binding_set, + expected_context); +} + +base::Closure Sequence(const base::Closure& first, + const base::Closure& second) { + return base::Bind( + [] (const base::Closure& first, const base::Closure& second) { + first.Run(); + second.Run(); + }, first, second); +} + +class PingImpl : public PingService { + public: + PingImpl() {} + ~PingImpl() override {} + + void set_ping_handler(const base::Closure& handler) { + ping_handler_ = handler; + } + + private: + // PingService: + void Ping(const PingCallback& callback) override { + if (!ping_handler_.is_null()) + ping_handler_.Run(); + callback.Run(); + } + + base::Closure ping_handler_; +}; + +TEST_F(BindingSetTest, BindingSetContext) { + PingImpl impl; + + BindingSet bindings; + PingServicePtr ping_a, ping_b; + bindings.AddBinding(&impl, MakeRequest(&ping_a), 1); + bindings.AddBinding(&impl, MakeRequest(&ping_b), 2); + + { + impl.set_ping_handler(ExpectContext(&bindings, 1)); + base::RunLoop loop; + ping_a->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + impl.set_ping_handler(ExpectContext(&bindings, 2)); + base::RunLoop loop; + ping_b->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_connection_error_handler( + Sequence(ExpectContext(&bindings, 1), loop.QuitClosure())); + ping_a.reset(); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_connection_error_handler( + Sequence(ExpectContext(&bindings, 2), loop.QuitClosure())); + ping_b.reset(); + loop.Run(); + } + + EXPECT_TRUE(bindings.empty()); +} + +TEST_F(BindingSetTest, BindingSetConnectionErrorWithReason) { + PingImpl impl; + PingServicePtr ptr; + BindingSet bindings; + bindings.AddBinding(&impl, MakeRequest(&ptr)); + + base::RunLoop run_loop; + bindings.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(1024u, custom_reason); + EXPECT_EQ("bye", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(1024u, "bye"); +} + +class PingProviderImpl : public AssociatedPingProvider, public PingService { + public: + PingProviderImpl() {} + ~PingProviderImpl() override {} + + void set_new_ping_context(int context) { new_ping_context_ = context; } + + void set_new_ping_handler(const base::Closure& handler) { + new_ping_handler_ = handler; + } + + void set_ping_handler(const base::Closure& handler) { + ping_handler_ = handler; + } + + AssociatedBindingSet& ping_bindings() { + return ping_bindings_; + } + + private: + // AssociatedPingProvider: + void GetPing(PingServiceAssociatedRequest request) override { + ping_bindings_.AddBinding(this, std::move(request), new_ping_context_); + if (!new_ping_handler_.is_null()) + new_ping_handler_.Run(); + } + + // PingService: + void Ping(const PingCallback& callback) override { + if (!ping_handler_.is_null()) + ping_handler_.Run(); + callback.Run(); + } + + AssociatedBindingSet ping_bindings_; + int new_ping_context_ = -1; + base::Closure ping_handler_; + base::Closure new_ping_handler_; +}; + +TEST_F(BindingSetTest, AssociatedBindingSetContext) { + AssociatedPingProviderPtr provider; + PingProviderImpl impl; + Binding binding(&impl, MakeRequest(&provider)); + + PingServiceAssociatedPtr ping_a; + { + base::RunLoop loop; + impl.set_new_ping_context(1); + impl.set_new_ping_handler(loop.QuitClosure()); + provider->GetPing(MakeRequest(&ping_a)); + loop.Run(); + } + + PingServiceAssociatedPtr ping_b; + { + base::RunLoop loop; + impl.set_new_ping_context(2); + impl.set_new_ping_handler(loop.QuitClosure()); + provider->GetPing(MakeRequest(&ping_b)); + loop.Run(); + } + + { + impl.set_ping_handler(ExpectContext(&impl.ping_bindings(), 1)); + base::RunLoop loop; + ping_a->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + impl.set_ping_handler(ExpectContext(&impl.ping_bindings(), 2)); + base::RunLoop loop; + ping_b->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + base::RunLoop loop; + impl.ping_bindings().set_connection_error_handler( + Sequence(ExpectContext(&impl.ping_bindings(), 1), loop.QuitClosure())); + ping_a.reset(); + loop.Run(); + } + + { + base::RunLoop loop; + impl.ping_bindings().set_connection_error_handler( + Sequence(ExpectContext(&impl.ping_bindings(), 2), loop.QuitClosure())); + ping_b.reset(); + loop.Run(); + } + + EXPECT_TRUE(impl.ping_bindings().empty()); +} + +TEST_F(BindingSetTest, MasterInterfaceBindingSetContext) { + AssociatedPingProviderPtr provider_a, provider_b; + PingProviderImpl impl; + BindingSet bindings; + + bindings.AddBinding(&impl, MakeRequest(&provider_a), 1); + bindings.AddBinding(&impl, MakeRequest(&provider_b), 2); + + { + PingServiceAssociatedPtr ping; + base::RunLoop loop; + impl.set_new_ping_handler( + Sequence(ExpectContext(&bindings, 1), loop.QuitClosure())); + provider_a->GetPing(MakeRequest(&ping)); + loop.Run(); + } + + { + PingServiceAssociatedPtr ping; + base::RunLoop loop; + impl.set_new_ping_handler( + Sequence(ExpectContext(&bindings, 2), loop.QuitClosure())); + provider_b->GetPing(MakeRequest(&ping)); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_connection_error_handler( + Sequence(ExpectContext(&bindings, 1), loop.QuitClosure())); + provider_a.reset(); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_connection_error_handler( + Sequence(ExpectContext(&bindings, 2), loop.QuitClosure())); + provider_b.reset(); + loop.Run(); + } + + EXPECT_TRUE(bindings.empty()); +} + +TEST_F(BindingSetTest, PreDispatchHandler) { + PingImpl impl; + + BindingSet bindings; + PingServicePtr ping_a, ping_b; + bindings.AddBinding(&impl, MakeRequest(&ping_a), 1); + bindings.AddBinding(&impl, MakeRequest(&ping_b), 2); + + { + bindings.set_pre_dispatch_handler(base::Bind([] (const int& context) { + EXPECT_EQ(1, context); + })); + base::RunLoop loop; + ping_a->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + bindings.set_pre_dispatch_handler(base::Bind([] (const int& context) { + EXPECT_EQ(2, context); + })); + base::RunLoop loop; + ping_b->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_pre_dispatch_handler( + base::Bind([](base::RunLoop* loop, const int& context) { + EXPECT_EQ(1, context); + loop->Quit(); + }, &loop)); + ping_a.reset(); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_pre_dispatch_handler( + base::Bind([](base::RunLoop* loop, const int& context) { + EXPECT_EQ(2, context); + loop->Quit(); + }, &loop)); + ping_b.reset(); + loop.Run(); + } + + EXPECT_TRUE(bindings.empty()); +} + +TEST_F(BindingSetTest, AssociatedBindingSetConnectionErrorWithReason) { + AssociatedPingProviderPtr master_ptr; + PingProviderImpl master_impl; + Binding master_binding(&master_impl, &master_ptr); + + base::RunLoop run_loop; + master_impl.ping_bindings().set_connection_error_with_reason_handler( + base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(2048u, custom_reason); + EXPECT_EQ("bye", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + PingServiceAssociatedPtr ptr; + master_ptr->GetPing(MakeRequest(&ptr)); + + ptr.ResetWithReason(2048u, "bye"); + + run_loop.Run(); +} + +class PingInstanceCounter : public PingService { + public: + PingInstanceCounter() { ++instance_count; } + ~PingInstanceCounter() override { --instance_count; } + + void Ping(const PingCallback& callback) override {} + + static int instance_count; +}; +int PingInstanceCounter::instance_count = 0; + +TEST_F(BindingSetTest, StrongBinding_Destructor) { + PingServicePtr ping_a, ping_b; + auto bindings = base::MakeUnique>(); + + bindings->AddBinding(base::MakeUnique(), + mojo::MakeRequest(&ping_a)); + EXPECT_EQ(1, PingInstanceCounter::instance_count); + + bindings->AddBinding(base::MakeUnique(), + mojo::MakeRequest(&ping_b)); + EXPECT_EQ(2, PingInstanceCounter::instance_count); + + bindings.reset(); + EXPECT_EQ(0, PingInstanceCounter::instance_count); +} + +TEST_F(BindingSetTest, StrongBinding_ConnectionError) { + PingServicePtr ping_a, ping_b; + StrongBindingSet bindings; + bindings.AddBinding(base::MakeUnique(), + mojo::MakeRequest(&ping_a)); + bindings.AddBinding(base::MakeUnique(), + mojo::MakeRequest(&ping_b)); + EXPECT_EQ(2, PingInstanceCounter::instance_count); + + ping_a.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, PingInstanceCounter::instance_count); + + ping_b.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, PingInstanceCounter::instance_count); +} + +TEST_F(BindingSetTest, StrongBinding_RemoveBinding) { + PingServicePtr ping_a, ping_b; + StrongBindingSet bindings; + BindingId binding_id_a = bindings.AddBinding( + base::MakeUnique(), mojo::MakeRequest(&ping_a)); + BindingId binding_id_b = bindings.AddBinding( + base::MakeUnique(), mojo::MakeRequest(&ping_b)); + EXPECT_EQ(2, PingInstanceCounter::instance_count); + + EXPECT_TRUE(bindings.RemoveBinding(binding_id_a)); + EXPECT_EQ(1, PingInstanceCounter::instance_count); + + EXPECT_TRUE(bindings.RemoveBinding(binding_id_b)); + EXPECT_EQ(0, PingInstanceCounter::instance_count); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/binding_unittest.cc b/mojo/public/cpp/bindings/tests/binding_unittest.cc new file mode 100644 index 0000000000..e76993bb68 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/binding_unittest.cc @@ -0,0 +1,611 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Note: This file tests both binding.h (mojo::Binding) and strong_binding.h +// (mojo::StrongBinding). + +#include "mojo/public/cpp/bindings/binding.h" + +#include +#include + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +class BindingTestBase : public testing::Test { + public: + BindingTestBase() {} + ~BindingTestBase() override {} + + base::MessageLoop& loop() { return loop_; } + + private: + base::MessageLoop loop_; + + DISALLOW_COPY_AND_ASSIGN(BindingTestBase); +}; + +class ServiceImpl : public sample::Service { + public: + explicit ServiceImpl(bool* was_deleted = nullptr) + : was_deleted_(was_deleted) {} + ~ServiceImpl() override { + if (was_deleted_) + *was_deleted_ = true; + } + + private: + // sample::Service implementation + void Frobinate(sample::FooPtr foo, + BazOptions options, + sample::PortPtr port, + const FrobinateCallback& callback) override { + callback.Run(1); + } + void GetPort(InterfaceRequest port) override {} + + bool* const was_deleted_; + + DISALLOW_COPY_AND_ASSIGN(ServiceImpl); +}; + +template +void DoSetFlagAndRunClosure(bool* flag, + const base::Closure& closure, + Args... args) { + *flag = true; + if (!closure.is_null()) + closure.Run(); +} + +template +base::Callback SetFlagAndRunClosure( + bool* flag, + const base::Closure& callback = base::Closure()) { + return base::Bind(&DoSetFlagAndRunClosure, flag, callback); +} + +// BindingTest ----------------------------------------------------------------- + +using BindingTest = BindingTestBase; + +TEST_F(BindingTest, Close) { + bool called = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + base::RunLoop run_loop; + ptr.set_connection_error_handler( + SetFlagAndRunClosure(&called, run_loop.QuitClosure())); + ServiceImpl impl; + Binding binding(&impl, std::move(request)); + + binding.Close(); + EXPECT_FALSE(called); + run_loop.Run(); + EXPECT_TRUE(called); +} + +// Tests that destroying a mojo::Binding closes the bound message pipe handle. +TEST_F(BindingTest, DestroyClosesMessagePipe) { + bool encountered_error = false; + ServiceImpl impl; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + base::RunLoop run_loop; + ptr.set_connection_error_handler( + SetFlagAndRunClosure(&encountered_error, run_loop.QuitClosure())); + bool called = false; + base::RunLoop run_loop2; + { + Binding binding(&impl, std::move(request)); + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure(&called, + run_loop2.QuitClosure())); + run_loop2.Run(); + EXPECT_TRUE(called); + EXPECT_FALSE(encountered_error); + } + // Now that the Binding is out of scope we should detect an error on the other + // end of the pipe. + run_loop.Run(); + EXPECT_TRUE(encountered_error); + + // And calls should fail. + called = false; + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure(&called, + run_loop2.QuitClosure())); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(called); +} + +// Tests that the binding's connection error handler gets called when the other +// end is closed. +TEST_F(BindingTest, ConnectionError) { + bool called = false; + { + ServiceImpl impl; + sample::ServicePtr ptr; + Binding binding(&impl, MakeRequest(&ptr)); + base::RunLoop run_loop; + binding.set_connection_error_handler( + SetFlagAndRunClosure(&called, run_loop.QuitClosure())); + ptr.reset(); + EXPECT_FALSE(called); + run_loop.Run(); + EXPECT_TRUE(called); + // We want to make sure that it isn't called again during destruction. + called = false; + } + EXPECT_FALSE(called); +} + +// Tests that calling Close doesn't result in the connection error handler being +// called. +TEST_F(BindingTest, CloseDoesntCallConnectionErrorHandler) { + ServiceImpl impl; + sample::ServicePtr ptr; + Binding binding(&impl, MakeRequest(&ptr)); + bool called = false; + binding.set_connection_error_handler(SetFlagAndRunClosure(&called)); + binding.Close(); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(called); + + // We can also close the other end, and the error handler still won't be + // called. + ptr.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(called); +} + +class ServiceImplWithBinding : public ServiceImpl { + public: + ServiceImplWithBinding(bool* was_deleted, + const base::Closure& closure, + InterfaceRequest request) + : ServiceImpl(was_deleted), + binding_(this, std::move(request)), + closure_(closure) { + binding_.set_connection_error_handler( + base::Bind(&ServiceImplWithBinding::OnConnectionError, + base::Unretained(this))); + } + + private: + ~ServiceImplWithBinding() override{ + closure_.Run(); + } + + void OnConnectionError() { delete this; } + + Binding binding_; + base::Closure closure_; + + DISALLOW_COPY_AND_ASSIGN(ServiceImplWithBinding); +}; + +// Tests that the binding may be deleted in the connection error handler. +TEST_F(BindingTest, SelfDeleteOnConnectionError) { + bool was_deleted = false; + sample::ServicePtr ptr; + // This should delete itself on connection error. + base::RunLoop run_loop; + new ServiceImplWithBinding(&was_deleted, run_loop.QuitClosure(), + MakeRequest(&ptr)); + ptr.reset(); + EXPECT_FALSE(was_deleted); + run_loop.Run(); + EXPECT_TRUE(was_deleted); +} + +// Tests that explicitly calling Unbind followed by rebinding works. +TEST_F(BindingTest, Unbind) { + ServiceImpl impl; + sample::ServicePtr ptr; + Binding binding(&impl, MakeRequest(&ptr)); + + bool called = false; + base::RunLoop run_loop; + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure(&called, + run_loop.QuitClosure())); + run_loop.Run(); + EXPECT_TRUE(called); + + called = false; + auto request = binding.Unbind(); + EXPECT_FALSE(binding.is_bound()); + // All calls should fail when not bound... + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure(&called, + run_loop.QuitClosure())); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(called); + + called = false; + binding.Bind(std::move(request)); + EXPECT_TRUE(binding.is_bound()); + // ...and should succeed again when the rebound. + base::RunLoop run_loop2; + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure(&called, + run_loop2.QuitClosure())); + run_loop2.Run(); + EXPECT_TRUE(called); +} + +class IntegerAccessorImpl : public sample::IntegerAccessor { + public: + IntegerAccessorImpl() {} + ~IntegerAccessorImpl() override {} + + private: + // sample::IntegerAccessor implementation. + void GetInteger(const GetIntegerCallback& callback) override { + callback.Run(1, sample::Enum::VALUE); + } + void SetInteger(int64_t data, sample::Enum type) override {} + + DISALLOW_COPY_AND_ASSIGN(IntegerAccessorImpl); +}; + +TEST_F(BindingTest, SetInterfacePtrVersion) { + IntegerAccessorImpl impl; + sample::IntegerAccessorPtr ptr; + Binding binding(&impl, &ptr); + EXPECT_EQ(3u, ptr.version()); +} + +TEST_F(BindingTest, PauseResume) { + bool called = false; + base::RunLoop run_loop; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding binding(&impl, std::move(request)); + binding.PauseIncomingMethodCallProcessing(); + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure(&called, + run_loop.QuitClosure())); + EXPECT_FALSE(called); + base::RunLoop().RunUntilIdle(); + // Frobinate() should not be called as the binding is paused. + EXPECT_FALSE(called); + + // Resume the binding, which should trigger processing. + binding.ResumeIncomingMethodCallProcessing(); + run_loop.Run(); + EXPECT_TRUE(called); +} + +// Verifies the connection error handler is not run while a binding is paused. +TEST_F(BindingTest, ErrorHandleNotRunWhilePaused) { + bool called = false; + base::RunLoop run_loop; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding binding(&impl, std::move(request)); + binding.set_connection_error_handler( + SetFlagAndRunClosure(&called, run_loop.QuitClosure())); + binding.PauseIncomingMethodCallProcessing(); + + ptr.reset(); + base::RunLoop().RunUntilIdle(); + // The connection error handle should not be called as the binding is paused. + EXPECT_FALSE(called); + + // Resume the binding, which should trigger the error handler. + binding.ResumeIncomingMethodCallProcessing(); + run_loop.Run(); + EXPECT_TRUE(called); +} + +class PingServiceImpl : public test::PingService { + public: + PingServiceImpl() {} + ~PingServiceImpl() override {} + + // test::PingService: + void Ping(const PingCallback& callback) override { + if (!ping_handler_.is_null()) + ping_handler_.Run(); + callback.Run(); + } + + void set_ping_handler(const base::Closure& handler) { + ping_handler_ = handler; + } + + private: + base::Closure ping_handler_; + + DISALLOW_COPY_AND_ASSIGN(PingServiceImpl); +}; + +class CallbackFilter : public MessageReceiver { + public: + explicit CallbackFilter(const base::Closure& callback) + : callback_(callback) {} + ~CallbackFilter() override {} + + static std::unique_ptr Wrap(const base::Closure& callback) { + return base::MakeUnique(callback); + } + + // MessageReceiver: + bool Accept(Message* message) override { + callback_.Run(); + return true; + } + + private: + const base::Closure callback_; +}; + +// Verifies that message filters are notified in the order they were added and +// are always notified before a message is dispatched. +TEST_F(BindingTest, MessageFilter) { + test::PingServicePtr ptr; + PingServiceImpl impl; + mojo::Binding binding(&impl, MakeRequest(&ptr)); + + int status = 0; + auto handler_helper = [] (int* status, int expected_status, int new_status) { + EXPECT_EQ(expected_status, *status); + *status = new_status; + }; + auto create_handler = [&] (int expected_status, int new_status) { + return base::Bind(handler_helper, &status, expected_status, new_status); + }; + + binding.AddFilter(CallbackFilter::Wrap(create_handler(0, 1))); + binding.AddFilter(CallbackFilter::Wrap(create_handler(1, 2))); + impl.set_ping_handler(create_handler(2, 3)); + + for (int i = 0; i < 10; ++i) { + status = 0; + base::RunLoop loop; + ptr->Ping(loop.QuitClosure()); + loop.Run(); + EXPECT_EQ(3, status); + } +} + +void Fail() { + FAIL() << "Unexpected connection error"; +} + +TEST_F(BindingTest, FlushForTesting) { + bool called = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding binding(&impl, std::move(request)); + binding.set_connection_error_handler(base::Bind(&Fail)); + + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure(&called)); + EXPECT_FALSE(called); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + binding.FlushForTesting(); + binding.FlushForTesting(); + EXPECT_TRUE(called); +} + +TEST_F(BindingTest, FlushForTestingWithClosedPeer) { + bool called = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding binding(&impl, std::move(request)); + binding.set_connection_error_handler(SetFlagAndRunClosure(&called)); + ptr.reset(); + + EXPECT_FALSE(called); + binding.FlushForTesting(); + EXPECT_TRUE(called); + binding.FlushForTesting(); +} + +TEST_F(BindingTest, ConnectionErrorWithReason) { + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding binding(&impl, std::move(request)); + + base::RunLoop run_loop; + binding.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(1234u, custom_reason); + EXPECT_EQ("hello", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(1234u, "hello"); + + run_loop.Run(); +} + +template +struct WeakPtrImplRefTraits { + using PointerType = base::WeakPtr; + + static bool IsNull(const base::WeakPtr& ptr) { return !ptr; } + static T* GetRawPointer(base::WeakPtr* ptr) { return ptr->get(); } +}; + +template +using WeakBinding = Binding>; + +TEST_F(BindingTest, CustomImplPointerType) { + PingServiceImpl impl; + base::WeakPtrFactory weak_factory(&impl); + + test::PingServicePtr proxy; + WeakBinding binding(weak_factory.GetWeakPtr(), + MakeRequest(&proxy)); + + { + // Ensure the binding is functioning. + base::RunLoop run_loop; + proxy->Ping(run_loop.QuitClosure()); + run_loop.Run(); + } + + { + // Attempt to dispatch another message after the WeakPtr is invalidated. + base::Closure assert_not_reached = base::Bind([] { NOTREACHED(); }); + impl.set_ping_handler(assert_not_reached); + proxy->Ping(assert_not_reached); + + // The binding will close its end of the pipe which will trigger a + // connection error on |proxy|. + base::RunLoop run_loop; + proxy.set_connection_error_handler(run_loop.QuitClosure()); + weak_factory.InvalidateWeakPtrs(); + run_loop.Run(); + } +} + +// StrongBindingTest ----------------------------------------------------------- + +using StrongBindingTest = BindingTestBase; + +// Tests that destroying a mojo::StrongBinding closes the bound message pipe +// handle but does *not* destroy the implementation object. +TEST_F(StrongBindingTest, DestroyClosesMessagePipe) { + base::RunLoop run_loop; + bool encountered_error = false; + bool was_deleted = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ptr.set_connection_error_handler( + SetFlagAndRunClosure(&encountered_error, run_loop.QuitClosure())); + bool called = false; + base::RunLoop run_loop2; + + auto binding = MakeStrongBinding(base::MakeUnique(&was_deleted), + std::move(request)); + ptr->Frobinate( + nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure(&called, run_loop2.QuitClosure())); + run_loop2.Run(); + EXPECT_TRUE(called); + EXPECT_FALSE(encountered_error); + binding->Close(); + + // Now that the StrongBinding is closed we should detect an error on the other + // end of the pipe. + run_loop.Run(); + EXPECT_TRUE(encountered_error); + + // Destroying the StrongBinding also destroys the impl. + ASSERT_TRUE(was_deleted); +} + +// Tests the typical case, where the implementation object owns the +// StrongBinding (and should be destroyed on connection error). +TEST_F(StrongBindingTest, ConnectionErrorDestroysImpl) { + sample::ServicePtr ptr; + bool was_deleted = false; + // Will delete itself. + base::RunLoop run_loop; + new ServiceImplWithBinding(&was_deleted, run_loop.QuitClosure(), + MakeRequest(&ptr)); + + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(was_deleted); + + ptr.reset(); + EXPECT_FALSE(was_deleted); + run_loop.Run(); + EXPECT_TRUE(was_deleted); +} + +TEST_F(StrongBindingTest, FlushForTesting) { + bool called = false; + bool was_deleted = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + auto binding = MakeStrongBinding(base::MakeUnique(&was_deleted), + std::move(request)); + binding->set_connection_error_handler(base::Bind(&Fail)); + + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure(&called)); + EXPECT_FALSE(called); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + ASSERT_TRUE(binding); + binding->FlushForTesting(); + ASSERT_TRUE(binding); + binding->FlushForTesting(); + EXPECT_TRUE(called); + EXPECT_FALSE(was_deleted); + ptr.reset(); + ASSERT_TRUE(binding); + binding->set_connection_error_handler(base::Closure()); + binding->FlushForTesting(); + EXPECT_TRUE(was_deleted); +} + +TEST_F(StrongBindingTest, FlushForTestingWithClosedPeer) { + bool called = false; + bool was_deleted = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + auto binding = MakeStrongBinding(base::MakeUnique(&was_deleted), + std::move(request)); + binding->set_connection_error_handler(SetFlagAndRunClosure(&called)); + ptr.reset(); + + EXPECT_FALSE(called); + EXPECT_FALSE(was_deleted); + ASSERT_TRUE(binding); + binding->FlushForTesting(); + EXPECT_TRUE(called); + EXPECT_TRUE(was_deleted); + ASSERT_FALSE(binding); +} + +TEST_F(StrongBindingTest, ConnectionErrorWithReason) { + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + auto binding = + MakeStrongBinding(base::MakeUnique(), std::move(request)); + base::RunLoop run_loop; + binding->set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(5678u, custom_reason); + EXPECT_EQ("hello", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(5678u, "hello"); + + run_loop.Run(); +} + +} // namespace +} // mojo diff --git a/mojo/public/cpp/bindings/tests/bindings_perftest.cc b/mojo/public/cpp/bindings/tests/bindings_perftest.cc new file mode 100644 index 0000000000..65b3c8c1d4 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/bindings_perftest.cc @@ -0,0 +1,286 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +const double kMojoTicksPerSecond = 1000000.0; + +double MojoTicksToSeconds(MojoTimeTicks ticks) { + return ticks / kMojoTicksPerSecond; +} + +class PingServiceImpl : public test::PingService { + public: + PingServiceImpl() {} + ~PingServiceImpl() override {} + + // |PingService| methods: + void Ping(const PingCallback& callback) override; + + private: + DISALLOW_COPY_AND_ASSIGN(PingServiceImpl); +}; + +void PingServiceImpl::Ping(const PingCallback& callback) { + callback.Run(); +} + +class PingPongTest { + public: + explicit PingPongTest(test::PingServicePtr service); + + void Run(unsigned int iterations); + + private: + void OnPingDone(); + + test::PingServicePtr service_; + unsigned int iterations_to_run_; + unsigned int current_iterations_; + + base::Closure quit_closure_; + + DISALLOW_COPY_AND_ASSIGN(PingPongTest); +}; + +PingPongTest::PingPongTest(test::PingServicePtr service) + : service_(std::move(service)) {} + +void PingPongTest::Run(unsigned int iterations) { + iterations_to_run_ = iterations; + current_iterations_ = 0; + + base::RunLoop run_loop; + quit_closure_ = run_loop.QuitClosure(); + service_->Ping(base::Bind(&PingPongTest::OnPingDone, base::Unretained(this))); + run_loop.Run(); +} + +void PingPongTest::OnPingDone() { + current_iterations_++; + if (current_iterations_ >= iterations_to_run_) { + quit_closure_.Run(); + return; + } + + service_->Ping(base::Bind(&PingPongTest::OnPingDone, base::Unretained(this))); +} + +struct BoundPingService { + BoundPingService() : binding(&impl) { binding.Bind(MakeRequest(&service)); } + + PingServiceImpl impl; + test::PingServicePtr service; + Binding binding; +}; + +class MojoBindingsPerftest : public testing::Test { + public: + MojoBindingsPerftest() {} + + protected: + base::MessageLoop loop_; +}; + +TEST_F(MojoBindingsPerftest, InProcessPingPong) { + test::PingServicePtr service; + PingServiceImpl impl; + Binding binding(&impl, MakeRequest(&service)); + PingPongTest test(std::move(service)); + + { + const unsigned int kIterations = 100000; + const MojoTimeTicks start_time = MojoGetTimeTicksNow(); + test.Run(kIterations); + const MojoTimeTicks end_time = MojoGetTimeTicksNow(); + test::LogPerfResult( + "InProcessPingPong", "0_Inactive", + kIterations / MojoTicksToSeconds(end_time - start_time), + "pings/second"); + } + + { + const size_t kNumInactiveServices = 1000; + BoundPingService* inactive_services = + new BoundPingService[kNumInactiveServices]; + + const unsigned int kIterations = 10000; + const MojoTimeTicks start_time = MojoGetTimeTicksNow(); + test.Run(kIterations); + const MojoTimeTicks end_time = MojoGetTimeTicksNow(); + test::LogPerfResult( + "InProcessPingPong", "1000_Inactive", + kIterations / MojoTicksToSeconds(end_time - start_time), + "pings/second"); + + delete[] inactive_services; + } +} + +class PingPongPaddle : public MessageReceiverWithResponderStatus { + public: + PingPongPaddle(MessageReceiver* sender) : sender_(sender) {} + + void set_sender(MessageReceiver* sender) { sender_ = sender; } + + bool Accept(Message* message) override { + uint32_t count = message->header()->name; + if (!quit_closure_.is_null()) { + count++; + if (count >= expected_count_) { + end_time_ = base::TimeTicks::Now(); + quit_closure_.Run(); + return true; + } + } + + internal::MessageBuilder builder(count, 0, 8, 0); + bool result = sender_->Accept(builder.message()); + DCHECK(result); + return true; + } + + bool AcceptWithResponder( + Message* message, + std::unique_ptr responder) override { + NOTREACHED(); + return true; + } + + base::TimeDelta Serve(uint32_t expected_count) { + base::RunLoop run_loop; + + expected_count_ = expected_count; + quit_closure_ = run_loop.QuitClosure(); + + start_time_ = base::TimeTicks::Now(); + internal::MessageBuilder builder(0, 0, 8, 0); + bool result = sender_->Accept(builder.message()); + DCHECK(result); + + run_loop.Run(); + + return end_time_ - start_time_; + } + + private: + base::TimeTicks start_time_; + base::TimeTicks end_time_; + uint32_t expected_count_ = 0; + MessageReceiver* sender_; + base::Closure quit_closure_; +}; + +TEST_F(MojoBindingsPerftest, MultiplexRouterPingPong) { + MessagePipe pipe; + scoped_refptr router0( + new internal::MultiplexRouter(std::move(pipe.handle0), + internal::MultiplexRouter::SINGLE_INTERFACE, + true, base::ThreadTaskRunnerHandle::Get())); + scoped_refptr router1( + new internal::MultiplexRouter( + std::move(pipe.handle1), internal::MultiplexRouter::SINGLE_INTERFACE, + false, base::ThreadTaskRunnerHandle::Get())); + + PingPongPaddle paddle0(nullptr); + PingPongPaddle paddle1(nullptr); + + InterfaceEndpointClient client0( + router0->CreateLocalEndpointHandle(kMasterInterfaceId), &paddle0, nullptr, + false, base::ThreadTaskRunnerHandle::Get(), 0u); + InterfaceEndpointClient client1( + router1->CreateLocalEndpointHandle(kMasterInterfaceId), &paddle1, nullptr, + false, base::ThreadTaskRunnerHandle::Get(), 0u); + + paddle0.set_sender(&client0); + paddle1.set_sender(&client1); + + static const uint32_t kWarmUpIterations = 1000; + static const uint32_t kTestIterations = 1000000; + + paddle0.Serve(kWarmUpIterations); + + base::TimeDelta duration = paddle0.Serve(kTestIterations); + + test::LogPerfResult("MultiplexRouterPingPong", nullptr, + kTestIterations / duration.InSecondsF(), "pings/second"); +} + +class CounterReceiver : public MessageReceiverWithResponderStatus { + public: + bool Accept(Message* message) override { + counter_++; + return true; + } + + bool AcceptWithResponder( + Message* message, + std::unique_ptr responder) override { + NOTREACHED(); + return true; + } + + uint32_t counter() const { return counter_; } + + void Reset() { counter_ = 0; } + + private: + uint32_t counter_ = 0; +}; + +TEST_F(MojoBindingsPerftest, MultiplexRouterDispatchCost) { + MessagePipe pipe; + scoped_refptr router(new internal::MultiplexRouter( + std::move(pipe.handle0), internal::MultiplexRouter::SINGLE_INTERFACE, + true, base::ThreadTaskRunnerHandle::Get())); + CounterReceiver receiver; + InterfaceEndpointClient client( + router->CreateLocalEndpointHandle(kMasterInterfaceId), &receiver, nullptr, + false, base::ThreadTaskRunnerHandle::Get(), 0u); + + static const uint32_t kIterations[] = {1000, 3000000}; + + for (size_t i = 0; i < 2; ++i) { + receiver.Reset(); + base::TimeTicks start_time = base::TimeTicks::Now(); + for (size_t j = 0; j < kIterations[i]; ++j) { + internal::MessageBuilder builder(0, 0, 8, 0); + bool result = + router->SimulateReceivingMessageForTesting(builder.message()); + DCHECK(result); + } + + base::TimeTicks end_time = base::TimeTicks::Now(); + base::TimeDelta duration = end_time - start_time; + CHECK_EQ(kIterations[i], receiver.counter()); + + if (i == 1) { + test::LogPerfResult("MultiplexRouterDispatchCost", nullptr, + kIterations[i] / duration.InSecondsF(), + "times/second"); + } + } +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/blink_typemaps.gni b/mojo/public/cpp/bindings/tests/blink_typemaps.gni new file mode 100644 index 0000000000..b71dcf8d46 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/blink_typemaps.gni @@ -0,0 +1,8 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +typemaps = [ + "//mojo/public/cpp/bindings/tests/rect_blink.typemap", + "//mojo/public/cpp/bindings/tests/test_native_types_blink.typemap", +] diff --git a/mojo/public/cpp/bindings/tests/buffer_unittest.cc b/mojo/public/cpp/bindings/tests/buffer_unittest.cc new file mode 100644 index 0000000000..d75bdd0785 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/buffer_unittest.cc @@ -0,0 +1,93 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +bool IsZero(void* p_buf, size_t size) { + char* buf = reinterpret_cast(p_buf); + for (size_t i = 0; i < size; ++i) { + if (buf[i] != 0) + return false; + } + return true; +} + +// Tests that FixedBuffer allocates memory aligned to 8 byte boundaries. +TEST(FixedBufferTest, Alignment) { + internal::FixedBufferForTesting buf(internal::Align(10) * 2); + ASSERT_EQ(buf.size(), 16u * 2); + + void* a = buf.Allocate(10); + ASSERT_TRUE(a); + EXPECT_TRUE(IsZero(a, 10)); + EXPECT_EQ(0, reinterpret_cast(a) % 8); + + void* b = buf.Allocate(10); + ASSERT_TRUE(b); + EXPECT_TRUE(IsZero(b, 10)); + EXPECT_EQ(0, reinterpret_cast(b) % 8); + + // Any more allocations would result in an assert, but we can't test that. +} + +// Tests that FixedBufferForTesting::Leak passes ownership to the caller. +TEST(FixedBufferTest, Leak) { + void* ptr = nullptr; + void* buf_ptr = nullptr; + { + internal::FixedBufferForTesting buf(8); + ASSERT_EQ(8u, buf.size()); + + ptr = buf.Allocate(8); + ASSERT_TRUE(ptr); + buf_ptr = buf.Leak(); + + // The buffer should point to the first element allocated. + // TODO(mpcomplete): Is this a reasonable expectation? + EXPECT_EQ(ptr, buf_ptr); + + // The FixedBufferForTesting should be empty now. + EXPECT_EQ(0u, buf.size()); + EXPECT_FALSE(buf.Leak()); + } + + // Since we called Leak, ptr is still writable after FixedBufferForTesting + // went out of scope. + memset(ptr, 1, 8); + free(buf_ptr); +} + +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) +TEST(FixedBufferTest, TooBig) { + internal::FixedBufferForTesting buf(24); + + // A little bit too large. + EXPECT_EQ(reinterpret_cast(0), buf.Allocate(32)); + + // Move the cursor forward. + EXPECT_NE(reinterpret_cast(0), buf.Allocate(16)); + + // A lot too large. + EXPECT_EQ(reinterpret_cast(0), + buf.Allocate(std::numeric_limits::max() - 1024u)); + + // A lot too large, leading to possible integer overflow. + EXPECT_EQ(reinterpret_cast(0), + buf.Allocate(std::numeric_limits::max() - 8u)); +} +#endif + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/chromium_typemaps.gni b/mojo/public/cpp/bindings/tests/chromium_typemaps.gni new file mode 100644 index 0000000000..1da7cbfa3e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/chromium_typemaps.gni @@ -0,0 +1,9 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +typemaps = [ + "//mojo/public/cpp/bindings/tests/rect_chromium.typemap", + "//mojo/public/cpp/bindings/tests/struct_with_traits.typemap", + "//mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap", +] diff --git a/mojo/public/cpp/bindings/tests/connector_unittest.cc b/mojo/public/cpp/bindings/tests/connector_unittest.cc new file mode 100644 index 0000000000..74ecb7a9ee --- /dev/null +++ b/mojo/public/cpp/bindings/tests/connector_unittest.cc @@ -0,0 +1,599 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/connector.h" + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/tests/message_queue.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class MessageAccumulator : public MessageReceiver { + public: + MessageAccumulator() {} + explicit MessageAccumulator(const base::Closure& closure) + : closure_(closure) {} + + bool Accept(Message* message) override { + queue_.Push(message); + if (!closure_.is_null()) + base::ResetAndReturn(&closure_).Run(); + return true; + } + + bool IsEmpty() const { return queue_.IsEmpty(); } + + void Pop(Message* message) { queue_.Pop(message); } + + void set_closure(const base::Closure& closure) { closure_ = closure; } + + size_t size() const { return queue_.size(); } + + private: + MessageQueue queue_; + base::Closure closure_; +}; + +class ConnectorDeletingMessageAccumulator : public MessageAccumulator { + public: + ConnectorDeletingMessageAccumulator(Connector** connector) + : connector_(connector) {} + + bool Accept(Message* message) override { + delete *connector_; + *connector_ = nullptr; + return MessageAccumulator::Accept(message); + } + + private: + Connector** connector_; +}; + +class ReentrantMessageAccumulator : public MessageAccumulator { + public: + ReentrantMessageAccumulator(Connector* connector) + : connector_(connector), number_of_calls_(0) {} + + bool Accept(Message* message) override { + if (!MessageAccumulator::Accept(message)) + return false; + number_of_calls_++; + if (number_of_calls_ == 1) { + return connector_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + } + return true; + } + + int number_of_calls() { return number_of_calls_; } + + private: + Connector* connector_; + int number_of_calls_; +}; + +class ConnectorTest : public testing::Test { + public: + ConnectorTest() {} + + void SetUp() override { + CreateMessagePipe(nullptr, &handle0_, &handle1_); + } + + void TearDown() override {} + + void AllocMessage(const char* text, Message* message) { + size_t payload_size = strlen(text) + 1; // Plus null terminator. + internal::MessageBuilder builder(1, 0, payload_size, 0); + memcpy(builder.buffer()->Allocate(payload_size), text, payload_size); + + *message = std::move(*builder.message()); + } + + protected: + ScopedMessagePipeHandle handle0_; + ScopedMessagePipeHandle handle1_; + + private: + base::MessageLoop loop_; +}; + +TEST_F(ConnectorTest, Basic) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + base::RunLoop run_loop; + MessageAccumulator accumulator(run_loop.QuitClosure()); + connector1.set_incoming_receiver(&accumulator); + + run_loop.Run(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast(message_received.payload()))); +} + +TEST_F(ConnectorTest, Basic_Synchronous) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + connector1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast(message_received.payload()))); +} + +TEST_F(ConnectorTest, Basic_EarlyIncomingReceiver) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + base::RunLoop run_loop; + MessageAccumulator accumulator(run_loop.QuitClosure()); + connector1.set_incoming_receiver(&accumulator); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + run_loop.Run(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast(message_received.payload()))); +} + +TEST_F(ConnectorTest, Basic_TwoMessages) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char* kText[] = {"hello", "world"}; + + for (size_t i = 0; i < arraysize(kText); ++i) { + Message message; + AllocMessage(kText[i], &message); + + connector0.Accept(&message); + } + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + for (size_t i = 0; i < arraysize(kText); ++i) { + if (accumulator.IsEmpty()) { + base::RunLoop run_loop; + accumulator.set_closure(run_loop.QuitClosure()); + run_loop.Run(); + } + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText[i]), + std::string(reinterpret_cast(message_received.payload()))); + } +} + +TEST_F(ConnectorTest, Basic_TwoMessages_Synchronous) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char* kText[] = {"hello", "world"}; + + for (size_t i = 0; i < arraysize(kText); ++i) { + Message message; + AllocMessage(kText[i], &message); + + connector0.Accept(&message); + } + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + connector1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText[0]), + std::string(reinterpret_cast(message_received.payload()))); + + ASSERT_TRUE(accumulator.IsEmpty()); +} + +TEST_F(ConnectorTest, WriteToClosedPipe) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + // Close the other end of the pipe. + handle1_.reset(); + + // Not observed yet because we haven't spun the message loop yet. + EXPECT_FALSE(connector0.encountered_error()); + + // Write failures are not reported. + bool ok = connector0.Accept(&message); + EXPECT_TRUE(ok); + + // Still not observed. + EXPECT_FALSE(connector0.encountered_error()); + + // Spin the message loop, and then we should start observing the closed pipe. + base::RunLoop run_loop; + connector0.set_connection_error_handler(run_loop.QuitClosure()); + run_loop.Run(); + + EXPECT_TRUE(connector0.encountered_error()); +} + +TEST_F(ConnectorTest, MessageWithHandles) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message1; + AllocMessage(kText, &message1); + + MessagePipe pipe; + message1.mutable_handles()->push_back(pipe.handle0.release()); + + connector0.Accept(&message1); + + // The message should have been transferred, releasing the handles. + EXPECT_TRUE(message1.handles()->empty()); + + base::RunLoop run_loop; + MessageAccumulator accumulator(run_loop.QuitClosure()); + connector1.set_incoming_receiver(&accumulator); + + run_loop.Run(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast(message_received.payload()))); + ASSERT_EQ(1U, message_received.handles()->size()); + + // Now send a message to the transferred handle and confirm it's sent through + // to the orginal pipe. + // TODO(vtl): Do we need a better way of "downcasting" the handle types? + ScopedMessagePipeHandle smph; + smph.reset(MessagePipeHandle(message_received.handles()->front().value())); + message_received.mutable_handles()->front() = Handle(); + // |smph| now owns this handle. + + Connector connector_received(std::move(smph), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector_original(std::move(pipe.handle1), + Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + Message message2; + AllocMessage(kText, &message2); + + connector_received.Accept(&message2); + base::RunLoop run_loop2; + MessageAccumulator accumulator2(run_loop2.QuitClosure()); + connector_original.set_incoming_receiver(&accumulator2); + run_loop2.Run(); + + ASSERT_FALSE(accumulator2.IsEmpty()); + + accumulator2.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast(message_received.payload()))); +} + +TEST_F(ConnectorTest, WaitForIncomingMessageWithError) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + // Close the other end of the pipe. + handle1_.reset(); + ASSERT_FALSE(connector0.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE)); +} + +TEST_F(ConnectorTest, WaitForIncomingMessageWithDeletion) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector* connector1 = + new Connector(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + ConnectorDeletingMessageAccumulator accumulator(&connector1); + connector1->set_incoming_receiver(&accumulator); + + connector1->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + ASSERT_FALSE(connector1); + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast(message_received.payload()))); +} + +TEST_F(ConnectorTest, WaitForIncomingMessageWithReentrancy) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char* kText[] = {"hello", "world"}; + + for (size_t i = 0; i < arraysize(kText); ++i) { + Message message; + AllocMessage(kText[i], &message); + + connector0.Accept(&message); + } + + ReentrantMessageAccumulator accumulator(&connector1); + connector1.set_incoming_receiver(&accumulator); + + for (size_t i = 0; i < arraysize(kText); ++i) { + if (accumulator.IsEmpty()) { + base::RunLoop run_loop; + accumulator.set_closure(run_loop.QuitClosure()); + run_loop.Run(); + } + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText[i]), + std::string(reinterpret_cast(message_received.payload()))); + } + + ASSERT_EQ(2, accumulator.number_of_calls()); +} + +void ForwardErrorHandler(bool* called, const base::Closure& callback) { + *called = true; + callback.Run(); +} + +TEST_F(ConnectorTest, RaiseError) { + base::RunLoop run_loop, run_loop2; + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + bool error_handler_called0 = false; + connector0.set_connection_error_handler( + base::Bind(&ForwardErrorHandler, &error_handler_called0, + run_loop.QuitClosure())); + + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + bool error_handler_called1 = false; + connector1.set_connection_error_handler( + base::Bind(&ForwardErrorHandler, &error_handler_called1, + run_loop2.QuitClosure())); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + connector0.RaiseError(); + + base::RunLoop run_loop3; + MessageAccumulator accumulator(run_loop3.QuitClosure()); + connector1.set_incoming_receiver(&accumulator); + + run_loop3.Run(); + + // Messages sent prior to RaiseError() still arrive at the other end. + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast(message_received.payload()))); + + run_loop.Run(); + run_loop2.Run(); + + // Connection error handler is called at both sides. + EXPECT_TRUE(error_handler_called0); + EXPECT_TRUE(error_handler_called1); + + // The error flag is set at both sides. + EXPECT_TRUE(connector0.encountered_error()); + EXPECT_TRUE(connector1.encountered_error()); + + // The message pipe handle is valid at both sides. + EXPECT_TRUE(connector0.is_valid()); + EXPECT_TRUE(connector1.is_valid()); +} + +void PauseConnectorAndRunClosure(Connector* connector, + const base::Closure& closure) { + connector->PauseIncomingMethodCallProcessing(); + closure.Run(); +} + +TEST_F(ConnectorTest, PauseWithQueuedMessages) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + // Queue up two messages. + Message message; + AllocMessage(kText, &message); + connector0.Accept(&message); + AllocMessage(kText, &message); + connector0.Accept(&message); + + base::RunLoop run_loop; + // Configure the accumulator such that it pauses after the first message is + // received. + MessageAccumulator accumulator( + base::Bind(&PauseConnectorAndRunClosure, &connector1, + run_loop.QuitClosure())); + connector1.set_incoming_receiver(&accumulator); + + run_loop.Run(); + + // As we paused after the first message we should only have gotten one + // message. + ASSERT_EQ(1u, accumulator.size()); +} + +void AccumulateWithNestedLoop(MessageAccumulator* accumulator, + const base::Closure& closure) { + base::RunLoop nested_run_loop; + base::MessageLoop::ScopedNestableTaskAllower allow( + base::MessageLoop::current()); + accumulator->set_closure(nested_run_loop.QuitClosure()); + nested_run_loop.Run(); + closure.Run(); +} + +TEST_F(ConnectorTest, ProcessWhenNested) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + // Queue up two messages. + Message message; + AllocMessage(kText, &message); + connector0.Accept(&message); + AllocMessage(kText, &message); + connector0.Accept(&message); + + base::RunLoop run_loop; + MessageAccumulator accumulator; + // When the accumulator gets the first message it spins a nested message + // loop. The loop is quit when another message is received. + accumulator.set_closure(base::Bind(&AccumulateWithNestedLoop, &accumulator, + run_loop.QuitClosure())); + connector1.set_incoming_receiver(&accumulator); + + run_loop.Run(); + + ASSERT_EQ(2u, accumulator.size()); +} + +TEST_F(ConnectorTest, DestroyOnDifferentThreadAfterClose) { + std::unique_ptr connector( + new Connector(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get())); + + connector->CloseMessagePipe(); + + base::Thread another_thread("ThreadForDestroyingConnector"); + another_thread.Start(); + + base::RunLoop run_loop; + another_thread.task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind( + [](std::unique_ptr connector) { connector.reset(); }, + base::Passed(std::move(connector))), + run_loop.QuitClosure()); + + run_loop.Run(); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/constant_unittest.cc b/mojo/public/cpp/bindings/tests/constant_unittest.cc new file mode 100644 index 0000000000..caa6464cf4 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/constant_unittest.cc @@ -0,0 +1,60 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/strings/string_piece.h" +#include "mojo/public/interfaces/bindings/tests/test_constants.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +TEST(ConstantTest, GlobalConstants) { + // Compile-time constants. + static_assert(kBoolValue == true, ""); + static_assert(kInt8Value == -2, ""); + static_assert(kUint8Value == 128U, ""); + static_assert(kInt16Value == -233, ""); + static_assert(kUint16Value == 44204U, ""); + static_assert(kInt32Value == -44204, ""); + static_assert(kUint32Value == 4294967295U, ""); + static_assert(kInt64Value == -9223372036854775807, ""); + static_assert(kUint64Value == 9999999999999999999ULL, ""); + static_assert(kDoubleValue == 3.14159, ""); + static_assert(kFloatValue == 2.71828f, ""); + + EXPECT_EQ(base::StringPiece(kStringValue), "test string contents"); + EXPECT_TRUE(std::isnan(kDoubleNaN)); + EXPECT_TRUE(std::isinf(kDoubleInfinity)); + EXPECT_TRUE(std::isinf(kDoubleNegativeInfinity)); + EXPECT_NE(kDoubleInfinity, kDoubleNegativeInfinity); + EXPECT_TRUE(std::isnan(kFloatNaN)); + EXPECT_TRUE(std::isinf(kFloatInfinity)); + EXPECT_TRUE(std::isinf(kFloatNegativeInfinity)); + EXPECT_NE(kFloatInfinity, kFloatNegativeInfinity); +} + +TEST(ConstantTest, StructConstants) { + // Compile-time constants. + static_assert(StructWithConstants::kInt8Value == 5U, ""); + static_assert(StructWithConstants::kFloatValue == 765.432f, ""); + + EXPECT_EQ(base::StringPiece(StructWithConstants::kStringValue), + "struct test string contents"); +} + +TEST(ConstantTest, InterfaceConstants) { + // Compile-time constants. + static_assert(InterfaceWithConstants::kUint32Value == 20100722, ""); + static_assert(InterfaceWithConstants::kDoubleValue == 12.34567, ""); + + EXPECT_EQ(base::StringPiece(InterfaceWithConstants::kStringValue), + "interface test string contents"); + EXPECT_EQ(base::StringPiece(InterfaceWithConstants::Name_), + "mojo::test::InterfaceWithConstants"); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/container_test_util.cc b/mojo/public/cpp/bindings/tests/container_test_util.cc new file mode 100644 index 0000000000..a53d351e0c --- /dev/null +++ b/mojo/public/cpp/bindings/tests/container_test_util.cc @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "mojo/public/cpp/bindings/tests/container_test_util.h" + +namespace mojo { + +size_t CopyableType::num_instances_ = 0; +size_t MoveOnlyType::num_instances_ = 0; + +CopyableType::CopyableType() : copied_(false), ptr_(this) { + num_instances_++; +} + +CopyableType::CopyableType(const CopyableType& other) + : copied_(true), ptr_(other.ptr()) { + num_instances_++; +} + +CopyableType& CopyableType::operator=(const CopyableType& other) { + copied_ = true; + ptr_ = other.ptr(); + return *this; +} + +CopyableType::~CopyableType() { + num_instances_--; +} + +MoveOnlyType::MoveOnlyType() : moved_(false), ptr_(this) { + num_instances_++; +} + +MoveOnlyType::MoveOnlyType(MoveOnlyType&& other) + : moved_(true), ptr_(other.ptr()) { + num_instances_++; +} + +MoveOnlyType& MoveOnlyType::operator=(MoveOnlyType&& other) { + moved_ = true; + ptr_ = other.ptr(); + return *this; +} + +MoveOnlyType::~MoveOnlyType() { + num_instances_--; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/container_test_util.h b/mojo/public/cpp/bindings/tests/container_test_util.h new file mode 100644 index 0000000000..f709c1561e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/container_test_util.h @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_ + +#include + +#include "base/macros.h" + +namespace mojo { + +class CopyableType { + public: + CopyableType(); + CopyableType(const CopyableType& other); + CopyableType& operator=(const CopyableType& other); + ~CopyableType(); + + bool copied() const { return copied_; } + static size_t num_instances() { return num_instances_; } + CopyableType* ptr() const { return ptr_; } + void ResetCopied() { copied_ = false; } + + private: + bool copied_; + static size_t num_instances_; + CopyableType* ptr_; +}; + +class MoveOnlyType { + public: + typedef MoveOnlyType Data_; + MoveOnlyType(); + MoveOnlyType(MoveOnlyType&& other); + MoveOnlyType& operator=(MoveOnlyType&& other); + ~MoveOnlyType(); + + bool moved() const { return moved_; } + static size_t num_instances() { return num_instances_; } + MoveOnlyType* ptr() const { return ptr_; } + void ResetMoved() { moved_ = false; } + + private: + bool moved_; + static size_t num_instances_; + MoveOnlyType* ptr_; + + DISALLOW_COPY_AND_ASSIGN(MoveOnlyType); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_ diff --git a/mojo/public/cpp/bindings/tests/data_view_unittest.cc b/mojo/public/cpp/bindings/tests/data_view_unittest.cc new file mode 100644 index 0000000000..0ebfda5d12 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/data_view_unittest.cc @@ -0,0 +1,303 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "base/message_loop/message_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/interfaces/bindings/tests/test_data_view.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace data_view { +namespace { + +class DataViewTest : public testing::Test { + private: + base::MessageLoop message_loop_; +}; + +struct DataViewHolder { + std::unique_ptr data_view; + std::unique_ptr buf; + mojo::internal::SerializationContext context; +}; + +std::unique_ptr SerializeTestStruct(TestStructPtr input) { + std::unique_ptr result(new DataViewHolder); + + size_t size = mojo::internal::PrepareToSerialize( + input, &result->context); + + result->buf.reset(new mojo::internal::FixedBufferForTesting(size)); + internal::TestStruct_Data* data = nullptr; + mojo::internal::Serialize(input, result->buf.get(), &data, + &result->context); + + result->data_view.reset(new TestStructDataView(data, &result->context)); + return result; +} + +class TestInterfaceImpl : public TestInterface { + public: + explicit TestInterfaceImpl(TestInterfaceRequest request) + : binding_(this, std::move(request)) {} + ~TestInterfaceImpl() override {} + + // TestInterface implementation: + void Echo(int32_t value, const EchoCallback& callback) override { + callback.Run(value); + } + + private: + Binding binding_; +}; + +} // namespace + +TEST_F(DataViewTest, String) { + TestStructPtr obj(TestStruct::New()); + obj->f_string = "hello"; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + StringDataView string_data_view; + data_view.GetFStringDataView(&string_data_view); + + ASSERT_FALSE(string_data_view.is_null()); + EXPECT_EQ(std::string("hello"), + std::string(string_data_view.storage(), string_data_view.size())); +} + +TEST_F(DataViewTest, NestedStruct) { + TestStructPtr obj(TestStruct::New()); + obj->f_struct = NestedStruct::New(); + obj->f_struct->f_int32 = 42; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + NestedStructDataView struct_data_view; + data_view.GetFStructDataView(&struct_data_view); + + ASSERT_FALSE(struct_data_view.is_null()); + EXPECT_EQ(42, struct_data_view.f_int32()); +} + +TEST_F(DataViewTest, NativeStruct) { + TestStructPtr obj(TestStruct::New()); + obj->f_native_struct = NativeStruct::New(); + obj->f_native_struct->data = std::vector({3, 2, 1}); + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + NativeStructDataView struct_data_view; + data_view.GetFNativeStructDataView(&struct_data_view); + + ASSERT_FALSE(struct_data_view.is_null()); + ASSERT_EQ(3u, struct_data_view.size()); + EXPECT_EQ(3, struct_data_view[0]); + EXPECT_EQ(2, struct_data_view[1]); + EXPECT_EQ(1, struct_data_view[2]); + EXPECT_EQ(3, *struct_data_view.data()); +} + +TEST_F(DataViewTest, BoolArray) { + TestStructPtr obj(TestStruct::New()); + obj->f_bool_array = {true, false}; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView array_data_view; + data_view.GetFBoolArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(2u, array_data_view.size()); + EXPECT_TRUE(array_data_view[0]); + EXPECT_FALSE(array_data_view[1]); +} + +TEST_F(DataViewTest, IntegerArray) { + TestStructPtr obj(TestStruct::New()); + obj->f_int32_array = {1024, 128}; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView array_data_view; + data_view.GetFInt32ArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(2u, array_data_view.size()); + EXPECT_EQ(1024, array_data_view[0]); + EXPECT_EQ(128, array_data_view[1]); + EXPECT_EQ(1024, *array_data_view.data()); +} + +TEST_F(DataViewTest, EnumArray) { + TestStructPtr obj(TestStruct::New()); + obj->f_enum_array = {TestEnum::VALUE_1, TestEnum::VALUE_0}; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView array_data_view; + data_view.GetFEnumArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(2u, array_data_view.size()); + EXPECT_EQ(TestEnum::VALUE_1, array_data_view[0]); + EXPECT_EQ(TestEnum::VALUE_0, array_data_view[1]); + EXPECT_EQ(TestEnum::VALUE_0, *(array_data_view.data() + 1)); + + TestEnum output; + ASSERT_TRUE(array_data_view.Read(0, &output)); + EXPECT_EQ(TestEnum::VALUE_1, output); +} + +TEST_F(DataViewTest, InterfaceArray) { + TestInterfacePtr ptr; + TestInterfaceImpl impl(MakeRequest(&ptr)); + + TestStructPtr obj(TestStruct::New()); + obj->f_interface_array.push_back(std::move(ptr)); + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView array_data_view; + data_view.GetFInterfaceArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(1u, array_data_view.size()); + + TestInterfacePtr ptr2 = array_data_view.Take(0); + ASSERT_TRUE(ptr2); + int32_t result = 0; + ASSERT_TRUE(ptr2->Echo(42, &result)); + EXPECT_EQ(42, result); +} + +TEST_F(DataViewTest, NestedArray) { + TestStructPtr obj(TestStruct::New()); + obj->f_nested_array = {{3, 4}, {2}}; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView> array_data_view; + data_view.GetFNestedArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(2u, array_data_view.size()); + + ArrayDataView nested_array_data_view; + array_data_view.GetDataView(0, &nested_array_data_view); + ASSERT_FALSE(nested_array_data_view.is_null()); + ASSERT_EQ(2u, nested_array_data_view.size()); + EXPECT_EQ(4, nested_array_data_view[1]); + + std::vector vec; + ASSERT_TRUE(array_data_view.Read(1, &vec)); + ASSERT_EQ(1u, vec.size()); + EXPECT_EQ(2, vec[0]); +} + +TEST_F(DataViewTest, StructArray) { + NestedStructPtr nested_struct(NestedStruct::New()); + nested_struct->f_int32 = 42; + + TestStructPtr obj(TestStruct::New()); + obj->f_struct_array.push_back(std::move(nested_struct)); + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView array_data_view; + data_view.GetFStructArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(1u, array_data_view.size()); + + NestedStructDataView struct_data_view; + array_data_view.GetDataView(0, &struct_data_view); + ASSERT_FALSE(struct_data_view.is_null()); + EXPECT_EQ(42, struct_data_view.f_int32()); + + NestedStructPtr nested_struct2; + ASSERT_TRUE(array_data_view.Read(0, &nested_struct2)); + ASSERT_TRUE(nested_struct2); + EXPECT_EQ(42, nested_struct2->f_int32); +} + +TEST_F(DataViewTest, Map) { + TestStructPtr obj(TestStruct::New()); + obj->f_map["1"] = 1; + obj->f_map["2"] = 2; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + MapDataView map_data_view; + data_view.GetFMapDataView(&map_data_view); + + ASSERT_FALSE(map_data_view.is_null()); + ASSERT_EQ(2u, map_data_view.size()); + + ASSERT_FALSE(map_data_view.keys().is_null()); + ASSERT_EQ(2u, map_data_view.keys().size()); + + ASSERT_FALSE(map_data_view.values().is_null()); + ASSERT_EQ(2u, map_data_view.values().size()); + + std::vector keys; + ASSERT_TRUE(map_data_view.ReadKeys(&keys)); + std::vector values; + ASSERT_TRUE(map_data_view.ReadValues(&values)); + + std::unordered_map map; + for (size_t i = 0; i < 2; ++i) + map[keys[i]] = values[i]; + + EXPECT_EQ(1, map["1"]); + EXPECT_EQ(2, map["2"]); +} + +TEST_F(DataViewTest, UnionArray) { + TestUnionPtr union_ptr(TestUnion::New()); + union_ptr->set_f_int32(1024); + + TestStructPtr obj(TestStruct::New()); + obj->f_union_array.push_back(std::move(union_ptr)); + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView array_data_view; + data_view.GetFUnionArrayDataView(&array_data_view); + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(1u, array_data_view.size()); + + TestUnionDataView union_data_view; + array_data_view.GetDataView(0, &union_data_view); + ASSERT_FALSE(union_data_view.is_null()); + + TestUnionPtr union_ptr2; + ASSERT_TRUE(array_data_view.Read(0, &union_ptr2)); + ASSERT_TRUE(union_ptr2->is_f_int32()); + EXPECT_EQ(1024, union_ptr2->get_f_int32()); +} + +} // namespace data_view +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/e2e_perftest.cc b/mojo/public/cpp/bindings/tests/e2e_perftest.cc new file mode 100644 index 0000000000..bc69e0f727 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/e2e_perftest.cc @@ -0,0 +1,204 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/test/perf_time_logger.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +class EchoServiceImpl : public test::EchoService { + public: + explicit EchoServiceImpl(const base::Closure& quit_closure); + ~EchoServiceImpl() override; + + // |EchoService| methods: + void Echo(const std::string& test_data, + const EchoCallback& callback) override; + + private: + const base::Closure quit_closure_; +}; + +EchoServiceImpl::EchoServiceImpl(const base::Closure& quit_closure) + : quit_closure_(quit_closure) {} + +EchoServiceImpl::~EchoServiceImpl() { + quit_closure_.Run(); +} + +void EchoServiceImpl::Echo(const std::string& test_data, + const EchoCallback& callback) { + callback.Run(test_data); +} + +class PingPongTest { + public: + explicit PingPongTest(test::EchoServicePtr service); + + void RunTest(int iterations, int batch_size, int message_size); + + private: + void DoPing(); + void OnPingDone(const std::string& reply); + + test::EchoServicePtr service_; + const base::Callback ping_done_callback_; + + int iterations_; + int batch_size_; + std::string message_; + + int current_iterations_; + int calls_outstanding_; + + base::Closure quit_closure_; +}; + +PingPongTest::PingPongTest(test::EchoServicePtr service) + : service_(std::move(service)), + ping_done_callback_( + base::Bind(&PingPongTest::OnPingDone, base::Unretained(this))) {} + +void PingPongTest::RunTest(int iterations, int batch_size, int message_size) { + iterations_ = iterations; + batch_size_ = batch_size; + message_ = std::string(message_size, 'a'); + current_iterations_ = 0; + calls_outstanding_ = 0; + + base::MessageLoop::current()->SetNestableTasksAllowed(true); + base::RunLoop run_loop; + quit_closure_ = run_loop.QuitClosure(); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&PingPongTest::DoPing, base::Unretained(this))); + run_loop.Run(); +} + +void PingPongTest::DoPing() { + DCHECK_EQ(0, calls_outstanding_); + current_iterations_++; + if (current_iterations_ > iterations_) { + quit_closure_.Run(); + return; + } + + calls_outstanding_ = batch_size_; + for (int i = 0; i < batch_size_; i++) { + service_->Echo(message_, ping_done_callback_); + } +} + +void PingPongTest::OnPingDone(const std::string& reply) { + DCHECK_GT(calls_outstanding_, 0); + calls_outstanding_--; + + if (!calls_outstanding_) + DoPing(); +} + +class MojoE2EPerftest : public edk::test::MojoTestBase { + public: + void RunTestOnTaskRunner(base::TaskRunner* runner, + MojoHandle client_mp, + const std::string& test_name) { + if (runner == base::ThreadTaskRunnerHandle::Get().get()) { + RunTests(client_mp, test_name); + } else { + base::RunLoop run_loop; + runner->PostTaskAndReply( + FROM_HERE, base::Bind(&MojoE2EPerftest::RunTests, + base::Unretained(this), client_mp, test_name), + run_loop.QuitClosure()); + run_loop.Run(); + } + } + + protected: + base::MessageLoop message_loop_; + + private: + void RunTests(MojoHandle client_mp, const std::string& test_name) { + const int kMessages = 10000; + const int kBatchSizes[] = {1, 10, 100}; + const int kMessageSizes[] = {8, 64, 512, 4096, 65536}; + + test::EchoServicePtr service; + service.Bind(InterfacePtrInfo( + ScopedMessagePipeHandle(MessagePipeHandle(client_mp)), + service.version())); + PingPongTest test(std::move(service)); + + for (int batch_size : kBatchSizes) { + for (int message_size : kMessageSizes) { + int num_messages = kMessages; + if (message_size == 65536) + num_messages /= 10; + std::string sub_test_name = base::StringPrintf( + "%s/%dx%d/%dbytes", test_name.c_str(), num_messages / batch_size, + batch_size, message_size); + base::PerfTimeLogger timer(sub_test_name.c_str()); + test.RunTest(num_messages / batch_size, batch_size, message_size); + } + } + } +}; + +void CreateAndRunService(InterfaceRequest request, + const base::Closure& cb) { + MakeStrongBinding(base::MakeUnique(cb), std::move(request)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(PingService, MojoE2EPerftest, mp) { + MojoHandle service_mp; + EXPECT_EQ("hello", ReadMessageWithHandles(mp, &service_mp, 1)); + + InterfaceRequest request; + request.Bind(ScopedMessagePipeHandle(MessagePipeHandle(service_mp))); + base::RunLoop run_loop; + edk::GetIOTaskRunner()->PostTask( + FROM_HERE, + base::Bind(&CreateAndRunService, base::Passed(&request), + base::Bind(base::IgnoreResult(&base::TaskRunner::PostTask), + message_loop_.task_runner(), FROM_HERE, + run_loop.QuitClosure()))); + run_loop.Run(); +} + +TEST_F(MojoE2EPerftest, MultiProcessEchoMainThread) { + RUN_CHILD_ON_PIPE(PingService, mp) + MojoHandle client_mp, service_mp; + CreateMessagePipe(&client_mp, &service_mp); + WriteMessageWithHandles(mp, "hello", &service_mp, 1); + RunTestOnTaskRunner(message_loop_.task_runner().get(), client_mp, + "MultiProcessEchoMainThread"); + END_CHILD() +} + +TEST_F(MojoE2EPerftest, MultiProcessEchoIoThread) { + RUN_CHILD_ON_PIPE(PingService, mp) + MojoHandle client_mp, service_mp; + CreateMessagePipe(&client_mp, &service_mp); + WriteMessageWithHandles(mp, "hello", &service_mp, 1); + RunTestOnTaskRunner(edk::GetIOTaskRunner().get(), client_mp, + "MultiProcessEchoIoThread"); + END_CHILD() +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/equals_unittest.cc b/mojo/public/cpp/bindings/tests/equals_unittest.cc new file mode 100644 index 0000000000..6483baf8f0 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/equals_unittest.cc @@ -0,0 +1,122 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/message_loop/message_loop.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +namespace { + +RectPtr CreateRect() { + return Rect::New(1, 2, 3, 4); +} + +using EqualsTest = testing::Test; + +} // namespace + +TEST_F(EqualsTest, NullStruct) { + RectPtr r1; + RectPtr r2; + EXPECT_TRUE(r1.Equals(r2)); + EXPECT_TRUE(r2.Equals(r1)); + + r1 = CreateRect(); + EXPECT_FALSE(r1.Equals(r2)); + EXPECT_FALSE(r2.Equals(r1)); +} + +TEST_F(EqualsTest, Struct) { + RectPtr r1(CreateRect()); + RectPtr r2(r1.Clone()); + EXPECT_TRUE(r1.Equals(r2)); + r2->y = 1; + EXPECT_FALSE(r1.Equals(r2)); + r2.reset(); + EXPECT_FALSE(r1.Equals(r2)); +} + +TEST_F(EqualsTest, StructNested) { + RectPairPtr p1(RectPair::New(CreateRect(), CreateRect())); + RectPairPtr p2(p1.Clone()); + EXPECT_TRUE(p1.Equals(p2)); + p2->second->width = 0; + EXPECT_FALSE(p1.Equals(p2)); + p2->second.reset(); + EXPECT_FALSE(p1.Equals(p2)); +} + +TEST_F(EqualsTest, Array) { + std::vector rects; + rects.push_back(CreateRect()); + NamedRegionPtr n1(NamedRegion::New(std::string("n1"), std::move(rects))); + NamedRegionPtr n2(n1.Clone()); + EXPECT_TRUE(n1.Equals(n2)); + + n2->rects = base::nullopt; + EXPECT_FALSE(n1.Equals(n2)); + n2->rects.emplace(); + EXPECT_FALSE(n1.Equals(n2)); + + n2->rects->push_back(CreateRect()); + n2->rects->push_back(CreateRect()); + EXPECT_FALSE(n1.Equals(n2)); + + n2->rects->resize(1); + (*n2->rects)[0]->width = 0; + EXPECT_FALSE(n1.Equals(n2)); + + (*n2->rects)[0] = CreateRect(); + EXPECT_TRUE(n1.Equals(n2)); +} + +TEST_F(EqualsTest, InterfacePtr) { + base::MessageLoop message_loop; + + SomeInterfacePtr inf1; + SomeInterfacePtr inf2; + + EXPECT_TRUE(inf1.Equals(inf1)); + EXPECT_TRUE(inf1.Equals(inf2)); + + auto inf1_request = MakeRequest(&inf1); + ALLOW_UNUSED_LOCAL(inf1_request); + + EXPECT_TRUE(inf1.Equals(inf1)); + EXPECT_FALSE(inf1.Equals(inf2)); + + auto inf2_request = MakeRequest(&inf2); + ALLOW_UNUSED_LOCAL(inf2_request); + + EXPECT_FALSE(inf1.Equals(inf2)); +} + +TEST_F(EqualsTest, InterfaceRequest) { + base::MessageLoop message_loop; + + InterfaceRequest req1; + InterfaceRequest req2; + + EXPECT_TRUE(req1.Equals(req1)); + EXPECT_TRUE(req1.Equals(req2)); + + SomeInterfacePtr inf1; + req1 = MakeRequest(&inf1); + + EXPECT_TRUE(req1.Equals(req1)); + EXPECT_FALSE(req1.Equals(req2)); + + SomeInterfacePtr inf2; + req2 = MakeRequest(&inf2); + + EXPECT_FALSE(req1.Equals(req2)); +} + +} // test +} // mojo diff --git a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc new file mode 100644 index 0000000000..ef977af935 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc @@ -0,0 +1,356 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/system/wait.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/sample_factory.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +const char kText1[] = "hello"; +const char kText2[] = "world"; + +void RecordString(std::string* storage, + const base::Closure& closure, + const std::string& str) { + *storage = str; + closure.Run(); +} + +base::Callback MakeStringRecorder( + std::string* storage, + const base::Closure& closure) { + return base::Bind(&RecordString, storage, closure); +} + +class ImportedInterfaceImpl : public imported::ImportedInterface { + public: + ImportedInterfaceImpl( + InterfaceRequest request, + const base::Closure& closure) + : binding_(this, std::move(request)), closure_(closure) {} + + void DoSomething() override { + do_something_count_++; + closure_.Run(); + } + + static int do_something_count() { return do_something_count_; } + + private: + static int do_something_count_; + Binding binding_; + base::Closure closure_; +}; +int ImportedInterfaceImpl::do_something_count_ = 0; + +class SampleNamedObjectImpl : public sample::NamedObject { + public: + SampleNamedObjectImpl() {} + + void SetName(const std::string& name) override { name_ = name; } + + void GetName(const GetNameCallback& callback) override { + callback.Run(name_); + } + + private: + std::string name_; +}; + +class SampleFactoryImpl : public sample::Factory { + public: + explicit SampleFactoryImpl(InterfaceRequest request) + : binding_(this, std::move(request)) {} + + void DoStuff(sample::RequestPtr request, + ScopedMessagePipeHandle pipe, + const DoStuffCallback& callback) override { + std::string text1; + if (pipe.is_valid()) + EXPECT_TRUE(ReadTextMessage(pipe.get(), &text1)); + + std::string text2; + if (request->pipe.is_valid()) { + EXPECT_TRUE(ReadTextMessage(request->pipe.get(), &text2)); + + // Ensure that simply accessing request->pipe does not close it. + EXPECT_TRUE(request->pipe.is_valid()); + } + + ScopedMessagePipeHandle pipe0; + if (!text2.empty()) { + CreateMessagePipe(nullptr, &pipe0, &pipe1_); + EXPECT_TRUE(WriteTextMessage(pipe1_.get(), text2)); + } + + sample::ResponsePtr response(sample::Response::New(2, std::move(pipe0))); + callback.Run(std::move(response), text1); + + if (request->obj) + request->obj->DoSomething(); + } + + void DoStuff2(ScopedDataPipeConsumerHandle pipe, + const DoStuff2Callback& callback) override { + // Read the data from the pipe, writing the response (as a string) to + // DidStuff2(). + ASSERT_TRUE(pipe.is_valid()); + uint32_t data_size = 0; + + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + mojo::Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); + ASSERT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); + ASSERT_EQ(MOJO_RESULT_OK, + ReadDataRaw( + pipe.get(), nullptr, &data_size, MOJO_READ_DATA_FLAG_QUERY)); + ASSERT_NE(0, static_cast(data_size)); + char data[64]; + ASSERT_LT(static_cast(data_size), 64); + ASSERT_EQ( + MOJO_RESULT_OK, + ReadDataRaw( + pipe.get(), data, &data_size, MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + callback.Run(data); + } + + void CreateNamedObject( + InterfaceRequest object_request) override { + EXPECT_TRUE(object_request.is_pending()); + MakeStrongBinding(base::MakeUnique(), + std::move(object_request)); + } + + // These aren't called or implemented, but exist here to test that the + // methods are generated with the correct argument types for imported + // interfaces. + void RequestImportedInterface( + InterfaceRequest imported, + const RequestImportedInterfaceCallback& callback) override {} + void TakeImportedInterface( + imported::ImportedInterfacePtr imported, + const TakeImportedInterfaceCallback& callback) override {} + + private: + ScopedMessagePipeHandle pipe1_; + Binding binding_; +}; + +class HandlePassingTest : public testing::Test { + public: + HandlePassingTest() {} + + void TearDown() override { PumpMessages(); } + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +void DoStuff(bool* got_response, + std::string* got_text_reply, + const base::Closure& closure, + sample::ResponsePtr response, + const std::string& text_reply) { + *got_text_reply = text_reply; + + if (response->pipe.is_valid()) { + std::string text2; + EXPECT_TRUE(ReadTextMessage(response->pipe.get(), &text2)); + + // Ensure that simply accessing response.pipe does not close it. + EXPECT_TRUE(response->pipe.is_valid()); + + EXPECT_EQ(std::string(kText2), text2); + + // Do some more tests of handle passing: + ScopedMessagePipeHandle p = std::move(response->pipe); + EXPECT_TRUE(p.is_valid()); + EXPECT_FALSE(response->pipe.is_valid()); + } + + *got_response = true; + closure.Run(); +} + +void DoStuff2(bool* got_response, + std::string* got_text_reply, + const base::Closure& closure, + const std::string& text_reply) { + *got_response = true; + *got_text_reply = text_reply; + closure.Run(); +} + +TEST_F(HandlePassingTest, Basic) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + MessagePipe pipe0; + EXPECT_TRUE(WriteTextMessage(pipe0.handle1.get(), kText1)); + + MessagePipe pipe1; + EXPECT_TRUE(WriteTextMessage(pipe1.handle1.get(), kText2)); + + imported::ImportedInterfacePtr imported; + base::RunLoop run_loop; + ImportedInterfaceImpl imported_impl(MakeRequest(&imported), + run_loop.QuitClosure()); + + sample::RequestPtr request(sample::Request::New( + 1, std::move(pipe1.handle0), base::nullopt, std::move(imported))); + bool got_response = false; + std::string got_text_reply; + base::RunLoop run_loop2; + factory->DoStuff(std::move(request), std::move(pipe0.handle0), + base::Bind(&DoStuff, &got_response, &got_text_reply, + run_loop2.QuitClosure())); + + EXPECT_FALSE(got_response); + int count_before = ImportedInterfaceImpl::do_something_count(); + + run_loop.Run(); + run_loop2.Run(); + + EXPECT_TRUE(got_response); + EXPECT_EQ(kText1, got_text_reply); + EXPECT_EQ(1, ImportedInterfaceImpl::do_something_count() - count_before); +} + +TEST_F(HandlePassingTest, PassInvalid) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + sample::RequestPtr request( + sample::Request::New(1, ScopedMessagePipeHandle(), base::nullopt, + imported::ImportedInterfacePtr())); + bool got_response = false; + std::string got_text_reply; + base::RunLoop run_loop; + factory->DoStuff(std::move(request), ScopedMessagePipeHandle(), + base::Bind(&DoStuff, &got_response, &got_text_reply, + run_loop.QuitClosure())); + + EXPECT_FALSE(got_response); + + run_loop.Run(); + + EXPECT_TRUE(got_response); +} + +// Verifies DataPipeConsumer can be passed and read from. +TEST_F(HandlePassingTest, DataPipe) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + // Writes a string to a data pipe and passes the data pipe (consumer) to the + // factory. + ScopedDataPipeProducerHandle producer_handle; + ScopedDataPipeConsumerHandle consumer_handle; + MojoCreateDataPipeOptions options = {sizeof(MojoCreateDataPipeOptions), + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, + 1, + 1024}; + ASSERT_EQ(MOJO_RESULT_OK, + CreateDataPipe(&options, &producer_handle, &consumer_handle)); + std::string expected_text_reply = "got it"; + // +1 for \0. + uint32_t data_size = static_cast(expected_text_reply.size() + 1); + ASSERT_EQ(MOJO_RESULT_OK, + WriteDataRaw(producer_handle.get(), + expected_text_reply.c_str(), + &data_size, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + + bool got_response = false; + std::string got_text_reply; + base::RunLoop run_loop; + factory->DoStuff2(std::move(consumer_handle), + base::Bind(&DoStuff2, &got_response, &got_text_reply, + run_loop.QuitClosure())); + + EXPECT_FALSE(got_response); + + run_loop.Run(); + + EXPECT_TRUE(got_response); + EXPECT_EQ(expected_text_reply, got_text_reply); +} + +TEST_F(HandlePassingTest, PipesAreClosed) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + MessagePipe extra_pipe; + + MojoHandle handle0_value = extra_pipe.handle0.get().value(); + MojoHandle handle1_value = extra_pipe.handle1.get().value(); + + { + std::vector pipes(2); + pipes[0] = std::move(extra_pipe.handle0); + pipes[1] = std::move(extra_pipe.handle1); + + sample::RequestPtr request(sample::Request::New()); + request->more_pipes = std::move(pipes); + + factory->DoStuff(std::move(request), ScopedMessagePipeHandle(), + sample::Factory::DoStuffCallback()); + } + + // We expect the pipes to have been closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle0_value)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle1_value)); +} + +TEST_F(HandlePassingTest, CreateNamedObject) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + sample::NamedObjectPtr object1; + EXPECT_FALSE(object1); + + InterfaceRequest object1_request(&object1); + EXPECT_TRUE(object1_request.is_pending()); + factory->CreateNamedObject(std::move(object1_request)); + EXPECT_FALSE(object1_request.is_pending()); // We've passed the request. + + ASSERT_TRUE(object1); + object1->SetName("object1"); + + sample::NamedObjectPtr object2; + factory->CreateNamedObject(MakeRequest(&object2)); + object2->SetName("object2"); + + base::RunLoop run_loop, run_loop2; + std::string name1; + object1->GetName(MakeStringRecorder(&name1, run_loop.QuitClosure())); + + std::string name2; + object2->GetName(MakeStringRecorder(&name2, run_loop2.QuitClosure())); + + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(std::string("object1"), name1); + EXPECT_EQ(std::string("object2"), name2); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/hash_unittest.cc b/mojo/public/cpp/bindings/tests/hash_unittest.cc new file mode 100644 index 0000000000..9ce1f5bc7b --- /dev/null +++ b/mojo/public/cpp/bindings/tests/hash_unittest.cc @@ -0,0 +1,35 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/hash_util.h" + +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using HashTest = testing::Test; + +TEST_F(HashTest, NestedStruct) { + // Just check that this template instantiation compiles. + ASSERT_EQ( + ::mojo::internal::Hash(::mojo::internal::kHashSeed, + SimpleNestedStruct::New(ContainsOther::New(1))), + ::mojo::internal::Hash(::mojo::internal::kHashSeed, + SimpleNestedStruct::New(ContainsOther::New(1)))); +} + +TEST_F(HashTest, UnmappedNativeStruct) { + // Just check that this template instantiation compiles. + ASSERT_EQ(::mojo::internal::Hash(::mojo::internal::kHashSeed, + UnmappedNativeStruct::New()), + ::mojo::internal::Hash(::mojo::internal::kHashSeed, + UnmappedNativeStruct::New())); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc new file mode 100644 index 0000000000..431a844250 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc @@ -0,0 +1,937 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/threading/thread.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h" +#include "mojo/public/interfaces/bindings/tests/math_calculator.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" +#include "mojo/public/interfaces/bindings/tests/scoping.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +typedef base::Callback CalcCallback; + +class MathCalculatorImpl : public math::Calculator { + public: + explicit MathCalculatorImpl(InterfaceRequest request) + : total_(0.0), binding_(this, std::move(request)) {} + ~MathCalculatorImpl() override {} + + void Clear(const CalcCallback& callback) override { + total_ = 0.0; + callback.Run(total_); + } + + void Add(double value, const CalcCallback& callback) override { + total_ += value; + callback.Run(total_); + } + + void Multiply(double value, const CalcCallback& callback) override { + total_ *= value; + callback.Run(total_); + } + + Binding* binding() { return &binding_; } + + private: + double total_; + Binding binding_; +}; + +class MathCalculatorUI { + public: + explicit MathCalculatorUI(math::CalculatorPtr calculator) + : calculator_(std::move(calculator)), + output_(0.0) {} + + bool encountered_error() const { return calculator_.encountered_error(); } + void set_connection_error_handler(const base::Closure& closure) { + calculator_.set_connection_error_handler(closure); + } + + void Add(double value, const base::Closure& closure) { + calculator_->Add( + value, + base::Bind(&MathCalculatorUI::Output, base::Unretained(this), closure)); + } + + void Multiply(double value, const base::Closure& closure) { + calculator_->Multiply( + value, + base::Bind(&MathCalculatorUI::Output, base::Unretained(this), closure)); + } + + double GetOutput() const { return output_; } + + math::CalculatorPtr& GetInterfacePtr() { return calculator_; } + + private: + void Output(const base::Closure& closure, double output) { + output_ = output; + if (!closure.is_null()) + closure.Run(); + } + + math::CalculatorPtr calculator_; + double output_; + base::Closure closure_; +}; + +class SelfDestructingMathCalculatorUI { + public: + explicit SelfDestructingMathCalculatorUI(math::CalculatorPtr calculator) + : calculator_(std::move(calculator)), nesting_level_(0) { + ++num_instances_; + } + + void BeginTest(bool nested, const base::Closure& closure) { + nesting_level_ = nested ? 2 : 1; + calculator_->Add( + 1.0, + base::Bind(&SelfDestructingMathCalculatorUI::Output, + base::Unretained(this), closure)); + } + + static int num_instances() { return num_instances_; } + + void Output(const base::Closure& closure, double value) { + if (--nesting_level_ > 0) { + // Add some more and wait for re-entrant call to Output! + calculator_->Add( + 1.0, + base::Bind(&SelfDestructingMathCalculatorUI::Output, + base::Unretained(this), closure)); + } else { + closure.Run(); + delete this; + } + } + + private: + ~SelfDestructingMathCalculatorUI() { --num_instances_; } + + math::CalculatorPtr calculator_; + int nesting_level_; + static int num_instances_; +}; + +// static +int SelfDestructingMathCalculatorUI::num_instances_ = 0; + +class ReentrantServiceImpl : public sample::Service { + public: + ~ReentrantServiceImpl() override {} + + explicit ReentrantServiceImpl(InterfaceRequest request) + : call_depth_(0), + max_call_depth_(0), + binding_(this, std::move(request)) {} + + int max_call_depth() { return max_call_depth_; } + + void Frobinate(sample::FooPtr foo, + sample::Service::BazOptions baz, + sample::PortPtr port, + const sample::Service::FrobinateCallback& callback) override { + max_call_depth_ = std::max(++call_depth_, max_call_depth_); + if (call_depth_ == 1) { + EXPECT_TRUE(binding_.WaitForIncomingMethodCall()); + } + call_depth_--; + callback.Run(5); + } + + void GetPort(mojo::InterfaceRequest port) override {} + + private: + int call_depth_; + int max_call_depth_; + Binding binding_; +}; + +class IntegerAccessorImpl : public sample::IntegerAccessor { + public: + IntegerAccessorImpl() : integer_(0) {} + ~IntegerAccessorImpl() override {} + + int64_t integer() const { return integer_; } + + void set_closure(const base::Closure& closure) { closure_ = closure; } + + private: + // sample::IntegerAccessor implementation. + void GetInteger(const GetIntegerCallback& callback) override { + callback.Run(integer_, sample::Enum::VALUE); + } + void SetInteger(int64_t data, sample::Enum type) override { + integer_ = data; + if (!closure_.is_null()) { + closure_.Run(); + closure_.Reset(); + } + } + + int64_t integer_; + base::Closure closure_; +}; + +class InterfacePtrTest : public testing::Test { + public: + InterfacePtrTest() {} + ~InterfacePtrTest() override { base::RunLoop().RunUntilIdle(); } + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +void SetFlagAndRunClosure(bool* flag, const base::Closure& closure) { + *flag = true; + closure.Run(); +} + +void IgnoreValueAndRunClosure(const base::Closure& closure, int32_t value) { + closure.Run(); +} + +void ExpectValueAndRunClosure(uint32_t expected_value, + const base::Closure& closure, + uint32_t value) { + EXPECT_EQ(expected_value, value); + closure.Run(); +} + +TEST_F(InterfacePtrTest, IsBound) { + math::CalculatorPtr calc; + EXPECT_FALSE(calc.is_bound()); + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + EXPECT_TRUE(calc.is_bound()); +} + +TEST_F(InterfacePtrTest, EndToEnd) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + + // Suppose this is instantiated in a process that has pipe1_. + MathCalculatorUI calculator_ui(std::move(calc)); + + base::RunLoop run_loop, run_loop2; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + calculator_ui.Multiply(5.0, run_loop2.QuitClosure()); + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); +} + +TEST_F(InterfacePtrTest, EndToEnd_Synchronous) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + + // Suppose this is instantiated in a process that has pipe1_. + MathCalculatorUI calculator_ui(std::move(calc)); + + EXPECT_EQ(0.0, calculator_ui.GetOutput()); + + base::RunLoop run_loop; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + EXPECT_EQ(0.0, calculator_ui.GetOutput()); + calc_impl.binding()->WaitForIncomingMethodCall(); + run_loop.Run(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + + base::RunLoop run_loop2; + calculator_ui.Multiply(5.0, run_loop2.QuitClosure()); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + calc_impl.binding()->WaitForIncomingMethodCall(); + run_loop2.Run(); + EXPECT_EQ(10.0, calculator_ui.GetOutput()); +} + +TEST_F(InterfacePtrTest, Movable) { + math::CalculatorPtr a; + math::CalculatorPtr b; + MathCalculatorImpl calc_impl(MakeRequest(&b)); + + EXPECT_TRUE(!a); + EXPECT_FALSE(!b); + + a = std::move(b); + + EXPECT_FALSE(!a); + EXPECT_TRUE(!b); +} + +TEST_F(InterfacePtrTest, Resettable) { + math::CalculatorPtr a; + + EXPECT_TRUE(!a); + + MessagePipe pipe; + + // Save this so we can test it later. + Handle handle = pipe.handle0.get(); + + a = MakeProxy( + InterfacePtrInfo(std::move(pipe.handle0), 0u)); + + EXPECT_FALSE(!a); + + a.reset(); + + EXPECT_TRUE(!a); + EXPECT_FALSE(a.internal_state()->is_bound()); + + // Test that handle was closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, CloseRaw(handle)); +} + +TEST_F(InterfacePtrTest, BindInvalidHandle) { + math::CalculatorPtr ptr; + EXPECT_FALSE(ptr.get()); + EXPECT_FALSE(ptr); + + ptr.Bind(InterfacePtrInfo()); + EXPECT_FALSE(ptr.get()); + EXPECT_FALSE(ptr); +} + +TEST_F(InterfacePtrTest, EncounteredError) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(MakeRequest(&proxy)); + + MathCalculatorUI calculator_ui(std::move(proxy)); + + base::RunLoop run_loop; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + run_loop.Run(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + calculator_ui.Multiply(5.0, base::Closure()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + // Close the server. + calc_impl.binding()->Close(); + + // The state change isn't picked up locally yet. + base::RunLoop run_loop2; + calculator_ui.set_connection_error_handler(run_loop2.QuitClosure()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + run_loop2.Run(); + + // OK, now we see the error. + EXPECT_TRUE(calculator_ui.encountered_error()); +} + +TEST_F(InterfacePtrTest, EncounteredErrorCallback) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(MakeRequest(&proxy)); + + bool encountered_error = false; + base::RunLoop run_loop; + proxy.set_connection_error_handler( + base::Bind(&SetFlagAndRunClosure, &encountered_error, + run_loop.QuitClosure())); + + MathCalculatorUI calculator_ui(std::move(proxy)); + + base::RunLoop run_loop2; + calculator_ui.Add(2.0, run_loop2.QuitClosure()); + run_loop2.Run(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + calculator_ui.Multiply(5.0, base::Closure()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + // Close the server. + calc_impl.binding()->Close(); + + // The state change isn't picked up locally yet. + EXPECT_FALSE(calculator_ui.encountered_error()); + + run_loop.Run(); + + // OK, now we see the error. + EXPECT_TRUE(calculator_ui.encountered_error()); + + // We should have also been able to observe the error through the error + // handler. + EXPECT_TRUE(encountered_error); +} + +TEST_F(InterfacePtrTest, DestroyInterfacePtrOnMethodResponse) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(MakeRequest(&proxy)); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); + + SelfDestructingMathCalculatorUI* impl = + new SelfDestructingMathCalculatorUI(std::move(proxy)); + base::RunLoop run_loop; + impl->BeginTest(false, run_loop.QuitClosure()); + run_loop.Run(); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); +} + +TEST_F(InterfacePtrTest, NestedDestroyInterfacePtrOnMethodResponse) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(MakeRequest(&proxy)); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); + + SelfDestructingMathCalculatorUI* impl = + new SelfDestructingMathCalculatorUI(std::move(proxy)); + base::RunLoop run_loop; + impl->BeginTest(true, run_loop.QuitClosure()); + run_loop.Run(); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); +} + +TEST_F(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) { + sample::ServicePtr proxy; + ReentrantServiceImpl impl(MakeRequest(&proxy)); + + base::RunLoop run_loop, run_loop2; + proxy->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + base::Bind(&IgnoreValueAndRunClosure, + run_loop.QuitClosure())); + proxy->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + base::Bind(&IgnoreValueAndRunClosure, + run_loop2.QuitClosure())); + + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(2, impl.max_call_depth()); +} + +TEST_F(InterfacePtrTest, QueryVersion) { + IntegerAccessorImpl impl; + sample::IntegerAccessorPtr ptr; + Binding binding(&impl, MakeRequest(&ptr)); + + EXPECT_EQ(0u, ptr.version()); + + base::RunLoop run_loop; + ptr.QueryVersion(base::Bind(&ExpectValueAndRunClosure, 3u, + run_loop.QuitClosure())); + run_loop.Run(); + + EXPECT_EQ(3u, ptr.version()); +} + +TEST_F(InterfacePtrTest, RequireVersion) { + IntegerAccessorImpl impl; + sample::IntegerAccessorPtr ptr; + Binding binding(&impl, MakeRequest(&ptr)); + + EXPECT_EQ(0u, ptr.version()); + + ptr.RequireVersion(1u); + EXPECT_EQ(1u, ptr.version()); + base::RunLoop run_loop; + impl.set_closure(run_loop.QuitClosure()); + ptr->SetInteger(123, sample::Enum::VALUE); + run_loop.Run(); + EXPECT_FALSE(ptr.encountered_error()); + EXPECT_EQ(123, impl.integer()); + + ptr.RequireVersion(3u); + EXPECT_EQ(3u, ptr.version()); + base::RunLoop run_loop2; + impl.set_closure(run_loop2.QuitClosure()); + ptr->SetInteger(456, sample::Enum::VALUE); + run_loop2.Run(); + EXPECT_FALSE(ptr.encountered_error()); + EXPECT_EQ(456, impl.integer()); + + // Require a version that is not supported by the impl side. + ptr.RequireVersion(4u); + // This value is set to the input of RequireVersion() synchronously. + EXPECT_EQ(4u, ptr.version()); + base::RunLoop run_loop3; + ptr.set_connection_error_handler(run_loop3.QuitClosure()); + ptr->SetInteger(789, sample::Enum::VALUE); + run_loop3.Run(); + EXPECT_TRUE(ptr.encountered_error()); + // The call to SetInteger() after RequireVersion(4u) is ignored. + EXPECT_EQ(456, impl.integer()); +} + +class StrongMathCalculatorImpl : public math::Calculator { + public: + StrongMathCalculatorImpl(bool* destroyed) : destroyed_(destroyed) {} + ~StrongMathCalculatorImpl() override { *destroyed_ = true; } + + // math::Calculator implementation. + void Clear(const CalcCallback& callback) override { callback.Run(total_); } + + void Add(double value, const CalcCallback& callback) override { + total_ += value; + callback.Run(total_); + } + + void Multiply(double value, const CalcCallback& callback) override { + total_ *= value; + callback.Run(total_); + } + + private: + double total_ = 0.0; + bool* destroyed_; +}; + +TEST(StrongConnectorTest, Math) { + base::MessageLoop loop; + + bool error_received = false; + bool destroyed = false; + math::CalculatorPtr calc; + base::RunLoop run_loop; + + auto binding = + MakeStrongBinding(base::MakeUnique(&destroyed), + MakeRequest(&calc)); + binding->set_connection_error_handler(base::Bind( + &SetFlagAndRunClosure, &error_received, run_loop.QuitClosure())); + + { + // Suppose this is instantiated in a process that has the other end of the + // message pipe. + MathCalculatorUI calculator_ui(std::move(calc)); + + base::RunLoop run_loop, run_loop2; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + calculator_ui.Multiply(5.0, run_loop2.QuitClosure()); + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); + EXPECT_FALSE(error_received); + EXPECT_FALSE(destroyed); + } + // Destroying calculator_ui should close the pipe and generate an error on the + // other + // end which will destroy the instance since it is strongly bound. + + run_loop.Run(); + EXPECT_TRUE(error_received); + EXPECT_TRUE(destroyed); +} + +class WeakMathCalculatorImpl : public math::Calculator { + public: + WeakMathCalculatorImpl(ScopedMessagePipeHandle handle, + bool* error_received, + bool* destroyed, + const base::Closure& closure) + : error_received_(error_received), + destroyed_(destroyed), + closure_(closure), + binding_(this, std::move(handle)) { + binding_.set_connection_error_handler( + base::Bind(&SetFlagAndRunClosure, error_received_, closure_)); + } + ~WeakMathCalculatorImpl() override { *destroyed_ = true; } + + void Clear(const CalcCallback& callback) override { callback.Run(total_); } + + void Add(double value, const CalcCallback& callback) override { + total_ += value; + callback.Run(total_); + } + + void Multiply(double value, const CalcCallback& callback) override { + total_ *= value; + callback.Run(total_); + } + + private: + double total_ = 0.0; + bool* error_received_; + bool* destroyed_; + base::Closure closure_; + + Binding binding_; +}; + +TEST(WeakConnectorTest, Math) { + base::MessageLoop loop; + + bool error_received = false; + bool destroyed = false; + MessagePipe pipe; + base::RunLoop run_loop; + WeakMathCalculatorImpl impl(std::move(pipe.handle0), &error_received, + &destroyed, run_loop.QuitClosure()); + + math::CalculatorPtr calc; + calc.Bind(InterfacePtrInfo(std::move(pipe.handle1), 0u)); + + { + // Suppose this is instantiated in a process that has the other end of the + // message pipe. + MathCalculatorUI calculator_ui(std::move(calc)); + + base::RunLoop run_loop, run_loop2; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + calculator_ui.Multiply(5.0, run_loop2.QuitClosure()); + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); + EXPECT_FALSE(error_received); + EXPECT_FALSE(destroyed); + // Destroying calculator_ui should close the pipe and generate an error on + // the other + // end which will destroy the instance since it is strongly bound. + } + + run_loop.Run(); + EXPECT_TRUE(error_received); + EXPECT_FALSE(destroyed); +} + +class CImpl : public C { + public: + CImpl(bool* d_called, const base::Closure& closure) + : d_called_(d_called), closure_(closure) {} + ~CImpl() override {} + + private: + void D() override { + *d_called_ = true; + closure_.Run(); + } + + bool* d_called_; + base::Closure closure_; +}; + +class BImpl : public B { + public: + BImpl(bool* d_called, const base::Closure& closure) + : d_called_(d_called), closure_(closure) {} + ~BImpl() override {} + + private: + void GetC(InterfaceRequest c) override { + MakeStrongBinding(base::MakeUnique(d_called_, closure_), + std::move(c)); + } + + bool* d_called_; + base::Closure closure_; +}; + +class AImpl : public A { + public: + AImpl(InterfaceRequest request, const base::Closure& closure) + : d_called_(false), binding_(this, std::move(request)), + closure_(closure) {} + ~AImpl() override {} + + bool d_called() const { return d_called_; } + + private: + void GetB(InterfaceRequest b) override { + MakeStrongBinding(base::MakeUnique(&d_called_, closure_), + std::move(b)); + } + + bool d_called_; + Binding binding_; + base::Closure closure_; +}; + +TEST_F(InterfacePtrTest, Scoping) { + APtr a; + base::RunLoop run_loop; + AImpl a_impl(MakeRequest(&a), run_loop.QuitClosure()); + + EXPECT_FALSE(a_impl.d_called()); + + { + BPtr b; + a->GetB(MakeRequest(&b)); + CPtr c; + b->GetC(MakeRequest(&c)); + c->D(); + } + + // While B & C have fallen out of scope, the pipes will remain until they are + // flushed. + EXPECT_FALSE(a_impl.d_called()); + run_loop.Run(); + EXPECT_TRUE(a_impl.d_called()); +} + +class PingTestImpl : public sample::PingTest { + public: + explicit PingTestImpl(InterfaceRequest request) + : binding_(this, std::move(request)) {} + ~PingTestImpl() override {} + + private: + // sample::PingTest: + void Ping(const PingCallback& callback) override { callback.Run(); } + + Binding binding_; +}; + +// Tests that FuseProxy does what it's supposed to do. +TEST_F(InterfacePtrTest, Fusion) { + sample::PingTestPtr proxy; + PingTestImpl impl(MakeRequest(&proxy)); + + // Create another PingTest pipe. + sample::PingTestPtr ptr; + sample::PingTestRequest request(&ptr); + + // Fuse the new pipe to the one hanging off |impl|. + EXPECT_TRUE(FuseInterface(std::move(request), proxy.PassInterface())); + + // Ping! + bool called = false; + base::RunLoop loop; + ptr->Ping(base::Bind(&SetFlagAndRunClosure, &called, loop.QuitClosure())); + loop.Run(); + EXPECT_TRUE(called); +} + +void Fail() { + FAIL() << "Unexpected connection error"; +} + +TEST_F(InterfacePtrTest, FlushForTesting) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + calc.set_connection_error_handler(base::Bind(&Fail)); + + MathCalculatorUI calculator_ui(std::move(calc)); + + calculator_ui.Add(2.0, base::Bind(&base::DoNothing)); + calculator_ui.GetInterfacePtr().FlushForTesting(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + + calculator_ui.Multiply(5.0, base::Bind(&base::DoNothing)); + calculator_ui.GetInterfacePtr().FlushForTesting(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); +} + +void SetBool(bool* value) { + *value = true; +} + +TEST_F(InterfacePtrTest, FlushForTestingWithClosedPeer) { + math::CalculatorPtr calc; + MakeRequest(&calc); + bool called = false; + calc.set_connection_error_handler(base::Bind(&SetBool, &called)); + calc.FlushForTesting(); + EXPECT_TRUE(called); + calc.FlushForTesting(); +} + +TEST_F(InterfacePtrTest, ConnectionErrorWithReason) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + + base::RunLoop run_loop; + calc.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(42u, custom_reason); + EXPECT_EQ("hey", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + calc_impl.binding()->CloseWithReason(42u, "hey"); + + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, InterfaceRequestResetWithReason) { + math::CalculatorPtr calc; + auto request = MakeRequest(&calc); + + base::RunLoop run_loop; + calc.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(88u, custom_reason); + EXPECT_EQ("greetings", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + request.ResetWithReason(88u, "greetings"); + + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, CallbackIsPassedInterfacePtr) { + sample::PingTestPtr ptr; + sample::PingTestRequest request(&ptr); + + base::RunLoop run_loop; + + // Make a call with the proxy's lifetime bound to the response callback. + sample::PingTest* raw_proxy = ptr.get(); + ptr.set_connection_error_handler(run_loop.QuitClosure()); + raw_proxy->Ping( + base::Bind([](sample::PingTestPtr ptr) {}, base::Passed(&ptr))); + + // Trigger an error on |ptr|. This will ultimately lead to the proxy's + // response callbacks being destroyed, which will in turn lead to the proxy + // being destroyed. This should not crash. + request.PassMessagePipe(); + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, ConnectionErrorHandlerOwnsInterfacePtr) { + sample::PingTestPtr* ptr = new sample::PingTestPtr; + sample::PingTestRequest request(ptr); + + base::RunLoop run_loop; + + // Make a call with |ptr|'s lifetime bound to the connection error handler + // callback. + ptr->set_connection_error_handler(base::Bind( + [](const base::Closure& quit, sample::PingTestPtr* ptr) { + ptr->reset(); + quit.Run(); + }, + run_loop.QuitClosure(), base::Owned(ptr))); + + // Trigger an error on |ptr|. In the error handler |ptr| is reset. This + // shouldn't immediately destroy the callback (and |ptr| that it owns), before + // the callback is completed. + request.PassMessagePipe(); + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, ThreadSafeInterfacePointer) { + math::CalculatorPtr ptr; + MathCalculatorImpl calc_impl(MakeRequest(&ptr)); + scoped_refptr thread_safe_ptr = + math::ThreadSafeCalculatorPtr::Create(std::move(ptr)); + + base::RunLoop run_loop; + + // Create and start the thread from where we'll call the interface pointer. + base::Thread other_thread("service test thread"); + other_thread.Start(); + + auto run_method = base::Bind( + [](const scoped_refptr& main_task_runner, + const base::Closure& quit_closure, + const scoped_refptr& thread_safe_ptr) { + auto calc_callback = base::Bind( + [](const scoped_refptr& main_task_runner, + const base::Closure& quit_closure, + base::PlatformThreadId thread_id, + double result) { + EXPECT_EQ(123, result); + // Validate the callback is invoked on the calling thread. + EXPECT_EQ(thread_id, base::PlatformThread::CurrentId()); + // Notify the run_loop to quit. + main_task_runner->PostTask(FROM_HERE, quit_closure); + }); + (*thread_safe_ptr)->Add( + 123, base::Bind(calc_callback, main_task_runner, quit_closure, + base::PlatformThread::CurrentId())); + }, + base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(), + thread_safe_ptr); + other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method); + + // Block until the method callback is called on the background thread. + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, ThreadSafeInterfacePointerWithTaskRunner) { + // Create and start the thread from where we'll bind the interface pointer. + base::Thread other_thread("service test thread"); + other_thread.Start(); + const scoped_refptr& other_thread_task_runner = + other_thread.message_loop()->task_runner(); + + math::CalculatorPtr ptr; + math::CalculatorRequest request(&ptr); + + // Create a ThreadSafeInterfacePtr that we'll bind from a different thread. + scoped_refptr thread_safe_ptr = + math::ThreadSafeCalculatorPtr::Create(ptr.PassInterface(), + other_thread_task_runner); + ASSERT_TRUE(thread_safe_ptr); + + MathCalculatorImpl* math_calc_impl = nullptr; + { + base::RunLoop run_loop; + auto run_method = base::Bind( + [](const scoped_refptr& main_task_runner, + const base::Closure& quit_closure, + const scoped_refptr& thread_safe_ptr, + math::CalculatorRequest request, + MathCalculatorImpl** math_calc_impl) { + math::CalculatorPtr ptr; + // In real life, the implementation would have a legitimate owner. + *math_calc_impl = new MathCalculatorImpl(std::move(request)); + main_task_runner->PostTask(FROM_HERE, quit_closure); + }, + base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(), + thread_safe_ptr, base::Passed(&request), &math_calc_impl); + other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method); + run_loop.Run(); + } + + { + // The interface ptr is bound, we can call methods on it. + auto calc_callback = + base::Bind([](const base::Closure& quit_closure, double result) { + EXPECT_EQ(123, result); + quit_closure.Run(); + }); + base::RunLoop run_loop; + (*thread_safe_ptr) + ->Add(123, base::Bind(calc_callback, run_loop.QuitClosure())); + // Block until the method callback is called. + run_loop.Run(); + } + + other_thread_task_runner->DeleteSoon(FROM_HERE, math_calc_impl); + + // Reset the pointer now so the InterfacePtr associated resources can be + // deleted before the background thread's message loop is invalidated. + thread_safe_ptr = nullptr; +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/map_unittest.cc b/mojo/public/cpp/bindings/tests/map_unittest.cc new file mode 100644 index 0000000000..8d630a5862 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/map_unittest.cc @@ -0,0 +1,46 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "mojo/public/cpp/bindings/tests/rect_chromium.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +TEST(MapTest, StructKey) { + std::unordered_map map; + map.insert(std::make_pair(Rect::New(1, 2, 3, 4), 123)); + + RectPtr key = Rect::New(1, 2, 3, 4); + ASSERT_NE(map.end(), map.find(key)); + ASSERT_EQ(123, map.find(key)->second); + + map.erase(key); + ASSERT_EQ(0u, map.size()); +} + +TEST(MapTest, TypemappedStructKey) { + std::unordered_map map; + map.insert( + std::make_pair(ContainsHashable::New(RectChromium(1, 2, 3, 4)), 123)); + + ContainsHashablePtr key = ContainsHashable::New(RectChromium(1, 2, 3, 4)); + ASSERT_NE(map.end(), map.find(key)); + ASSERT_EQ(123, map.find(key)->second); + + map.erase(key); + ASSERT_EQ(0u, map.size()); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/message_queue.cc b/mojo/public/cpp/bindings/tests/message_queue.cc new file mode 100644 index 0000000000..32ed76328c --- /dev/null +++ b/mojo/public/cpp/bindings/tests/message_queue.cc @@ -0,0 +1,39 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/message_queue.h" + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace test { + +MessageQueue::MessageQueue() { +} + +MessageQueue::~MessageQueue() { +} + +bool MessageQueue::IsEmpty() const { + return queue_.empty(); +} + +void MessageQueue::Push(Message* message) { + queue_.emplace(std::move(*message)); +} + +void MessageQueue::Pop(Message* message) { + DCHECK(!queue_.empty()); + *message = std::move(queue_.front()); + Pop(); +} + +void MessageQueue::Pop() { + DCHECK(!queue_.empty()); + queue_.pop(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/message_queue.h b/mojo/public/cpp/bindings/tests/message_queue.h new file mode 100644 index 0000000000..8f13f7ab6d --- /dev/null +++ b/mojo/public/cpp/bindings/tests/message_queue.h @@ -0,0 +1,44 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_ + +#include + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace test { + +// A queue for Message objects. +class MessageQueue { + public: + MessageQueue(); + ~MessageQueue(); + + bool IsEmpty() const; + + // This method copies the message data and steals ownership of its handles. + void Push(Message* message); + + // Removes the next message from the queue, copying its data and transferring + // ownership of its handles to the given |message|. + void Pop(Message* message); + + size_t size() const { return queue_.size(); } + + private: + void Pop(); + + std::queue queue_; + + DISALLOW_COPY_AND_ASSIGN(MessageQueue); +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_ diff --git a/mojo/public/cpp/bindings/tests/mojo_test_blink_export.h b/mojo/public/cpp/bindings/tests/mojo_test_blink_export.h new file mode 100644 index 0000000000..b3bbe27421 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/mojo_test_blink_export.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_BLINK_EXPORT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_BLINK_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_TEST_BLINK_IMPLEMENTATION) +#define MOJO_TEST_BLINK_EXPORT __declspec(dllexport) +#else +#define MOJO_TEST_BLINK_EXPORT __declspec(dllimport) +#endif // defined(MOJO_TEST_BLINK_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MOJO_TEST_BLINK_IMPLEMENTATION) +#define MOJO_TEST_BLINK_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_TEST_BLINK_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MOJO_TEST_BLINK_EXPORT +#endif + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_BLINK_EXPORT_H_ diff --git a/mojo/public/cpp/bindings/tests/mojo_test_export.h b/mojo/public/cpp/bindings/tests/mojo_test_export.h new file mode 100644 index 0000000000..a48a1ba8ae --- /dev/null +++ b/mojo/public/cpp/bindings/tests/mojo_test_export.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_EXPORT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_TEST_IMPLEMENTATION) +#define MOJO_TEST_EXPORT __declspec(dllexport) +#else +#define MOJO_TEST_EXPORT __declspec(dllimport) +#endif // defined(MOJO_TEST_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MOJO_TEST_IMPLEMENTATION) +#define MOJO_TEST_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_TEST_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MOJO_TEST_EXPORT +#endif + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_EXPORT_H_ diff --git a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc new file mode 100644 index 0000000000..89509283c4 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc @@ -0,0 +1,314 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" + +#include + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "mojo/public/cpp/bindings/tests/message_queue.h" +#include "mojo/public/cpp/bindings/tests/router_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using mojo::internal::MultiplexRouter; + +class MultiplexRouterTest : public testing::Test { + public: + MultiplexRouterTest() {} + + void SetUp() override { + MessagePipe pipe; + router0_ = new MultiplexRouter(std::move(pipe.handle0), + MultiplexRouter::MULTI_INTERFACE, false, + base::ThreadTaskRunnerHandle::Get()); + router1_ = new MultiplexRouter(std::move(pipe.handle1), + MultiplexRouter::MULTI_INTERFACE, true, + base::ThreadTaskRunnerHandle::Get()); + ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&endpoint0_, + &endpoint1_); + auto id = router0_->AssociateInterface(std::move(endpoint1_)); + endpoint1_ = router1_->CreateLocalEndpointHandle(id); + } + + void TearDown() override {} + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + protected: + scoped_refptr router0_; + scoped_refptr router1_; + ScopedInterfaceEndpointHandle endpoint0_; + ScopedInterfaceEndpointHandle endpoint1_; + + private: + base::MessageLoop loop_; +}; + +TEST_F(MultiplexRouterTest, BasicRequestResponse) { + InterfaceEndpointClient client0(std::move(endpoint0_), nullptr, + base::MakeUnique(), false, + base::ThreadTaskRunnerHandle::Get(), 0u); + ResponseGenerator generator; + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::MakeUnique(), false, + base::ThreadTaskRunnerHandle::Get(), 0u); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + base::RunLoop run_loop; + client0.AcceptWithResponder( + &request, base::MakeUnique(&message_queue, + run_loop.QuitClosure())); + + run_loop.Run(); + + EXPECT_FALSE(message_queue.IsEmpty()); + + Message response; + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello world!"), + std::string(reinterpret_cast(response.payload()))); + + // Send a second message on the pipe. + Message request2; + AllocRequestMessage(1, "hello again", &request2); + + base::RunLoop run_loop2; + client0.AcceptWithResponder( + &request2, base::MakeUnique(&message_queue, + run_loop2.QuitClosure())); + + run_loop2.Run(); + + EXPECT_FALSE(message_queue.IsEmpty()); + + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello again world!"), + std::string(reinterpret_cast(response.payload()))); +} + +TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) { + InterfaceEndpointClient client0(std::move(endpoint0_), nullptr, + base::MakeUnique(), false, + base::ThreadTaskRunnerHandle::Get(), 0u); + ResponseGenerator generator; + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::MakeUnique(), false, + base::ThreadTaskRunnerHandle::Get(), 0u); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + client0.AcceptWithResponder( + &request, base::MakeUnique(&message_queue)); + + router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + EXPECT_FALSE(message_queue.IsEmpty()); + + Message response; + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello world!"), + std::string(reinterpret_cast(response.payload()))); + + // Send a second message on the pipe. + Message request2; + AllocRequestMessage(1, "hello again", &request2); + + client0.AcceptWithResponder( + &request2, base::MakeUnique(&message_queue)); + + router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + EXPECT_FALSE(message_queue.IsEmpty()); + + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello again world!"), + std::string(reinterpret_cast(response.payload()))); +} + +// Tests MultiplexRouter using the LazyResponseGenerator. The responses will not +// be sent until after the requests have been accepted. +TEST_F(MultiplexRouterTest, LazyResponses) { + InterfaceEndpointClient client0( + std::move(endpoint0_), nullptr, base::WrapUnique(new PassThroughFilter()), + false, base::ThreadTaskRunnerHandle::Get(), 0u); + base::RunLoop run_loop; + LazyResponseGenerator generator(run_loop.QuitClosure()); + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::WrapUnique(new PassThroughFilter()), + false, base::ThreadTaskRunnerHandle::Get(), + 0u); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + base::RunLoop run_loop2; + client0.AcceptWithResponder( + &request, base::MakeUnique(&message_queue, + run_loop2.QuitClosure())); + run_loop.Run(); + + // The request has been received but the response has not been sent yet. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Send the response. + EXPECT_TRUE(generator.responder_is_valid()); + generator.CompleteWithResponse(); + run_loop2.Run(); + + // Check the response. + EXPECT_FALSE(message_queue.IsEmpty()); + Message response; + message_queue.Pop(&response); + EXPECT_EQ(std::string("hello world!"), + std::string(reinterpret_cast(response.payload()))); + + // Send a second message on the pipe. + base::RunLoop run_loop3; + generator.set_closure(run_loop3.QuitClosure()); + Message request2; + AllocRequestMessage(1, "hello again", &request2); + + base::RunLoop run_loop4; + client0.AcceptWithResponder( + &request2, base::MakeUnique(&message_queue, + run_loop4.QuitClosure())); + run_loop3.Run(); + + // The request has been received but the response has not been sent yet. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Send the second response. + EXPECT_TRUE(generator.responder_is_valid()); + generator.CompleteWithResponse(); + run_loop4.Run(); + + // Check the second response. + EXPECT_FALSE(message_queue.IsEmpty()); + message_queue.Pop(&response); + EXPECT_EQ(std::string("hello again world!"), + std::string(reinterpret_cast(response.payload()))); +} + +void ForwardErrorHandler(bool* called, const base::Closure& callback) { + *called = true; + callback.Run(); +} + +// Tests that if the receiving application destroys the responder_ without +// sending a response, then we trigger connection error at both sides. Moreover, +// both sides still appear to have a valid message pipe handle bound. +TEST_F(MultiplexRouterTest, MissingResponses) { + base::RunLoop run_loop0, run_loop1; + InterfaceEndpointClient client0( + std::move(endpoint0_), nullptr, base::WrapUnique(new PassThroughFilter()), + false, base::ThreadTaskRunnerHandle::Get(), 0u); + bool error_handler_called0 = false; + client0.set_connection_error_handler( + base::Bind(&ForwardErrorHandler, &error_handler_called0, + run_loop0.QuitClosure())); + + base::RunLoop run_loop3; + LazyResponseGenerator generator(run_loop3.QuitClosure()); + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::WrapUnique(new PassThroughFilter()), + false, base::ThreadTaskRunnerHandle::Get(), + 0u); + bool error_handler_called1 = false; + client1.set_connection_error_handler( + base::Bind(&ForwardErrorHandler, &error_handler_called1, + run_loop1.QuitClosure())); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + client0.AcceptWithResponder( + &request, base::MakeUnique(&message_queue)); + run_loop3.Run(); + + // The request has been received but no response has been sent. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Destroy the responder MessagerReceiver but don't send any response. + generator.CompleteWithoutResponse(); + run_loop0.Run(); + run_loop1.Run(); + + // Check that no response was received. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Connection error handler is called at both sides. + EXPECT_TRUE(error_handler_called0); + EXPECT_TRUE(error_handler_called1); + + // The error flag is set at both sides. + EXPECT_TRUE(client0.encountered_error()); + EXPECT_TRUE(client1.encountered_error()); + + // The message pipe handle is valid at both sides. + EXPECT_TRUE(router0_->is_valid()); + EXPECT_TRUE(router1_->is_valid()); +} + +TEST_F(MultiplexRouterTest, LateResponse) { + // Test that things won't blow up if we try to send a message to a + // MessageReceiver, which was given to us via AcceptWithResponder, + // after the router has gone away. + + base::RunLoop run_loop; + LazyResponseGenerator generator(run_loop.QuitClosure()); + { + InterfaceEndpointClient client0( + std::move(endpoint0_), nullptr, base::MakeUnique(), + false, base::ThreadTaskRunnerHandle::Get(), 0u); + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::MakeUnique(), + false, base::ThreadTaskRunnerHandle::Get(), + 0u); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + client0.AcceptWithResponder( + &request, base::MakeUnique(&message_queue)); + + run_loop.Run(); + + EXPECT_TRUE(generator.has_responder()); + } + + EXPECT_FALSE(generator.responder_is_valid()); + generator.CompleteWithResponse(); // This should end up doing nothing. +} + +// TODO(yzshen): add more tests. + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/pickle_unittest.cc b/mojo/public/cpp/bindings/tests/pickle_unittest.cc new file mode 100644 index 0000000000..a5947ce9ed --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickle_unittest.cc @@ -0,0 +1,403 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/tests/pickled_types_blink.h" +#include "mojo/public/cpp/bindings/tests/pickled_types_chromium.h" +#include "mojo/public/cpp/bindings/tests/variant_test_util.h" +#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template +void DoExpectResult(int foo, int bar, const base::Closure& callback, T actual) { + EXPECT_EQ(foo, actual.foo()); + EXPECT_EQ(bar, actual.bar()); + callback.Run(); +} + +template +base::Callback ExpectResult(const T& t, + const base::Closure& callback) { + return base::Bind(&DoExpectResult, t.foo(), t.bar(), callback); +} + +template +void DoFail(const std::string& reason, T) { + EXPECT_TRUE(false) << reason; +} + +template +base::Callback Fail(const std::string& reason) { + return base::Bind(&DoFail, reason); +} + +template +void DoExpectEnumResult(T expected, const base::Closure& callback, T actual) { + EXPECT_EQ(expected, actual); + callback.Run(); +} + +template +base::Callback ExpectEnumResult(T t, const base::Closure& callback) { + return base::Bind(&DoExpectEnumResult, t, callback); +} + +template +void DoEnumFail(const std::string& reason, T) { + EXPECT_TRUE(false) << reason; +} + +template +base::Callback EnumFail(const std::string& reason) { + return base::Bind(&DoEnumFail, reason); +} + +template +void ExpectError(InterfacePtr* proxy, const base::Closure& callback) { + proxy->set_connection_error_handler(callback); +} + +template +void RunSimpleLambda(Func func, Arg arg) { func(std::move(arg)); } + +template +base::Callback BindSimpleLambda(Func func) { + return base::Bind(&RunSimpleLambda, func); +} + +// This implements the generated Chromium variant of PicklePasser. +class ChromiumPicklePasserImpl : public PicklePasser { + public: + ChromiumPicklePasserImpl() {} + + // mojo::test::PicklePasser: + void PassPickledStruct(PickledStructChromium pickle, + const PassPickledStructCallback& callback) override { + callback.Run(std::move(pickle)); + } + + void PassPickledEnum(PickledEnumChromium pickle, + const PassPickledEnumCallback& callback) override { + callback.Run(pickle); + } + + void PassPickleContainer( + PickleContainerPtr container, + const PassPickleContainerCallback& callback) override { + callback.Run(std::move(container)); + } + + void PassPickles(std::vector pickles, + const PassPicklesCallback& callback) override { + callback.Run(std::move(pickles)); + } + + void PassPickleArrays( + std::vector> pickle_arrays, + const PassPickleArraysCallback& callback) override { + callback.Run(std::move(pickle_arrays)); + } +}; + +// This implements the generated Blink variant of PicklePasser. +class BlinkPicklePasserImpl : public blink::PicklePasser { + public: + BlinkPicklePasserImpl() {} + + // mojo::test::blink::PicklePasser: + void PassPickledStruct(PickledStructBlink pickle, + const PassPickledStructCallback& callback) override { + callback.Run(std::move(pickle)); + } + + void PassPickledEnum(PickledEnumBlink pickle, + const PassPickledEnumCallback& callback) override { + callback.Run(pickle); + } + + void PassPickleContainer( + blink::PickleContainerPtr container, + const PassPickleContainerCallback& callback) override { + callback.Run(std::move(container)); + } + + void PassPickles(WTF::Vector pickles, + const PassPicklesCallback& callback) override { + callback.Run(std::move(pickles)); + } + + void PassPickleArrays( + WTF::Vector> pickle_arrays, + const PassPickleArraysCallback& callback) override { + callback.Run(std::move(pickle_arrays)); + } +}; + +// A test which runs both Chromium and Blink implementations of the +// PicklePasser service. +class PickleTest : public testing::Test { + public: + PickleTest() {} + + template + InterfacePtr ConnectToChromiumService() { + InterfacePtr proxy; + InterfaceRequest request(&proxy); + chromium_bindings_.AddBinding( + &chromium_service_, + ConvertInterfaceRequest(std::move(request))); + return proxy; + } + + template + InterfacePtr ConnectToBlinkService() { + InterfacePtr proxy; + InterfaceRequest request(&proxy); + blink_bindings_.AddBinding( + &blink_service_, + ConvertInterfaceRequest(std::move(request))); + return proxy; + } + + private: + base::MessageLoop loop_; + ChromiumPicklePasserImpl chromium_service_; + BindingSet chromium_bindings_; + BlinkPicklePasserImpl blink_service_; + BindingSet blink_bindings_; +}; + +} // namespace + +TEST_F(PickleTest, ChromiumProxyToChromiumService) { + auto chromium_proxy = ConnectToChromiumService(); + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(1, 2), + ExpectResult(PickledStructChromium(1, 2), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(4, 5), + ExpectResult(PickledStructChromium(4, 5), loop.QuitClosure())); + loop.Run(); + } + + { + base::RunLoop loop; + chromium_proxy->PassPickledEnum( + PickledEnumChromium::VALUE_1, + ExpectEnumResult(PickledEnumChromium::VALUE_1, loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(PickleTest, ChromiumProxyToBlinkService) { + auto chromium_proxy = ConnectToBlinkService(); + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(1, 2), + ExpectResult(PickledStructChromium(1, 2), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(4, 5), + ExpectResult(PickledStructChromium(4, 5), loop.QuitClosure())); + loop.Run(); + } + // The Blink service should drop our connection because the + // PickledStructBlink ParamTraits deserializer rejects negative values. + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(-1, -1), + Fail("Blink service should reject this.")); + ExpectError(&chromium_proxy, loop.QuitClosure()); + loop.Run(); + } + + chromium_proxy = ConnectToBlinkService(); + { + base::RunLoop loop; + chromium_proxy->PassPickledEnum( + PickledEnumChromium::VALUE_0, + ExpectEnumResult(PickledEnumChromium::VALUE_0, loop.QuitClosure())); + loop.Run(); + } + + // The Blink service should drop our connection because the + // PickledEnumBlink ParamTraits deserializer rejects this value. + { + base::RunLoop loop; + chromium_proxy->PassPickledEnum( + PickledEnumChromium::VALUE_2, + EnumFail("Blink service should reject this.")); + ExpectError(&chromium_proxy, loop.QuitClosure()); + loop.Run(); + } +} + +TEST_F(PickleTest, BlinkProxyToBlinkService) { + auto blink_proxy = ConnectToBlinkService(); + { + base::RunLoop loop; + blink_proxy->PassPickledStruct( + PickledStructBlink(1, 1), + ExpectResult(PickledStructBlink(1, 1), loop.QuitClosure())); + loop.Run(); + } + + { + base::RunLoop loop; + blink_proxy->PassPickledEnum( + PickledEnumBlink::VALUE_0, + ExpectEnumResult(PickledEnumBlink::VALUE_0, loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(PickleTest, BlinkProxyToChromiumService) { + auto blink_proxy = ConnectToChromiumService(); + { + base::RunLoop loop; + blink_proxy->PassPickledStruct( + PickledStructBlink(1, 1), + ExpectResult(PickledStructBlink(1, 1), loop.QuitClosure())); + loop.Run(); + } + + { + base::RunLoop loop; + blink_proxy->PassPickledEnum( + PickledEnumBlink::VALUE_1, + ExpectEnumResult(PickledEnumBlink::VALUE_1, loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(PickleTest, PickleArray) { + auto proxy = ConnectToChromiumService(); + auto pickles = std::vector(2); + pickles[0].set_foo(1); + pickles[0].set_bar(2); + pickles[0].set_baz(100); + pickles[1].set_foo(3); + pickles[1].set_bar(4); + pickles[1].set_baz(100); + { + base::RunLoop run_loop; + // Verify that the array of pickled structs can be serialized and + // deserialized intact. This ensures that the ParamTraits are actually used + // rather than doing a byte-for-byte copy of the element data, beacuse the + // |baz| field should never be serialized. + proxy->PassPickles(std::move(pickles), + BindSimpleLambda>( + [&](std::vector passed) { + ASSERT_EQ(2u, passed.size()); + EXPECT_EQ(1, passed[0].foo()); + EXPECT_EQ(2, passed[0].bar()); + EXPECT_EQ(0, passed[0].baz()); + EXPECT_EQ(3, passed[1].foo()); + EXPECT_EQ(4, passed[1].bar()); + EXPECT_EQ(0, passed[1].baz()); + run_loop.Quit(); + })); + run_loop.Run(); + } +} + +TEST_F(PickleTest, PickleArrayArray) { + auto proxy = ConnectToChromiumService(); + auto pickle_arrays = std::vector>(2); + for (size_t i = 0; i < 2; ++i) + pickle_arrays[i] = std::vector(2); + + pickle_arrays[0][0].set_foo(1); + pickle_arrays[0][0].set_bar(2); + pickle_arrays[0][0].set_baz(100); + pickle_arrays[0][1].set_foo(3); + pickle_arrays[0][1].set_bar(4); + pickle_arrays[0][1].set_baz(100); + pickle_arrays[1][0].set_foo(5); + pickle_arrays[1][0].set_bar(6); + pickle_arrays[1][0].set_baz(100); + pickle_arrays[1][1].set_foo(7); + pickle_arrays[1][1].set_bar(8); + pickle_arrays[1][1].set_baz(100); + { + base::RunLoop run_loop; + // Verify that the array-of-arrays serializes and deserializes properly. + proxy->PassPickleArrays( + std::move(pickle_arrays), + BindSimpleLambda>>( + [&](std::vector> passed) { + ASSERT_EQ(2u, passed.size()); + ASSERT_EQ(2u, passed[0].size()); + ASSERT_EQ(2u, passed[1].size()); + EXPECT_EQ(1, passed[0][0].foo()); + EXPECT_EQ(2, passed[0][0].bar()); + EXPECT_EQ(0, passed[0][0].baz()); + EXPECT_EQ(3, passed[0][1].foo()); + EXPECT_EQ(4, passed[0][1].bar()); + EXPECT_EQ(0, passed[0][1].baz()); + EXPECT_EQ(5, passed[1][0].foo()); + EXPECT_EQ(6, passed[1][0].bar()); + EXPECT_EQ(0, passed[1][0].baz()); + EXPECT_EQ(7, passed[1][1].foo()); + EXPECT_EQ(8, passed[1][1].bar()); + EXPECT_EQ(0, passed[1][1].baz()); + run_loop.Quit(); + })); + run_loop.Run(); + } +} + +TEST_F(PickleTest, PickleContainer) { + auto proxy = ConnectToChromiumService(); + PickleContainerPtr pickle_container = PickleContainer::New(); + pickle_container->f_struct.set_foo(42); + pickle_container->f_struct.set_bar(43); + pickle_container->f_struct.set_baz(44); + pickle_container->f_enum = PickledEnumChromium::VALUE_1; + EXPECT_TRUE(pickle_container.Equals(pickle_container)); + EXPECT_FALSE(pickle_container.Equals(PickleContainer::New())); + { + base::RunLoop run_loop; + proxy->PassPickleContainer(std::move(pickle_container), + BindSimpleLambda( + [&](PickleContainerPtr passed) { + ASSERT_FALSE(passed.is_null()); + EXPECT_EQ(42, passed->f_struct.foo()); + EXPECT_EQ(43, passed->f_struct.bar()); + EXPECT_EQ(0, passed->f_struct.baz()); + EXPECT_EQ(PickledEnumChromium::VALUE_1, + passed->f_enum); + run_loop.Quit(); + })); + run_loop.Run(); + } +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/pickled_types_blink.cc b/mojo/public/cpp/bindings/tests/pickled_types_blink.cc new file mode 100644 index 0000000000..7e556507bb --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickled_types_blink.cc @@ -0,0 +1,67 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/pickled_types_blink.h" + +#include "base/logging.h" +#include "base/pickle.h" + +namespace mojo { +namespace test { + +PickledStructBlink::PickledStructBlink() {} + +PickledStructBlink::PickledStructBlink(int foo, int bar) + : foo_(foo), bar_(bar) { + DCHECK_GE(foo_, 0); + DCHECK_GE(bar_, 0); +} + +PickledStructBlink::~PickledStructBlink() {} + +} // namespace test +} // namespace mojo + +namespace IPC { + +void ParamTraits::GetSize( + base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt(); + sizer->AddInt(); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + m->WriteInt(p.foo()); + m->WriteInt(p.bar()); +} + +bool ParamTraits::Read( + const base::Pickle* m, + base::PickleIterator* iter, + param_type* p) { + int foo, bar; + if (!iter->ReadInt(&foo) || !iter->ReadInt(&bar) || foo < 0 || bar < 0) + return false; + + p->set_foo(foo); + p->set_bar(bar); + return true; +} + +#include "ipc/param_traits_size_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) +#include "ipc/param_traits_write_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) +#include "ipc/param_traits_read_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) +#include "ipc/param_traits_log_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) + +} // namespace IPC diff --git a/mojo/public/cpp/bindings/tests/pickled_types_blink.h b/mojo/public/cpp/bindings/tests/pickled_types_blink.h new file mode 100644 index 0000000000..37e9e70578 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickled_types_blink.h @@ -0,0 +1,88 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_BLINK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_BLINK_H_ + +#include + +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_param_traits.h" + +namespace base { +class Pickle; +class PickleIterator; +class PickleSizer; +} + +namespace mojo { +namespace test { + +// Implementation of types with IPC::ParamTraits for consumers in Blink. + +enum class PickledEnumBlink { VALUE_0, VALUE_1 }; + +// To make things slightly more interesting, this variation of the type doesn't +// support negative values. It'll DCHECK if you try to construct it with any, +// and it will fail deserialization if negative values are decoded. +class PickledStructBlink { + public: + PickledStructBlink(); + PickledStructBlink(int foo, int bar); + PickledStructBlink(PickledStructBlink&& other) = default; + ~PickledStructBlink(); + + PickledStructBlink& operator=(PickledStructBlink&& other) = default; + + int foo() const { return foo_; } + void set_foo(int foo) { + DCHECK_GE(foo, 0); + foo_ = foo; + } + + int bar() const { return bar_; } + void set_bar(int bar) { + DCHECK_GE(bar, 0); + bar_ = bar; + } + + // The |baz| field should never be serialized. + int baz() const { return baz_; } + void set_baz(int baz) { baz_ = baz; } + + private: + int foo_ = 0; + int bar_ = 0; + int baz_ = 0; + + DISALLOW_COPY_AND_ASSIGN(PickledStructBlink); +}; + +} // namespace test +} // namespace mojo + +namespace IPC { + +template <> +struct ParamTraits { + using param_type = mojo::test::PickledStructBlink; + + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l) {} +}; + +} // namespace IPC + +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_BLINK_H_ diff --git a/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc b/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc new file mode 100644 index 0000000000..9957c9a4d0 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc @@ -0,0 +1,69 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/pickled_types_chromium.h" + +#include "base/pickle.h" + +namespace mojo { +namespace test { + +PickledStructChromium::PickledStructChromium() {} + +PickledStructChromium::PickledStructChromium(int foo, int bar) + : foo_(foo), bar_(bar) {} + +PickledStructChromium::~PickledStructChromium() {} + +bool operator==(const PickledStructChromium& a, + const PickledStructChromium& b) { + return a.foo() == b.foo() && a.bar() == b.bar() && a.baz() == b.baz(); +} + +} // namespace test +} // namespace mojo + +namespace IPC { + +void ParamTraits::GetSize( + base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt(); + sizer->AddInt(); +} + +void ParamTraits::Write( + base::Pickle* m, + const param_type& p) { + m->WriteInt(p.foo()); + m->WriteInt(p.bar()); +} + +bool ParamTraits::Read( + const base::Pickle* m, + base::PickleIterator* iter, + param_type* p) { + int foo, bar; + if (!iter->ReadInt(&foo) || !iter->ReadInt(&bar)) + return false; + + p->set_foo(foo); + p->set_bar(bar); + return true; +} + +#include "ipc/param_traits_size_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) +#include "ipc/param_traits_write_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) +#include "ipc/param_traits_read_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) +#include "ipc/param_traits_log_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) + +} // namespace IPC diff --git a/mojo/public/cpp/bindings/tests/pickled_types_chromium.h b/mojo/public/cpp/bindings/tests/pickled_types_chromium.h new file mode 100644 index 0000000000..d9287b62e7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickled_types_chromium.h @@ -0,0 +1,81 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_CHROMIUM_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_CHROMIUM_H_ + +#include + +#include + +#include "base/macros.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_param_traits.h" + +namespace base { +class Pickle; +class PickleIterator; +class PickleSizer; +} + +namespace mojo { +namespace test { + +// Implementation of types with IPC::ParamTraits for consumers in the greater +// Chromium tree. + +enum class PickledEnumChromium { VALUE_0, VALUE_1, VALUE_2 }; + +class PickledStructChromium { + public: + PickledStructChromium(); + PickledStructChromium(int foo, int bar); + PickledStructChromium(PickledStructChromium&& other) = default; + ~PickledStructChromium(); + + PickledStructChromium& operator=(PickledStructChromium&& other) = default; + + int foo() const { return foo_; } + void set_foo(int foo) { foo_ = foo; } + + int bar() const { return bar_; } + void set_bar(int bar) { bar_ = bar; } + + // The |baz| field should never be serialized. + int baz() const { return baz_; } + void set_baz(int baz) { baz_ = baz; } + + private: + int foo_ = 0; + int bar_ = 0; + int baz_ = 0; + + DISALLOW_COPY_AND_ASSIGN(PickledStructChromium); +}; + +bool operator==(const PickledStructChromium& a, const PickledStructChromium& b); + +} // namespace test +} // namespace mojo + +namespace IPC { + +template <> +struct ParamTraits { + using param_type = mojo::test::PickledStructChromium; + + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l) {} +}; + +} // namespace IPC + +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_CHROMIUM_H_ diff --git a/mojo/public/cpp/bindings/tests/rect_blink.h b/mojo/public/cpp/bindings/tests/rect_blink.h new file mode 100644 index 0000000000..7335989593 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_blink.h @@ -0,0 +1,83 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_H_ + +#include "base/logging.h" + +namespace mojo { +namespace test { + +// An implementation of a hypothetical Rect type specifically for consumers in +// in Blink. Unlike the Chromium variant (see rect_chromium.h) this does not +// support negative origin coordinates and is not copyable. +class RectBlink { + public: + RectBlink() {} + RectBlink(int x, int y, int width, int height) : + x_(x), y_(y), width_(width), height_(height) { + DCHECK_GE(x_, 0); + DCHECK_GE(y_, 0); + DCHECK_GE(width_, 0); + DCHECK_GE(height_, 0); + } + ~RectBlink() {} + + int x() const { return x_; } + void setX(int x) { + DCHECK_GE(x, 0); + x_ = x; + } + + int y() const { return y_; } + void setY(int y) { + DCHECK_GE(y, 0); + y_ = y; + } + + int width() const { return width_; } + void setWidth(int width) { + DCHECK_GE(width, 0); + width_ = width; + } + + int height() const { return height_; } + void setHeight(int height) { + DCHECK_GE(height, 0); + height_ = height; + } + + int computeArea() const { return width_ * height_; } + + bool operator==(const RectBlink& other) const { + return (x() == other.x() && y() == other.y() && width() == other.width() && + height() == other.height()); + } + bool operator!=(const RectBlink& other) const { return !(*this == other); } + + private: + int x_ = 0; + int y_ = 0; + int width_ = 0; + int height_ = 0; +}; + +} // namespace test +} // namespace mojo + +namespace std { + +template <> +struct hash { + size_t operator()(const mojo::test::RectBlink& value) { + // Terrible hash function: + return (std::hash()(value.x()) ^ std::hash()(value.y()) ^ + std::hash()(value.width()) ^ std::hash()(value.height())); + } +}; + +} // namespace std + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_H_ diff --git a/mojo/public/cpp/bindings/tests/rect_blink.typemap b/mojo/public/cpp/bindings/tests/rect_blink.typemap new file mode 100644 index 0000000000..657ea1a6ca --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_blink.typemap @@ -0,0 +1,18 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +mojom = "//mojo/public/interfaces/bindings/tests/rect.mojom" +public_headers = [ + "//mojo/public/cpp/bindings/tests/rect_blink.h", + "//mojo/public/cpp/bindings/tests/shared_rect.h", +] +traits_headers = [ + "//mojo/public/cpp/bindings/tests/rect_blink_traits.h", + "//mojo/public/cpp/bindings/tests/shared_rect_traits.h", +] + +type_mappings = [ + "mojo.test.TypemappedRect=mojo::test::RectBlink[hashable]", + "mojo.test.SharedTypemappedRect=mojo::test::SharedRect", +] diff --git a/mojo/public/cpp/bindings/tests/rect_blink_traits.h b/mojo/public/cpp/bindings/tests/rect_blink_traits.h new file mode 100644 index 0000000000..7258739a84 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_blink_traits.h @@ -0,0 +1,35 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_TRAITS_H_ + +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/tests/rect_blink.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom-blink.h" + +namespace mojo { + +template <> +struct StructTraits { + static int x(const test::RectBlink& r) { return r.x(); } + static int y(const test::RectBlink& r) { return r.y(); } + static int width(const test::RectBlink& r) { return r.width(); } + static int height(const test::RectBlink& r) { return r.height(); } + + static bool Read(test::TypemappedRectDataView r, test::RectBlink* out) { + if (r.x() < 0 || r.y() < 0 || r.width() < 0 || r.height() < 0) { + return false; + } + out->setX(r.x()); + out->setY(r.y()); + out->setWidth(r.width()); + out->setHeight(r.height()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/tests/rect_chromium.h b/mojo/public/cpp/bindings/tests/rect_chromium.h new file mode 100644 index 0000000000..d2e0a3e635 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_chromium.h @@ -0,0 +1,87 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_H_ + +#include "base/logging.h" + +namespace mojo { +namespace test { + +// An implementation of a hypothetical Rect type specifically for consumers in +// in Chromium. +class RectChromium { + public: + RectChromium() {} + RectChromium(const RectChromium& other) + : x_(other.x_), + y_(other.y_), + width_(other.width_), + height_(other.height_) {} + RectChromium(int x, int y, int width, int height) : + x_(x), y_(y), width_(width), height_(height) { + DCHECK_GE(width_, 0); + DCHECK_GE(height_, 0); + } + ~RectChromium() {} + + RectChromium& operator=(const RectChromium& other) { + x_ = other.x_; + y_ = other.y_; + width_ = other.width_; + height_ = other.height_; + return *this; + } + + int x() const { return x_; } + void set_x(int x) { x_ = x; } + + int y() const { return y_; } + void set_y(int y) { y_ = y; } + + int width() const { return width_; } + void set_width(int width) { + DCHECK_GE(width, 0); + width_ = width; + } + + int height() const { return height_; } + void set_height(int height) { + DCHECK_GE(height, 0); + height_ = height; + } + + int GetArea() const { return width_ * height_; } + + bool operator==(const RectChromium& other) const { + return (x() == other.x() && y() == other.y() && width() == other.width() && + height() == other.height()); + } + bool operator!=(const RectChromium& other) const { return !(*this == other); } + + private: + int x_ = 0; + int y_ = 0; + int width_ = 0; + int height_ = 0; +}; + +} // namespace test +} // namespace mojo + +namespace std { + +template <> +struct hash { + size_t operator()(const mojo::test::RectChromium& value) { + // Terrible hash function: + return (std::hash()(value.x()) ^ std::hash()(value.y()) ^ + std::hash()(value.width()) ^ std::hash()(value.height())); + } +}; + +} // namespace std + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_H_ diff --git a/mojo/public/cpp/bindings/tests/rect_chromium.typemap b/mojo/public/cpp/bindings/tests/rect_chromium.typemap new file mode 100644 index 0000000000..7e5df8401a --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_chromium.typemap @@ -0,0 +1,18 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +mojom = "//mojo/public/interfaces/bindings/tests/rect.mojom" +public_headers = [ + "//mojo/public/cpp/bindings/tests/rect_chromium.h", + "//mojo/public/cpp/bindings/tests/shared_rect.h", +] +traits_headers = [ + "//mojo/public/cpp/bindings/tests/rect_chromium_traits.h", + "//mojo/public/cpp/bindings/tests/shared_rect_traits.h", +] + +type_mappings = [ + "mojo.test.TypemappedRect=mojo::test::RectChromium[hashable]", + "mojo.test.SharedTypemappedRect=mojo::test::SharedRect", +] diff --git a/mojo/public/cpp/bindings/tests/rect_chromium_traits.h b/mojo/public/cpp/bindings/tests/rect_chromium_traits.h new file mode 100644 index 0000000000..b446d7d34a --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_chromium_traits.h @@ -0,0 +1,34 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_TRAITS_H_ + +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/tests/rect_chromium.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom.h" + +namespace mojo { + +template <> +struct StructTraits { + static int x(const test::RectChromium& r) { return r.x(); } + static int y(const test::RectChromium& r) { return r.y(); } + static int width(const test::RectChromium& r) { return r.width(); } + static int height(const test::RectChromium& r) { return r.height(); } + + static bool Read(test::TypemappedRectDataView r, test::RectChromium* out) { + if (r.width() < 0 || r.height() < 0) + return false; + out->set_x(r.x()); + out->set_y(r.y()); + out->set_width(r.width()); + out->set_height(r.height()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc b/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc new file mode 100644 index 0000000000..1bf3f7a4b7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc @@ -0,0 +1,194 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/interfaces/bindings/tests/test_bad_messages.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class TestBadMessagesImpl : public TestBadMessages { + public: + TestBadMessagesImpl() : binding_(this) {} + ~TestBadMessagesImpl() override {} + + void BindImpl(TestBadMessagesRequest request) { + binding_.Bind(std::move(request)); + } + + const ReportBadMessageCallback& bad_message_callback() { + return bad_message_callback_; + } + + private: + // TestBadMessages: + void RejectEventually(const RejectEventuallyCallback& callback) override { + bad_message_callback_ = GetBadMessageCallback(); + callback.Run(); + } + + void RequestResponse(const RequestResponseCallback& callback) override { + callback.Run(); + } + + void RejectSync(const RejectSyncCallback& callback) override { + callback.Run(); + ReportBadMessage("go away"); + } + + void RequestResponseSync( + const RequestResponseSyncCallback& callback) override { + callback.Run(); + } + + ReportBadMessageCallback bad_message_callback_; + mojo::Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(TestBadMessagesImpl); +}; + +class ReportBadMessageTest : public testing::Test { + public: + ReportBadMessageTest() {} + + void SetUp() override { + mojo::edk::SetDefaultProcessErrorCallback( + base::Bind(&ReportBadMessageTest::OnProcessError, + base::Unretained(this))); + + impl_.BindImpl(MakeRequest(&proxy_)); + } + + void TearDown() override { + mojo::edk::SetDefaultProcessErrorCallback( + mojo::edk::ProcessErrorCallback()); + } + + TestBadMessages* proxy() { return proxy_.get(); } + + TestBadMessagesImpl* impl() { return &impl_; } + + void SetErrorHandler(const base::Closure& handler) { + error_handler_ = handler; + } + + private: + void OnProcessError(const std::string& error) { + if (!error_handler_.is_null()) + error_handler_.Run(); + } + + TestBadMessagesPtr proxy_; + TestBadMessagesImpl impl_; + base::Closure error_handler_; + base::MessageLoop message_loop; +}; + +TEST_F(ReportBadMessageTest, Request) { + // Verify that basic immediate error reporting works. + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + EXPECT_TRUE(proxy()->RejectSync()); + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, RequestAsync) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + // This should capture a bad message reporting callback in the impl. + base::RunLoop loop; + proxy()->RejectEventually(loop.QuitClosure()); + loop.Run(); + + EXPECT_FALSE(error); + + // Now we can run the callback and it should trigger a bad message report. + DCHECK(!impl()->bad_message_callback().is_null()); + impl()->bad_message_callback().Run("bad!"); + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, Response) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + base::RunLoop loop; + proxy()->RequestResponse( + base::Bind([] (const base::Closure& quit) { + // Report a bad message inside the response callback. This should + // trigger the error handler. + ReportBadMessage("no way!"); + quit.Run(); + }, + loop.QuitClosure())); + loop.Run(); + + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, ResponseAsync) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + ReportBadMessageCallback bad_message_callback; + base::RunLoop loop; + proxy()->RequestResponse( + base::Bind([] (const base::Closure& quit, + ReportBadMessageCallback* callback) { + // Capture the bad message callback inside the response callback. + *callback = GetBadMessageCallback(); + quit.Run(); + }, + loop.QuitClosure(), &bad_message_callback)); + loop.Run(); + + EXPECT_FALSE(error); + + // Invoking this callback should report a bad message and trigger the error + // handler immediately. + bad_message_callback.Run("this message is bad and should feel bad"); + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, ResponseSync) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + SyncMessageResponseContext context; + proxy()->RequestResponseSync(); + + EXPECT_FALSE(error); + context.ReportBadMessage("i don't like this response"); + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, ResponseSyncDeferred) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + ReportBadMessageCallback bad_message_callback; + { + SyncMessageResponseContext context; + proxy()->RequestResponseSync(); + bad_message_callback = context.GetBadMessageCallback(); + } + + EXPECT_FALSE(error); + bad_message_callback.Run("nope nope nope"); + EXPECT_TRUE(error); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/request_response_unittest.cc b/mojo/public/cpp/bindings/tests/request_response_unittest.cc new file mode 100644 index 0000000000..43b8f0dc90 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/request_response_unittest.cc @@ -0,0 +1,157 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/sample_import.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class ProviderImpl : public sample::Provider { + public: + explicit ProviderImpl(InterfaceRequest request) + : binding_(this, std::move(request)) {} + + void EchoString(const std::string& a, + const EchoStringCallback& callback) override { + EchoStringCallback callback_copy; + // Make sure operator= is used. + callback_copy = callback; + callback_copy.Run(a); + } + + void EchoStrings(const std::string& a, + const std::string& b, + const EchoStringsCallback& callback) override { + callback.Run(a, b); + } + + void EchoMessagePipeHandle( + ScopedMessagePipeHandle a, + const EchoMessagePipeHandleCallback& callback) override { + callback.Run(std::move(a)); + } + + void EchoEnum(sample::Enum a, const EchoEnumCallback& callback) override { + callback.Run(a); + } + + void EchoInt(int32_t a, const EchoIntCallback& callback) override { + callback.Run(a); + } + + Binding binding_; +}; + +void RecordString(std::string* storage, + const base::Closure& closure, + const std::string& str) { + *storage = str; + closure.Run(); +} + +void RecordStrings(std::string* storage, + const base::Closure& closure, + const std::string& a, + const std::string& b) { + *storage = a + b; + closure.Run(); +} + +void WriteToMessagePipe(const char* text, + const base::Closure& closure, + ScopedMessagePipeHandle handle) { + WriteTextMessage(handle.get(), text); + closure.Run(); +} + +void RecordEnum(sample::Enum* storage, + const base::Closure& closure, + sample::Enum value) { + *storage = value; + closure.Run(); +} + +class RequestResponseTest : public testing::Test { + public: + RequestResponseTest() {} + ~RequestResponseTest() override { base::RunLoop().RunUntilIdle(); } + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +TEST_F(RequestResponseTest, EchoString) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(MakeRequest(&provider)); + + std::string buf; + base::RunLoop run_loop; + provider->EchoString("hello", + base::Bind(&RecordString, &buf, run_loop.QuitClosure())); + + run_loop.Run(); + + EXPECT_EQ(std::string("hello"), buf); +} + +TEST_F(RequestResponseTest, EchoStrings) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(MakeRequest(&provider)); + + std::string buf; + base::RunLoop run_loop; + provider->EchoStrings("hello", " world", base::Bind(&RecordStrings, &buf, + run_loop.QuitClosure())); + + run_loop.Run(); + + EXPECT_EQ(std::string("hello world"), buf); +} + +TEST_F(RequestResponseTest, EchoMessagePipeHandle) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(MakeRequest(&provider)); + + MessagePipe pipe2; + base::RunLoop run_loop; + provider->EchoMessagePipeHandle( + std::move(pipe2.handle1), + base::Bind(&WriteToMessagePipe, "hello", run_loop.QuitClosure())); + + run_loop.Run(); + + std::string value; + ReadTextMessage(pipe2.handle0.get(), &value); + + EXPECT_EQ(std::string("hello"), value); +} + +TEST_F(RequestResponseTest, EchoEnum) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(MakeRequest(&provider)); + + sample::Enum value; + base::RunLoop run_loop; + provider->EchoEnum(sample::Enum::VALUE, + base::Bind(&RecordEnum, &value, run_loop.QuitClosure())); + run_loop.Run(); + + EXPECT_EQ(sample::Enum::VALUE, value); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/router_test_util.cc b/mojo/public/cpp/bindings/tests/router_test_util.cc new file mode 100644 index 0000000000..9bab1cb360 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/router_test_util.cc @@ -0,0 +1,111 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/router_test_util.h" + +#include +#include +#include + +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/tests/message_queue.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +void AllocRequestMessage(uint32_t name, const char* text, Message* message) { + size_t payload_size = strlen(text) + 1; // Plus null terminator. + internal::MessageBuilder builder(name, Message::kFlagExpectsResponse, + payload_size, 0); + memcpy(builder.buffer()->Allocate(payload_size), text, payload_size); + *message = std::move(*builder.message()); +} + +void AllocResponseMessage(uint32_t name, + const char* text, + uint64_t request_id, + Message* message) { + size_t payload_size = strlen(text) + 1; // Plus null terminator. + internal::MessageBuilder builder(name, Message::kFlagIsResponse, payload_size, + 0); + builder.message()->set_request_id(request_id); + memcpy(builder.buffer()->Allocate(payload_size), text, payload_size); + *message = std::move(*builder.message()); +} + +MessageAccumulator::MessageAccumulator(MessageQueue* queue, + const base::Closure& closure) + : queue_(queue), closure_(closure) {} + +MessageAccumulator::~MessageAccumulator() {} + +bool MessageAccumulator::Accept(Message* message) { + queue_->Push(message); + if (!closure_.is_null()) { + closure_.Run(); + closure_.Reset(); + } + return true; +} + +ResponseGenerator::ResponseGenerator() {} + +bool ResponseGenerator::Accept(Message* message) { + return false; +} + +bool ResponseGenerator::AcceptWithResponder( + Message* message, + std::unique_ptr responder) { + EXPECT_TRUE(message->has_flag(Message::kFlagExpectsResponse)); + + bool result = SendResponse(message->name(), message->request_id(), + reinterpret_cast(message->payload()), + responder.get()); + EXPECT_TRUE(responder->IsValid()); + return result; +} + +bool ResponseGenerator::SendResponse(uint32_t name, + uint64_t request_id, + const char* request_string, + MessageReceiver* responder) { + Message response; + std::string response_string(request_string); + response_string += " world!"; + AllocResponseMessage(name, response_string.c_str(), request_id, &response); + + return responder->Accept(&response); +} + +LazyResponseGenerator::LazyResponseGenerator(const base::Closure& closure) + : responder_(nullptr), name_(0), request_id_(0), closure_(closure) {} + +LazyResponseGenerator::~LazyResponseGenerator() = default; + +bool LazyResponseGenerator::AcceptWithResponder( + Message* message, + std::unique_ptr responder) { + name_ = message->name(); + request_id_ = message->request_id(); + request_string_ = + std::string(reinterpret_cast(message->payload())); + responder_ = std::move(responder); + if (!closure_.is_null()) { + closure_.Run(); + closure_.Reset(); + } + return true; +} + +void LazyResponseGenerator::Complete(bool send_response) { + if (send_response) { + SendResponse(name_, request_id_, request_string_.c_str(), responder_.get()); + } + responder_ = nullptr; +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/router_test_util.h b/mojo/public/cpp/bindings/tests/router_test_util.h new file mode 100644 index 0000000000..dd6aff63da --- /dev/null +++ b/mojo/public/cpp/bindings/tests/router_test_util.h @@ -0,0 +1,92 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_ROUTER_TEST_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_ROUTER_TEST_UTIL_H_ + +#include + +#include + +#include "base/callback.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace test { + +class MessageQueue; + +void AllocRequestMessage(uint32_t name, const char* text, Message* message); +void AllocResponseMessage(uint32_t name, + const char* text, + uint64_t request_id, + Message* message); + +class MessageAccumulator : public MessageReceiver { + public: + MessageAccumulator(MessageQueue* queue, + const base::Closure& closure = base::Closure()); + ~MessageAccumulator() override; + + bool Accept(Message* message) override; + + private: + MessageQueue* queue_; + base::Closure closure_; +}; + +class ResponseGenerator : public MessageReceiverWithResponderStatus { + public: + ResponseGenerator(); + + bool Accept(Message* message) override; + + bool AcceptWithResponder( + Message* message, + std::unique_ptr responder) override; + bool SendResponse(uint32_t name, + uint64_t request_id, + const char* request_string, + MessageReceiver* responder); +}; + +class LazyResponseGenerator : public ResponseGenerator { + public: + explicit LazyResponseGenerator( + const base::Closure& closure = base::Closure()); + + ~LazyResponseGenerator() override; + + bool AcceptWithResponder( + Message* message, + std::unique_ptr responder) override; + + bool has_responder() const { return !!responder_; } + + bool responder_is_valid() const { return responder_->IsValid(); } + + void set_closure(const base::Closure& closure) { closure_ = closure; } + + // Sends the response and delete the responder. + void CompleteWithResponse() { Complete(true); } + + // Deletes the responder without sending a response. + void CompleteWithoutResponse() { Complete(false); } + + private: + // Completes the request handling by deleting responder_. Optionally + // also sends a response. + void Complete(bool send_response); + + std::unique_ptr responder_; + uint32_t name_; + uint64_t request_id_; + std::string request_string_; + base::Closure closure_; +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_ROUTER_TEST_UTIL_H_ diff --git a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc new file mode 100644 index 0000000000..1f95a27a5e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc @@ -0,0 +1,362 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { + +template <> +struct TypeConverter { + static int32_t Convert(const sample::BarPtr& bar) { + return static_cast(bar->alpha) << 16 | + static_cast(bar->beta) << 8 | + static_cast(bar->gamma); + } +}; + +} // namespace mojo + +namespace sample { +namespace { + +// Set this variable to true to print the message in hex. +bool g_dump_message_as_hex = false; + +// Set this variable to true to print the message in human readable form. +bool g_dump_message_as_text = false; + +// Make a sample |Foo|. +FooPtr MakeFoo() { + std::string name("foopy"); + + BarPtr bar(Bar::New(20, 40, 60, Bar::Type::VERTICAL)); + + std::vector extra_bars(3); + for (size_t i = 0; i < extra_bars.size(); ++i) { + Bar::Type type = i % 2 == 0 ? Bar::Type::VERTICAL : Bar::Type::HORIZONTAL; + uint8_t base = static_cast(i * 100); + extra_bars[i] = Bar::New(base, base + 20, base + 40, type); + } + + std::vector data(10); + for (size_t i = 0; i < data.size(); ++i) + data[i] = static_cast(data.size() - i); + + std::vector input_streams(2); + std::vector output_streams(2); + for (size_t i = 0; i < input_streams.size(); ++i) { + MojoCreateDataPipeOptions options; + options.struct_size = sizeof(MojoCreateDataPipeOptions); + options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; + options.element_num_bytes = 1; + options.capacity_num_bytes = 1024; + mojo::ScopedDataPipeProducerHandle producer; + mojo::ScopedDataPipeConsumerHandle consumer; + mojo::CreateDataPipe(&options, &producer, &consumer); + input_streams[i] = std::move(consumer); + output_streams[i] = std::move(producer); + } + + std::vector> array_of_array_of_bools(2); + for (size_t i = 0; i < 2; ++i) { + std::vector array_of_bools(2); + for (size_t j = 0; j < 2; ++j) + array_of_bools[j] = j; + array_of_array_of_bools[i] = std::move(array_of_bools); + } + + mojo::MessagePipe pipe; + return Foo::New(name, 1, 2, false, true, false, std::move(bar), + std::move(extra_bars), std::move(data), + std::move(pipe.handle1), std::move(input_streams), + std::move(output_streams), std::move(array_of_array_of_bools), + base::nullopt, base::nullopt); +} + +// Check that the given |Foo| is identical to the one made by |MakeFoo()|. +void CheckFoo(const Foo& foo) { + const std::string kName("foopy"); + EXPECT_EQ(kName.size(), foo.name.size()); + for (size_t i = 0; i < std::min(kName.size(), foo.name.size()); i++) { + // Test both |operator[]| and |at|. + EXPECT_EQ(kName[i], foo.name.at(i)) << i; + EXPECT_EQ(kName[i], foo.name[i]) << i; + } + EXPECT_EQ(kName, foo.name); + + EXPECT_EQ(1, foo.x); + EXPECT_EQ(2, foo.y); + EXPECT_FALSE(foo.a); + EXPECT_TRUE(foo.b); + EXPECT_FALSE(foo.c); + + EXPECT_EQ(20, foo.bar->alpha); + EXPECT_EQ(40, foo.bar->beta); + EXPECT_EQ(60, foo.bar->gamma); + EXPECT_EQ(Bar::Type::VERTICAL, foo.bar->type); + + EXPECT_EQ(3u, foo.extra_bars->size()); + for (size_t i = 0; i < foo.extra_bars->size(); i++) { + uint8_t base = static_cast(i * 100); + Bar::Type type = i % 2 == 0 ? Bar::Type::VERTICAL : Bar::Type::HORIZONTAL; + EXPECT_EQ(base, (*foo.extra_bars)[i]->alpha) << i; + EXPECT_EQ(base + 20, (*foo.extra_bars)[i]->beta) << i; + EXPECT_EQ(base + 40, (*foo.extra_bars)[i]->gamma) << i; + EXPECT_EQ(type, (*foo.extra_bars)[i]->type) << i; + } + + EXPECT_EQ(10u, foo.data->size()); + for (size_t i = 0; i < foo.data->size(); ++i) { + EXPECT_EQ(static_cast(foo.data->size() - i), (*foo.data)[i]) << i; + } + + EXPECT_TRUE(foo.input_streams); + EXPECT_EQ(2u, foo.input_streams->size()); + + EXPECT_TRUE(foo.output_streams); + EXPECT_EQ(2u, foo.output_streams->size()); + + EXPECT_EQ(2u, foo.array_of_array_of_bools->size()); + for (size_t i = 0; i < foo.array_of_array_of_bools->size(); ++i) { + EXPECT_EQ(2u, (*foo.array_of_array_of_bools)[i].size()); + for (size_t j = 0; j < (*foo.array_of_array_of_bools)[i].size(); ++j) { + EXPECT_EQ(bool(j), (*foo.array_of_array_of_bools)[i][j]); + } + } +} + +void PrintSpacer(int depth) { + for (int i = 0; i < depth; ++i) + std::cout << " "; +} + +void Print(int depth, const char* name, bool value) { + PrintSpacer(depth); + std::cout << name << ": " << (value ? "true" : "false") << std::endl; +} + +void Print(int depth, const char* name, int32_t value) { + PrintSpacer(depth); + std::cout << name << ": " << value << std::endl; +} + +void Print(int depth, const char* name, uint8_t value) { + PrintSpacer(depth); + std::cout << name << ": " << uint32_t(value) << std::endl; +} + +template +void Print(int depth, + const char* name, + const mojo::ScopedHandleBase& value) { + PrintSpacer(depth); + std::cout << name << ": 0x" << std::hex << value.get().value() << std::endl; +} + +void Print(int depth, const char* name, const std::string& str) { + PrintSpacer(depth); + std::cout << name << ": \"" << str << "\"" << std::endl; +} + +void Print(int depth, const char* name, const BarPtr& bar) { + PrintSpacer(depth); + std::cout << name << ":" << std::endl; + if (!bar.is_null()) { + ++depth; + Print(depth, "alpha", bar->alpha); + Print(depth, "beta", bar->beta); + Print(depth, "gamma", bar->gamma); + Print(depth, "packed", bar.To()); + --depth; + } +} + +template +void Print(int depth, const char* name, const std::vector& array) { + PrintSpacer(depth); + std::cout << name << ":" << std::endl; + ++depth; + for (size_t i = 0; i < array.size(); ++i) { + std::stringstream buf; + buf << i; + Print(depth, buf.str().data(), array.at(i)); + } + --depth; +} + +template +void Print(int depth, + const char* name, + const base::Optional>& array) { + if (array) + Print(depth, name, *array); + else + Print(depth, name, std::vector()); +} + +void Print(int depth, const char* name, const FooPtr& foo) { + PrintSpacer(depth); + std::cout << name << ":" << std::endl; + if (!foo.is_null()) { + ++depth; + Print(depth, "name", foo->name); + Print(depth, "x", foo->x); + Print(depth, "y", foo->y); + Print(depth, "a", foo->a); + Print(depth, "b", foo->b); + Print(depth, "c", foo->c); + Print(depth, "bar", foo->bar); + Print(depth, "extra_bars", foo->extra_bars); + Print(depth, "data", foo->data); + Print(depth, "source", foo->source); + Print(depth, "input_streams", foo->input_streams); + Print(depth, "output_streams", foo->output_streams); + Print(depth, "array_of_array_of_bools", foo->array_of_array_of_bools); + --depth; + } +} + +void DumpHex(const uint8_t* bytes, uint32_t num_bytes) { + for (uint32_t i = 0; i < num_bytes; ++i) { + std::cout << std::setw(2) << std::setfill('0') << std::hex + << uint32_t(bytes[i]); + + if (i % 16 == 15) { + std::cout << std::endl; + continue; + } + + if (i % 2 == 1) + std::cout << " "; + if (i % 8 == 7) + std::cout << " "; + } +} + +class ServiceImpl : public Service { + public: + void Frobinate(FooPtr foo, + BazOptions baz, + PortPtr port, + const Service::FrobinateCallback& callback) override { + // Users code goes here to handle the incoming Frobinate message. + + // We mainly check that we're given the expected arguments. + EXPECT_FALSE(foo.is_null()); + if (!foo.is_null()) + CheckFoo(*foo); + EXPECT_EQ(BazOptions::EXTRA, baz); + + if (g_dump_message_as_text) { + // Also dump the Foo structure and all of its members. + std::cout << "Frobinate:" << std::endl; + int depth = 1; + Print(depth, "foo", foo); + Print(depth, "baz", static_cast(baz)); + Print(depth, "port", port.get()); + } + callback.Run(5); + } + + void GetPort(mojo::InterfaceRequest port_request) override {} +}; + +class ServiceProxyImpl : public ServiceProxy { + public: + explicit ServiceProxyImpl(mojo::MessageReceiverWithResponder* receiver) + : ServiceProxy(receiver) {} +}; + +class SimpleMessageReceiver : public mojo::MessageReceiverWithResponder { + public: + bool Accept(mojo::Message* message) override { + // Imagine some IPC happened here. + + if (g_dump_message_as_hex) { + DumpHex(reinterpret_cast(message->data()), + message->data_num_bytes()); + } + + // In the receiving process, an implementation of ServiceStub is known to + // the system. It receives the incoming message. + ServiceImpl impl; + + ServiceStub<> stub; + stub.set_sink(&impl); + return stub.Accept(message); + } + + bool AcceptWithResponder( + mojo::Message* message, + std::unique_ptr responder) override { + return false; + } +}; + +using BindingsSampleTest = testing::Test; + +TEST_F(BindingsSampleTest, Basic) { + SimpleMessageReceiver receiver; + + // User has a proxy to a Service somehow. + Service* service = new ServiceProxyImpl(&receiver); + + // User constructs a message to send. + + // Notice that it doesn't matter in what order the structs / arrays are + // allocated. Here, the various members of Foo are allocated before Foo is + // allocated. + + FooPtr foo = MakeFoo(); + CheckFoo(*foo); + + PortPtr port; + service->Frobinate(std::move(foo), Service::BazOptions::EXTRA, + std::move(port), Service::FrobinateCallback()); + + delete service; +} + +TEST_F(BindingsSampleTest, DefaultValues) { + DefaultsTestPtr defaults(DefaultsTest::New()); + EXPECT_EQ(-12, defaults->a0); + EXPECT_EQ(kTwelve, defaults->a1); + EXPECT_EQ(1234, defaults->a2); + EXPECT_EQ(34567U, defaults->a3); + EXPECT_EQ(123456, defaults->a4); + EXPECT_EQ(3456789012U, defaults->a5); + EXPECT_EQ(-111111111111LL, defaults->a6); + EXPECT_EQ(9999999999999999999ULL, defaults->a7); + EXPECT_EQ(0x12345, defaults->a8); + EXPECT_EQ(-0x12345, defaults->a9); + EXPECT_EQ(1234, defaults->a10); + EXPECT_TRUE(defaults->a11); + EXPECT_FALSE(defaults->a12); + EXPECT_FLOAT_EQ(123.25f, defaults->a13); + EXPECT_DOUBLE_EQ(1234567890.123, defaults->a14); + EXPECT_DOUBLE_EQ(1E10, defaults->a15); + EXPECT_DOUBLE_EQ(-1.2E+20, defaults->a16); + EXPECT_DOUBLE_EQ(1.23E-20, defaults->a17); + EXPECT_TRUE(defaults->a18.empty()); + EXPECT_TRUE(defaults->a19.empty()); + EXPECT_EQ(Bar::Type::BOTH, defaults->a20); + EXPECT_TRUE(defaults->a21.is_null()); + ASSERT_FALSE(defaults->a22.is_null()); + EXPECT_EQ(imported::Shape::RECTANGLE, defaults->a22->shape); + EXPECT_EQ(imported::Color::BLACK, defaults->a22->color); + EXPECT_EQ(0xFFFFFFFFFFFFFFFFULL, defaults->a23); + EXPECT_EQ(0x123456789, defaults->a24); + EXPECT_EQ(-0x123456789, defaults->a25); +} + +} // namespace +} // namespace sample diff --git a/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc new file mode 100644 index 0000000000..275f10f9e7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc @@ -0,0 +1,251 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Serialization warnings are only recorded when DLOG is enabled. +#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) + +#include +#include + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_unions.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using mojo::internal::ContainerValidateParams; + +// Creates an array of arrays of handles (2 X 3) for testing. +std::vector>> +CreateTestNestedHandleArray() { + std::vector>> array(2); + for (size_t i = 0; i < array.size(); ++i) { + std::vector nested_array(3); + for (size_t j = 0; j < nested_array.size(); ++j) { + MessagePipe pipe; + nested_array[j] = ScopedHandle::From(std::move(pipe.handle1)); + } + array[i].emplace(std::move(nested_array)); + } + + return array; +} + +class SerializationWarningTest : public testing::Test { + public: + ~SerializationWarningTest() override {} + + protected: + template + void TestWarning(T obj, mojo::internal::ValidationError expected_warning) { + using MojomType = typename T::Struct::DataView; + + warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE); + + mojo::internal::SerializationContext context; + mojo::internal::FixedBufferForTesting buf( + mojo::internal::PrepareToSerialize(obj, &context)); + typename mojo::internal::MojomTypeTraits::Data* data; + mojo::internal::Serialize(obj, &buf, &data, &context); + + EXPECT_EQ(expected_warning, warning_observer_.last_warning()); + } + + template + void TestArrayWarning(T obj, + mojo::internal::ValidationError expected_warning, + const ContainerValidateParams* validate_params) { + warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE); + + mojo::internal::SerializationContext context; + mojo::internal::FixedBufferForTesting buf( + mojo::internal::PrepareToSerialize(obj, &context)); + typename mojo::internal::MojomTypeTraits::Data* data; + mojo::internal::Serialize(obj, &buf, &data, validate_params, + &context); + + EXPECT_EQ(expected_warning, warning_observer_.last_warning()); + } + + template + void TestUnionWarning(T obj, + mojo::internal::ValidationError expected_warning) { + using MojomType = typename T::Struct::DataView; + + warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE); + + mojo::internal::SerializationContext context; + mojo::internal::FixedBufferForTesting buf( + mojo::internal::PrepareToSerialize(obj, false, &context)); + typename mojo::internal::MojomTypeTraits::Data* data; + mojo::internal::Serialize(obj, &buf, &data, false, &context); + + EXPECT_EQ(expected_warning, warning_observer_.last_warning()); + } + + mojo::internal::SerializationWarningObserverForTesting warning_observer_; +}; + +TEST_F(SerializationWarningTest, HandleInStruct) { + Struct2Ptr test_struct(Struct2::New()); + EXPECT_FALSE(test_struct->hdl.is_valid()); + + TestWarning(std::move(test_struct), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE); + + test_struct = Struct2::New(); + MessagePipe pipe; + test_struct->hdl = ScopedHandle::From(std::move(pipe.handle1)); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, StructInStruct) { + Struct3Ptr test_struct(Struct3::New()); + EXPECT_TRUE(!test_struct->struct_1); + + TestWarning(std::move(test_struct), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); + + test_struct = Struct3::New(); + test_struct->struct_1 = Struct1::New(); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, ArrayOfStructsInStruct) { + Struct4Ptr test_struct(Struct4::New()); + test_struct->data.resize(1); + + TestWarning(std::move(test_struct), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); + + test_struct = Struct4::New(); + test_struct->data.resize(0); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); + + test_struct = Struct4::New(); + test_struct->data.resize(1); + test_struct->data[0] = Struct1::New(); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, FixedArrayOfStructsInStruct) { + Struct5Ptr test_struct(Struct5::New()); + test_struct->pair.resize(1); + test_struct->pair[0] = Struct1::New(); + + TestWarning(std::move(test_struct), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER); + + test_struct = Struct5::New(); + test_struct->pair.resize(2); + test_struct->pair[0] = Struct1::New(); + test_struct->pair[1] = Struct1::New(); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, ArrayOfArraysOfHandles) { + using MojomType = ArrayDataView>; + auto test_array = CreateTestNestedHandleArray(); + test_array[0] = base::nullopt; + (*test_array[1])[0] = ScopedHandle(); + + ContainerValidateParams validate_params_0( + 0, true, new ContainerValidateParams(0, true, nullptr)); + TestArrayWarning(std::move(test_array), + mojo::internal::VALIDATION_ERROR_NONE, + &validate_params_0); + + test_array = CreateTestNestedHandleArray(); + test_array[0] = base::nullopt; + ContainerValidateParams validate_params_1( + 0, false, new ContainerValidateParams(0, true, nullptr)); + TestArrayWarning( + std::move(test_array), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + &validate_params_1); + + test_array = CreateTestNestedHandleArray(); + (*test_array[1])[0] = ScopedHandle(); + ContainerValidateParams validate_params_2( + 0, true, new ContainerValidateParams(0, false, nullptr)); + TestArrayWarning( + std::move(test_array), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, + &validate_params_2); +} + +TEST_F(SerializationWarningTest, ArrayOfStrings) { + using MojomType = ArrayDataView; + + std::vector test_array(3); + for (size_t i = 0; i < test_array.size(); ++i) + test_array[i] = "hello"; + + ContainerValidateParams validate_params_0( + 0, true, new ContainerValidateParams(0, false, nullptr)); + TestArrayWarning(std::move(test_array), + mojo::internal::VALIDATION_ERROR_NONE, + &validate_params_0); + + std::vector> optional_test_array(3); + ContainerValidateParams validate_params_1( + 0, false, new ContainerValidateParams(0, false, nullptr)); + TestArrayWarning( + std::move(optional_test_array), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + &validate_params_1); + + test_array = std::vector(2); + ContainerValidateParams validate_params_2( + 3, true, new ContainerValidateParams(0, false, nullptr)); + TestArrayWarning( + std::move(test_array), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + &validate_params_2); +} + +TEST_F(SerializationWarningTest, StructInUnion) { + DummyStructPtr dummy(nullptr); + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + TestUnionWarning(std::move(obj), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); +} + +TEST_F(SerializationWarningTest, UnionInUnion) { + PodUnionPtr pod(nullptr); + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + TestUnionWarning(std::move(obj), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); +} + +TEST_F(SerializationWarningTest, HandleInUnion) { + ScopedMessagePipeHandle pipe; + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe)); + + TestUnionWarning(std::move(handle), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE); +} + +} // namespace +} // namespace test +} // namespace mojo + +#endif diff --git a/mojo/public/cpp/bindings/tests/shared_rect.h b/mojo/public/cpp/bindings/tests/shared_rect.h new file mode 100644 index 0000000000..c0a4771c14 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/shared_rect.h @@ -0,0 +1,43 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_H_ + +#include "base/logging.h" + +namespace mojo { +namespace test { + +// An implementation of a hypothetical Rect type specifically for consumers in +// both Chromium and Blink. +class SharedRect { + public: + SharedRect() {} + SharedRect(int x, int y, int width, int height) + : x_(x), y_(y), width_(width), height_(height) {} + + int x() const { return x_; } + void set_x(int x) { x_ = x; } + + int y() const { return y_; } + void set_y(int y) { y_ = y; } + + int width() const { return width_; } + void set_width(int width) { width_ = width; } + + int height() const { return height_; } + void set_height(int height) { height_ = height; } + + private: + int x_ = 0; + int y_ = 0; + int width_ = 0; + int height_ = 0; +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_H_ diff --git a/mojo/public/cpp/bindings/tests/shared_rect_traits.h b/mojo/public/cpp/bindings/tests/shared_rect_traits.h new file mode 100644 index 0000000000..bbf04d5f6e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/shared_rect_traits.h @@ -0,0 +1,33 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_TRAITS_H_ + +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/tests/shared_rect.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom-shared.h" + +namespace mojo { + +template <> +struct StructTraits { + static int x(const test::SharedRect& r) { return r.x(); } + static int y(const test::SharedRect& r) { return r.y(); } + static int width(const test::SharedRect& r) { return r.width(); } + static int height(const test::SharedRect& r) { return r.height(); } + + static bool Read(test::SharedTypemappedRectDataView r, + test::SharedRect* out) { + out->set_x(r.x()); + out->set_y(r.y()); + out->set_width(r.width()); + out->set_height(r.height()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc new file mode 100644 index 0000000000..77b448a215 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc @@ -0,0 +1,553 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/tests/rect_blink.h" +#include "mojo/public/cpp/bindings/tests/rect_chromium.h" +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h" +#include "mojo/public/cpp/bindings/tests/variant_test_util.h" +#include "mojo/public/cpp/system/wait.h" +#include "mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template +void DoExpectResult(const T& expected, + const base::Closure& callback, + const T& actual) { + EXPECT_EQ(expected.x(), actual.x()); + EXPECT_EQ(expected.y(), actual.y()); + EXPECT_EQ(expected.width(), actual.width()); + EXPECT_EQ(expected.height(), actual.height()); + callback.Run(); +} + +template +base::Callback ExpectResult(const T& r, + const base::Closure& callback) { + return base::Bind(&DoExpectResult, r, callback); +} + +template +void DoFail(const std::string& reason, const T&) { + EXPECT_TRUE(false) << reason; +} + +template +base::Callback Fail(const std::string& reason) { + return base::Bind(&DoFail, reason); +} + +template +void ExpectError(InterfacePtr *proxy, const base::Closure& callback) { + proxy->set_connection_error_handler(callback); +} + +// This implements the generated Chromium variant of RectService. +class ChromiumRectServiceImpl : public RectService { + public: + ChromiumRectServiceImpl() {} + + // mojo::test::RectService: + void AddRect(const RectChromium& r) override { + if (r.GetArea() > largest_rect_.GetArea()) + largest_rect_ = r; + } + + void GetLargestRect(const GetLargestRectCallback& callback) override { + callback.Run(largest_rect_); + } + + void PassSharedRect(const SharedRect& r, + const PassSharedRectCallback& callback) override { + callback.Run(r); + } + + private: + RectChromium largest_rect_; +}; + +// This implements the generated Blink variant of RectService. +class BlinkRectServiceImpl : public blink::RectService { + public: + BlinkRectServiceImpl() {} + + // mojo::test::blink::RectService: + void AddRect(const RectBlink& r) override { + if (r.computeArea() > largest_rect_.computeArea()) { + largest_rect_.setX(r.x()); + largest_rect_.setY(r.y()); + largest_rect_.setWidth(r.width()); + largest_rect_.setHeight(r.height()); + } + } + + void GetLargestRect(const GetLargestRectCallback& callback) override { + callback.Run(largest_rect_); + } + + void PassSharedRect(const SharedRect& r, + const PassSharedRectCallback& callback) override { + callback.Run(r); + } + + private: + RectBlink largest_rect_; +}; + +// A test which runs both Chromium and Blink implementations of a RectService. +class StructTraitsTest : public testing::Test, + public TraitsTestService { + public: + StructTraitsTest() {} + + protected: + void BindToChromiumService(RectServiceRequest request) { + chromium_bindings_.AddBinding(&chromium_service_, std::move(request)); + } + void BindToChromiumService(blink::RectServiceRequest request) { + chromium_bindings_.AddBinding( + &chromium_service_, + ConvertInterfaceRequest(std::move(request))); + } + + void BindToBlinkService(blink::RectServiceRequest request) { + blink_bindings_.AddBinding(&blink_service_, std::move(request)); + } + void BindToBlinkService(RectServiceRequest request) { + blink_bindings_.AddBinding( + &blink_service_, + ConvertInterfaceRequest(std::move(request))); + } + + TraitsTestServicePtr GetTraitsTestProxy() { + return traits_test_bindings_.CreateInterfacePtrAndBind(this); + } + + private: + // TraitsTestService: + void EchoStructWithTraits( + const StructWithTraitsImpl& s, + const EchoStructWithTraitsCallback& callback) override { + callback.Run(s); + } + + void EchoTrivialStructWithTraits( + TrivialStructWithTraitsImpl s, + const EchoTrivialStructWithTraitsCallback& callback) override { + callback.Run(s); + } + + void EchoMoveOnlyStructWithTraits( + MoveOnlyStructWithTraitsImpl s, + const EchoMoveOnlyStructWithTraitsCallback& callback) override { + callback.Run(std::move(s)); + } + + void EchoNullableMoveOnlyStructWithTraits( + base::Optional s, + const EchoNullableMoveOnlyStructWithTraitsCallback& callback) override { + callback.Run(std::move(s)); + } + + void EchoEnumWithTraits(EnumWithTraitsImpl e, + const EchoEnumWithTraitsCallback& callback) override { + callback.Run(e); + } + + void EchoStructWithTraitsForUniquePtr( + std::unique_ptr e, + const EchoStructWithTraitsForUniquePtrCallback& callback) override { + callback.Run(std::move(e)); + } + + void EchoNullableStructWithTraitsForUniquePtr( + std::unique_ptr e, + const EchoNullableStructWithTraitsForUniquePtrCallback& callback) + override { + callback.Run(std::move(e)); + } + + void EchoUnionWithTraits( + std::unique_ptr u, + const EchoUnionWithTraitsCallback& callback) override { + callback.Run(std::move(u)); + } + + base::MessageLoop loop_; + + ChromiumRectServiceImpl chromium_service_; + BindingSet chromium_bindings_; + + BlinkRectServiceImpl blink_service_; + BindingSet blink_bindings_; + + BindingSet traits_test_bindings_; +}; + +} // namespace + +TEST_F(StructTraitsTest, ChromiumProxyToChromiumService) { + RectServicePtr chromium_proxy; + BindToChromiumService(MakeRequest(&chromium_proxy)); + { + base::RunLoop loop; + chromium_proxy->AddRect(RectChromium(1, 1, 4, 5)); + chromium_proxy->AddRect(RectChromium(-1, -1, 2, 2)); + chromium_proxy->GetLargestRect( + ExpectResult(RectChromium(1, 1, 4, 5), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + chromium_proxy->PassSharedRect( + {1, 2, 3, 4}, + ExpectResult(SharedRect({1, 2, 3, 4}), loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(StructTraitsTest, ChromiumToBlinkService) { + RectServicePtr chromium_proxy; + BindToBlinkService(MakeRequest(&chromium_proxy)); + { + base::RunLoop loop; + chromium_proxy->AddRect(RectChromium(1, 1, 4, 5)); + chromium_proxy->AddRect(RectChromium(2, 2, 5, 5)); + chromium_proxy->GetLargestRect( + ExpectResult(RectChromium(2, 2, 5, 5), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + chromium_proxy->PassSharedRect( + {1, 2, 3, 4}, + ExpectResult(SharedRect({1, 2, 3, 4}), loop.QuitClosure())); + loop.Run(); + } + // The Blink service should drop our connection because RectBlink's + // deserializer rejects negative origins. + { + base::RunLoop loop; + ExpectError(&chromium_proxy, loop.QuitClosure()); + chromium_proxy->AddRect(RectChromium(-1, -1, 2, 2)); + chromium_proxy->GetLargestRect( + Fail("The pipe should have been closed.")); + loop.Run(); + } +} + +TEST_F(StructTraitsTest, BlinkProxyToBlinkService) { + blink::RectServicePtr blink_proxy; + BindToBlinkService(MakeRequest(&blink_proxy)); + { + base::RunLoop loop; + blink_proxy->AddRect(RectBlink(1, 1, 4, 5)); + blink_proxy->AddRect(RectBlink(10, 10, 20, 20)); + blink_proxy->GetLargestRect( + ExpectResult(RectBlink(10, 10, 20, 20), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + blink_proxy->PassSharedRect( + {4, 3, 2, 1}, + ExpectResult(SharedRect({4, 3, 2, 1}), loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(StructTraitsTest, BlinkProxyToChromiumService) { + blink::RectServicePtr blink_proxy; + BindToChromiumService(MakeRequest(&blink_proxy)); + { + base::RunLoop loop; + blink_proxy->AddRect(RectBlink(1, 1, 4, 5)); + blink_proxy->AddRect(RectBlink(10, 10, 2, 2)); + blink_proxy->GetLargestRect( + ExpectResult(RectBlink(1, 1, 4, 5), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + blink_proxy->PassSharedRect( + {4, 3, 2, 1}, + ExpectResult(SharedRect({4, 3, 2, 1}), loop.QuitClosure())); + loop.Run(); + } +} + +void ExpectStructWithTraits(const StructWithTraitsImpl& expected, + const base::Closure& closure, + const StructWithTraitsImpl& passed) { + EXPECT_EQ(expected.get_enum(), passed.get_enum()); + EXPECT_EQ(expected.get_bool(), passed.get_bool()); + EXPECT_EQ(expected.get_uint32(), passed.get_uint32()); + EXPECT_EQ(expected.get_uint64(), passed.get_uint64()); + EXPECT_EQ(expected.get_string(), passed.get_string()); + EXPECT_EQ(expected.get_string_array(), passed.get_string_array()); + EXPECT_EQ(expected.get_struct(), passed.get_struct()); + EXPECT_EQ(expected.get_struct_array(), passed.get_struct_array()); + EXPECT_EQ(expected.get_struct_map(), passed.get_struct_map()); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoStructWithTraits) { + StructWithTraitsImpl input; + input.set_enum(EnumWithTraitsImpl::CUSTOM_VALUE_1); + input.set_bool(true); + input.set_uint32(7); + input.set_uint64(42); + input.set_string("hello world!"); + input.get_mutable_string_array().assign({"hello", "world!"}); + input.get_mutable_string_set().insert("hello"); + input.get_mutable_string_set().insert("world!"); + input.get_mutable_struct().value = 42; + input.get_mutable_struct_array().resize(2); + input.get_mutable_struct_array()[0].value = 1; + input.get_mutable_struct_array()[1].value = 2; + input.get_mutable_struct_map()["hello"] = NestedStructWithTraitsImpl(1024); + input.get_mutable_struct_map()["world"] = NestedStructWithTraitsImpl(2048); + + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + proxy->EchoStructWithTraits( + input, + base::Bind(&ExpectStructWithTraits, input, loop.QuitClosure())); + loop.Run(); +} + +TEST_F(StructTraitsTest, CloneStructWithTraitsContainer) { + StructWithTraitsContainerPtr container = StructWithTraitsContainer::New(); + container->f_struct.set_uint32(7); + container->f_struct.set_uint64(42); + StructWithTraitsContainerPtr cloned_container = container.Clone(); + EXPECT_EQ(7u, cloned_container->f_struct.get_uint32()); + EXPECT_EQ(42u, cloned_container->f_struct.get_uint64()); +} + +void ExpectTrivialStructWithTraits(TrivialStructWithTraitsImpl expected, + const base::Closure& closure, + TrivialStructWithTraitsImpl passed) { + EXPECT_EQ(expected.value, passed.value); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoTrivialStructWithTraits) { + TrivialStructWithTraitsImpl input; + input.value = 42; + + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + proxy->EchoTrivialStructWithTraits( + input, + base::Bind(&ExpectTrivialStructWithTraits, input, loop.QuitClosure())); + loop.Run(); +} + +void CaptureMessagePipe(ScopedMessagePipeHandle* storage, + const base::Closure& closure, + MoveOnlyStructWithTraitsImpl passed) { + storage->reset(MessagePipeHandle( + passed.get_mutable_handle().release().value())); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoMoveOnlyStructWithTraits) { + MessagePipe mp; + MoveOnlyStructWithTraitsImpl input; + input.get_mutable_handle().reset(mp.handle0.release()); + + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + ScopedMessagePipeHandle received; + proxy->EchoMoveOnlyStructWithTraits( + std::move(input), + base::Bind(&CaptureMessagePipe, &received, loop.QuitClosure())); + loop.Run(); + + ASSERT_TRUE(received.is_valid()); + + // Verify that the message pipe handle is correctly passed. + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast(sizeof(kHello)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(mp.handle1.get(), kHello, kHelloSize, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, Wait(received.get(), MOJO_HANDLE_SIGNAL_READABLE)); + + char buffer[10] = {0}; + uint32_t buffer_size = static_cast(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(received.get(), buffer, &buffer_size, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, buffer_size); + EXPECT_STREQ(kHello, buffer); +} + +void CaptureNullableMoveOnlyStructWithTraitsImpl( + base::Optional* storage, + const base::Closure& closure, + base::Optional passed) { + *storage = std::move(passed); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoNullableMoveOnlyStructWithTraits) { + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + base::Optional received; + proxy->EchoNullableMoveOnlyStructWithTraits( + base::nullopt, base::Bind(&CaptureNullableMoveOnlyStructWithTraitsImpl, + &received, loop.QuitClosure())); + loop.Run(); + + EXPECT_FALSE(received); +} + +void ExpectEnumWithTraits(EnumWithTraitsImpl expected_value, + const base::Closure& closure, + EnumWithTraitsImpl value) { + EXPECT_EQ(expected_value, value); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoEnumWithTraits) { + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + proxy->EchoEnumWithTraits( + EnumWithTraitsImpl::CUSTOM_VALUE_1, + base::Bind(&ExpectEnumWithTraits, EnumWithTraitsImpl::CUSTOM_VALUE_1, + loop.QuitClosure())); + loop.Run(); +} + +TEST_F(StructTraitsTest, SerializeStructWithTraits) { + StructWithTraitsImpl input; + input.set_enum(EnumWithTraitsImpl::CUSTOM_VALUE_1); + input.set_bool(true); + input.set_uint32(7); + input.set_uint64(42); + input.set_string("hello world!"); + input.get_mutable_string_array().assign({ "hello", "world!" }); + input.get_mutable_string_set().insert("hello"); + input.get_mutable_string_set().insert("world!"); + input.get_mutable_struct().value = 42; + input.get_mutable_struct_array().resize(2); + input.get_mutable_struct_array()[0].value = 1; + input.get_mutable_struct_array()[1].value = 2; + input.get_mutable_struct_map()["hello"] = NestedStructWithTraitsImpl(1024); + input.get_mutable_struct_map()["world"] = NestedStructWithTraitsImpl(2048); + + auto data = StructWithTraits::Serialize(&input); + StructWithTraitsImpl output; + ASSERT_TRUE(StructWithTraits::Deserialize(std::move(data), &output)); + + EXPECT_EQ(input.get_enum(), output.get_enum()); + EXPECT_EQ(input.get_bool(), output.get_bool()); + EXPECT_EQ(input.get_uint32(), output.get_uint32()); + EXPECT_EQ(input.get_uint64(), output.get_uint64()); + EXPECT_EQ(input.get_string(), output.get_string()); + EXPECT_EQ(input.get_string_array(), output.get_string_array()); + EXPECT_EQ(input.get_string_set(), output.get_string_set()); + EXPECT_EQ(input.get_struct(), output.get_struct()); + EXPECT_EQ(input.get_struct_array(), output.get_struct_array()); + EXPECT_EQ(input.get_struct_map(), output.get_struct_map()); +} + +void ExpectUniquePtr(std::unique_ptr expected, + const base::Closure& closure, + std::unique_ptr value) { + ASSERT_EQ(!expected, !value); + if (expected) + EXPECT_EQ(*expected, *value); + closure.Run(); +} + +TEST_F(StructTraitsTest, TypemapUniquePtr) { + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + { + base::RunLoop loop; + proxy->EchoStructWithTraitsForUniquePtr( + base::MakeUnique(12345), + base::Bind(&ExpectUniquePtr, base::Passed(base::MakeUnique(12345)), + loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + proxy->EchoNullableStructWithTraitsForUniquePtr( + nullptr, base::Bind(&ExpectUniquePtr, nullptr, loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(StructTraitsTest, EchoUnionWithTraits) { + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + { + std::unique_ptr input( + new test::UnionWithTraitsInt32(1234)); + base::RunLoop loop; + proxy->EchoUnionWithTraits( + std::move(input), + base::Bind( + [](const base::Closure& quit_closure, + std::unique_ptr passed) { + ASSERT_EQ(test::UnionWithTraitsBase::Type::INT32, passed->type()); + EXPECT_EQ(1234, + static_cast(passed.get()) + ->value()); + quit_closure.Run(); + + }, + loop.QuitClosure())); + loop.Run(); + } + + { + std::unique_ptr input( + new test::UnionWithTraitsStruct(4321)); + base::RunLoop loop; + proxy->EchoUnionWithTraits( + std::move(input), + base::Bind( + [](const base::Closure& quit_closure, + std::unique_ptr passed) { + ASSERT_EQ(test::UnionWithTraitsBase::Type::STRUCT, + passed->type()); + EXPECT_EQ(4321, + static_cast(passed.get()) + ->get_struct() + .value); + quit_closure.Run(); + + }, + loop.QuitClosure())); + loop.Run(); + } +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/struct_unittest.cc b/mojo/public/cpp/bindings/tests/struct_unittest.cc new file mode 100644 index 0000000000..a687052706 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_unittest.cc @@ -0,0 +1,526 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/interfaces/bindings/tests/test_export2.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +RectPtr MakeRect(int32_t factor = 1) { + return Rect::New(1 * factor, 2 * factor, 10 * factor, 20 * factor); +} + +void CheckRect(const Rect& rect, int32_t factor = 1) { + EXPECT_EQ(1 * factor, rect.x); + EXPECT_EQ(2 * factor, rect.y); + EXPECT_EQ(10 * factor, rect.width); + EXPECT_EQ(20 * factor, rect.height); +} + +MultiVersionStructPtr MakeMultiVersionStruct() { + MessagePipe pipe; + return MultiVersionStruct::New(123, MakeRect(5), std::string("hello"), + std::vector{10, 9, 8}, + std::move(pipe.handle0), false, 42); +} + +template +U SerializeAndDeserialize(T input) { + using InputMojomType = typename T::Struct::DataView; + using OutputMojomType = typename U::Struct::DataView; + + using InputDataType = + typename mojo::internal::MojomTypeTraits::Data*; + using OutputDataType = + typename mojo::internal::MojomTypeTraits::Data*; + + mojo::internal::SerializationContext context; + size_t size = + mojo::internal::PrepareToSerialize(input, &context); + mojo::internal::FixedBufferForTesting buf(size + 32); + InputDataType data; + mojo::internal::Serialize(input, &buf, &data, &context); + + // Set the subsequent area to a special value, so that we can find out if we + // mistakenly access the area. + void* subsequent_area = buf.Allocate(32); + memset(subsequent_area, 0xAA, 32); + + OutputDataType output_data = reinterpret_cast(data); + + U output; + mojo::internal::Deserialize(output_data, &output, &context); + return std::move(output); +} + +using StructTest = testing::Test; + +} // namespace + +TEST_F(StructTest, Rect) { + RectPtr rect; + EXPECT_TRUE(rect.is_null()); + EXPECT_TRUE(!rect); + EXPECT_FALSE(rect); + + rect = nullptr; + EXPECT_TRUE(rect.is_null()); + EXPECT_TRUE(!rect); + EXPECT_FALSE(rect); + + rect = MakeRect(); + EXPECT_FALSE(rect.is_null()); + EXPECT_FALSE(!rect); + EXPECT_TRUE(rect); + + RectPtr null_rect = nullptr; + EXPECT_TRUE(null_rect.is_null()); + EXPECT_TRUE(!null_rect); + EXPECT_FALSE(null_rect); + + CheckRect(*rect); +} + +TEST_F(StructTest, Clone) { + NamedRegionPtr region; + + NamedRegionPtr clone_region = region.Clone(); + EXPECT_TRUE(clone_region.is_null()); + + region = NamedRegion::New(); + clone_region = region.Clone(); + EXPECT_FALSE(clone_region->name); + EXPECT_FALSE(clone_region->rects); + + region->name.emplace("hello world"); + clone_region = region.Clone(); + EXPECT_EQ(region->name, clone_region->name); + + region->rects.emplace(2); + (*region->rects)[1] = MakeRect(); + clone_region = region.Clone(); + EXPECT_EQ(2u, clone_region->rects->size()); + EXPECT_TRUE((*clone_region->rects)[0].is_null()); + CheckRect(*(*clone_region->rects)[1]); + + // NoDefaultFieldValues contains handles, so Clone() is not available, but + // NoDefaultFieldValuesPtr should still compile. + NoDefaultFieldValuesPtr no_default_field_values(NoDefaultFieldValues::New()); + EXPECT_FALSE(no_default_field_values->f13.is_valid()); +} + +// Serialization test of a struct with no pointer or handle members. +TEST_F(StructTest, Serialization_Basic) { + RectPtr rect(MakeRect()); + + size_t size = mojo::internal::PrepareToSerialize(rect, nullptr); + EXPECT_EQ(8U + 16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::Rect_Data* data; + mojo::internal::Serialize(rect, &buf, &data, nullptr); + + RectPtr rect2; + mojo::internal::Deserialize(data, &rect2, nullptr); + + CheckRect(*rect2); +} + +// Construction of a struct with struct pointers from null. +TEST_F(StructTest, Construction_StructPointers) { + RectPairPtr pair; + EXPECT_TRUE(pair.is_null()); + + pair = RectPair::New(); + EXPECT_FALSE(pair.is_null()); + EXPECT_TRUE(pair->first.is_null()); + EXPECT_TRUE(pair->first.is_null()); + + pair = nullptr; + EXPECT_TRUE(pair.is_null()); +} + +// Serialization test of a struct with struct pointers. +TEST_F(StructTest, Serialization_StructPointers) { + RectPairPtr pair(RectPair::New(MakeRect(), MakeRect())); + + size_t size = + mojo::internal::PrepareToSerialize(pair, nullptr); + EXPECT_EQ(8U + 16U + 2 * (8U + 16U), size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::RectPair_Data* data; + mojo::internal::Serialize(pair, &buf, &data, nullptr); + + RectPairPtr pair2; + mojo::internal::Deserialize(data, &pair2, nullptr); + + CheckRect(*pair2->first); + CheckRect(*pair2->second); +} + +// Serialization test of a struct with an array member. +TEST_F(StructTest, Serialization_ArrayPointers) { + std::vector rects; + for (size_t i = 0; i < 4; ++i) + rects.push_back(MakeRect(static_cast(i) + 1)); + + NamedRegionPtr region( + NamedRegion::New(std::string("region"), std::move(rects))); + + size_t size = + mojo::internal::PrepareToSerialize(region, nullptr); + EXPECT_EQ(8U + // header + 8U + // name pointer + 8U + // rects pointer + 8U + // name header + 8U + // name payload (rounded up) + 8U + // rects header + 4 * 8U + // rects payload (four pointers) + 4 * (8U + // rect header + 16U), // rect payload (four ints) + size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::NamedRegion_Data* data; + mojo::internal::Serialize(region, &buf, &data, nullptr); + + NamedRegionPtr region2; + mojo::internal::Deserialize(data, ®ion2, nullptr); + + EXPECT_EQ("region", *region2->name); + + EXPECT_EQ(4U, region2->rects->size()); + for (size_t i = 0; i < region2->rects->size(); ++i) + CheckRect(*(*region2->rects)[i], static_cast(i) + 1); +} + +// Serialization test of a struct with null array pointers. +TEST_F(StructTest, Serialization_NullArrayPointers) { + NamedRegionPtr region(NamedRegion::New()); + EXPECT_FALSE(region->name); + EXPECT_FALSE(region->rects); + + size_t size = + mojo::internal::PrepareToSerialize(region, nullptr); + EXPECT_EQ(8U + // header + 8U + // name pointer + 8U, // rects pointer + size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::NamedRegion_Data* data; + mojo::internal::Serialize(region, &buf, &data, nullptr); + + NamedRegionPtr region2; + mojo::internal::Deserialize(data, ®ion2, nullptr); + + EXPECT_FALSE(region2->name); + EXPECT_FALSE(region2->rects); +} + +// Tests deserializing structs as a newer version. +TEST_F(StructTest, Versioning_OldToNew) { + { + MultiVersionStructV0Ptr input(MultiVersionStructV0::New(123)); + MultiVersionStructPtr expected_output(MultiVersionStruct::New(123)); + + MultiVersionStructPtr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV1Ptr input(MultiVersionStructV1::New(123, MakeRect(5))); + MultiVersionStructPtr expected_output( + MultiVersionStruct::New(123, MakeRect(5))); + + MultiVersionStructPtr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV3Ptr input( + MultiVersionStructV3::New(123, MakeRect(5), std::string("hello"))); + MultiVersionStructPtr expected_output( + MultiVersionStruct::New(123, MakeRect(5), std::string("hello"))); + + MultiVersionStructPtr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV5Ptr input(MultiVersionStructV5::New( + 123, MakeRect(5), std::string("hello"), std::vector{10, 9, 8})); + MultiVersionStructPtr expected_output(MultiVersionStruct::New( + 123, MakeRect(5), std::string("hello"), std::vector{10, 9, 8})); + + MultiVersionStructPtr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MessagePipe pipe; + MultiVersionStructV7Ptr input(MultiVersionStructV7::New( + 123, MakeRect(5), std::string("hello"), std::vector{10, 9, 8}, + std::move(pipe.handle0), false)); + + MultiVersionStructPtr expected_output(MultiVersionStruct::New( + 123, MakeRect(5), std::string("hello"), std::vector{10, 9, 8})); + // Save the raw handle value separately so that we can compare later. + MojoHandle expected_handle = input->f_message_pipe.get().value(); + + MultiVersionStructPtr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_EQ(expected_handle, output->f_message_pipe.get().value()); + output->f_message_pipe.reset(); + EXPECT_TRUE(output->Equals(*expected_output)); + } +} + +// Tests deserializing structs as an older version. +TEST_F(StructTest, Versioning_NewToOld) { + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV7Ptr expected_output(MultiVersionStructV7::New( + 123, MakeRect(5), std::string("hello"), std::vector{10, 9, 8})); + // Save the raw handle value separately so that we can compare later. + MojoHandle expected_handle = input->f_message_pipe.get().value(); + + MultiVersionStructV7Ptr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_EQ(expected_handle, output->f_message_pipe.get().value()); + output->f_message_pipe.reset(); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV5Ptr expected_output(MultiVersionStructV5::New( + 123, MakeRect(5), std::string("hello"), std::vector{10, 9, 8})); + + MultiVersionStructV5Ptr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV3Ptr expected_output( + MultiVersionStructV3::New(123, MakeRect(5), std::string("hello"))); + + MultiVersionStructV3Ptr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV1Ptr expected_output( + MultiVersionStructV1::New(123, MakeRect(5))); + + MultiVersionStructV1Ptr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV0Ptr expected_output(MultiVersionStructV0::New(123)); + + MultiVersionStructV0Ptr output = + SerializeAndDeserialize(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } +} + +// Serialization test for native struct. +TEST_F(StructTest, Serialization_NativeStruct) { + using Data = mojo::internal::NativeStruct_Data; + { + // Serialization of a null native struct. + NativeStructPtr native; + size_t size = mojo::internal::PrepareToSerialize( + native, nullptr); + EXPECT_EQ(0u, size); + mojo::internal::FixedBufferForTesting buf(size); + + Data* data = nullptr; + mojo::internal::Serialize(std::move(native), &buf, + &data, nullptr); + + EXPECT_EQ(nullptr, data); + + NativeStructPtr output_native; + mojo::internal::Deserialize(data, &output_native, + nullptr); + EXPECT_TRUE(output_native.is_null()); + } + + { + // Serialization of a native struct with null data. + NativeStructPtr native(NativeStruct::New()); + size_t size = mojo::internal::PrepareToSerialize( + native, nullptr); + EXPECT_EQ(0u, size); + mojo::internal::FixedBufferForTesting buf(size); + + Data* data = nullptr; + mojo::internal::Serialize(std::move(native), &buf, + &data, nullptr); + + EXPECT_EQ(nullptr, data); + + NativeStructPtr output_native; + mojo::internal::Deserialize(data, &output_native, + nullptr); + EXPECT_TRUE(output_native.is_null()); + } + + { + NativeStructPtr native(NativeStruct::New()); + native->data = std::vector{'X', 'Y'}; + + size_t size = mojo::internal::PrepareToSerialize( + native, nullptr); + EXPECT_EQ(16u, size); + mojo::internal::FixedBufferForTesting buf(size); + + Data* data = nullptr; + mojo::internal::Serialize(std::move(native), &buf, + &data, nullptr); + + EXPECT_NE(nullptr, data); + + NativeStructPtr output_native; + mojo::internal::Deserialize(data, &output_native, + nullptr); + ASSERT_TRUE(output_native); + ASSERT_FALSE(output_native->data->empty()); + EXPECT_EQ(2u, output_native->data->size()); + EXPECT_EQ('X', (*output_native->data)[0]); + EXPECT_EQ('Y', (*output_native->data)[1]); + } +} + +TEST_F(StructTest, Serialization_PublicAPI) { + { + // A null struct pointer. + RectPtr null_struct; + auto data = Rect::Serialize(&null_struct); + EXPECT_TRUE(data.empty()); + + // Initialize it to non-null. + RectPtr output(Rect::New()); + ASSERT_TRUE(Rect::Deserialize(data, &output)); + EXPECT_TRUE(output.is_null()); + } + + { + // A struct with no fields. + EmptyStructPtr empty_struct(EmptyStruct::New()); + auto data = EmptyStruct::Serialize(&empty_struct); + EXPECT_FALSE(data.empty()); + + EmptyStructPtr output; + ASSERT_TRUE(EmptyStruct::Deserialize(data, &output)); + EXPECT_FALSE(output.is_null()); + } + + { + // A simple struct. + RectPtr rect = MakeRect(); + RectPtr cloned_rect = rect.Clone(); + auto data = Rect::Serialize(&rect); + + RectPtr output; + ASSERT_TRUE(Rect::Deserialize(data, &output)); + EXPECT_TRUE(output.Equals(cloned_rect)); + } + + { + // A struct containing other objects. + std::vector rects; + for (size_t i = 0; i < 3; ++i) + rects.push_back(MakeRect(static_cast(i) + 1)); + NamedRegionPtr region( + NamedRegion::New(std::string("region"), std::move(rects))); + + NamedRegionPtr cloned_region = region.Clone(); + auto data = NamedRegion::Serialize(®ion); + + // Make sure that the serialized result gets pointers encoded properly. + auto cloned_data = data; + NamedRegionPtr output; + ASSERT_TRUE(NamedRegion::Deserialize(cloned_data, &output)); + EXPECT_TRUE(output.Equals(cloned_region)); + } + + { + // Deserialization failure. + RectPtr rect = MakeRect(); + auto data = Rect::Serialize(&rect); + + NamedRegionPtr output; + EXPECT_FALSE(NamedRegion::Deserialize(data, &output)); + } + + { + // A struct from another component. + auto pair = test_export2::StringPair::New("hello", "world"); + auto data = test_export2::StringPair::Serialize(&pair); + + test_export2::StringPairPtr output; + ASSERT_TRUE(test_export2::StringPair::Deserialize(data, &output)); + EXPECT_TRUE(output.Equals(pair)); + } +} + +TEST_F(StructTest, VersionedStructConstructor) { + auto reordered = ReorderedStruct::New(123, 456, 789); + EXPECT_EQ(123, reordered->a); + EXPECT_EQ(456, reordered->b); + EXPECT_EQ(789, reordered->c); + + reordered = ReorderedStruct::New(123, 456); + EXPECT_EQ(123, reordered->a); + EXPECT_EQ(6, reordered->b); + EXPECT_EQ(456, reordered->c); + + reordered = ReorderedStruct::New(123); + EXPECT_EQ(3, reordered->a); + EXPECT_EQ(6, reordered->b); + EXPECT_EQ(123, reordered->c); + + reordered = ReorderedStruct::New(); + EXPECT_EQ(3, reordered->a); + EXPECT_EQ(6, reordered->b); + EXPECT_EQ(1, reordered->c); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits.typemap b/mojo/public/cpp/bindings/tests/struct_with_traits.typemap new file mode 100644 index 0000000000..752ce44b58 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits.typemap @@ -0,0 +1,26 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +mojom = "//mojo/public/interfaces/bindings/tests/struct_with_traits.mojom" +public_headers = + [ "//mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" ] +traits_headers = + [ "//mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h" ] +sources = [ + "//mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc", +] +deps = [ + "//mojo/public/cpp/bindings/tests:struct_with_traits_impl", + "//mojo/public/cpp/system:system", +] + +type_mappings = [ + "mojo.test.EnumWithTraits=mojo::test::EnumWithTraitsImpl", + "mojo.test.StructWithTraits=mojo::test::StructWithTraitsImpl", + "mojo.test.NestedStructWithTraits=mojo::test::NestedStructWithTraitsImpl", + "mojo.test.TrivialStructWithTraits=mojo::test::TrivialStructWithTraitsImpl[copyable_pass_by_value]", + "mojo.test.MoveOnlyStructWithTraits=mojo::test::MoveOnlyStructWithTraitsImpl[move_only]", + "mojo.test.StructWithTraitsForUniquePtr=std::unique_ptr[move_only,nullable_is_same_type]", + "mojo.test.UnionWithTraits=std::unique_ptr[move_only,nullable_is_same_type]", +] diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc new file mode 100644 index 0000000000..cbdd4bfde7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc @@ -0,0 +1,36 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" + +namespace mojo { +namespace test { + +NestedStructWithTraitsImpl::NestedStructWithTraitsImpl() {} +NestedStructWithTraitsImpl::NestedStructWithTraitsImpl(int32_t in_value) + : value(in_value) {} + +StructWithTraitsImpl::StructWithTraitsImpl() {} + +StructWithTraitsImpl::~StructWithTraitsImpl() {} + +StructWithTraitsImpl::StructWithTraitsImpl(const StructWithTraitsImpl& other) = + default; + +MoveOnlyStructWithTraitsImpl::MoveOnlyStructWithTraitsImpl() {} + +MoveOnlyStructWithTraitsImpl::MoveOnlyStructWithTraitsImpl( + MoveOnlyStructWithTraitsImpl&& other) = default; + +MoveOnlyStructWithTraitsImpl::~MoveOnlyStructWithTraitsImpl() {} + +MoveOnlyStructWithTraitsImpl& MoveOnlyStructWithTraitsImpl::operator=( + MoveOnlyStructWithTraitsImpl&& other) = default; + +UnionWithTraitsInt32::~UnionWithTraitsInt32() {} + +UnionWithTraitsStruct::~UnionWithTraitsStruct() {} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h new file mode 100644 index 0000000000..7b007cc083 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h @@ -0,0 +1,168 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_H_ + +#include + +#include +#include +#include +#include + +#include "base/strings/string_piece.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { +namespace test { + +struct NestedStructWithTraitsImpl { + public: + NestedStructWithTraitsImpl(); + explicit NestedStructWithTraitsImpl(int32_t in_value); + + bool operator==(const NestedStructWithTraitsImpl& other) const { + return value == other.value; + } + + int32_t value = 0; +}; + +enum class EnumWithTraitsImpl { CUSTOM_VALUE_0 = 10, CUSTOM_VALUE_1 = 11 }; + +// A type which knows how to look like a mojo::test::StructWithTraits mojom type +// by way of mojo::StructTraits. +class StructWithTraitsImpl { + public: + StructWithTraitsImpl(); + ~StructWithTraitsImpl(); + + StructWithTraitsImpl(const StructWithTraitsImpl& other); + + void set_enum(EnumWithTraitsImpl value) { enum_ = value; } + EnumWithTraitsImpl get_enum() const { return enum_; } + + void set_bool(bool value) { bool_ = value; } + bool get_bool() const { return bool_; } + + void set_uint32(uint32_t value) { uint32_ = value; } + uint32_t get_uint32() const { return uint32_; } + + void set_uint64(uint64_t value) { uint64_ = value; } + uint64_t get_uint64() const { return uint64_; } + + void set_string(std::string value) { string_ = value; } + base::StringPiece get_string_as_string_piece() const { return string_; } + const std::string& get_string() const { return string_; } + + const std::vector& get_string_array() const { + return string_array_; + } + std::vector& get_mutable_string_array() { return string_array_; } + + const std::set& get_string_set() const { + return string_set_; + } + std::set& get_mutable_string_set() { return string_set_; } + + const NestedStructWithTraitsImpl& get_struct() const { return struct_; } + NestedStructWithTraitsImpl& get_mutable_struct() { return struct_; } + + const std::vector& get_struct_array() const { + return struct_array_; + } + std::vector& get_mutable_struct_array() { + return struct_array_; + } + + const std::map& get_struct_map() + const { + return struct_map_; + } + std::map& get_mutable_struct_map() { + return struct_map_; + } + + private: + EnumWithTraitsImpl enum_ = EnumWithTraitsImpl::CUSTOM_VALUE_0; + bool bool_ = false; + uint32_t uint32_ = 0; + uint64_t uint64_ = 0; + std::string string_; + std::vector string_array_; + std::set string_set_; + NestedStructWithTraitsImpl struct_; + std::vector struct_array_; + std::map struct_map_; +}; + +// A type which knows how to look like a mojo::test::TrivialStructWithTraits +// mojom type by way of mojo::StructTraits. +struct TrivialStructWithTraitsImpl { + int32_t value; +}; + +// A type which knows how to look like a mojo::test::MoveOnlyStructWithTraits +// mojom type by way of mojo::StructTraits. +class MoveOnlyStructWithTraitsImpl { + public: + MoveOnlyStructWithTraitsImpl(); + MoveOnlyStructWithTraitsImpl(MoveOnlyStructWithTraitsImpl&& other); + ~MoveOnlyStructWithTraitsImpl(); + + ScopedHandle& get_mutable_handle() { return handle_; } + + MoveOnlyStructWithTraitsImpl& operator=(MoveOnlyStructWithTraitsImpl&& other); + + private: + ScopedHandle handle_; + DISALLOW_COPY_AND_ASSIGN(MoveOnlyStructWithTraitsImpl); +}; + +class UnionWithTraitsBase { + public: + enum class Type { INT32, STRUCT }; + + virtual ~UnionWithTraitsBase() {} + + Type type() const { return type_; } + + protected: + Type type_ = Type::INT32; +}; + +class UnionWithTraitsInt32 : public UnionWithTraitsBase { + public: + UnionWithTraitsInt32() {} + explicit UnionWithTraitsInt32(int32_t value) : value_(value) {} + + ~UnionWithTraitsInt32() override; + + int32_t value() const { return value_; } + void set_value(int32_t value) { value_ = value; } + + private: + int32_t value_ = 0; +}; + +class UnionWithTraitsStruct : public UnionWithTraitsBase { + public: + UnionWithTraitsStruct() { type_ = Type::STRUCT; } + explicit UnionWithTraitsStruct(int32_t value) : struct_(value) { + type_ = Type::STRUCT; + } + ~UnionWithTraitsStruct() override; + + NestedStructWithTraitsImpl& get_mutable_struct() { return struct_; } + const NestedStructWithTraitsImpl& get_struct() const { return struct_; } + + private: + NestedStructWithTraitsImpl struct_; +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_H_ diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc new file mode 100644 index 0000000000..6b770b1a49 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc @@ -0,0 +1,137 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h" + +namespace mojo { +namespace { + +struct Context { + int32_t value; +}; + +} // namespace + +// static +void* StructTraits:: + SetUpContext(const test::NestedStructWithTraitsImpl& input) { + Context* context = new Context; + context->value = input.value; + return context; +} + +// static +void StructTraits:: + TearDownContext(const test::NestedStructWithTraitsImpl& input, + void* context) { + Context* context_obj = static_cast(context); + CHECK_EQ(context_obj->value, input.value); + delete context_obj; +} + +// static +int32_t StructTraits:: + value(const test::NestedStructWithTraitsImpl& input, void* context) { + Context* context_obj = static_cast(context); + CHECK_EQ(context_obj->value, input.value); + return input.value; +} + +// static +bool StructTraits:: + Read(test::NestedStructWithTraits::DataView data, + test::NestedStructWithTraitsImpl* output) { + output->value = data.value(); + return true; +} + +test::EnumWithTraits +EnumTraits::ToMojom( + test::EnumWithTraitsImpl input) { + switch (input) { + case test::EnumWithTraitsImpl::CUSTOM_VALUE_0: + return test::EnumWithTraits::VALUE_0; + case test::EnumWithTraitsImpl::CUSTOM_VALUE_1: + return test::EnumWithTraits::VALUE_1; + }; + + NOTREACHED(); + return test::EnumWithTraits::VALUE_0; +} + +bool EnumTraits::FromMojom( + test::EnumWithTraits input, + test::EnumWithTraitsImpl* output) { + switch (input) { + case test::EnumWithTraits::VALUE_0: + *output = test::EnumWithTraitsImpl::CUSTOM_VALUE_0; + return true; + case test::EnumWithTraits::VALUE_1: + *output = test::EnumWithTraitsImpl::CUSTOM_VALUE_1; + return true; + }; + + return false; +} + +// static +bool StructTraits:: + Read(test::StructWithTraits::DataView data, + test::StructWithTraitsImpl* out) { + test::EnumWithTraitsImpl f_enum; + if (!data.ReadFEnum(&f_enum)) + return false; + out->set_enum(f_enum); + + out->set_bool(data.f_bool()); + out->set_uint32(data.f_uint32()); + out->set_uint64(data.f_uint64()); + + base::StringPiece f_string; + std::string f_string2; + if (!data.ReadFString(&f_string) || !data.ReadFString2(&f_string2) || + f_string != f_string2) { + return false; + } + out->set_string(f_string2); + + if (!data.ReadFStringArray(&out->get_mutable_string_array())) + return false; + + // We can't deserialize as a std::set, so we have to manually copy from the + // data view. + ArrayDataView string_set_data_view; + data.GetFStringSetDataView(&string_set_data_view); + for (size_t i = 0; i < string_set_data_view.size(); ++i) { + std::string value; + string_set_data_view.Read(i, &value); + out->get_mutable_string_set().insert(value); + } + + if (!data.ReadFStruct(&out->get_mutable_struct())) + return false; + + if (!data.ReadFStructArray(&out->get_mutable_struct_array())) + return false; + + if (!data.ReadFStructMap(&out->get_mutable_struct_map())) + return false; + + return true; +} + +// static +bool StructTraits:: + Read(test::MoveOnlyStructWithTraits::DataView data, + test::MoveOnlyStructWithTraitsImpl* out) { + out->get_mutable_handle() = data.TakeFHandle(); + return true; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h new file mode 100644 index 0000000000..adcad8aa9e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h @@ -0,0 +1,196 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_ + +#include + +#include +#include + +#include "base/strings/string_piece.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" +#include "mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.h" + +namespace mojo { + +template <> +struct StructTraits { + static void* SetUpContext(const test::NestedStructWithTraitsImpl& input); + static void TearDownContext(const test::NestedStructWithTraitsImpl& input, + void* context); + + static int32_t value(const test::NestedStructWithTraitsImpl& input, + void* context); + + static bool Read(test::NestedStructWithTraitsDataView data, + test::NestedStructWithTraitsImpl* output); +}; + +template <> +struct EnumTraits { + static test::EnumWithTraits ToMojom(test::EnumWithTraitsImpl input); + static bool FromMojom(test::EnumWithTraits input, + test::EnumWithTraitsImpl* output); +}; + +template <> +struct StructTraits { + // Deserialization to test::StructTraitsImpl. + static bool Read(test::StructWithTraitsDataView data, + test::StructWithTraitsImpl* out); + + // Fields in test::StructWithTraits. + // See src/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom. + static test::EnumWithTraitsImpl f_enum( + const test::StructWithTraitsImpl& value) { + return value.get_enum(); + } + + static bool f_bool(const test::StructWithTraitsImpl& value) { + return value.get_bool(); + } + + static uint32_t f_uint32(const test::StructWithTraitsImpl& value) { + return value.get_uint32(); + } + + static uint64_t f_uint64(const test::StructWithTraitsImpl& value) { + return value.get_uint64(); + } + + static base::StringPiece f_string(const test::StructWithTraitsImpl& value) { + return value.get_string_as_string_piece(); + } + + static const std::string& f_string2(const test::StructWithTraitsImpl& value) { + return value.get_string(); + } + + static const std::vector& f_string_array( + const test::StructWithTraitsImpl& value) { + return value.get_string_array(); + } + + static const std::set& f_string_set( + const test::StructWithTraitsImpl& value) { + return value.get_string_set(); + } + + static const test::NestedStructWithTraitsImpl& f_struct( + const test::StructWithTraitsImpl& value) { + return value.get_struct(); + } + + static const std::vector& f_struct_array( + const test::StructWithTraitsImpl& value) { + return value.get_struct_array(); + } + + static const std::map& + f_struct_map(const test::StructWithTraitsImpl& value) { + return value.get_struct_map(); + } +}; + +template <> +struct StructTraits { + // Deserialization to test::TrivialStructTraitsImpl. + static bool Read(test::TrivialStructWithTraitsDataView data, + test::TrivialStructWithTraitsImpl* out) { + out->value = data.value(); + return true; + } + + // Fields in test::TrivialStructWithTraits. + // See src/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom. + static int32_t value(test::TrivialStructWithTraitsImpl& input) { + return input.value; + } +}; + +template <> +struct StructTraits { + // Deserialization to test::MoveOnlyStructTraitsImpl. + static bool Read(test::MoveOnlyStructWithTraitsDataView data, + test::MoveOnlyStructWithTraitsImpl* out); + + // Fields in test::MoveOnlyStructWithTraits. + // See src/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom. + static ScopedHandle f_handle(test::MoveOnlyStructWithTraitsImpl& value) { + return std::move(value.get_mutable_handle()); + } +}; + +template <> +struct StructTraits> { + static bool IsNull(const std::unique_ptr& data) { return !data; } + static void SetToNull(std::unique_ptr* data) { data->reset(); } + + static int f_int32(const std::unique_ptr& data) { return *data; } + + static bool Read(test::StructWithTraitsForUniquePtrDataView data, + std::unique_ptr* out) { + out->reset(new int(data.f_int32())); + return true; + } +}; + +template <> +struct UnionTraits> { + static bool IsNull(const std::unique_ptr& data) { + return !data; + } + static void SetToNull(std::unique_ptr* data) { + data->reset(); + } + + static test::UnionWithTraitsDataView::Tag GetTag( + const std::unique_ptr& data) { + if (data->type() == test::UnionWithTraitsBase::Type::INT32) + return test::UnionWithTraitsDataView::Tag::F_INT32; + + return test::UnionWithTraitsDataView::Tag::F_STRUCT; + } + + static int32_t f_int32( + const std::unique_ptr& data) { + return static_cast(data.get())->value(); + } + + static const test::NestedStructWithTraitsImpl& f_struct( + const std::unique_ptr& data) { + return static_cast(data.get())->get_struct(); + } + + static bool Read(test::UnionWithTraitsDataView data, + std::unique_ptr* out) { + switch (data.tag()) { + case test::UnionWithTraitsDataView::Tag::F_INT32: { + out->reset(new test::UnionWithTraitsInt32(data.f_int32())); + return true; + } + case test::UnionWithTraitsDataView::Tag::F_STRUCT: { + auto* struct_object = new test::UnionWithTraitsStruct(); + out->reset(struct_object); + return data.ReadFStruct(&struct_object->get_mutable_struct()); + } + } + + NOTREACHED(); + return false; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc new file mode 100644 index 0000000000..084e080ad3 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc @@ -0,0 +1,831 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/interfaces/bindings/tests/test_sync_methods.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template +struct LambdaBinder { + using CallbackType = base::Callback; + + template + static void RunLambda(Func func, Args... args) { + func(std::move(args)...); + } + + template + static CallbackType BindLambda(Func func) { + return base::Bind(&LambdaBinder::RunLambda, func); + } +}; + +class TestSyncCommonImpl { + public: + TestSyncCommonImpl() {} + + using PingHandler = base::Callback&)>; + using PingBinder = LambdaBinder&>; + template + void set_ping_handler(Func handler) { + ping_handler_ = PingBinder::BindLambda(handler); + } + + using EchoHandler = + base::Callback&)>; + using EchoBinder = + LambdaBinder&>; + template + void set_echo_handler(Func handler) { + echo_handler_ = EchoBinder::BindLambda(handler); + } + + using AsyncEchoHandler = + base::Callback&)>; + using AsyncEchoBinder = + LambdaBinder&>; + template + void set_async_echo_handler(Func handler) { + async_echo_handler_ = AsyncEchoBinder::BindLambda(handler); + } + + using SendInterfaceHandler = base::Callback; + using SendInterfaceBinder = LambdaBinder; + template + void set_send_interface_handler(Func handler) { + send_interface_handler_ = SendInterfaceBinder::BindLambda(handler); + } + + using SendRequestHandler = base::Callback; + using SendRequestBinder = LambdaBinder; + template + void set_send_request_handler(Func handler) { + send_request_handler_ = SendRequestBinder::BindLambda(handler); + } + + void PingImpl(const base::Callback& callback) { + if (ping_handler_.is_null()) { + callback.Run(); + return; + } + ping_handler_.Run(callback); + } + void EchoImpl(int32_t value, const base::Callback& callback) { + if (echo_handler_.is_null()) { + callback.Run(value); + return; + } + echo_handler_.Run(value, callback); + } + void AsyncEchoImpl(int32_t value, + const base::Callback& callback) { + if (async_echo_handler_.is_null()) { + callback.Run(value); + return; + } + async_echo_handler_.Run(value, callback); + } + void SendInterfaceImpl(TestSyncAssociatedPtrInfo ptr) { + send_interface_handler_.Run(std::move(ptr)); + } + void SendRequestImpl(TestSyncAssociatedRequest request) { + send_request_handler_.Run(std::move(request)); + } + + private: + PingHandler ping_handler_; + EchoHandler echo_handler_; + AsyncEchoHandler async_echo_handler_; + SendInterfaceHandler send_interface_handler_; + SendRequestHandler send_request_handler_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncCommonImpl); +}; + +class TestSyncImpl : public TestSync, public TestSyncCommonImpl { + public: + explicit TestSyncImpl(TestSyncRequest request) + : binding_(this, std::move(request)) {} + + // TestSync implementation: + void Ping(const PingCallback& callback) override { PingImpl(callback); } + void Echo(int32_t value, const EchoCallback& callback) override { + EchoImpl(value, callback); + } + void AsyncEcho(int32_t value, const AsyncEchoCallback& callback) override { + AsyncEchoImpl(value, callback); + } + + Binding* binding() { return &binding_; } + + private: + Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncImpl); +}; + +class TestSyncMasterImpl : public TestSyncMaster, public TestSyncCommonImpl { + public: + explicit TestSyncMasterImpl(TestSyncMasterRequest request) + : binding_(this, std::move(request)) {} + + // TestSyncMaster implementation: + void Ping(const PingCallback& callback) override { PingImpl(callback); } + void Echo(int32_t value, const EchoCallback& callback) override { + EchoImpl(value, callback); + } + void AsyncEcho(int32_t value, const AsyncEchoCallback& callback) override { + AsyncEchoImpl(value, callback); + } + void SendInterface(TestSyncAssociatedPtrInfo ptr) override { + SendInterfaceImpl(std::move(ptr)); + } + void SendRequest(TestSyncAssociatedRequest request) override { + SendRequestImpl(std::move(request)); + } + + Binding* binding() { return &binding_; } + + private: + Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncMasterImpl); +}; + +class TestSyncAssociatedImpl : public TestSync, public TestSyncCommonImpl { + public: + explicit TestSyncAssociatedImpl(TestSyncAssociatedRequest request) + : binding_(this, std::move(request)) {} + + // TestSync implementation: + void Ping(const PingCallback& callback) override { PingImpl(callback); } + void Echo(int32_t value, const EchoCallback& callback) override { + EchoImpl(value, callback); + } + void AsyncEcho(int32_t value, const AsyncEchoCallback& callback) override { + AsyncEchoImpl(value, callback); + } + + AssociatedBinding* binding() { return &binding_; } + + private: + AssociatedBinding binding_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncAssociatedImpl); +}; + +template +struct ImplTraits; + +template <> +struct ImplTraits { + using Type = TestSyncImpl; +}; + +template <> +struct ImplTraits { + using Type = TestSyncMasterImpl; +}; + +template +using ImplTypeFor = typename ImplTraits::Type; + +// A wrapper for either an InterfacePtr or scoped_refptr +// that exposes the InterfacePtr interface. +template +class PtrWrapper { + public: + explicit PtrWrapper(InterfacePtr ptr) : ptr_(std::move(ptr)) {} + + explicit PtrWrapper( + scoped_refptr> thread_safe_ptr) + : thread_safe_ptr_(thread_safe_ptr) {} + + PtrWrapper(PtrWrapper&& other) = default; + + Interface* operator->() { + return thread_safe_ptr_ ? thread_safe_ptr_->get() : ptr_.get(); + } + + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(!thread_safe_ptr_); + ptr_.set_connection_error_handler(error_handler); + } + + void reset() { + ptr_ = nullptr; + thread_safe_ptr_ = nullptr; + } + + private: + InterfacePtr ptr_; + scoped_refptr> thread_safe_ptr_; + + DISALLOW_COPY_AND_ASSIGN(PtrWrapper); +}; + +// The type parameter for SyncMethodCommonTests for varying the Interface and +// whether to use InterfacePtr or ThreadSafeInterfacePtr. +template +struct TestParams { + using Interface = InterfaceT; + static const bool kIsThreadSafeInterfacePtrTest = use_thread_safe_ptr; + + static PtrWrapper Wrap(InterfacePtr ptr) { + if (kIsThreadSafeInterfacePtrTest) { + return PtrWrapper( + ThreadSafeInterfacePtr::Create(std::move(ptr))); + } else { + return PtrWrapper(std::move(ptr)); + } + } +}; + +template +class TestSyncServiceThread { + public: + TestSyncServiceThread() + : thread_("TestSyncServiceThread"), ping_called_(false) { + thread_.Start(); + } + + void SetUp(InterfaceRequest request) { + CHECK(thread_.task_runner()->BelongsToCurrentThread()); + impl_.reset(new ImplTypeFor(std::move(request))); + impl_->set_ping_handler( + [this](const typename Interface::PingCallback& callback) { + { + base::AutoLock locker(lock_); + ping_called_ = true; + } + callback.Run(); + }); + } + + void TearDown() { + CHECK(thread_.task_runner()->BelongsToCurrentThread()); + impl_.reset(); + } + + base::Thread* thread() { return &thread_; } + bool ping_called() const { + base::AutoLock locker(lock_); + return ping_called_; + } + + private: + base::Thread thread_; + + std::unique_ptr> impl_; + + mutable base::Lock lock_; + bool ping_called_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncServiceThread); +}; + +class SyncMethodTest : public testing::Test { + public: + SyncMethodTest() {} + ~SyncMethodTest() override { base::RunLoop().RunUntilIdle(); } + + protected: + base::MessageLoop loop_; +}; + +template +class SyncMethodCommonTest : public SyncMethodTest { + public: + SyncMethodCommonTest() {} + ~SyncMethodCommonTest() override {} +}; + +class SyncMethodAssociatedTest : public SyncMethodTest { + public: + SyncMethodAssociatedTest() {} + ~SyncMethodAssociatedTest() override {} + + protected: + void SetUp() override { + master_impl_.reset(new TestSyncMasterImpl(MakeRequest(&master_ptr_))); + + asso_request_ = MakeRequest(&asso_ptr_info_); + opposite_asso_request_ = MakeRequest(&opposite_asso_ptr_info_); + + master_impl_->set_send_interface_handler( + [this](TestSyncAssociatedPtrInfo ptr) { + opposite_asso_ptr_info_ = std::move(ptr); + }); + base::RunLoop run_loop; + master_impl_->set_send_request_handler( + [this, &run_loop](TestSyncAssociatedRequest request) { + asso_request_ = std::move(request); + run_loop.Quit(); + }); + + master_ptr_->SendInterface(std::move(opposite_asso_ptr_info_)); + master_ptr_->SendRequest(std::move(asso_request_)); + run_loop.Run(); + } + + void TearDown() override { + asso_ptr_info_ = TestSyncAssociatedPtrInfo(); + asso_request_ = TestSyncAssociatedRequest(); + opposite_asso_ptr_info_ = TestSyncAssociatedPtrInfo(); + opposite_asso_request_ = TestSyncAssociatedRequest(); + + master_ptr_ = nullptr; + master_impl_.reset(); + } + + InterfacePtr master_ptr_; + std::unique_ptr master_impl_; + + // An associated interface whose binding lives at the |master_impl_| side. + TestSyncAssociatedPtrInfo asso_ptr_info_; + TestSyncAssociatedRequest asso_request_; + + // An associated interface whose binding lives at the |master_ptr_| side. + TestSyncAssociatedPtrInfo opposite_asso_ptr_info_; + TestSyncAssociatedRequest opposite_asso_request_; +}; + +void SetFlagAndRunClosure(bool* flag, const base::Closure& closure) { + *flag = true; + closure.Run(); +} + +void ExpectValueAndRunClosure(int32_t expected_value, + const base::Closure& closure, + int32_t value) { + EXPECT_EQ(expected_value, value); + closure.Run(); +} + +template +void CallAsyncEchoCallback(Func func, int32_t value) { + func(value); +} + +template +TestSync::AsyncEchoCallback BindAsyncEchoCallback(Func func) { + return base::Bind(&CallAsyncEchoCallback, func); +} + +// TestSync (without associated interfaces) and TestSyncMaster (with associated +// interfaces) exercise MultiplexRouter with different configurations. +// Each test is run once with an InterfacePtr and once with a +// ThreadSafeInterfacePtr to ensure that they behave the same with respect to +// sync calls. +using InterfaceTypes = testing::Types, + TestParams, + TestParams, + TestParams>; +TYPED_TEST_CASE(SyncMethodCommonTest, InterfaceTypes); + +TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) { + using Interface = typename TypeParam::Interface; + InterfacePtr interface_ptr; + ImplTypeFor impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + base::RunLoop run_loop; + ptr->Echo(123, base::Bind(&ExpectValueAndRunClosure, 123, + run_loop.QuitClosure())); + run_loop.Run(); +} + +TYPED_TEST(SyncMethodCommonTest, BasicSyncCalls) { + using Interface = typename TypeParam::Interface; + InterfacePtr interface_ptr; + InterfaceRequest request = MakeRequest(&interface_ptr); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + TestSyncServiceThread service_thread; + service_thread.thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestSyncServiceThread::SetUp, + base::Unretained(&service_thread), base::Passed(&request))); + ASSERT_TRUE(ptr->Ping()); + ASSERT_TRUE(service_thread.ping_called()); + + int32_t output_value = -1; + ASSERT_TRUE(ptr->Echo(42, &output_value)); + ASSERT_EQ(42, output_value); + + base::RunLoop run_loop; + service_thread.thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestSyncServiceThread::TearDown, + base::Unretained(&service_thread)), + run_loop.QuitClosure()); + run_loop.Run(); +} + +TYPED_TEST(SyncMethodCommonTest, ReenteredBySyncMethodBinding) { + // Test that an interface pointer waiting for a sync call response can be + // reentered by a binding serving sync methods on the same thread. + + using Interface = typename TypeParam::Interface; + InterfacePtr interface_ptr; + // The binding lives on the same thread as the interface pointer. + ImplTypeFor impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + int32_t output_value = -1; + ASSERT_TRUE(ptr->Echo(42, &output_value)); + EXPECT_EQ(42, output_value); +} + +TYPED_TEST(SyncMethodCommonTest, InterfacePtrDestroyedDuringSyncCall) { + // Test that it won't result in crash or hang if an interface pointer is + // destroyed while it is waiting for a sync call response. + + using Interface = typename TypeParam::Interface; + InterfacePtr interface_ptr; + ImplTypeFor impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + impl.set_ping_handler([&ptr](const TestSync::PingCallback& callback) { + ptr.reset(); + callback.Run(); + }); + ASSERT_FALSE(ptr->Ping()); +} + +TYPED_TEST(SyncMethodCommonTest, BindingDestroyedDuringSyncCall) { + // Test that it won't result in crash or hang if a binding is + // closed (and therefore the message pipe handle is closed) while the + // corresponding interface pointer is waiting for a sync call response. + + using Interface = typename TypeParam::Interface; + InterfacePtr interface_ptr; + ImplTypeFor impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + impl.set_ping_handler([&impl](const TestSync::PingCallback& callback) { + impl.binding()->Close(); + callback.Run(); + }); + ASSERT_FALSE(ptr->Ping()); +} + +TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithInOrderResponses) { + // Test that we can call a sync method on an interface ptr, while there is + // already a sync call ongoing. The responses arrive in order. + + using Interface = typename TypeParam::Interface; + InterfacePtr interface_ptr; + ImplTypeFor impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + // The same variable is used to store the output of the two sync calls, in + // order to test that responses are handled in the correct order. + int32_t result_value = -1; + + bool first_call = true; + impl.set_echo_handler([&first_call, &ptr, &result_value]( + int32_t value, const TestSync::EchoCallback& callback) { + if (first_call) { + first_call = false; + ASSERT_TRUE(ptr->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); + } + callback.Run(value); + }); + + ASSERT_TRUE(ptr->Echo(123, &result_value)); + EXPECT_EQ(123, result_value); +} + +TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithOutOfOrderResponses) { + // Test that we can call a sync method on an interface ptr, while there is + // already a sync call ongoing. The responses arrive out of order. + + using Interface = typename TypeParam::Interface; + InterfacePtr interface_ptr; + ImplTypeFor impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + // The same variable is used to store the output of the two sync calls, in + // order to test that responses are handled in the correct order. + int32_t result_value = -1; + + bool first_call = true; + impl.set_echo_handler([&first_call, &ptr, &result_value]( + int32_t value, const TestSync::EchoCallback& callback) { + callback.Run(value); + if (first_call) { + first_call = false; + ASSERT_TRUE(ptr->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); + } + }); + + ASSERT_TRUE(ptr->Echo(123, &result_value)); + EXPECT_EQ(123, result_value); +} + +TYPED_TEST(SyncMethodCommonTest, AsyncResponseQueuedDuringSyncCall) { + // Test that while an interface pointer is waiting for the response to a sync + // call, async responses are queued until the sync call completes. + + using Interface = typename TypeParam::Interface; + InterfacePtr interface_ptr; + ImplTypeFor impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + int32_t async_echo_request_value = -1; + TestSync::AsyncEchoCallback async_echo_request_callback; + base::RunLoop run_loop1; + impl.set_async_echo_handler( + [&async_echo_request_value, &async_echo_request_callback, &run_loop1]( + int32_t value, const TestSync::AsyncEchoCallback& callback) { + async_echo_request_value = value; + async_echo_request_callback = callback; + run_loop1.Quit(); + }); + + bool async_echo_response_dispatched = false; + base::RunLoop run_loop2; + ptr->AsyncEcho( + 123, + BindAsyncEchoCallback( + [&async_echo_response_dispatched, &run_loop2](int32_t result) { + async_echo_response_dispatched = true; + EXPECT_EQ(123, result); + run_loop2.Quit(); + })); + // Run until the AsyncEcho request reaches the service side. + run_loop1.Run(); + + impl.set_echo_handler( + [&async_echo_request_value, &async_echo_request_callback]( + int32_t value, const TestSync::EchoCallback& callback) { + // Send back the async response first. + EXPECT_FALSE(async_echo_request_callback.is_null()); + async_echo_request_callback.Run(async_echo_request_value); + + callback.Run(value); + }); + + int32_t result_value = -1; + ASSERT_TRUE(ptr->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); + + // Although the AsyncEcho response arrives before the Echo response, it should + // be queued and not yet dispatched. + EXPECT_FALSE(async_echo_response_dispatched); + + // Run until the AsyncEcho response is dispatched. + run_loop2.Run(); + + EXPECT_TRUE(async_echo_response_dispatched); +} + +TYPED_TEST(SyncMethodCommonTest, AsyncRequestQueuedDuringSyncCall) { + // Test that while an interface pointer is waiting for the response to a sync + // call, async requests for a binding running on the same thread are queued + // until the sync call completes. + + using Interface = typename TypeParam::Interface; + InterfacePtr interface_ptr; + ImplTypeFor impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + bool async_echo_request_dispatched = false; + impl.set_async_echo_handler([&async_echo_request_dispatched]( + int32_t value, const TestSync::AsyncEchoCallback& callback) { + async_echo_request_dispatched = true; + callback.Run(value); + }); + + bool async_echo_response_dispatched = false; + base::RunLoop run_loop; + ptr->AsyncEcho( + 123, + BindAsyncEchoCallback( + [&async_echo_response_dispatched, &run_loop](int32_t result) { + async_echo_response_dispatched = true; + EXPECT_EQ(123, result); + run_loop.Quit(); + })); + + impl.set_echo_handler([&async_echo_request_dispatched]( + int32_t value, const TestSync::EchoCallback& callback) { + // Although the AsyncEcho request is sent before the Echo request, it + // shouldn't be dispatched yet at this point, because there is an ongoing + // sync call on the same thread. + EXPECT_FALSE(async_echo_request_dispatched); + callback.Run(value); + }); + + int32_t result_value = -1; + ASSERT_TRUE(ptr->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); + + // Although the AsyncEcho request is sent before the Echo request, it + // shouldn't be dispatched yet. + EXPECT_FALSE(async_echo_request_dispatched); + + // Run until the AsyncEcho response is dispatched. + run_loop.Run(); + + EXPECT_TRUE(async_echo_response_dispatched); +} + +TYPED_TEST(SyncMethodCommonTest, + QueuedMessagesProcessedBeforeErrorNotification) { + // Test that while an interface pointer is waiting for the response to a sync + // call, async responses are queued. If the message pipe is disconnected + // before the queued messages are processed, the connection error + // notification is delayed until all the queued messages are processed. + + // ThreadSafeInterfacePtr doesn't guarantee that messages are delivered before + // error notifications, so skip it for this test. + if (TypeParam::kIsThreadSafeInterfacePtrTest) + return; + + using Interface = typename TypeParam::Interface; + InterfacePtr ptr; + ImplTypeFor impl(MakeRequest(&ptr)); + + int32_t async_echo_request_value = -1; + TestSync::AsyncEchoCallback async_echo_request_callback; + base::RunLoop run_loop1; + impl.set_async_echo_handler( + [&async_echo_request_value, &async_echo_request_callback, &run_loop1]( + int32_t value, const TestSync::AsyncEchoCallback& callback) { + async_echo_request_value = value; + async_echo_request_callback = callback; + run_loop1.Quit(); + }); + + bool async_echo_response_dispatched = false; + bool connection_error_dispatched = false; + base::RunLoop run_loop2; + ptr->AsyncEcho( + 123, + BindAsyncEchoCallback( + [&async_echo_response_dispatched, &connection_error_dispatched, &ptr, + &run_loop2](int32_t result) { + async_echo_response_dispatched = true; + // At this point, error notification should not be dispatched + // yet. + EXPECT_FALSE(connection_error_dispatched); + EXPECT_FALSE(ptr.encountered_error()); + EXPECT_EQ(123, result); + run_loop2.Quit(); + })); + // Run until the AsyncEcho request reaches the service side. + run_loop1.Run(); + + impl.set_echo_handler( + [&impl, &async_echo_request_value, &async_echo_request_callback]( + int32_t value, const TestSync::EchoCallback& callback) { + // Send back the async response first. + EXPECT_FALSE(async_echo_request_callback.is_null()); + async_echo_request_callback.Run(async_echo_request_value); + + impl.binding()->Close(); + }); + + base::RunLoop run_loop3; + ptr.set_connection_error_handler( + base::Bind(&SetFlagAndRunClosure, &connection_error_dispatched, + run_loop3.QuitClosure())); + + int32_t result_value = -1; + ASSERT_FALSE(ptr->Echo(456, &result_value)); + EXPECT_EQ(-1, result_value); + ASSERT_FALSE(connection_error_dispatched); + EXPECT_FALSE(ptr.encountered_error()); + + // Although the AsyncEcho response arrives before the Echo response, it should + // be queued and not yet dispatched. + EXPECT_FALSE(async_echo_response_dispatched); + + // Run until the AsyncEcho response is dispatched. + run_loop2.Run(); + + EXPECT_TRUE(async_echo_response_dispatched); + + // Run until the error notification is dispatched. + run_loop3.Run(); + + ASSERT_TRUE(connection_error_dispatched); + EXPECT_TRUE(ptr.encountered_error()); +} + +TYPED_TEST(SyncMethodCommonTest, InvalidMessageDuringSyncCall) { + // Test that while an interface pointer is waiting for the response to a sync + // call, an invalid incoming message will disconnect the message pipe, cause + // the sync call to return false, and run the connection error handler + // asynchronously. + + using Interface = typename TypeParam::Interface; + MessagePipe pipe; + + InterfacePtr interface_ptr; + interface_ptr.Bind(InterfacePtrInfo(std::move(pipe.handle0), 0u)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + MessagePipeHandle raw_binding_handle = pipe.handle1.get(); + ImplTypeFor impl(MakeRequest(std::move(pipe.handle1))); + + impl.set_echo_handler([&raw_binding_handle]( + int32_t value, const TestSync::EchoCallback& callback) { + // Write a 1-byte message, which is considered invalid. + char invalid_message = 0; + MojoResult result = + WriteMessageRaw(raw_binding_handle, &invalid_message, 1u, nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE); + ASSERT_EQ(MOJO_RESULT_OK, result); + callback.Run(value); + }); + + bool connection_error_dispatched = false; + base::RunLoop run_loop; + // ThreadSafeInterfacePtr doesn't support setting connection error handlers. + if (!TypeParam::kIsThreadSafeInterfacePtrTest) { + ptr.set_connection_error_handler(base::Bind(&SetFlagAndRunClosure, + &connection_error_dispatched, + run_loop.QuitClosure())); + } + + int32_t result_value = -1; + ASSERT_FALSE(ptr->Echo(456, &result_value)); + EXPECT_EQ(-1, result_value); + ASSERT_FALSE(connection_error_dispatched); + + if (!TypeParam::kIsThreadSafeInterfacePtrTest) { + run_loop.Run(); + ASSERT_TRUE(connection_error_dispatched); + } +} + +TEST_F(SyncMethodAssociatedTest, ReenteredBySyncMethodAssoBindingOfSameRouter) { + // Test that an interface pointer waiting for a sync call response can be + // reentered by an associated binding serving sync methods on the same thread. + // The associated binding belongs to the same MultiplexRouter as the waiting + // interface pointer. + + TestSyncAssociatedImpl opposite_asso_impl(std::move(opposite_asso_request_)); + TestSyncAssociatedPtr opposite_asso_ptr; + opposite_asso_ptr.Bind(std::move(opposite_asso_ptr_info_)); + + master_impl_->set_echo_handler([&opposite_asso_ptr]( + int32_t value, const TestSyncMaster::EchoCallback& callback) { + int32_t result_value = -1; + + ASSERT_TRUE(opposite_asso_ptr->Echo(123, &result_value)); + EXPECT_EQ(123, result_value); + callback.Run(value); + }); + + int32_t result_value = -1; + ASSERT_TRUE(master_ptr_->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); +} + +TEST_F(SyncMethodAssociatedTest, + ReenteredBySyncMethodAssoBindingOfDifferentRouter) { + // Test that an interface pointer waiting for a sync call response can be + // reentered by an associated binding serving sync methods on the same thread. + // The associated binding belongs to a different MultiplexRouter as the + // waiting interface pointer. + + TestSyncAssociatedImpl asso_impl(std::move(asso_request_)); + TestSyncAssociatedPtr asso_ptr; + asso_ptr.Bind(std::move(asso_ptr_info_)); + + master_impl_->set_echo_handler( + [&asso_ptr](int32_t value, const TestSyncMaster::EchoCallback& callback) { + int32_t result_value = -1; + + ASSERT_TRUE(asso_ptr->Echo(123, &result_value)); + EXPECT_EQ(123, result_value); + callback.Run(value); + }); + + int32_t result_value = -1; + ASSERT_TRUE(master_ptr_->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); +} + +// TODO(yzshen): Add more tests related to associated interfaces. + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/test_native_types_blink.typemap b/mojo/public/cpp/bindings/tests/test_native_types_blink.typemap new file mode 100644 index 0000000000..1bdfbbc1a7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/test_native_types_blink.typemap @@ -0,0 +1,17 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +mojom = "//mojo/public/interfaces/bindings/tests/test_native_types.mojom" +public_headers = [ "//mojo/public/cpp/bindings/tests/pickled_types_blink.h" ] +sources = [ + "//mojo/public/cpp/bindings/tests/pickled_types_blink.cc", +] +deps = [ + "//ipc", +] + +type_mappings = [ + "mojo.test.PickledEnum=mojo::test::PickledEnumBlink", + "mojo.test.PickledStruct=mojo::test::PickledStructBlink[move_only]", +] diff --git a/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap b/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap new file mode 100644 index 0000000000..50e8076a50 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap @@ -0,0 +1,17 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +mojom = "//mojo/public/interfaces/bindings/tests/test_native_types.mojom" +public_headers = [ "//mojo/public/cpp/bindings/tests/pickled_types_chromium.h" ] +sources = [ + "//mojo/public/cpp/bindings/tests/pickled_types_chromium.cc", +] +deps = [ + "//ipc", +] + +type_mappings = [ + "mojo.test.PickledEnum=mojo::test::PickledEnumChromium", + "mojo.test.PickledStruct=mojo::test::PickledStructChromium[move_only]", +] diff --git a/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc new file mode 100644 index 0000000000..b0124aa0e2 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc @@ -0,0 +1,161 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +struct RedmondRect { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +}; + +struct RedmondNamedRegion { + std::string name; + std::vector rects; +}; + +bool AreEqualRectArrays(const std::vector& rects1, + const std::vector& rects2) { + if (rects1.size() != rects2.size()) + return false; + + for (size_t i = 0; i < rects1.size(); ++i) { + if (rects1[i]->x != rects2[i]->x || rects1[i]->y != rects2[i]->y || + rects1[i]->width != rects2[i]->width || + rects1[i]->height != rects2[i]->height) { + return false; + } + } + + return true; +} + +} // namespace + +template <> +struct TypeConverter { + static test::RectPtr Convert(const RedmondRect& input) { + return test::Rect::New(input.left, input.top, input.right - input.left, + input.bottom - input.top); + } +}; + +template <> +struct TypeConverter { + static RedmondRect Convert(const test::RectPtr& input) { + RedmondRect rect; + rect.left = input->x; + rect.top = input->y; + rect.right = input->x + input->width; + rect.bottom = input->y + input->height; + return rect; + } +}; + +template <> +struct TypeConverter { + static test::NamedRegionPtr Convert(const RedmondNamedRegion& input) { + return test::NamedRegion::New( + input.name, ConvertTo>(input.rects)); + } +}; + +template <> +struct TypeConverter { + static RedmondNamedRegion Convert(const test::NamedRegionPtr& input) { + RedmondNamedRegion region; + if (input->name) + region.name = input->name.value(); + if (input->rects) { + region.rects.reserve(input->rects->size()); + for (const auto& element : *input->rects) + region.rects.push_back(element.To()); + } + return region; + } +}; + +namespace test { +namespace { + +TEST(TypeConversionTest, CustomTypeConverter) { + RectPtr rect(Rect::New(10, 20, 50, 45)); + + RedmondRect rr = rect.To(); + EXPECT_EQ(10, rr.left); + EXPECT_EQ(20, rr.top); + EXPECT_EQ(60, rr.right); + EXPECT_EQ(65, rr.bottom); + + RectPtr rect2(Rect::From(rr)); + EXPECT_EQ(rect->x, rect2->x); + EXPECT_EQ(rect->y, rect2->y); + EXPECT_EQ(rect->width, rect2->width); + EXPECT_EQ(rect->height, rect2->height); +} + +TEST(TypeConversionTest, CustomTypeConverter_Array_Null) { + std::vector rects; + + auto redmond_rects = ConvertTo>(rects); + + EXPECT_TRUE(redmond_rects.empty()); +} + +TEST(TypeConversionTest, CustomTypeConverter_Array) { + const RedmondRect kBase = {10, 20, 30, 40}; + + std::vector rects(10); + for (size_t i = 0; i < rects.size(); ++i) { + RedmondRect rr = kBase; + rr.left += static_cast(i); + rr.top += static_cast(i); + rects[i] = Rect::From(rr); + } + + auto redmond_rects = ConvertTo>(rects); + + auto rects2 = ConvertTo>(redmond_rects); + EXPECT_TRUE(AreEqualRectArrays(rects, rects2)); +} + +TEST(TypeConversionTest, CustomTypeConverter_Nested) { + RedmondNamedRegion redmond_region; + redmond_region.name = "foopy"; + + const RedmondRect kBase = {10, 20, 30, 40}; + + for (size_t i = 0; i < 10; ++i) { + RedmondRect rect = kBase; + rect.left += static_cast(i); + rect.top += static_cast(i); + redmond_region.rects.push_back(rect); + } + + // Round-trip through generated struct and TypeConverter. + + NamedRegionPtr copy = NamedRegion::From(redmond_region); + RedmondNamedRegion redmond_region2 = copy.To(); + + EXPECT_EQ(redmond_region.name, redmond_region2.name); + EXPECT_EQ(redmond_region.rects.size(), redmond_region2.rects.size()); + for (size_t i = 0; i < redmond_region.rects.size(); ++i) { + EXPECT_EQ(redmond_region.rects[i].left, redmond_region2.rects[i].left); + EXPECT_EQ(redmond_region.rects[i].top, redmond_region2.rects[i].top); + EXPECT_EQ(redmond_region.rects[i].right, redmond_region2.rects[i].right); + EXPECT_EQ(redmond_region.rects[i].bottom, redmond_region2.rects[i].bottom); + } +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/union_unittest.cc b/mojo/public/cpp/bindings/tests/union_unittest.cc new file mode 100644 index 0000000000..bdf27dfff3 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/union_unittest.cc @@ -0,0 +1,1246 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_unions.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +TEST(UnionTest, PlainOldDataGetterSetter) { + PodUnionPtr pod(PodUnion::New()); + + pod->set_f_int8(10); + EXPECT_EQ(10, pod->get_f_int8()); + EXPECT_TRUE(pod->is_f_int8()); + EXPECT_FALSE(pod->is_f_int8_other()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT8); + + pod->set_f_uint8(11); + EXPECT_EQ(11, pod->get_f_uint8()); + EXPECT_TRUE(pod->is_f_uint8()); + EXPECT_FALSE(pod->is_f_int8()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT8); + + pod->set_f_int16(12); + EXPECT_EQ(12, pod->get_f_int16()); + EXPECT_TRUE(pod->is_f_int16()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT16); + + pod->set_f_uint16(13); + EXPECT_EQ(13, pod->get_f_uint16()); + EXPECT_TRUE(pod->is_f_uint16()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT16); + + pod->set_f_int32(14); + EXPECT_EQ(14, pod->get_f_int32()); + EXPECT_TRUE(pod->is_f_int32()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT32); + + pod->set_f_uint32(static_cast(15)); + EXPECT_EQ(static_cast(15), pod->get_f_uint32()); + EXPECT_TRUE(pod->is_f_uint32()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT32); + + pod->set_f_int64(16); + EXPECT_EQ(16, pod->get_f_int64()); + EXPECT_TRUE(pod->is_f_int64()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT64); + + pod->set_f_uint64(static_cast(17)); + EXPECT_EQ(static_cast(17), pod->get_f_uint64()); + EXPECT_TRUE(pod->is_f_uint64()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT64); + + pod->set_f_float(1.5); + EXPECT_EQ(1.5, pod->get_f_float()); + EXPECT_TRUE(pod->is_f_float()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_FLOAT); + + pod->set_f_double(1.9); + EXPECT_EQ(1.9, pod->get_f_double()); + EXPECT_TRUE(pod->is_f_double()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_DOUBLE); + + pod->set_f_bool(true); + EXPECT_TRUE(pod->get_f_bool()); + pod->set_f_bool(false); + EXPECT_FALSE(pod->get_f_bool()); + EXPECT_TRUE(pod->is_f_bool()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_BOOL); + + pod->set_f_enum(AnEnum::SECOND); + EXPECT_EQ(AnEnum::SECOND, pod->get_f_enum()); + EXPECT_TRUE(pod->is_f_enum()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_ENUM); +} + +TEST(UnionTest, PodEquals) { + PodUnionPtr pod1(PodUnion::New()); + PodUnionPtr pod2(PodUnion::New()); + + pod1->set_f_int8(10); + pod2->set_f_int8(10); + EXPECT_TRUE(pod1.Equals(pod2)); + + pod2->set_f_int8(11); + EXPECT_FALSE(pod1.Equals(pod2)); + + pod2->set_f_int8_other(10); + EXPECT_FALSE(pod1.Equals(pod2)); +} + +TEST(UnionTest, PodClone) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + PodUnionPtr pod_clone = pod.Clone(); + EXPECT_EQ(10, pod_clone->get_f_int8()); + EXPECT_TRUE(pod_clone->is_f_int8()); + EXPECT_EQ(pod_clone->which(), PodUnion::Tag::F_INT8); +} + +TEST(UnionTest, PodSerialization) { + PodUnionPtr pod1(PodUnion::New()); + pod1->set_f_int8(10); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + pod1, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize(pod1, &buf, &data, false, + &context); + + PodUnionPtr pod2; + mojo::internal::Deserialize(data, &pod2, &context); + + EXPECT_EQ(10, pod2->get_f_int8()); + EXPECT_TRUE(pod2->is_f_int8()); + EXPECT_EQ(pod2->which(), PodUnion::Tag::F_INT8); +} + +TEST(UnionTest, EnumSerialization) { + PodUnionPtr pod1(PodUnion::New()); + pod1->set_f_enum(AnEnum::SECOND); + + size_t size = mojo::internal::PrepareToSerialize( + pod1, false, nullptr); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize(pod1, &buf, &data, false, + nullptr); + + PodUnionPtr pod2; + mojo::internal::Deserialize(data, &pod2, nullptr); + + EXPECT_EQ(AnEnum::SECOND, pod2->get_f_enum()); + EXPECT_TRUE(pod2->is_f_enum()); + EXPECT_EQ(pod2->which(), PodUnion::Tag::F_ENUM); +} + +TEST(UnionTest, PodValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + size_t size = + mojo::internal::PrepareToSerialize(pod, false, nullptr); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize(pod, &buf, &data, false, nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_TRUE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, SerializeNotNull) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(0); + size_t size = + mojo::internal::PrepareToSerialize(pod, false, nullptr); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize(pod, &buf, &data, false, nullptr); + EXPECT_FALSE(data->is_null()); +} + +TEST(UnionTest, SerializeIsNullInlined) { + PodUnionPtr pod; + size_t size = + mojo::internal::PrepareToSerialize(pod, false, nullptr); + EXPECT_EQ(16U, size); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf); + + // Check that dirty output buffers are handled correctly by serialization. + data->size = 16U; + data->tag = PodUnion::Tag::F_UINT16; + data->data.f_f_int16 = 20; + + mojo::internal::Serialize(pod, &buf, &data, true, nullptr); + EXPECT_TRUE(data->is_null()); + + PodUnionPtr pod2; + mojo::internal::Deserialize(data, &pod2, nullptr); + EXPECT_TRUE(pod2.is_null()); +} + +TEST(UnionTest, SerializeIsNullNotInlined) { + PodUnionPtr pod; + size_t size = + mojo::internal::PrepareToSerialize(pod, false, nullptr); + EXPECT_EQ(16U, size); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize(pod, &buf, &data, false, nullptr); + EXPECT_EQ(nullptr, data); +} + +TEST(UnionTest, NullValidation) { + void* buf = nullptr; + mojo::internal::ValidationContext validation_context(buf, 0, 0, 0); + EXPECT_TRUE(internal::PodUnion_Data::Validate( + buf, &validation_context, false)); +} + +TEST(UnionTest, OutOfAlignmentValidation) { + size_t size = sizeof(internal::PodUnion_Data); + // Get an aligned object and shift the alignment. + mojo::internal::FixedBufferForTesting aligned_buf(size + 1); + void* raw_buf = aligned_buf.Leak(); + char* buf = reinterpret_cast(raw_buf) + 1; + + internal::PodUnion_Data* data = + reinterpret_cast(buf); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_FALSE(internal::PodUnion_Data::Validate( + buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, OOBValidation) { + size_t size = sizeof(internal::PodUnion_Data) - 1; + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnknownTagValidation) { + size_t size = sizeof(internal::PodUnion_Data); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf); + data->tag = static_cast(0xFFFFFF); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnknownEnumValueValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_enum(static_cast(0xFFFF)); + + size_t size = + mojo::internal::PrepareToSerialize(pod, false, nullptr); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize(pod, &buf, &data, false, nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_FALSE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnknownExtensibleEnumValueValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_extensible_enum(static_cast(0xFFFF)); + + size_t size = + mojo::internal::PrepareToSerialize(pod, false, nullptr); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize(pod, &buf, &data, false, nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_TRUE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StringGetterSetter) { + ObjectUnionPtr pod(ObjectUnion::New()); + + std::string hello("hello world"); + pod->set_f_string(hello); + EXPECT_EQ(hello, pod->get_f_string()); + EXPECT_TRUE(pod->is_f_string()); + EXPECT_EQ(pod->which(), ObjectUnion::Tag::F_STRING); +} + +TEST(UnionTest, StringEquals) { + ObjectUnionPtr pod1(ObjectUnion::New()); + ObjectUnionPtr pod2(ObjectUnion::New()); + + pod1->set_f_string("hello world"); + pod2->set_f_string("hello world"); + EXPECT_TRUE(pod1.Equals(pod2)); + + pod2->set_f_string("hello universe"); + EXPECT_FALSE(pod1.Equals(pod2)); +} + +TEST(UnionTest, StringClone) { + ObjectUnionPtr pod(ObjectUnion::New()); + + std::string hello("hello world"); + pod->set_f_string(hello); + ObjectUnionPtr pod_clone = pod.Clone(); + EXPECT_EQ(hello, pod_clone->get_f_string()); + EXPECT_TRUE(pod_clone->is_f_string()); + EXPECT_EQ(pod_clone->which(), ObjectUnion::Tag::F_STRING); +} + +TEST(UnionTest, StringSerialization) { + ObjectUnionPtr pod1(ObjectUnion::New()); + + std::string hello("hello world"); + pod1->set_f_string(hello); + + size_t size = mojo::internal::PrepareToSerialize( + pod1, false, nullptr); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(pod1, &buf, &data, false, + nullptr); + + ObjectUnionPtr pod2; + mojo::internal::Deserialize(data, &pod2, nullptr); + EXPECT_EQ(hello, pod2->get_f_string()); + EXPECT_TRUE(pod2->is_f_string()); + EXPECT_EQ(pod2->which(), ObjectUnion::Tag::F_STRING); +} + +TEST(UnionTest, NullStringValidation) { + size_t size = sizeof(internal::ObjectUnion_Data); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf); + data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING; + data->data.unknown = 0x0; + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StringPointerOverflowValidation) { + size_t size = sizeof(internal::ObjectUnion_Data); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf); + data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING; + data->data.unknown = 0xFFFFFFFFFFFFFFFF; + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StringValidateOOB) { + size_t size = 32; + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf); + data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING; + + data->data.f_f_string.offset = 8; + char* ptr = reinterpret_cast(&data->data.f_f_string); + mojo::internal::ArrayHeader* array_header = + reinterpret_cast(ptr + *ptr); + array_header->num_bytes = 20; // This should go out of bounds. + array_header->num_elements = 20; + mojo::internal::ValidationContext validation_context(data, 32, 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +// TODO(azani): Move back in array_unittest.cc when possible. +// Array tests +TEST(UnionTest, PodUnionInArray) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union_array.emplace(2); + small_struct->pod_union_array.value()[0] = PodUnion::New(); + small_struct->pod_union_array.value()[1] = PodUnion::New(); + + small_struct->pod_union_array.value()[0]->set_f_int8(10); + small_struct->pod_union_array.value()[1]->set_f_int16(12); + + EXPECT_EQ(10, small_struct->pod_union_array.value()[0]->get_f_int8()); + EXPECT_EQ(12, small_struct->pod_union_array.value()[1]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInArraySerialization) { + std::vector array(2); + array[0] = PodUnion::New(); + array[1] = PodUnion::New(); + + array[0]->set_f_int8(10); + array[1]->set_f_int16(12); + EXPECT_EQ(2U, array.size()); + + size_t size = + mojo::internal::PrepareToSerialize>( + array, nullptr); + EXPECT_EQ(40U, size); + + mojo::internal::FixedBufferForTesting buf(size); + mojo::internal::Array_Data* data; + mojo::internal::ContainerValidateParams validate_params(0, false, nullptr); + mojo::internal::Serialize>( + array, &buf, &data, &validate_params, nullptr); + + std::vector array2; + mojo::internal::Deserialize>(data, &array2, + nullptr); + + EXPECT_EQ(2U, array2.size()); + + EXPECT_EQ(10, array2[0]->get_f_int8()); + EXPECT_EQ(12, array2[1]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInArraySerializationWithNull) { + std::vector array(2); + array[0] = PodUnion::New(); + + array[0]->set_f_int8(10); + EXPECT_EQ(2U, array.size()); + + size_t size = + mojo::internal::PrepareToSerialize>( + array, nullptr); + EXPECT_EQ(40U, size); + + mojo::internal::FixedBufferForTesting buf(size); + mojo::internal::Array_Data* data; + mojo::internal::ContainerValidateParams validate_params(0, true, nullptr); + mojo::internal::Serialize>( + array, &buf, &data, &validate_params, nullptr); + + std::vector array2; + mojo::internal::Deserialize>(data, &array2, + nullptr); + + EXPECT_EQ(2U, array2.size()); + + EXPECT_EQ(10, array2[0]->get_f_int8()); + EXPECT_TRUE(array2[1].is_null()); +} + +TEST(UnionTest, ObjectUnionInArraySerialization) { + std::vector array(2); + array[0] = ObjectUnion::New(); + array[1] = ObjectUnion::New(); + + array[0]->set_f_string("hello"); + array[1]->set_f_string("world"); + EXPECT_EQ(2U, array.size()); + + size_t size = + mojo::internal::PrepareToSerialize>( + array, nullptr); + EXPECT_EQ(72U, size); + + mojo::internal::FixedBufferForTesting buf(size); + + mojo::internal::Array_Data* data; + mojo::internal::ContainerValidateParams validate_params(0, false, nullptr); + mojo::internal::Serialize>( + array, &buf, &data, &validate_params, nullptr); + + std::vector new_buf; + new_buf.resize(size); + + void* raw_buf = buf.Leak(); + memcpy(new_buf.data(), raw_buf, size); + free(raw_buf); + + data = + reinterpret_cast*>( + new_buf.data()); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + ASSERT_TRUE(mojo::internal::Array_Data::Validate( + data, &validation_context, &validate_params)); + + std::vector array2; + mojo::internal::Deserialize>(data, &array2, + nullptr); + + EXPECT_EQ(2U, array2.size()); + + EXPECT_EQ("hello", array2[0]->get_f_string()); + EXPECT_EQ("world", array2[1]->get_f_string()); +} + +// TODO(azani): Move back in struct_unittest.cc when possible. +// Struct tests +TEST(UnionTest, Clone_Union) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int8(10); + + SmallStructPtr clone = small_struct.Clone(); + EXPECT_EQ(10, clone->pod_union->get_f_int8()); +} + +// Serialization test of a struct with a union of plain old data. +TEST(UnionTest, Serialization_UnionOfPods) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int32(10); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + small_struct, &context); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + mojo::internal::Serialize(small_struct, &buf, &data, + &context); + + SmallStructPtr deserialized; + mojo::internal::Deserialize(data, &deserialized, + &context); + + EXPECT_EQ(10, deserialized->pod_union->get_f_int32()); +} + +// Serialization test of a struct with a union of structs. +TEST(UnionTest, Serialization_UnionOfObjects) { + SmallObjStructPtr obj_struct(SmallObjStruct::New()); + obj_struct->obj_union = ObjectUnion::New(); + std::string hello("hello world"); + obj_struct->obj_union->set_f_string(hello); + + size_t size = mojo::internal::PrepareToSerialize( + obj_struct, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallObjStruct_Data* data = nullptr; + mojo::internal::Serialize(obj_struct, &buf, &data, + nullptr); + + SmallObjStructPtr deserialized; + mojo::internal::Deserialize(data, &deserialized, + nullptr); + + EXPECT_EQ(hello, deserialized->obj_union->get_f_string()); +} + +// Validation test of a struct with a union. +TEST(UnionTest, Validation_UnionsInStruct) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int32(10); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + small_struct, &context); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + mojo::internal::Serialize(small_struct, &buf, &data, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_TRUE(internal::SmallStruct_Data::Validate( + raw_buf, &validation_context)); + free(raw_buf); +} + +// Validation test of a struct union fails due to unknown union tag. +TEST(UnionTest, Validation_PodUnionInStruct_Failure) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int32(10); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + small_struct, &context); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + mojo::internal::Serialize(small_struct, &buf, &data, + &context); + data->pod_union.tag = static_cast(100); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_FALSE(internal::SmallStruct_Data::Validate( + raw_buf, &validation_context)); + free(raw_buf); +} + +// Validation fails due to non-nullable null union in struct. +TEST(UnionTest, Validation_NullUnion_Failure) { + SmallStructNonNullableUnionPtr small_struct( + SmallStructNonNullableUnion::New()); + + size_t size = + mojo::internal::PrepareToSerialize( + small_struct, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStructNonNullableUnion_Data* data = + internal::SmallStructNonNullableUnion_Data::New(&buf); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_FALSE(internal::SmallStructNonNullableUnion_Data::Validate( + raw_buf, &validation_context)); + free(raw_buf); +} + +// Validation passes with nullable null union. +TEST(UnionTest, Validation_NullableUnion) { + SmallStructPtr small_struct(SmallStruct::New()); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + small_struct, &context); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + mojo::internal::Serialize(small_struct, &buf, &data, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_TRUE(internal::SmallStruct_Data::Validate( + raw_buf, &validation_context)); + free(raw_buf); +} + +// TODO(azani): Move back in map_unittest.cc when possible. +// Map Tests +TEST(UnionTest, PodUnionInMap) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union_map.emplace(); + small_struct->pod_union_map.value()["one"] = PodUnion::New(); + small_struct->pod_union_map.value()["two"] = PodUnion::New(); + + small_struct->pod_union_map.value()["one"]->set_f_int8(8); + small_struct->pod_union_map.value()["two"]->set_f_int16(16); + + EXPECT_EQ(8, small_struct->pod_union_map.value()["one"]->get_f_int8()); + EXPECT_EQ(16, small_struct->pod_union_map.value()["two"]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInMapSerialization) { + using MojomType = MapDataView; + + std::unordered_map map; + map.insert(std::make_pair("one", PodUnion::New())); + map.insert(std::make_pair("two", PodUnion::New())); + + map["one"]->set_f_int8(8); + map["two"]->set_f_int16(16); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize(map, &context); + EXPECT_EQ(120U, size); + + mojo::internal::FixedBufferForTesting buf(size); + + typename mojo::internal::MojomTypeTraits::Data* data; + mojo::internal::ContainerValidateParams validate_params( + new mojo::internal::ContainerValidateParams(0, false, nullptr), + new mojo::internal::ContainerValidateParams(0, false, nullptr)); + mojo::internal::Serialize(map, &buf, &data, &validate_params, + &context); + + std::unordered_map map2; + mojo::internal::Deserialize(data, &map2, &context); + + EXPECT_EQ(8, map2["one"]->get_f_int8()); + EXPECT_EQ(16, map2["two"]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInMapSerializationWithNull) { + using MojomType = MapDataView; + + std::unordered_map map; + map.insert(std::make_pair("one", PodUnion::New())); + map.insert(std::make_pair("two", nullptr)); + + map["one"]->set_f_int8(8); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize(map, &context); + EXPECT_EQ(120U, size); + + mojo::internal::FixedBufferForTesting buf(size); + typename mojo::internal::MojomTypeTraits::Data* data; + mojo::internal::ContainerValidateParams validate_params( + new mojo::internal::ContainerValidateParams(0, false, nullptr), + new mojo::internal::ContainerValidateParams(0, true, nullptr)); + mojo::internal::Serialize(map, &buf, &data, &validate_params, + &context); + + std::unordered_map map2; + mojo::internal::Deserialize(data, &map2, &context); + + EXPECT_EQ(8, map2["one"]->get_f_int8()); + EXPECT_TRUE(map2["two"].is_null()); +} + +TEST(UnionTest, StructInUnionGetterSetterPasser) { + DummyStructPtr dummy(DummyStruct::New()); + dummy->f_int8 = 8; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + EXPECT_EQ(8, obj->get_f_dummy()->f_int8); +} + +TEST(UnionTest, StructInUnionSerialization) { + DummyStructPtr dummy(DummyStruct::New()); + dummy->f_int8 = 8; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + size_t size = mojo::internal::PrepareToSerialize( + obj, false, nullptr); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + nullptr); + + ObjectUnionPtr obj2; + mojo::internal::Deserialize(data, &obj2, nullptr); + EXPECT_EQ(8, obj2->get_f_dummy()->f_int8); +} + +TEST(UnionTest, StructInUnionValidation) { + DummyStructPtr dummy(DummyStruct::New()); + dummy->f_int8 = 8; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + size_t size = mojo::internal::PrepareToSerialize( + obj, false, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StructInUnionValidationNonNullable) { + mojo::internal::SerializationWarningObserverForTesting suppress_warning; + + DummyStructPtr dummy(nullptr); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + size_t size = mojo::internal::PrepareToSerialize( + obj, false, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StructInUnionValidationNullable) { + DummyStructPtr dummy(nullptr); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_nullable(std::move(dummy)); + + size_t size = mojo::internal::PrepareToSerialize( + obj, false, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, ArrayInUnionGetterSetter) { + std::vector array(2); + array[0] = 8; + array[1] = 9; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_array_int8(std::move(array)); + + EXPECT_EQ(8, obj->get_f_array_int8()[0]); + EXPECT_EQ(9, obj->get_f_array_int8()[1]); +} + +TEST(UnionTest, ArrayInUnionSerialization) { + std::vector array(2); + array[0] = 8; + array[1] = 9; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_array_int8(std::move(array)); + + size_t size = mojo::internal::PrepareToSerialize( + obj, false, nullptr); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + nullptr); + + ObjectUnionPtr obj2; + mojo::internal::Deserialize(data, &obj2, nullptr); + + EXPECT_EQ(8, obj2->get_f_array_int8()[0]); + EXPECT_EQ(9, obj2->get_f_array_int8()[1]); +} + +TEST(UnionTest, ArrayInUnionValidation) { + std::vector array(2); + array[0] = 8; + array[1] = 9; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_array_int8(std::move(array)); + + size_t size = mojo::internal::PrepareToSerialize( + obj, false, nullptr); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, MapInUnionGetterSetter) { + std::unordered_map map; + map.insert({"one", 1}); + map.insert({"two", 2}); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_map_int8(std::move(map)); + + EXPECT_EQ(1, obj->get_f_map_int8()["one"]); + EXPECT_EQ(2, obj->get_f_map_int8()["two"]); +} + +TEST(UnionTest, MapInUnionSerialization) { + std::unordered_map map; + map.insert({"one", 1}); + map.insert({"two", 2}); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_map_int8(std::move(map)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + obj, false, &context); + EXPECT_EQ(112U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + &context); + + ObjectUnionPtr obj2; + mojo::internal::Deserialize(data, &obj2, &context); + + EXPECT_EQ(1, obj2->get_f_map_int8()["one"]); + EXPECT_EQ(2, obj2->get_f_map_int8()["two"]); +} + +TEST(UnionTest, MapInUnionValidation) { + std::unordered_map map; + map.insert({"one", 1}); + map.insert({"two", 2}); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_map_int8(std::move(map)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + obj, false, &context); + EXPECT_EQ(112U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnionInUnionGetterSetter) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + EXPECT_EQ(10, obj->get_f_pod_union()->get_f_int8()); +} + +TEST(UnionTest, UnionInUnionSerialization) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + size_t size = mojo::internal::PrepareToSerialize( + obj, false, nullptr); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + nullptr); + + ObjectUnionPtr obj2; + mojo::internal::Deserialize(data, &obj2, nullptr); + EXPECT_EQ(10, obj2->get_f_pod_union()->get_f_int8()); +} + +TEST(UnionTest, UnionInUnionValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + size_t size = mojo::internal::PrepareToSerialize( + obj, false, nullptr); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnionInUnionValidationNonNullable) { + mojo::internal::SerializationWarningObserverForTesting suppress_warning; + + PodUnionPtr pod(nullptr); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + size_t size = mojo::internal::PrepareToSerialize( + obj, false, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 0, 0); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, HandleInUnionGetterSetter) { + ScopedMessagePipeHandle pipe0; + ScopedMessagePipeHandle pipe1; + + CreateMessagePipe(nullptr, &pipe0, &pipe1); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe1)); + + std::string golden("hello world"); + WriteTextMessage(pipe0.get(), golden); + + std::string actual; + ReadTextMessage(handle->get_f_message_pipe().get(), &actual); + + EXPECT_EQ(golden, actual); +} + +TEST(UnionTest, HandleInUnionSerialization) { + ScopedMessagePipeHandle pipe0; + ScopedMessagePipeHandle pipe1; + + CreateMessagePipe(nullptr, &pipe0, &pipe1); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe1)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + handle, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + mojo::internal::Serialize(handle, &buf, &data, false, + &context); + EXPECT_EQ(1U, context.handles.size()); + + HandleUnionPtr handle2(HandleUnion::New()); + mojo::internal::Deserialize(data, &handle2, &context); + + std::string golden("hello world"); + WriteTextMessage(pipe0.get(), golden); + + std::string actual; + ReadTextMessage(handle2->get_f_message_pipe().get(), &actual); + + EXPECT_EQ(golden, actual); +} + +TEST(UnionTest, HandleInUnionValidation) { + ScopedMessagePipeHandle pipe0; + ScopedMessagePipeHandle pipe1; + + CreateMessagePipe(nullptr, &pipe0, &pipe1); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe1)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + handle, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + mojo::internal::Serialize(handle, &buf, &data, false, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 1, 0); + EXPECT_TRUE(internal::HandleUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, HandleInUnionValidationNull) { + mojo::internal::SerializationWarningObserverForTesting suppress_warning; + + ScopedMessagePipeHandle pipe; + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize( + handle, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + mojo::internal::Serialize(handle, &buf, &data, false, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast(size), 1, 0); + EXPECT_FALSE(internal::HandleUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +class SmallCacheImpl : public SmallCache { + public: + explicit SmallCacheImpl(const base::Closure& closure) + : int_value_(0), closure_(closure) {} + ~SmallCacheImpl() override {} + int64_t int_value() const { return int_value_; } + + private: + void SetIntValue(int64_t int_value) override { + int_value_ = int_value; + closure_.Run(); + } + void GetIntValue(const GetIntValueCallback& callback) override { + callback.Run(int_value_); + } + + int64_t int_value_; + base::Closure closure_; +}; + +TEST(UnionTest, InterfaceInUnion) { + base::MessageLoop message_loop; + base::RunLoop run_loop; + SmallCacheImpl impl(run_loop.QuitClosure()); + SmallCachePtr ptr; + Binding bindings(&impl, MakeRequest(&ptr)); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_small_cache(std::move(ptr)); + + handle->get_f_small_cache()->SetIntValue(10); + run_loop.Run(); + EXPECT_EQ(10, impl.int_value()); +} + +TEST(UnionTest, InterfaceInUnionSerialization) { + base::MessageLoop message_loop; + base::RunLoop run_loop; + SmallCacheImpl impl(run_loop.QuitClosure()); + SmallCachePtr ptr; + Binding bindings(&impl, MakeRequest(&ptr)); + + mojo::internal::SerializationContext context; + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_small_cache(std::move(ptr)); + size_t size = mojo::internal::PrepareToSerialize( + handle, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + mojo::internal::Serialize(handle, &buf, &data, false, + &context); + EXPECT_EQ(1U, context.handles.size()); + + HandleUnionPtr handle2(HandleUnion::New()); + mojo::internal::Deserialize(data, &handle2, &context); + + handle2->get_f_small_cache()->SetIntValue(10); + run_loop.Run(); + EXPECT_EQ(10, impl.int_value()); +} + +class UnionInterfaceImpl : public UnionInterface { + public: + UnionInterfaceImpl() {} + ~UnionInterfaceImpl() override {} + + private: + void Echo(PodUnionPtr in, const EchoCallback& callback) override { + callback.Run(std::move(in)); + } +}; + +void ExpectInt16(int16_t value, PodUnionPtr out) { + EXPECT_EQ(value, out->get_f_int16()); +} + +TEST(UnionTest, UnionInInterface) { + base::MessageLoop message_loop; + UnionInterfaceImpl impl; + UnionInterfacePtr ptr; + Binding bindings(&impl, MakeRequest(&ptr)); + + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int16(16); + + ptr->Echo(std::move(pod), base::Bind(&ExpectInt16, 16)); + base::RunLoop().RunUntilIdle(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/validation_context_unittest.cc b/mojo/public/cpp/bindings/tests/validation_context_unittest.cc new file mode 100644 index 0000000000..9ce9d60e49 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_context_unittest.cc @@ -0,0 +1,297 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include + +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/system/core.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using Handle_Data = mojo::internal::Handle_Data; +using AssociatedEndpointHandle_Data = + mojo::internal::AssociatedEndpointHandle_Data; + +const void* ToPtr(uintptr_t ptr) { + return reinterpret_cast(ptr); +} + +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) +TEST(ValidationContextTest, ConstructorRangeOverflow) { + { + // Test memory range overflow. + internal::ValidationContext context( + ToPtr(std::numeric_limits::max() - 3000), 5000, 0, 0); + + EXPECT_FALSE(context.IsValidRange( + ToPtr(std::numeric_limits::max() - 3000), 1)); + EXPECT_FALSE(context.ClaimMemory( + ToPtr(std::numeric_limits::max() - 3000), 1)); + } + + if (sizeof(size_t) <= sizeof(uint32_t)) + return; + + { + // Test handle index range overflow. + size_t num_handles = + static_cast(std::numeric_limits::max()) + 5; + internal::ValidationContext context(ToPtr(0), 0, num_handles, 0); + + EXPECT_FALSE(context.ClaimHandle(Handle_Data(0))); + EXPECT_FALSE(context.ClaimHandle( + Handle_Data(std::numeric_limits::max() - 1))); + + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + size_t num_associated_endpoint_handles = + static_cast(std::numeric_limits::max()) + 5; + internal::ValidationContext context(ToPtr(0), 0, 0, + num_associated_endpoint_handles); + + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + EXPECT_FALSE( + context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data( + std::numeric_limits::max() - 1))); + + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + } +} +#endif + +TEST(ValidationContextTest, IsValidRange) { + { + internal::ValidationContext context(ToPtr(1234), 100, 0, 0); + + // Basics. + EXPECT_FALSE(context.IsValidRange(ToPtr(100), 5)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1230), 50)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1234), 5)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1240), 50)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1234), 100)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 101)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1240), 100)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1333), 5)); + EXPECT_FALSE(context.IsValidRange(ToPtr(2234), 5)); + + // ClaimMemory() updates the valid range. + EXPECT_TRUE(context.ClaimMemory(ToPtr(1254), 10)); + + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 1)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1254), 10)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1263), 1)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1263), 10)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1264), 10)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1264), 70)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1264), 71)); + } + + { + internal::ValidationContext context(ToPtr(1234), 100, 0, 0); + // Should return false for empty ranges. + EXPECT_FALSE(context.IsValidRange(ToPtr(0), 0)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1200), 0)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 0)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1240), 0)); + EXPECT_FALSE(context.IsValidRange(ToPtr(2234), 0)); + } + + { + // The valid memory range is empty. + internal::ValidationContext context(ToPtr(1234), 0, 0, 0); + + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 1)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 0)); + } + + { + internal::ValidationContext context( + ToPtr(std::numeric_limits::max() - 2000), 1000, 0, 0); + + // Test overflow. + EXPECT_FALSE(context.IsValidRange( + ToPtr(std::numeric_limits::max() - 1500), 4000)); + EXPECT_FALSE(context.IsValidRange( + ToPtr(std::numeric_limits::max() - 1500), + std::numeric_limits::max())); + + // This should be fine. + EXPECT_TRUE(context.IsValidRange( + ToPtr(std::numeric_limits::max() - 1500), 200)); + } +} + +TEST(ValidationContextTest, ClaimHandle) { + { + internal::ValidationContext context(ToPtr(0), 0, 10, 0); + + // Basics. + EXPECT_TRUE(context.ClaimHandle(Handle_Data(0))); + EXPECT_FALSE(context.ClaimHandle(Handle_Data(0))); + + EXPECT_TRUE(context.ClaimHandle(Handle_Data(9))); + EXPECT_FALSE(context.ClaimHandle(Handle_Data(10))); + + // Should fail because it is smaller than the max index that has been + // claimed. + EXPECT_FALSE(context.ClaimHandle(Handle_Data(8))); + + // Should return true for invalid handle. + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + // No handle to claim. + internal::ValidationContext context(ToPtr(0), 0, 0, 0); + + EXPECT_FALSE(context.ClaimHandle(Handle_Data(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + // Test the case that |num_handles| is the same value as + // |internal::kEncodedInvalidHandleValue|. + EXPECT_EQ(internal::kEncodedInvalidHandleValue, + std::numeric_limits::max()); + internal::ValidationContext context( + ToPtr(0), 0, std::numeric_limits::max(), 0); + + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(std::numeric_limits::max() - 1))); + EXPECT_FALSE(context.ClaimHandle( + Handle_Data(std::numeric_limits::max() - 1))); + EXPECT_FALSE(context.ClaimHandle(Handle_Data(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + } +} + +TEST(ValidationContextTest, ClaimAssociatedEndpointHandle) { + { + internal::ValidationContext context(ToPtr(0), 0, 0, 10); + + // Basics. + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(9))); + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(10))); + + // Should fail because it is smaller than the max index that has been + // claimed. + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(8))); + + // Should return true for invalid handle. + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + // No handle to claim. + internal::ValidationContext context(ToPtr(0), 0, 0, 0); + + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + // Test the case that |num_associated_endpoint_handles| is the same value as + // |internal::kEncodedInvalidHandleValue|. + EXPECT_EQ(internal::kEncodedInvalidHandleValue, + std::numeric_limits::max()); + internal::ValidationContext context(ToPtr(0), 0, 0, + std::numeric_limits::max()); + + EXPECT_TRUE( + context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data( + std::numeric_limits::max() - 1))); + EXPECT_FALSE( + context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data( + std::numeric_limits::max() - 1))); + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + } +} + +TEST(ValidationContextTest, ClaimMemory) { + { + internal::ValidationContext context(ToPtr(1000), 2000, 0, 0); + + // Basics. + EXPECT_FALSE(context.ClaimMemory(ToPtr(500), 100)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(800), 300)); + EXPECT_TRUE(context.ClaimMemory(ToPtr(1000), 100)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(1099), 100)); + EXPECT_TRUE(context.ClaimMemory(ToPtr(1100), 200)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(2000), 1001)); + EXPECT_TRUE(context.ClaimMemory(ToPtr(2000), 500)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(2000), 500)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(1400), 100)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(3000), 1)); + EXPECT_TRUE(context.ClaimMemory(ToPtr(2500), 500)); + } + + { + // No memory to claim. + internal::ValidationContext context(ToPtr(10000), 0, 0, 0); + + EXPECT_FALSE(context.ClaimMemory(ToPtr(10000), 1)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(10000), 0)); + } + + { + internal::ValidationContext context( + ToPtr(std::numeric_limits::max() - 1000), 500, 0, 0); + + // Test overflow. + EXPECT_FALSE(context.ClaimMemory( + ToPtr(std::numeric_limits::max() - 750), 4000)); + EXPECT_FALSE( + context.ClaimMemory(ToPtr(std::numeric_limits::max() - 750), + std::numeric_limits::max())); + + // This should be fine. + EXPECT_TRUE(context.ClaimMemory( + ToPtr(std::numeric_limits::max() - 750), 200)); + } +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc b/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc new file mode 100644 index 0000000000..f3097372a4 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc @@ -0,0 +1,412 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mojo/public/c/system/macros.h" + +namespace mojo { +namespace test { +namespace { + +class ValidationTestInputParser { + public: + ValidationTestInputParser(const std::string& input, + std::vector* data, + size_t* num_handles, + std::string* error_message); + ~ValidationTestInputParser(); + + bool Run(); + + private: + struct DataType; + + typedef std::pair Range; + + typedef bool (ValidationTestInputParser::*ParseDataFunc)( + const DataType& type, + const std::string& value_string); + + struct DataType { + const char* name; + size_t name_size; + size_t data_size; + ParseDataFunc parse_data_func; + }; + + // A dist4/8 item that hasn't been matched with an anchr item. + struct PendingDistanceItem { + // Where this data item is located in |data_|. + size_t pos; + // Either 4 or 8 (bytes). + size_t data_size; + }; + + bool GetNextItem(Range* range); + + bool ParseItem(const Range& range); + + bool ParseUnsignedInteger(const DataType& type, + const std::string& value_string); + bool ParseSignedInteger(const DataType& type, + const std::string& value_string); + bool ParseFloat(const DataType& type, const std::string& value_string); + bool ParseDouble(const DataType& type, const std::string& value_string); + bool ParseBinarySequence(const DataType& type, + const std::string& value_string); + bool ParseDistance(const DataType& type, const std::string& value_string); + bool ParseAnchor(const DataType& type, const std::string& value_string); + bool ParseHandles(const DataType& type, const std::string& value_string); + + bool StartsWith(const Range& range, const char* prefix, size_t prefix_length); + + bool ConvertToUnsignedInteger(const std::string& value_string, + unsigned long long int* value); + + template + void AppendData(T data) { + size_t pos = data_->size(); + data_->resize(pos + sizeof(T)); + memcpy(&(*data_)[pos], &data, sizeof(T)); + } + + template + bool ConvertAndAppendData(InputType value) { + if (value > std::numeric_limits::max() || + value < std::numeric_limits::min()) { + return false; + } + AppendData(static_cast(value)); + return true; + } + + template + bool ConvertAndFillData(size_t pos, InputType value) { + if (value > std::numeric_limits::max() || + value < std::numeric_limits::min()) { + return false; + } + TargetType target_value = static_cast(value); + assert(pos + sizeof(TargetType) <= data_->size()); + memcpy(&(*data_)[pos], &target_value, sizeof(TargetType)); + return true; + } + + static const DataType kDataTypes[]; + static const size_t kDataTypeCount; + + const std::string& input_; + size_t input_cursor_; + + std::vector* data_; + size_t* num_handles_; + std::string* error_message_; + + std::map pending_distance_items_; + std::set anchors_; +}; + +#define DATA_TYPE(name, data_size, parse_data_func) \ + { name, sizeof(name) - 1, data_size, parse_data_func } + +const ValidationTestInputParser::DataType + ValidationTestInputParser::kDataTypes[] = { + DATA_TYPE("[u1]", 1, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[u2]", 2, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[u4]", 4, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[u8]", 8, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[s1]", 1, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[s2]", 2, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[s4]", 4, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[s8]", 8, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[b]", 1, &ValidationTestInputParser::ParseBinarySequence), + DATA_TYPE("[f]", 4, &ValidationTestInputParser::ParseFloat), + DATA_TYPE("[d]", 8, &ValidationTestInputParser::ParseDouble), + DATA_TYPE("[dist4]", 4, &ValidationTestInputParser::ParseDistance), + DATA_TYPE("[dist8]", 8, &ValidationTestInputParser::ParseDistance), + DATA_TYPE("[anchr]", 0, &ValidationTestInputParser::ParseAnchor), + DATA_TYPE("[handles]", 0, &ValidationTestInputParser::ParseHandles)}; + +const size_t ValidationTestInputParser::kDataTypeCount = + sizeof(ValidationTestInputParser::kDataTypes) / + sizeof(ValidationTestInputParser::kDataTypes[0]); + +ValidationTestInputParser::ValidationTestInputParser(const std::string& input, + std::vector* data, + size_t* num_handles, + std::string* error_message) + : input_(input), + input_cursor_(0), + data_(data), + num_handles_(num_handles), + error_message_(error_message) { + assert(data_); + assert(num_handles_); + assert(error_message_); + data_->clear(); + *num_handles_ = 0; + error_message_->clear(); +} + +ValidationTestInputParser::~ValidationTestInputParser() { +} + +bool ValidationTestInputParser::Run() { + Range range; + bool result = true; + while (result && GetNextItem(&range)) + result = ParseItem(range); + + if (!result) { + *error_message_ = + "Error occurred when parsing " + std::string(range.first, range.second); + } else if (!pending_distance_items_.empty()) { + // We have parsed all the contents in |input_| successfully, but there are + // unmatched dist4/8 items. + *error_message_ = "Error occurred when matching [dist4/8] and [anchr]."; + result = false; + } + if (!result) { + data_->clear(); + *num_handles_ = 0; + } else { + assert(error_message_->empty()); + } + + return result; +} + +bool ValidationTestInputParser::GetNextItem(Range* range) { + const char kWhitespaceChars[] = " \t\n\r"; + const char kItemDelimiters[] = " \t\n\r/"; + const char kEndOfLineChars[] = "\n\r"; + while (true) { + // Skip leading whitespaces. + // If there are no non-whitespace characters left, |input_cursor_| will be + // set to std::npos. + input_cursor_ = input_.find_first_not_of(kWhitespaceChars, input_cursor_); + + if (input_cursor_ >= input_.size()) + return false; + + if (StartsWith( + Range(&input_[0] + input_cursor_, &input_[0] + input_.size()), + "//", + 2)) { + // Skip contents until the end of the line. + input_cursor_ = input_.find_first_of(kEndOfLineChars, input_cursor_); + } else { + range->first = &input_[0] + input_cursor_; + input_cursor_ = input_.find_first_of(kItemDelimiters, input_cursor_); + range->second = input_cursor_ >= input_.size() + ? &input_[0] + input_.size() + : &input_[0] + input_cursor_; + return true; + } + } + return false; +} + +bool ValidationTestInputParser::ParseItem(const Range& range) { + for (size_t i = 0; i < kDataTypeCount; ++i) { + if (StartsWith(range, kDataTypes[i].name, kDataTypes[i].name_size)) { + return (this->*kDataTypes[i].parse_data_func)( + kDataTypes[i], + std::string(range.first + kDataTypes[i].name_size, range.second)); + } + } + + // "[u1]" is optional. + return ParseUnsignedInteger(kDataTypes[0], + std::string(range.first, range.second)); +} + +bool ValidationTestInputParser::ParseUnsignedInteger( + const DataType& type, + const std::string& value_string) { + unsigned long long int value; + if (!ConvertToUnsignedInteger(value_string, &value)) + return false; + + switch (type.data_size) { + case 1: + return ConvertAndAppendData(value); + case 2: + return ConvertAndAppendData(value); + case 4: + return ConvertAndAppendData(value); + case 8: + return ConvertAndAppendData(value); + default: + assert(false); + return false; + } +} + +bool ValidationTestInputParser::ParseSignedInteger( + const DataType& type, + const std::string& value_string) { + long long int value; + if (sscanf(value_string.c_str(), "%lli", &value) != 1) + return false; + + switch (type.data_size) { + case 1: + return ConvertAndAppendData(value); + case 2: + return ConvertAndAppendData(value); + case 4: + return ConvertAndAppendData(value); + case 8: + return ConvertAndAppendData(value); + default: + assert(false); + return false; + } +} + +bool ValidationTestInputParser::ParseFloat(const DataType& type, + const std::string& value_string) { + static_assert(sizeof(float) == 4, "sizeof(float) is not 4"); + + float value; + if (sscanf(value_string.c_str(), "%f", &value) != 1) + return false; + + AppendData(value); + return true; +} + +bool ValidationTestInputParser::ParseDouble(const DataType& type, + const std::string& value_string) { + static_assert(sizeof(double) == 8, "sizeof(double) is not 8"); + + double value; + if (sscanf(value_string.c_str(), "%lf", &value) != 1) + return false; + + AppendData(value); + return true; +} + +bool ValidationTestInputParser::ParseBinarySequence( + const DataType& type, + const std::string& value_string) { + if (value_string.size() != 8) + return false; + + uint8_t value = 0; + for (std::string::const_iterator iter = value_string.begin(); + iter != value_string.end(); + ++iter) { + value <<= 1; + if (*iter == '1') + value++; + else if (*iter != '0') + return false; + } + AppendData(value); + return true; +} + +bool ValidationTestInputParser::ParseDistance(const DataType& type, + const std::string& value_string) { + if (pending_distance_items_.find(value_string) != + pending_distance_items_.end()) + return false; + + PendingDistanceItem item = {data_->size(), type.data_size}; + data_->resize(data_->size() + type.data_size); + pending_distance_items_[value_string] = item; + + return true; +} + +bool ValidationTestInputParser::ParseAnchor(const DataType& type, + const std::string& value_string) { + if (anchors_.find(value_string) != anchors_.end()) + return false; + anchors_.insert(value_string); + + std::map::iterator iter = + pending_distance_items_.find(value_string); + if (iter == pending_distance_items_.end()) + return false; + + PendingDistanceItem dist_item = iter->second; + pending_distance_items_.erase(iter); + + size_t distance = data_->size() - dist_item.pos; + switch (dist_item.data_size) { + case 4: + return ConvertAndFillData(dist_item.pos, distance); + case 8: + return ConvertAndFillData(dist_item.pos, distance); + default: + assert(false); + return false; + } +} + +bool ValidationTestInputParser::ParseHandles(const DataType& type, + const std::string& value_string) { + // It should be the first item. + if (!data_->empty()) + return false; + + unsigned long long int value; + if (!ConvertToUnsignedInteger(value_string, &value)) + return false; + + if (value > std::numeric_limits::max()) + return false; + + *num_handles_ = static_cast(value); + return true; +} + +bool ValidationTestInputParser::StartsWith(const Range& range, + const char* prefix, + size_t prefix_length) { + if (static_cast(range.second - range.first) < prefix_length) + return false; + + return memcmp(range.first, prefix, prefix_length) == 0; +} + +bool ValidationTestInputParser::ConvertToUnsignedInteger( + const std::string& value_string, + unsigned long long int* value) { + const char* format = nullptr; + if (value_string.find_first_of("xX") != std::string::npos) + format = "%llx"; + else + format = "%llu"; + return sscanf(value_string.c_str(), format, value) == 1; +} + +} // namespace + +bool ParseValidationTestInput(const std::string& input, + std::vector* data, + size_t* num_handles, + std::string* error_message) { + ValidationTestInputParser parser(input, data, num_handles, error_message); + return parser.Run(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.h b/mojo/public/cpp/bindings/tests/validation_test_input_parser.h new file mode 100644 index 0000000000..d08f359c17 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.h @@ -0,0 +1,121 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_ + +#include +#include + +#include +#include + +namespace mojo { +namespace test { + +// Input Format of Mojo Message Validation Tests. +// +// Data items are separated by whitespaces: +// - ' ' (0x20) space; +// - '\t' (0x09) horizontal tab; +// - '\n' (0x0a) newline; +// - '\r' (0x0d) carriage return. +// A comment starts with //, extending to the end of the line. +// Each data item is of the format []. The types defined and the +// corresponding value formats are described below. +// +// Type: u1 / u2 / u4 / u8 +// Description: Little-endian 1/2/4/8-byte unsigned integer. +// Value Format: +// - Decimal integer: 0|[1-9][0-9]* +// - Hexadecimal integer: 0[xX][0-9a-fA-F]+ +// - The type prefix (including the square brackets) of 1-byte unsigned +// integer is optional. +// +// Type: s1 / s2 / s4 / s8 +// Description: Little-endian 1/2/4/8-byte signed integer. +// Value Format: +// - Decimal integer: [-+]?(0|[1-9][0-9]*) +// - Hexadecimal integer: [-+]?0[xX][0-9a-fA-F]+ +// +// Type: b +// Description: Binary sequence of 1 byte. +// Value Format: [01]{8} +// +// Type: f / d +// Description: Little-endian IEEE-754 format of float (4 bytes) and double (8 +// bytes). +// Value Format: [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? +// +// Type: dist4 / dist8 +// Description: Little-endian 4/8-byte unsigned integer. The actual value is set +// to the byte distance from the location of this integer to the location of the +// anchr item with the same ID. A dist8 and anchr pair can be used to easily +// represent an encoded pointer. A dist4 and anchr pair can be used to easily +// calculate struct/array size. +// Value Format: The value is an ID: [0-9a-zA-Z_]+ +// +// Type: anchr +// Description: Mark an anchor location. It doesn’t translate into any actual +// data. +// Value Format: The value is an ID of the same format as that of dist4/8. +// +// Type: handles +// Description: The number of handles that are associated with the message. This +// special item is not part of the message data. If specified, it should be the +// first item. +// Value Format: The same format as u1/2/4/8. +// +// EXAMPLE: +// +// Suppose you have the following Mojo types defined: +// struct Bar { +// int32_t a; +// bool b; +// bool c; +// }; +// struct Foo { +// Bar x; +// uint32_t y; +// }; +// +// The following describes a valid message whose payload is a Foo struct: +// // message header +// [dist4]message_header // num_bytes +// [u4]3 // version +// [u4]0 // type +// [u4]1 // flags +// [u8]1234 // request_id +// [anchr]message_header +// +// // payload +// [dist4]foo // num_bytes +// [u4]2 // version +// [dist8]bar_ptr // x +// [u4]0xABCD // y +// [u4]0 // padding +// [anchr]foo +// +// [anchr]bar_ptr +// [dist4]bar // num_bytes +// [u4]3 // version +// [s4]-1 // a +// [b]00000010 // b and c +// 0 0 0 // padding +// [anchr]bar + +// Parses validation test input. +// On success, |data| and |num_handles| store the parsing result, +// |error_message| is cleared; on failure, |error_message| is set to a message +// describing the error, |data| is cleared and |num_handles| set to 0. +// Note: For now, this method only works on little-endian platforms. +bool ParseValidationTestInput(const std::string& input, + std::vector* data, + size_t* num_handles, + std::string* error_message); + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_ diff --git a/mojo/public/cpp/bindings/tests/validation_unittest.cc b/mojo/public/cpp/bindings/tests/validation_unittest.cc new file mode 100644 index 0000000000..7af7396d4e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_unittest.cc @@ -0,0 +1,498 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/connector.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/message_header_validator.h" +#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom.h" +#include "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template +void Append(std::vector* data_vector, T data) { + size_t pos = data_vector->size(); + data_vector->resize(pos + sizeof(T)); + memcpy(&(*data_vector)[pos], &data, sizeof(T)); +} + +bool TestInputParser(const std::string& input, + bool expected_result, + const std::vector& expected_data, + size_t expected_num_handles) { + std::vector data; + size_t num_handles; + std::string error_message; + + bool result = + ParseValidationTestInput(input, &data, &num_handles, &error_message); + if (expected_result) { + if (result && error_message.empty() && expected_data == data && + expected_num_handles == num_handles) { + return true; + } + + // Compare with an empty string instead of checking |error_message.empty()|, + // so that the message will be printed out if the two are not equal. + EXPECT_EQ(std::string(), error_message); + EXPECT_EQ(expected_data, data); + EXPECT_EQ(expected_num_handles, num_handles); + return false; + } + + EXPECT_FALSE(error_message.empty()); + return !result && !error_message.empty(); +} + +std::vector GetMatchingTests(const std::vector& names, + const std::string& prefix) { + const std::string suffix = ".data"; + std::vector tests; + for (size_t i = 0; i < names.size(); ++i) { + if (names[i].size() >= suffix.size() && + names[i].substr(0, prefix.size()) == prefix && + names[i].substr(names[i].size() - suffix.size()) == suffix) + tests.push_back(names[i].substr(0, names[i].size() - suffix.size())); + } + return tests; +} + +bool ReadFile(const std::string& path, std::string* result) { + FILE* fp = OpenSourceRootRelativeFile(path.c_str()); + if (!fp) { + ADD_FAILURE() << "File not found: " << path; + return false; + } + fseek(fp, 0, SEEK_END); + size_t size = static_cast(ftell(fp)); + if (size == 0) { + result->clear(); + fclose(fp); + return true; + } + fseek(fp, 0, SEEK_SET); + result->resize(size); + size_t size_read = fread(&result->at(0), 1, size, fp); + fclose(fp); + return size == size_read; +} + +bool ReadAndParseDataFile(const std::string& path, + std::vector* data, + size_t* num_handles) { + std::string input; + if (!ReadFile(path, &input)) + return false; + + std::string error_message; + if (!ParseValidationTestInput(input, data, num_handles, &error_message)) { + ADD_FAILURE() << error_message; + return false; + } + + return true; +} + +bool ReadResultFile(const std::string& path, std::string* result) { + if (!ReadFile(path, result)) + return false; + + // Result files are new-line delimited text files. Remove any CRs. + result->erase(std::remove(result->begin(), result->end(), '\r'), + result->end()); + + // Remove trailing LFs. + size_t pos = result->find_last_not_of('\n'); + if (pos == std::string::npos) + result->clear(); + else + result->resize(pos + 1); + + return true; +} + +std::string GetPath(const std::string& root, const std::string& suffix) { + return "mojo/public/interfaces/bindings/tests/data/validation/" + root + + suffix; +} + +// |message| should be a newly created object. +bool ReadTestCase(const std::string& test, + Message* message, + std::string* expected) { + std::vector data; + size_t num_handles; + if (!ReadAndParseDataFile(GetPath(test, ".data"), &data, &num_handles) || + !ReadResultFile(GetPath(test, ".expected"), expected)) { + return false; + } + + message->Initialize(static_cast(data.size()), + false /* zero_initialized */); + if (!data.empty()) + memcpy(message->mutable_data(), &data[0], data.size()); + message->mutable_handles()->resize(num_handles); + + return true; +} + +void RunValidationTests(const std::string& prefix, + MessageReceiver* test_message_receiver) { + std::vector names = + EnumerateSourceRootRelativeDirectory(GetPath("", "")); + std::vector tests = GetMatchingTests(names, prefix); + ASSERT_FALSE(tests.empty()); + + for (size_t i = 0; i < tests.size(); ++i) { + Message message; + std::string expected; + ASSERT_TRUE(ReadTestCase(tests[i], &message, &expected)); + + std::string result; + base::RunLoop run_loop; + mojo::internal::ValidationErrorObserverForTesting observer( + run_loop.QuitClosure()); + ignore_result(test_message_receiver->Accept(&message)); + if (expected != "PASS") // Observer only gets called on errors. + run_loop.Run(); + if (observer.last_error() == mojo::internal::VALIDATION_ERROR_NONE) + result = "PASS"; + else + result = mojo::internal::ValidationErrorToString(observer.last_error()); + + EXPECT_EQ(expected, result) << "failed test: " << tests[i]; + } +} + +class DummyMessageReceiver : public MessageReceiver { + public: + bool Accept(Message* message) override { + return true; // Any message is OK. + } +}; + +class ValidationTest : public testing::Test { + public: + ValidationTest() {} + + protected: + base::MessageLoop loop_; +}; + +class ValidationIntegrationTest : public ValidationTest { + public: + ValidationIntegrationTest() : test_message_receiver_(nullptr) {} + + ~ValidationIntegrationTest() override {} + + void SetUp() override { + ScopedMessagePipeHandle tester_endpoint; + ASSERT_EQ(MOJO_RESULT_OK, + CreateMessagePipe(nullptr, &tester_endpoint, &testee_endpoint_)); + test_message_receiver_ = + new TestMessageReceiver(this, std::move(tester_endpoint)); + } + + void TearDown() override { + delete test_message_receiver_; + test_message_receiver_ = nullptr; + + // Make sure that the other end receives the OnConnectionError() + // notification. + PumpMessages(); + } + + MessageReceiver* test_message_receiver() { return test_message_receiver_; } + + ScopedMessagePipeHandle testee_endpoint() { + return std::move(testee_endpoint_); + } + + private: + class TestMessageReceiver : public MessageReceiver { + public: + TestMessageReceiver(ValidationIntegrationTest* owner, + ScopedMessagePipeHandle handle) + : owner_(owner), + connector_(std::move(handle), + mojo::Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()) { + connector_.set_enforce_errors_from_incoming_receiver(false); + } + ~TestMessageReceiver() override {} + + bool Accept(Message* message) override { + return connector_.Accept(message); + } + + public: + ValidationIntegrationTest* owner_; + mojo::Connector connector_; + }; + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + TestMessageReceiver* test_message_receiver_; + ScopedMessagePipeHandle testee_endpoint_; +}; + +class IntegrationTestInterfaceImpl : public IntegrationTestInterface { + public: + ~IntegrationTestInterfaceImpl() override {} + + void Method0(BasicStructPtr param0, + const Method0Callback& callback) override { + callback.Run(std::vector()); + } +}; + +TEST_F(ValidationTest, InputParser) { + { + // The parser, as well as Append() defined above, assumes that this code is + // running on a little-endian platform. Test whether that is true. + uint16_t x = 1; + ASSERT_EQ(1, *(reinterpret_cast(&x))); + } + { + // Test empty input. + std::string input; + std::vector expected; + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + // Test input that only consists of comments and whitespaces. + std::string input = " \t // hello world \n\r \t// the answer is 42 "; + std::vector expected; + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = + "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" + "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff"; + std::vector expected; + Append(&expected, static_cast(0x10)); + Append(&expected, static_cast(65535)); + Append(&expected, static_cast(65536)); + Append(&expected, static_cast(0xffffffffffffffff)); + Append(&expected, static_cast(0)); + Append(&expected, static_cast(0xff)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40"; + std::vector expected; + Append(&expected, -static_cast(0x800)); + Append(&expected, static_cast(-128)); + Append(&expected, static_cast(0)); + Append(&expected, static_cast(-40)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[b]00001011 [b]10000000 // hello world\r [b]00000000"; + std::vector expected; + Append(&expected, static_cast(11)); + Append(&expected, static_cast(128)); + Append(&expected, static_cast(0)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[f]+.3e9 [d]-10.03"; + std::vector expected; + Append(&expected, +.3e9f); + Append(&expected, -10.03); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar"; + std::vector expected; + Append(&expected, static_cast(14)); + Append(&expected, static_cast(0)); + Append(&expected, static_cast(9)); + Append(&expected, static_cast(0)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "// This message has handles! \n[handles]50 [u8]2"; + std::vector expected; + Append(&expected, static_cast(2)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 50)); + } + + // Test some failure cases. + { + const char* error_inputs[] = {"/ hello world", + "[u1]x", + "[u2]-1000", + "[u1]0x100", + "[s2]-0x8001", + "[b]1", + "[b]1111111k", + "[dist4]unmatched", + "[anchr]hello [dist8]hello", + "[dist4]a [dist4]a [anchr]a", + "[dist4]a [anchr]a [dist4]a [anchr]a", + "0 [handles]50", + nullptr}; + + for (size_t i = 0; error_inputs[i]; ++i) { + std::vector expected; + if (!TestInputParser(error_inputs[i], false, expected, 0)) + ADD_FAILURE() << "Unexpected test result for: " << error_inputs[i]; + } + } +} + +TEST_F(ValidationTest, Conformance) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append(); + validators.Append(); + + RunValidationTests("conformance_", &validators); +} + +TEST_F(ValidationTest, AssociatedConformace) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append(); + validators.Append(); + + RunValidationTests("associated_conformance_", &validators); +} + +// This test is similar to Conformance test but its goal is specifically +// do bounds-check testing of message validation. For example we test the +// detection of off-by-one errors in method ordinals. +TEST_F(ValidationTest, BoundsCheck) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append(); + validators.Append(); + + RunValidationTests("boundscheck_", &validators); +} + +// This test is similar to the Conformance test but for responses. +TEST_F(ValidationTest, ResponseConformance) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append(); + validators.Append(); + + RunValidationTests("resp_conformance_", &validators); +} + +// This test is similar to the BoundsCheck test but for responses. +TEST_F(ValidationTest, ResponseBoundsCheck) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append(); + validators.Append(); + + RunValidationTests("resp_boundscheck_", &validators); +} + +// Test that InterfacePtr applies the correct validators and they don't +// conflict with each other: +// - MessageHeaderValidator +// - X::ResponseValidator_ +TEST_F(ValidationIntegrationTest, InterfacePtr) { + IntegrationTestInterfacePtr interface_ptr = MakeProxy( + InterfacePtrInfo(testee_endpoint(), 0u)); + interface_ptr.internal_state()->EnableTestingMode(); + + RunValidationTests("integration_intf_resp", test_message_receiver()); + RunValidationTests("integration_msghdr", test_message_receiver()); +} + +// Test that Binding applies the correct validators and they don't +// conflict with each other: +// - MessageHeaderValidator +// - X::RequestValidator_ +TEST_F(ValidationIntegrationTest, Binding) { + IntegrationTestInterfaceImpl interface_impl; + Binding binding( + &interface_impl, + MakeRequest(testee_endpoint())); + binding.EnableTestingMode(); + + RunValidationTests("integration_intf_rqst", test_message_receiver()); + RunValidationTests("integration_msghdr", test_message_receiver()); +} + +// Test pointer validation (specifically, that the encoded offset is 32-bit) +TEST_F(ValidationTest, ValidateEncodedPointer) { + uint64_t offset; + + offset = 0ULL; + EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset)); + + offset = 1ULL; + EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset)); + + // offset must be <= 32-bit. + offset = std::numeric_limits::max() + 1ULL; + EXPECT_FALSE(mojo::internal::ValidateEncodedPointer(&offset)); +} + +// Tests the IsKnownEnumValue() function generated for BasicEnum. +TEST(EnumValueValidationTest, BasicEnum) { + // BasicEnum can have -3,0,1,10 as possible integral values. + EXPECT_FALSE(IsKnownEnumValue(static_cast(-4))); + EXPECT_TRUE(IsKnownEnumValue(static_cast(-3))); + EXPECT_FALSE(IsKnownEnumValue(static_cast(-2))); + EXPECT_FALSE(IsKnownEnumValue(static_cast(-1))); + EXPECT_TRUE(IsKnownEnumValue(static_cast(0))); + EXPECT_TRUE(IsKnownEnumValue(static_cast(1))); + EXPECT_FALSE(IsKnownEnumValue(static_cast(2))); + EXPECT_FALSE(IsKnownEnumValue(static_cast(9))); + // In the mojom, we represent this value as hex (0xa). + EXPECT_TRUE(IsKnownEnumValue(static_cast(10))); + EXPECT_FALSE(IsKnownEnumValue(static_cast(11))); +} + +// Tests the IsKnownEnumValue() method generated for StructWithEnum. +TEST(EnumValueValidationTest, EnumWithin) { + // StructWithEnum::EnumWithin can have [0,4] as possible integral values. + EXPECT_FALSE(IsKnownEnumValue(static_cast(-1))); + EXPECT_TRUE(IsKnownEnumValue(static_cast(0))); + EXPECT_TRUE(IsKnownEnumValue(static_cast(1))); + EXPECT_TRUE(IsKnownEnumValue(static_cast(2))); + EXPECT_TRUE(IsKnownEnumValue(static_cast(3))); + EXPECT_FALSE(IsKnownEnumValue(static_cast(4))); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/variant_test_util.h b/mojo/public/cpp/bindings/tests/variant_test_util.h new file mode 100644 index 0000000000..3f6b1f1b5f --- /dev/null +++ b/mojo/public/cpp/bindings/tests/variant_test_util.h @@ -0,0 +1,32 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_VARIANT_TEST_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_VARIANT_TEST_UTIL_H_ + +#include + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace mojo { +namespace test { + +// Converts a request of Interface1 to a request of Interface0. Interface0 and +// Interface1 are expected to be two variants of the same mojom interface. +// In real-world use cases, users shouldn't need to worry about this. Because it +// is rare to deal with two variants of the same interface in the same app. +template +InterfaceRequest ConvertInterfaceRequest( + InterfaceRequest request) { + DCHECK_EQ(0, strcmp(Interface0::Name_, Interface1::Name_)); + InterfaceRequest result; + result.Bind(request.PassMessagePipe()); + return result; +} + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_VARIANT_TEST_UTIL_H_ diff --git a/mojo/public/cpp/bindings/tests/versioning_apptest.cc b/mojo/public/cpp/bindings/tests/versioning_apptest.cc new file mode 100644 index 0000000000..95a89c0114 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/versioning_apptest.cc @@ -0,0 +1,123 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/macros.h" +#include "mojo/public/interfaces/bindings/tests/versioning_test_client.mojom.h" +#include "services/service_manager/public/cpp/application_test_base.h" +#include "services/service_manager/public/cpp/connector.h" + +namespace mojo { +namespace test { +namespace versioning { + +class VersioningApplicationTest : public ApplicationTestBase { + public: + VersioningApplicationTest() : ApplicationTestBase() {} + ~VersioningApplicationTest() override {} + + protected: + // ApplicationTestBase overrides. + void SetUp() override { + ApplicationTestBase::SetUp(); + + connector()->BindInterface("versioning_test_service", &database_); + } + + HumanResourceDatabasePtr database_; + + private: + DISALLOW_COPY_AND_ASSIGN(VersioningApplicationTest); +}; + +TEST_F(VersioningApplicationTest, Struct) { + // The service side uses a newer version of Employee defintion. + // The returned struct should be truncated. + EmployeePtr employee(Employee::New()); + employee->employee_id = 1; + employee->name = "Homer Simpson"; + employee->department = DEPARTMENT_DEV; + + database_->QueryEmployee(1, true, + [&employee](EmployeePtr returned_employee, + Array returned_finger_print) { + EXPECT_TRUE(employee->Equals(*returned_employee)); + EXPECT_FALSE(returned_finger_print.is_null()); + }); + database_.WaitForIncomingResponse(); + + // Passing a struct of older version to the service side works. + EmployeePtr new_employee(Employee::New()); + new_employee->employee_id = 2; + new_employee->name = "Marge Simpson"; + new_employee->department = DEPARTMENT_SALES; + + database_->AddEmployee(new_employee.Clone(), + [](bool success) { EXPECT_TRUE(success); }); + database_.WaitForIncomingResponse(); + + database_->QueryEmployee( + 2, false, [&new_employee](EmployeePtr returned_employee, + Array returned_finger_print) { + EXPECT_TRUE(new_employee->Equals(*returned_employee)); + EXPECT_TRUE(returned_finger_print.is_null()); + }); + database_.WaitForIncomingResponse(); +} + +TEST_F(VersioningApplicationTest, QueryVersion) { + EXPECT_EQ(0u, database_.version()); + database_.QueryVersion([](uint32_t version) { EXPECT_EQ(1u, version); }); + database_.WaitForIncomingResponse(); + EXPECT_EQ(1u, database_.version()); +} + +TEST_F(VersioningApplicationTest, RequireVersion) { + EXPECT_EQ(0u, database_.version()); + + database_.RequireVersion(1); + EXPECT_EQ(1u, database_.version()); + database_->QueryEmployee(3, false, + [](EmployeePtr returned_employee, + Array returned_finger_print) {}); + database_.WaitForIncomingResponse(); + EXPECT_FALSE(database_.encountered_error()); + + // Requiring a version higher than what the service side implements will close + // the pipe. + database_.RequireVersion(3); + EXPECT_EQ(3u, database_.version()); + database_->QueryEmployee(1, false, + [](EmployeePtr returned_employee, + Array returned_finger_print) {}); + database_.WaitForIncomingResponse(); + EXPECT_TRUE(database_.encountered_error()); +} + +TEST_F(VersioningApplicationTest, CallNonexistentMethod) { + EXPECT_EQ(0u, database_.version()); + + Array new_finger_print(128); + for (size_t i = 0; i < 128; ++i) + new_finger_print[i] = i + 13; + + // Although the client side doesn't know whether the service side supports + // version 1, calling a version 1 method succeeds as long as the service side + // supports version 1. + database_->AttachFingerPrint(1, new_finger_print.Clone(), + [](bool success) { EXPECT_TRUE(success); }); + database_.WaitForIncomingResponse(); + + // Calling a version 2 method (which the service side doesn't support) closes + // the pipe. + database_->ListEmployeeIds([](Array ids) { EXPECT_TRUE(false); }); + database_.WaitForIncomingResponse(); + EXPECT_TRUE(database_.encountered_error()); +} + +} // namespace versioning +} // namespace examples +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/versioning_test_service.cc b/mojo/public/cpp/bindings/tests/versioning_test_service.cc new file mode 100644 index 0000000000..313a6249e2 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/versioning_test_service.cc @@ -0,0 +1,127 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/interfaces/bindings/tests/versioning_test_service.mojom.h" +#include "services/service_manager/public/c/main.h" +#include "services/service_manager/public/cpp/interface_factory.h" +#include "services/service_manager/public/cpp/service.h" +#include "services/service_manager/public/cpp/service_runner.h" + +namespace mojo { +namespace test { +namespace versioning { + +struct EmployeeInfo { + public: + EmployeeInfo() {} + + EmployeePtr employee; + Array finger_print; + + private: + DISALLOW_COPY_AND_ASSIGN(EmployeeInfo); +}; + +class HumanResourceDatabaseImpl : public HumanResourceDatabase { + public: + explicit HumanResourceDatabaseImpl( + InterfaceRequest request) + : strong_binding_(this, std::move(request)) { + // Pretend that there is already some data in the system. + EmployeeInfo* info = new EmployeeInfo(); + employees_[1] = info; + info->employee = Employee::New(); + info->employee->employee_id = 1; + info->employee->name = "Homer Simpson"; + info->employee->department = DEPARTMENT_DEV; + info->employee->birthday = Date::New(); + info->employee->birthday->year = 1955; + info->employee->birthday->month = 5; + info->employee->birthday->day = 12; + info->finger_print.resize(1024); + for (uint32_t i = 0; i < 1024; ++i) + info->finger_print[i] = i; + } + + ~HumanResourceDatabaseImpl() override { + for (auto iter = employees_.begin(); iter != employees_.end(); ++iter) + delete iter->second; + } + + void AddEmployee(EmployeePtr employee, + const AddEmployeeCallback& callback) override { + uint64_t id = employee->employee_id; + if (employees_.find(id) == employees_.end()) + employees_[id] = new EmployeeInfo(); + employees_[id]->employee = std::move(employee); + callback.Run(true); + } + + void QueryEmployee(uint64_t id, + bool retrieve_finger_print, + const QueryEmployeeCallback& callback) override { + if (employees_.find(id) == employees_.end()) { + callback.Run(nullptr, Array()); + return; + } + callback.Run(employees_[id]->employee.Clone(), + retrieve_finger_print ? employees_[id]->finger_print.Clone() + : Array()); + } + + void AttachFingerPrint(uint64_t id, + Array finger_print, + const AttachFingerPrintCallback& callback) override { + if (employees_.find(id) == employees_.end()) { + callback.Run(false); + return; + } + employees_[id]->finger_print = std::move(finger_print); + callback.Run(true); + } + + private: + std::map employees_; + + StrongBinding strong_binding_; +}; + +class HumanResourceSystemServer + : public service_manager::Service, + public InterfaceFactory { + public: + HumanResourceSystemServer() {} + + // service_manager::Service implementation. + bool OnConnect(Connection* connection) override { + connection->AddInterface(this); + return true; + } + + // InterfaceFactory implementation. + void Create(Connection* connection, + InterfaceRequest request) override { + // It will be deleted automatically when the underlying pipe encounters a + // connection error. + new HumanResourceDatabaseImpl(std::move(request)); + } +}; + +} // namespace versioning +} // namespace test +} // namespace mojo + +MojoResult ServiceMain(MojoHandle request) { + mojo::ServiceRunner runner( + new mojo::test::versioning::HumanResourceSystemServer()); + + return runner.Run(request); +} diff --git a/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc new file mode 100644 index 0000000000..959d25b368 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc @@ -0,0 +1,60 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/wtf_hash_util.h" + +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom-blink.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/wtf/HashFunctions.h" + +namespace mojo { +namespace test { +namespace { + +using WTFHashTest = testing::Test; + +TEST_F(WTFHashTest, NestedStruct) { + // Just check that this template instantiation compiles. + ASSERT_EQ(::mojo::internal::Hash( + ::mojo::internal::kHashSeed, + blink::SimpleNestedStruct::New(blink::ContainsOther::New(1))), + ::mojo::internal::Hash( + ::mojo::internal::kHashSeed, + blink::SimpleNestedStruct::New(blink::ContainsOther::New(1)))); +} + +TEST_F(WTFHashTest, UnmappedNativeStruct) { + // Just check that this template instantiation compiles. + ASSERT_EQ(::mojo::internal::Hash(::mojo::internal::kHashSeed, + blink::UnmappedNativeStruct::New()), + ::mojo::internal::Hash(::mojo::internal::kHashSeed, + blink::UnmappedNativeStruct::New())); +} + +TEST_F(WTFHashTest, Enum) { + // Just check that this template instantiation compiles. + + // Top-level. + ASSERT_EQ(WTF::DefaultHash::Hash().hash( + blink::TopLevelEnum::E0), + WTF::DefaultHash::Hash().hash( + blink::TopLevelEnum::E0)); + + // Nested in struct. + ASSERT_EQ(WTF::DefaultHash::Hash().hash( + blink::TestWTFStruct::NestedEnum::E0), + WTF::DefaultHash::Hash().hash( + blink::TestWTFStruct::NestedEnum::E0)); + + // Nested in interface. + ASSERT_EQ(WTF::DefaultHash::Hash().hash( + blink::TestWTF::NestedEnum::E0), + WTF::DefaultHash::Hash().hash( + blink::TestWTF::NestedEnum::E0)); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc new file mode 100644 index 0000000000..dc40143168 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc @@ -0,0 +1,41 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/rect_blink.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom-blink.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +TEST(WTFMapTest, StructKey) { + WTF::HashMap map; + map.insert(blink::Rect::New(1, 2, 3, 4), 123); + + blink::RectPtr key = blink::Rect::New(1, 2, 3, 4); + ASSERT_NE(map.end(), map.find(key)); + ASSERT_EQ(123, map.find(key)->value); + + map.erase(key); + ASSERT_EQ(0u, map.size()); +} + +TEST(WTFMapTest, TypemappedStructKey) { + WTF::HashMap map; + map.insert(blink::ContainsHashable::New(RectBlink(1, 2, 3, 4)), 123); + + blink::ContainsHashablePtr key = + blink::ContainsHashable::New(RectBlink(1, 2, 3, 4)); + ASSERT_NE(map.end(), map.find(key)); + ASSERT_EQ(123, map.find(key)->value); + + map.erase(key); + ASSERT_EQ(0u, map.size()); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc new file mode 100644 index 0000000000..363ef7cdab --- /dev/null +++ b/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc @@ -0,0 +1,245 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/wtf_serialization.h" +#include "mojo/public/cpp/bindings/tests/variant_test_util.h" +#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/wtf/text/StringHash.h" + +namespace mojo { +namespace test { +namespace { + +const char kHelloWorld[] = "hello world"; + +// Replace the "o"s in "hello world" with "o"s with acute. +const char kUTF8HelloWorld[] = "hell\xC3\xB3 w\xC3\xB3rld"; + +class TestWTFImpl : public TestWTF { + public: + explicit TestWTFImpl(TestWTFRequest request) + : binding_(this, std::move(request)) {} + + // mojo::test::TestWTF implementation: + void EchoString(const base::Optional& str, + const EchoStringCallback& callback) override { + callback.Run(str); + } + + void EchoStringArray( + const base::Optional>>& arr, + const EchoStringArrayCallback& callback) override { + callback.Run(std::move(arr)); + } + + void EchoStringMap( + const base::Optional< + std::unordered_map>>& + str_map, + const EchoStringMapCallback& callback) override { + callback.Run(std::move(str_map)); + } + + private: + Binding binding_; +}; + +class WTFTypesTest : public testing::Test { + public: + WTFTypesTest() {} + + private: + base::MessageLoop loop_; +}; + +WTF::Vector ConstructStringArray() { + WTF::Vector strs(4); + // strs[0] is null. + // strs[1] is empty. + strs[1] = ""; + strs[2] = kHelloWorld; + strs[3] = WTF::String::fromUTF8(kUTF8HelloWorld); + + return strs; +} + +WTF::HashMap ConstructStringMap() { + WTF::HashMap str_map; + // A null string as value. + str_map.insert("0", WTF::String()); + str_map.insert("1", kHelloWorld); + str_map.insert("2", WTF::String::fromUTF8(kUTF8HelloWorld)); + + return str_map; +} + +void ExpectString(const WTF::String& expected_string, + const base::Closure& closure, + const WTF::String& string) { + EXPECT_EQ(expected_string, string); + closure.Run(); +} + +void ExpectStringArray(WTF::Optional>* expected_arr, + const base::Closure& closure, + const WTF::Optional>& arr) { + EXPECT_EQ(*expected_arr, arr); + closure.Run(); +} + +void ExpectStringMap( + WTF::Optional>* expected_map, + const base::Closure& closure, + const WTF::Optional>& map) { + EXPECT_EQ(*expected_map, map); + closure.Run(); +} + +} // namespace + +TEST_F(WTFTypesTest, Serialization_WTFVectorToWTFVector) { + using MojomType = ArrayDataView; + + WTF::Vector strs = ConstructStringArray(); + auto cloned_strs = strs; + + mojo::internal::SerializationContext context; + size_t size = + mojo::internal::PrepareToSerialize(cloned_strs, &context); + + mojo::internal::FixedBufferForTesting buf(size); + typename mojo::internal::MojomTypeTraits::Data* data; + mojo::internal::ContainerValidateParams validate_params( + 0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr)); + mojo::internal::Serialize(cloned_strs, &buf, &data, + &validate_params, &context); + + WTF::Vector strs2; + mojo::internal::Deserialize(data, &strs2, &context); + + EXPECT_EQ(strs, strs2); +} + +TEST_F(WTFTypesTest, Serialization_WTFVectorToStlVector) { + using MojomType = ArrayDataView; + + WTF::Vector strs = ConstructStringArray(); + auto cloned_strs = strs; + + mojo::internal::SerializationContext context; + size_t size = + mojo::internal::PrepareToSerialize(cloned_strs, &context); + + mojo::internal::FixedBufferForTesting buf(size); + typename mojo::internal::MojomTypeTraits::Data* data; + mojo::internal::ContainerValidateParams validate_params( + 0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr)); + mojo::internal::Serialize(cloned_strs, &buf, &data, + &validate_params, &context); + + std::vector> strs2; + mojo::internal::Deserialize(data, &strs2, &context); + + ASSERT_EQ(4u, strs2.size()); + EXPECT_FALSE(strs2[0]); + EXPECT_EQ("", *strs2[1]); + EXPECT_EQ(kHelloWorld, *strs2[2]); + EXPECT_EQ(kUTF8HelloWorld, *strs2[3]); +} + +TEST_F(WTFTypesTest, Serialization_PublicAPI) { + blink::TestWTFStructPtr input(blink::TestWTFStruct::New(kHelloWorld, 42)); + + blink::TestWTFStructPtr cloned_input = input.Clone(); + + auto data = blink::TestWTFStruct::Serialize(&input); + + blink::TestWTFStructPtr output; + ASSERT_TRUE(blink::TestWTFStruct::Deserialize(std::move(data), &output)); + EXPECT_TRUE(cloned_input.Equals(output)); +} + +TEST_F(WTFTypesTest, SendString) { + blink::TestWTFPtr ptr; + TestWTFImpl impl(ConvertInterfaceRequest(MakeRequest(&ptr))); + + WTF::Vector strs = ConstructStringArray(); + + for (size_t i = 0; i < strs.size(); ++i) { + base::RunLoop loop; + // Test that a WTF::String is unchanged after the following conversion: + // - serialized; + // - deserialized as base::Optional; + // - serialized; + // - deserialized as WTF::String. + ptr->EchoString(strs[i], + base::Bind(&ExpectString, strs[i], loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(WTFTypesTest, SendStringArray) { + blink::TestWTFPtr ptr; + TestWTFImpl impl(ConvertInterfaceRequest(MakeRequest(&ptr))); + + WTF::Optional> arrs[3]; + // arrs[0] is empty. + arrs[0].emplace(); + // arrs[1] is null. + arrs[2] = ConstructStringArray(); + + for (size_t i = 0; i < arraysize(arrs); ++i) { + base::RunLoop loop; + // Test that a WTF::Optional> is unchanged after + // the following conversion: + // - serialized; + // - deserialized as + // base::Optional>>; + // - serialized; + // - deserialized as WTF::Optional>. + ptr->EchoStringArray( + arrs[i], base::Bind(&ExpectStringArray, base::Unretained(&arrs[i]), + loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(WTFTypesTest, SendStringMap) { + blink::TestWTFPtr ptr; + TestWTFImpl impl(ConvertInterfaceRequest(MakeRequest(&ptr))); + + WTF::Optional> maps[3]; + // maps[0] is empty. + maps[0].emplace(); + // maps[1] is null. + maps[2] = ConstructStringMap(); + + for (size_t i = 0; i < arraysize(maps); ++i) { + base::RunLoop loop; + // Test that a WTF::Optional> is + // unchanged after the following conversion: + // - serialized; + // - deserialized as base::Optional< + // std::unordered_map>>; + // - serialized; + // - deserialized as WTF::Optional>. + ptr->EchoStringMap(maps[i], + base::Bind(&ExpectStringMap, base::Unretained(&maps[i]), + loop.QuitClosure())); + loop.Run(); + } +} + +} // namespace test +} // namespace mojo -- cgit v1.2.3