summaryrefslogtreecommitdiff
path: root/mojo/public/cpp/bindings/tests
diff options
context:
space:
mode:
authorHidehiko Abe <hidehiko@google.com>2018-04-23 20:01:13 -0700
committerandroid-build-merger <android-build-merger@google.com>2018-04-23 20:01:13 -0700
commited7128dca79cff94e99465e3c1bc31d91d83c76d (patch)
treebd2d04362f66c36d4279f7a9735ba21ea3a2a021 /mojo/public/cpp/bindings/tests
parentd6187ab7d79d95d101c2ecb83aa98c05bcdcccd1 (diff)
parent0ab20ac2283987e63b0e7c1318db2a5cf7c668d2 (diff)
downloadlibchrome-ed7128dca79cff94e99465e3c1bc31d91d83c76d.tar.gz
Migrate libmojo repository into libchrome, part 2. am: b268b43ac6
am: 0ab20ac228 Change-Id: I1b1afe0c902f1d122b100f5bf56d1df4a94eb090
Diffstat (limited to 'mojo/public/cpp/bindings/tests')
-rw-r--r--mojo/public/cpp/bindings/tests/BUILD.gn146
-rw-r--r--mojo/public/cpp/bindings/tests/associated_interface_unittest.cc1189
-rw-r--r--mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc395
-rw-r--r--mojo/public/cpp/bindings/tests/binding_callback_unittest.cc338
-rw-r--r--mojo/public/cpp/bindings/tests/binding_set_unittest.cc416
-rw-r--r--mojo/public/cpp/bindings/tests/binding_unittest.cc611
-rw-r--r--mojo/public/cpp/bindings/tests/bindings_perftest.cc286
-rw-r--r--mojo/public/cpp/bindings/tests/blink_typemaps.gni8
-rw-r--r--mojo/public/cpp/bindings/tests/buffer_unittest.cc93
-rw-r--r--mojo/public/cpp/bindings/tests/chromium_typemaps.gni9
-rw-r--r--mojo/public/cpp/bindings/tests/connector_unittest.cc599
-rw-r--r--mojo/public/cpp/bindings/tests/constant_unittest.cc60
-rw-r--r--mojo/public/cpp/bindings/tests/container_test_util.cc52
-rw-r--r--mojo/public/cpp/bindings/tests/container_test_util.h55
-rw-r--r--mojo/public/cpp/bindings/tests/data_view_unittest.cc303
-rw-r--r--mojo/public/cpp/bindings/tests/e2e_perftest.cc204
-rw-r--r--mojo/public/cpp/bindings/tests/equals_unittest.cc122
-rw-r--r--mojo/public/cpp/bindings/tests/handle_passing_unittest.cc356
-rw-r--r--mojo/public/cpp/bindings/tests/hash_unittest.cc35
-rw-r--r--mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc937
-rw-r--r--mojo/public/cpp/bindings/tests/map_unittest.cc46
-rw-r--r--mojo/public/cpp/bindings/tests/message_queue.cc39
-rw-r--r--mojo/public/cpp/bindings/tests/message_queue.h44
-rw-r--r--mojo/public/cpp/bindings/tests/mojo_test_blink_export.h29
-rw-r--r--mojo/public/cpp/bindings/tests/mojo_test_export.h29
-rw-r--r--mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc314
-rw-r--r--mojo/public/cpp/bindings/tests/pickle_unittest.cc403
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_blink.cc67
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_blink.h88
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_chromium.cc69
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_chromium.h81
-rw-r--r--mojo/public/cpp/bindings/tests/rect_blink.h83
-rw-r--r--mojo/public/cpp/bindings/tests/rect_blink.typemap18
-rw-r--r--mojo/public/cpp/bindings/tests/rect_blink_traits.h35
-rw-r--r--mojo/public/cpp/bindings/tests/rect_chromium.h87
-rw-r--r--mojo/public/cpp/bindings/tests/rect_chromium.typemap18
-rw-r--r--mojo/public/cpp/bindings/tests/rect_chromium_traits.h34
-rw-r--r--mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc194
-rw-r--r--mojo/public/cpp/bindings/tests/request_response_unittest.cc157
-rw-r--r--mojo/public/cpp/bindings/tests/router_test_util.cc111
-rw-r--r--mojo/public/cpp/bindings/tests/router_test_util.h92
-rw-r--r--mojo/public/cpp/bindings/tests/sample_service_unittest.cc362
-rw-r--r--mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc251
-rw-r--r--mojo/public/cpp/bindings/tests/shared_rect.h43
-rw-r--r--mojo/public/cpp/bindings/tests/shared_rect_traits.h33
-rw-r--r--mojo/public/cpp/bindings/tests/struct_traits_unittest.cc553
-rw-r--r--mojo/public/cpp/bindings/tests/struct_unittest.cc526
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits.typemap26
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc36
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl.h168
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc137
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h196
-rw-r--r--mojo/public/cpp/bindings/tests/sync_method_unittest.cc831
-rw-r--r--mojo/public/cpp/bindings/tests/test_native_types_blink.typemap17
-rw-r--r--mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap17
-rw-r--r--mojo/public/cpp/bindings/tests/type_conversion_unittest.cc161
-rw-r--r--mojo/public/cpp/bindings/tests/union_unittest.cc1246
-rw-r--r--mojo/public/cpp/bindings/tests/validation_context_unittest.cc297
-rw-r--r--mojo/public/cpp/bindings/tests/validation_test_input_parser.cc412
-rw-r--r--mojo/public/cpp/bindings/tests/validation_test_input_parser.h121
-rw-r--r--mojo/public/cpp/bindings/tests/validation_unittest.cc498
-rw-r--r--mojo/public/cpp/bindings/tests/variant_test_util.h32
-rw-r--r--mojo/public/cpp/bindings/tests/versioning_apptest.cc123
-rw-r--r--mojo/public/cpp/bindings/tests/versioning_test_service.cc127
-rw-r--r--mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc60
-rw-r--r--mojo/public/cpp/bindings/tests/wtf_map_unittest.cc41
-rw-r--r--mojo/public/cpp/bindings/tests/wtf_types_unittest.cc245
67 files changed, 14811 insertions, 0 deletions
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 <stddef.h>
+#include <stdint.h>
+#include <algorithm>
+#include <utility>
+
+#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<IntegerSender> request)
+ : binding_(this, std::move(request)) {}
+
+ ~IntegerSenderImpl() override {}
+
+ void set_notify_send_method_called(
+ const base::Callback<void(int32_t)>& 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<IntegerSender>* binding() { return &binding_; }
+
+ void set_connection_error_handler(const base::Closure& handler) {
+ binding_.set_connection_error_handler(handler);
+ }
+
+ private:
+ AssociatedBinding<IntegerSender> binding_;
+ base::Callback<void(int32_t)> notify_send_method_called_;
+};
+
+class IntegerSenderConnectionImpl : public IntegerSenderConnection {
+ public:
+ explicit IntegerSenderConnectionImpl(
+ InterfaceRequest<IntegerSenderConnection> request)
+ : binding_(this, std::move(request)) {}
+
+ ~IntegerSenderConnectionImpl() override {}
+
+ void GetSender(AssociatedInterfaceRequest<IntegerSender> 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<IntegerSenderConnection>* binding() { return &binding_; }
+
+ private:
+ static void DeleteSender(IntegerSenderImpl* sender) { delete sender; }
+
+ Binding<IntegerSenderConnection> binding_;
+};
+
+class AssociatedInterfaceTest : public testing::Test {
+ public:
+ AssociatedInterfaceTest() {}
+ ~AssociatedInterfaceTest() override { base::RunLoop().RunUntilIdle(); }
+
+ void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+
+ template <typename T>
+ AssociatedInterfacePtrInfo<T> EmulatePassingAssociatedPtrInfo(
+ AssociatedInterfacePtrInfo<T> ptr_info,
+ scoped_refptr<MultiplexRouter> source,
+ scoped_refptr<MultiplexRouter> target) {
+ ScopedInterfaceEndpointHandle handle = ptr_info.PassHandle();
+ CHECK(handle.pending_association());
+ auto id = source->AssociateInterface(std::move(handle));
+ return AssociatedInterfacePtrInfo<T>(target->CreateLocalEndpointHandle(id),
+ ptr_info.version());
+ }
+
+ void CreateRouterPair(scoped_refptr<MultiplexRouter>* router0,
+ scoped_refptr<MultiplexRouter>* 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<MultiplexRouter> router0,
+ IntegerSenderAssociatedPtrInfo* ptr_info0,
+ scoped_refptr<MultiplexRouter> 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<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> 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<void(int32_t)> 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<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> router1;
+ CreateRouterPair(&router0, &router1);
+
+ AssociatedInterfaceRequest<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSenderWithExistingRouters(router1, &ptr_info, router0, &request);
+
+ IntegerSenderImpl impl0(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> ptr0;
+ ptr0.Bind(std::move(ptr_info));
+
+ CreateIntegerSenderWithExistingRouters(router0, &ptr_info, router1, &request);
+
+ IntegerSenderImpl impl1(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> 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<IntegerSender> ptr_;
+};
+
+class TestReceiver {
+ public:
+ TestReceiver() : receiver_thread_("TestReceiver"), expected_calls_(0) {
+ receiver_thread_.Start();
+ }
+
+ void SetUp(AssociatedInterfaceRequest<IntegerSender> request0,
+ AssociatedInterfaceRequest<IntegerSender> 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<int32_t>& 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<IntegerSenderImpl> impl0_;
+ std::unique_ptr<IntegerSenderImpl> impl1_;
+
+ std::vector<int32_t> 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<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> router1;
+ CreateRouterPair(&router0, &router1);
+
+ AssociatedInterfaceRequest<IntegerSender> 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<size_t>(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<size_t>(kMaxValue / 2), receivers[0].values().size());
+ EXPECT_EQ(static_cast<size_t>(kMaxValue / 2), receivers[1].values().size());
+
+ std::vector<int32_t> 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<int32_t>(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<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> router1;
+ CreateRouterPair(&router0, &router1);
+
+ AssociatedInterfaceRequest<IntegerSender> 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<size_t>(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<size_t>(kMaxValue / 2), receivers[0].values().size());
+ EXPECT_EQ(static_cast<size_t>(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<PingService>& 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<PingService> 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<std::unique_ptr<PingServiceImpl>>& 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<AssociatedPingProvider> binding_;
+ std::vector<std::unique_ptr<PingServiceImpl>> 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<CallbackFilter> Wrap(const base::Closure& callback) {
+ return base::MakeUnique<CallbackFilter>(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<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ IntegerSenderImpl impl0(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> 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 <typename T>
+void SetBoolWithUnusedParameter(bool* value, T unused) {
+ *value = true;
+}
+
+TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTestingWithClosedPeer) {
+ AssociatedInterfaceRequest<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ AssociatedInterfacePtr<IntegerSender> 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<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ IntegerSenderImpl impl0(std::move(request));
+ impl0.set_connection_error_handler(base::Bind(&Fail));
+ AssociatedInterfacePtr<IntegerSender> 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<MultiplexRouter> router0;
+ scoped_refptr<MultiplexRouter> router1;
+ CreateRouterPair(&router0, &router1);
+
+ AssociatedInterfaceRequest<IntegerSender> 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<IntegerSenderAssociatedPtrInfo>, &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<IntegerSenderConnectionImpl>(
+ IntegerSenderConnectionRequest{}),
+ MakeRequest(&ptr));
+ bool called = false;
+ IntegerSenderAssociatedPtr sender_ptr;
+ ptr->GetSender(MakeRequest(&sender_ptr));
+ sender_ptr->Echo(1, base::Bind(&SetBoolWithUnusedParameter<int>, &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<IntegerSenderConnectionImpl>(
+ 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<IntegerSenderAssociatedPtrInfo>, &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<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ IntegerSenderImpl impl(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> 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<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ IntegerSenderImpl impl(std::move(request));
+ AssociatedInterfacePtr<IntegerSender> 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<IntegerSender> request;
+ IntegerSenderAssociatedPtrInfo ptr_info;
+ CreateIntegerSender(&ptr_info, &request);
+
+ AssociatedInterfacePtr<IntegerSender> 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<ThreadSafeIntegerSenderAssociatedPtr> 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<base::TaskRunner>& main_task_runner,
+ const base::Closure& quit_closure,
+ const scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr>&
+ thread_safe_sender) {
+ auto done_callback = base::Bind(
+ [](const scoped_refptr<base::TaskRunner>& 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<IntegerSenderConnectionImpl> 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<base::SingleThreadTaskRunner>& 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<IntegerSenderConnectionImpl>(
+ 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<ThreadSafeIntegerSenderAssociatedPtr> 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<AssociatedPingProviderProvider> 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 <utility>
+
+#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<base::OnceClosure> tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTaskRunner);
+};
+
+template <typename BindingType, typename RequestType>
+class IntegerSenderImpl : public IntegerSender {
+ public:
+ IntegerSenderImpl(RequestType request,
+ scoped_refptr<base::SingleThreadTaskRunner> runner)
+ : binding_(this, std::move(request), std::move(runner)) {}
+
+ ~IntegerSenderImpl() override {}
+
+ using EchoHandler = base::Callback<void(int32_t, const EchoCallback&)>;
+
+ 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<AssociatedBinding<IntegerSender>,
+ IntegerSenderAssociatedRequest>;
+
+ explicit IntegerSenderConnectionImpl(
+ IntegerSenderConnectionRequest request,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ scoped_refptr<base::SingleThreadTaskRunner> 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<IntegerSenderConnection>* binding() { return &binding_; }
+
+ SenderType* sender_impl() { return sender_impl_.get(); }
+
+ private:
+ Binding<IntegerSenderConnection> binding_;
+ std::unique_ptr<SenderType> sender_impl_;
+ scoped_refptr<base::SingleThreadTaskRunner> sender_runner_;
+ base::Closure get_sender_notification_;
+};
+
+class BindTaskRunnerTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ binding_task_runner_ = scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+ ptr_task_runner_ = scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+
+ auto request = MakeRequest(&ptr_, ptr_task_runner_);
+ impl_.reset(new ImplType(std::move(request), binding_task_runner_));
+ }
+
+ base::MessageLoop loop_;
+ scoped_refptr<TestTaskRunner> binding_task_runner_;
+ scoped_refptr<TestTaskRunner> ptr_task_runner_;
+
+ IntegerSenderPtr ptr_;
+ using ImplType =
+ IntegerSenderImpl<Binding<IntegerSender>, IntegerSenderRequest>;
+ std::unique_ptr<ImplType> impl_;
+};
+
+class AssociatedBindTaskRunnerTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ connection_binding_task_runner_ =
+ scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+ connection_ptr_task_runner_ =
+ scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+ sender_binding_task_runner_ =
+ scoped_refptr<TestTaskRunner>(new TestTaskRunner);
+ sender_ptr_task_runner_ = scoped_refptr<TestTaskRunner>(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<TestTaskRunner> connection_binding_task_runner_;
+ scoped_refptr<TestTaskRunner> connection_ptr_task_runner_;
+ scoped_refptr<TestTaskRunner> sender_binding_task_runner_;
+ scoped_refptr<TestTaskRunner> sender_ptr_task_runner_;
+
+ IntegerSenderConnectionPtr connection_ptr_;
+ std::unique_ptr<IntegerSenderConnectionImpl> connection_impl_;
+ IntegerSenderAssociatedPtr sender_ptr_;
+};
+
+void DoSetFlagAndQuitTaskRunner(bool* flag,
+ scoped_refptr<TestTaskRunner> task_runner) {
+ *flag = true;
+ if (task_runner)
+ task_runner->Quit();
+}
+
+void DoExpectValueSetFlagAndQuitTaskRunner(
+ int32_t expected_value,
+ bool* flag,
+ scoped_refptr<TestTaskRunner> task_runner,
+ int32_t value) {
+ EXPECT_EQ(expected_value, value);
+ DoSetFlagAndQuitTaskRunner(flag, task_runner);
+}
+
+void DoExpectValueSetFlagForwardValueAndQuitTaskRunner(
+ int32_t expected_value,
+ bool* flag,
+ scoped_refptr<TestTaskRunner> 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<TestTaskRunner> task_runner) {
+ return base::Bind(&DoSetFlagAndQuitTaskRunner, flag, task_runner);
+}
+
+base::Callback<void(int32_t)> ExpectValueSetFlagAndQuitTaskRunner(
+ int32_t expected_value,
+ bool* flag,
+ scoped_refptr<TestTaskRunner> 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 <stdint.h>
+
+#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<void(int32_t)> 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<sample::Provider> 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<sample::Provider> 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<sample::Provider> 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<sample::Provider> 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<sample::Provider> 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 <memory>
+#include <utility>
+
+#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 <typename BindingSetType, typename ContextType>
+void ExpectContextHelper(BindingSetType* binding_set,
+ ContextType expected_context) {
+ EXPECT_EQ(expected_context, binding_set->dispatch_context());
+}
+
+template <typename BindingSetType, typename ContextType>
+base::Closure ExpectContext(BindingSetType* binding_set,
+ ContextType expected_context) {
+ return base::Bind(
+ &ExpectContextHelper<BindingSetType, ContextType>, 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<PingService, int> 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<PingService> 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<PingService, int>& 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<PingService, int> 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<AssociatedPingProvider> 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<AssociatedPingProvider, int> 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<PingService, int> 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<AssociatedPingProvider> 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<StrongBindingSet<PingService>>();
+
+ bindings->AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ mojo::MakeRequest(&ping_a));
+ EXPECT_EQ(1, PingInstanceCounter::instance_count);
+
+ bindings->AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ 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<PingService> bindings;
+ bindings.AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ mojo::MakeRequest(&ping_a));
+ bindings.AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ 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<PingService> bindings;
+ BindingId binding_id_a = bindings.AddBinding(
+ base::MakeUnique<PingInstanceCounter>(), mojo::MakeRequest(&ping_a));
+ BindingId binding_id_b = bindings.AddBinding(
+ base::MakeUnique<PingInstanceCounter>(), 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 <stdint.h>
+#include <utility>
+
+#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<sample::Port> port) override {}
+
+ bool* const was_deleted_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServiceImpl);
+};
+
+template <typename... Args>
+void DoSetFlagAndRunClosure(bool* flag,
+ const base::Closure& closure,
+ Args... args) {
+ *flag = true;
+ if (!closure.is_null())
+ closure.Run();
+}
+
+template <typename... Args>
+base::Callback<void(Args...)> SetFlagAndRunClosure(
+ bool* flag,
+ const base::Closure& callback = base::Closure()) {
+ return base::Bind(&DoSetFlagAndRunClosure<Args...>, 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<sample::Service> 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<sample::Service> binding(&impl, std::move(request));
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&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<int32_t>(&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<sample::Service> 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<sample::Service> 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<sample::Service> 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<sample::Service> 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<sample::Service> binding(&impl, MakeRequest(&ptr));
+
+ bool called = false;
+ base::RunLoop run_loop;
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&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<int32_t>(&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<int32_t>(&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<sample::IntegerAccessor> 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<sample::Service> binding(&impl, std::move(request));
+ binding.PauseIncomingMethodCallProcessing();
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&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<sample::Service> 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<CallbackFilter> Wrap(const base::Closure& callback) {
+ return base::MakeUnique<CallbackFilter>(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<test::PingService> 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<sample::Service> binding(&impl, std::move(request));
+ binding.set_connection_error_handler(base::Bind(&Fail));
+
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&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<sample::Service> 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<sample::Service> 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 <typename T>
+struct WeakPtrImplRefTraits {
+ using PointerType = base::WeakPtr<T>;
+
+ static bool IsNull(const base::WeakPtr<T>& ptr) { return !ptr; }
+ static T* GetRawPointer(base::WeakPtr<T>* ptr) { return ptr->get(); }
+};
+
+template <typename T>
+using WeakBinding = Binding<T, WeakPtrImplRefTraits<T>>;
+
+TEST_F(BindingTest, CustomImplPointerType) {
+ PingServiceImpl impl;
+ base::WeakPtrFactory<test::PingService> weak_factory(&impl);
+
+ test::PingServicePtr proxy;
+ WeakBinding<test::PingService> 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<ServiceImpl>(&was_deleted),
+ std::move(request));
+ ptr->Frobinate(
+ nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&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<ServiceImpl>(&was_deleted),
+ std::move(request));
+ binding->set_connection_error_handler(base::Bind(&Fail));
+
+ ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr,
+ SetFlagAndRunClosure<int32_t>(&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<ServiceImpl>(&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<ServiceImpl>(), 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 <stddef.h>
+#include <utility>
+
+#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<test::PingService> binding;
+};
+
+class MojoBindingsPerftest : public testing::Test {
+ public:
+ MojoBindingsPerftest() {}
+
+ protected:
+ base::MessageLoop loop_;
+};
+
+TEST_F(MojoBindingsPerftest, InProcessPingPong) {
+ test::PingServicePtr service;
+ PingServiceImpl impl;
+ Binding<test::PingService> 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<MessageReceiverWithStatus> 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<internal::MultiplexRouter> router0(
+ new internal::MultiplexRouter(std::move(pipe.handle0),
+ internal::MultiplexRouter::SINGLE_INTERFACE,
+ true, base::ThreadTaskRunnerHandle::Get()));
+ scoped_refptr<internal::MultiplexRouter> 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<MessageReceiverWithStatus> 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<internal::MultiplexRouter> 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 <stddef.h>
+
+#include <limits>
+
+#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<char*>(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<ptrdiff_t>(a) % 8);
+
+ void* b = buf.Allocate(10);
+ ASSERT_TRUE(b);
+ EXPECT_TRUE(IsZero(b, 10));
+ EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(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<void*>(0), buf.Allocate(32));
+
+ // Move the cursor forward.
+ EXPECT_NE(reinterpret_cast<void*>(0), buf.Allocate(16));
+
+ // A lot too large.
+ EXPECT_EQ(reinterpret_cast<void*>(0),
+ buf.Allocate(std::numeric_limits<size_t>::max() - 1024u));
+
+ // A lot too large, leading to possible integer overflow.
+ EXPECT_EQ(reinterpret_cast<void*>(0),
+ buf.Allocate(std::numeric_limits<size_t>::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 <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <utility>
+
+#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<const char*>(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<const char*>(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<const char*>(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<const char*>(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<const char*>(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<const char*>(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<const char*>(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<const char*>(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<const char*>(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<const char*>(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> 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) { 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 <cmath>
+
+#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 <stddef.h>
+
+#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 <stddef.h>
+
+#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 <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#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<TestStructDataView> data_view;
+ std::unique_ptr<mojo::internal::FixedBufferForTesting> buf;
+ mojo::internal::SerializationContext context;
+};
+
+std::unique_ptr<DataViewHolder> SerializeTestStruct(TestStructPtr input) {
+ std::unique_ptr<DataViewHolder> result(new DataViewHolder);
+
+ size_t size = mojo::internal::PrepareToSerialize<TestStructDataView>(
+ input, &result->context);
+
+ result->buf.reset(new mojo::internal::FixedBufferForTesting(size));
+ internal::TestStruct_Data* data = nullptr;
+ mojo::internal::Serialize<TestStructDataView>(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<TestInterface> 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<uint8_t>({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<bool> 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<int32_t> 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<TestEnum> 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<TestInterfacePtrDataView> 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<TestInterfacePtr>(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<ArrayDataView<int32_t>> array_data_view;
+ data_view.GetFNestedArrayDataView(&array_data_view);
+
+ ASSERT_FALSE(array_data_view.is_null());
+ ASSERT_EQ(2u, array_data_view.size());
+
+ ArrayDataView<int32_t> 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<int32_t> 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<NestedStructDataView> 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<StringDataView, int32_t> 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<std::string> keys;
+ ASSERT_TRUE(map_data_view.ReadKeys(&keys));
+ std::vector<int32_t> values;
+ ASSERT_TRUE(map_data_view.ReadValues(&values));
+
+ std::unordered_map<std::string, int32_t> 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<TestUnionDataView> 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 <string>
+#include <utility>
+
+#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<void(const std::string&)> 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<test::EchoService>(
+ 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<test::EchoService> request,
+ const base::Closure& cb) {
+ MakeStrongBinding(base::MakeUnique<EchoServiceImpl>(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<test::EchoService> 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 <utility>
+
+#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<RectPtr> 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<SomeInterface> req1;
+ InterfaceRequest<SomeInterface> 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 <stdint.h>
+#include <utility>
+
+#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<void(const std::string&)> MakeStringRecorder(
+ std::string* storage,
+ const base::Closure& closure) {
+ return base::Bind(&RecordString, storage, closure);
+}
+
+class ImportedInterfaceImpl : public imported::ImportedInterface {
+ public:
+ ImportedInterfaceImpl(
+ InterfaceRequest<imported::ImportedInterface> 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<ImportedInterface> 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<sample::Factory> 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<int>(data_size));
+ char data[64];
+ ASSERT_LT(static_cast<int>(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<sample::NamedObject> object_request) override {
+ EXPECT_TRUE(object_request.is_pending());
+ MakeStrongBinding(base::MakeUnique<SampleNamedObjectImpl>(),
+ 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::ImportedInterface> imported,
+ const RequestImportedInterfaceCallback& callback) override {}
+ void TakeImportedInterface(
+ imported::ImportedInterfacePtr imported,
+ const TakeImportedInterfaceCallback& callback) override {}
+
+ private:
+ ScopedMessagePipeHandle pipe1_;
+ Binding<sample::Factory> 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<uint32_t>(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<ScopedMessagePipeHandle> 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<sample::NamedObject> 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 <stdint.h>
+#include <utility>
+
+#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<void(double)> CalcCallback;
+
+class MathCalculatorImpl : public math::Calculator {
+ public:
+ explicit MathCalculatorImpl(InterfaceRequest<math::Calculator> 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<math::Calculator>* binding() { return &binding_; }
+
+ private:
+ double total_;
+ Binding<math::Calculator> 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<sample::Service> 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<sample::Port> port) override {}
+
+ private:
+ int call_depth_;
+ int max_call_depth_;
+ Binding<sample::Service> 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<math::Calculator>(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<math::Calculator>());
+ 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<sample::IntegerAccessor> 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<sample::IntegerAccessor> 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<StrongMathCalculatorImpl>(&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<math::Calculator> 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<math::Calculator>(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> c) override {
+ MakeStrongBinding(base::MakeUnique<CImpl>(d_called_, closure_),
+ std::move(c));
+ }
+
+ bool* d_called_;
+ base::Closure closure_;
+};
+
+class AImpl : public A {
+ public:
+ AImpl(InterfaceRequest<A> 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> b) override {
+ MakeStrongBinding(base::MakeUnique<BImpl>(&d_called_, closure_),
+ std::move(b));
+ }
+
+ bool d_called_;
+ Binding<A> 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<sample::PingTest> request)
+ : binding_(this, std::move(request)) {}
+ ~PingTestImpl() override {}
+
+ private:
+ // sample::PingTest:
+ void Ping(const PingCallback& callback) override { callback.Run(); }
+
+ Binding<sample::PingTest> 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<math::ThreadSafeCalculatorPtr> 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<base::TaskRunner>& main_task_runner,
+ const base::Closure& quit_closure,
+ const scoped_refptr<math::ThreadSafeCalculatorPtr>& thread_safe_ptr) {
+ auto calc_callback = base::Bind(
+ [](const scoped_refptr<base::TaskRunner>& 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<base::SingleThreadTaskRunner>& 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<math::ThreadSafeCalculatorPtr> 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<base::TaskRunner>& main_task_runner,
+ const base::Closure& quit_closure,
+ const scoped_refptr<math::ThreadSafeCalculatorPtr>& 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 <stddef.h>
+#include <stdint.h>
+#include <unordered_map>
+#include <utility>
+
+#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<RectPtr, int32_t> 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<ContainsHashablePtr, int32_t> 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 <queue>
+
+#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<Message> 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 <utility>
+
+#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<MultiplexRouter> router0_;
+ scoped_refptr<MultiplexRouter> router1_;
+ ScopedInterfaceEndpointHandle endpoint0_;
+ ScopedInterfaceEndpointHandle endpoint1_;
+
+ private:
+ base::MessageLoop loop_;
+};
+
+TEST_F(MultiplexRouterTest, BasicRequestResponse) {
+ InterfaceEndpointClient client0(std::move(endpoint0_), nullptr,
+ base::MakeUnique<PassThroughFilter>(), false,
+ base::ThreadTaskRunnerHandle::Get(), 0u);
+ ResponseGenerator generator;
+ InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+ base::MakeUnique<PassThroughFilter>(), false,
+ base::ThreadTaskRunnerHandle::Get(), 0u);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ MessageQueue message_queue;
+ base::RunLoop run_loop;
+ client0.AcceptWithResponder(
+ &request, base::MakeUnique<MessageAccumulator>(&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<const char*>(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<MessageAccumulator>(&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<const char*>(response.payload())));
+}
+
+TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) {
+ InterfaceEndpointClient client0(std::move(endpoint0_), nullptr,
+ base::MakeUnique<PassThroughFilter>(), false,
+ base::ThreadTaskRunnerHandle::Get(), 0u);
+ ResponseGenerator generator;
+ InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+ base::MakeUnique<PassThroughFilter>(), false,
+ base::ThreadTaskRunnerHandle::Get(), 0u);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ MessageQueue message_queue;
+ client0.AcceptWithResponder(
+ &request, base::MakeUnique<MessageAccumulator>(&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<const char*>(response.payload())));
+
+ // Send a second message on the pipe.
+ Message request2;
+ AllocRequestMessage(1, "hello again", &request2);
+
+ client0.AcceptWithResponder(
+ &request2, base::MakeUnique<MessageAccumulator>(&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<const char*>(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<MessageAccumulator>(&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<const char*>(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<MessageAccumulator>(&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<const char*>(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<MessageAccumulator>(&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<PassThroughFilter>(),
+ false, base::ThreadTaskRunnerHandle::Get(), 0u);
+ InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+ base::MakeUnique<PassThroughFilter>(),
+ false, base::ThreadTaskRunnerHandle::Get(),
+ 0u);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ MessageQueue message_queue;
+ client0.AcceptWithResponder(
+ &request, base::MakeUnique<MessageAccumulator>(&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 <string>
+#include <utility>
+
+#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 <typename T>
+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 <typename T>
+base::Callback<void(T)> ExpectResult(const T& t,
+ const base::Closure& callback) {
+ return base::Bind(&DoExpectResult<T>, t.foo(), t.bar(), callback);
+}
+
+template <typename T>
+void DoFail(const std::string& reason, T) {
+ EXPECT_TRUE(false) << reason;
+}
+
+template <typename T>
+base::Callback<void(T)> Fail(const std::string& reason) {
+ return base::Bind(&DoFail<T>, reason);
+}
+
+template <typename T>
+void DoExpectEnumResult(T expected, const base::Closure& callback, T actual) {
+ EXPECT_EQ(expected, actual);
+ callback.Run();
+}
+
+template <typename T>
+base::Callback<void(T)> ExpectEnumResult(T t, const base::Closure& callback) {
+ return base::Bind(&DoExpectEnumResult<T>, t, callback);
+}
+
+template <typename T>
+void DoEnumFail(const std::string& reason, T) {
+ EXPECT_TRUE(false) << reason;
+}
+
+template <typename T>
+base::Callback<void(T)> EnumFail(const std::string& reason) {
+ return base::Bind(&DoEnumFail<T>, reason);
+}
+
+template <typename T>
+void ExpectError(InterfacePtr<T>* proxy, const base::Closure& callback) {
+ proxy->set_connection_error_handler(callback);
+}
+
+template <typename Func, typename Arg>
+void RunSimpleLambda(Func func, Arg arg) { func(std::move(arg)); }
+
+template <typename Arg, typename Func>
+base::Callback<void(Arg)> BindSimpleLambda(Func func) {
+ return base::Bind(&RunSimpleLambda<Func, Arg>, 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<PickledStructChromium> pickles,
+ const PassPicklesCallback& callback) override {
+ callback.Run(std::move(pickles));
+ }
+
+ void PassPickleArrays(
+ std::vector<std::vector<PickledStructChromium>> 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<PickledStructBlink> pickles,
+ const PassPicklesCallback& callback) override {
+ callback.Run(std::move(pickles));
+ }
+
+ void PassPickleArrays(
+ WTF::Vector<WTF::Vector<PickledStructBlink>> 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 <typename ProxyType = PicklePasser>
+ InterfacePtr<ProxyType> ConnectToChromiumService() {
+ InterfacePtr<ProxyType> proxy;
+ InterfaceRequest<ProxyType> request(&proxy);
+ chromium_bindings_.AddBinding(
+ &chromium_service_,
+ ConvertInterfaceRequest<PicklePasser>(std::move(request)));
+ return proxy;
+ }
+
+ template <typename ProxyType = blink::PicklePasser>
+ InterfacePtr<ProxyType> ConnectToBlinkService() {
+ InterfacePtr<ProxyType> proxy;
+ InterfaceRequest<ProxyType> request(&proxy);
+ blink_bindings_.AddBinding(
+ &blink_service_,
+ ConvertInterfaceRequest<blink::PicklePasser>(std::move(request)));
+ return proxy;
+ }
+
+ private:
+ base::MessageLoop loop_;
+ ChromiumPicklePasserImpl chromium_service_;
+ BindingSet<PicklePasser> chromium_bindings_;
+ BlinkPicklePasserImpl blink_service_;
+ BindingSet<blink::PicklePasser> 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<PicklePasser>();
+ {
+ 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<PickledStructChromium>("Blink service should reject this."));
+ ExpectError(&chromium_proxy, loop.QuitClosure());
+ loop.Run();
+ }
+
+ chromium_proxy = ConnectToBlinkService<PicklePasser>();
+ {
+ 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<PickledEnumChromium>("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<blink::PicklePasser>();
+ {
+ 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<PickledStructChromium>(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<PickledStructChromium>>(
+ [&](std::vector<PickledStructChromium> 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<std::vector<PickledStructChromium>>(2);
+ for (size_t i = 0; i < 2; ++i)
+ pickle_arrays[i] = std::vector<PickledStructChromium>(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<std::vector<PickledStructChromium>>>(
+ [&](std::vector<std::vector<PickledStructChromium>> 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>(
+ [&](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<mojo::test::PickledStructBlink>::GetSize(
+ base::PickleSizer* sizer,
+ const param_type& p) {
+ sizer->AddInt();
+ sizer->AddInt();
+}
+
+void ParamTraits<mojo::test::PickledStructBlink>::Write(base::Pickle* m,
+ const param_type& p) {
+ m->WriteInt(p.foo());
+ m->WriteInt(p.bar());
+}
+
+bool ParamTraits<mojo::test::PickledStructBlink>::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 <stddef.h>
+
+#include <string>
+
+#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<mojo::test::PickledStructBlink> {
+ 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<mojo::test::PickledStructChromium>::GetSize(
+ base::PickleSizer* sizer,
+ const param_type& p) {
+ sizer->AddInt();
+ sizer->AddInt();
+}
+
+void ParamTraits<mojo::test::PickledStructChromium>::Write(
+ base::Pickle* m,
+ const param_type& p) {
+ m->WriteInt(p.foo());
+ m->WriteInt(p.bar());
+}
+
+bool ParamTraits<mojo::test::PickledStructChromium>::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 <stddef.h>
+
+#include <string>
+
+#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<mojo::test::PickledStructChromium> {
+ 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<mojo::test::RectBlink> {
+ size_t operator()(const mojo::test::RectBlink& value) {
+ // Terrible hash function:
+ return (std::hash<int>()(value.x()) ^ std::hash<int>()(value.y()) ^
+ std::hash<int>()(value.width()) ^ std::hash<int>()(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<test::TypemappedRectDataView, test::RectBlink> {
+ 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<mojo::test::RectChromium> {
+ size_t operator()(const mojo::test::RectChromium& value) {
+ // Terrible hash function:
+ return (std::hash<int>()(value.x()) ^ std::hash<int>()(value.y()) ^
+ std::hash<int>()(value.width()) ^ std::hash<int>()(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<test::TypemappedRectDataView, test::RectChromium> {
+ 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<TestBadMessages> 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 <stdint.h>
+#include <utility>
+
+#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<sample::Provider> 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<sample::Provider> 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 <stddef.h>
+#include <stdint.h>
+#include <string.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 {
+
+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<MessageReceiverWithStatus> responder) {
+ EXPECT_TRUE(message->has_flag(Message::kFlagExpectsResponse));
+
+ bool result = SendResponse(message->name(), message->request_id(),
+ reinterpret_cast<const char*>(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<MessageReceiverWithStatus> responder) {
+ name_ = message->name();
+ request_id_ = message->request_id();
+ request_string_ =
+ std::string(reinterpret_cast<const char*>(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 <stdint.h>
+
+#include <string>
+
+#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<MessageReceiverWithStatus> 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<MessageReceiverWithStatus> 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<MessageReceiverWithStatus> 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 <stddef.h>
+#include <stdint.h>
+#include <algorithm>
+#include <ostream>
+#include <string>
+#include <utility>
+
+#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+template <>
+struct TypeConverter<int32_t, sample::BarPtr> {
+ static int32_t Convert(const sample::BarPtr& bar) {
+ return static_cast<int32_t>(bar->alpha) << 16 |
+ static_cast<int32_t>(bar->beta) << 8 |
+ static_cast<int32_t>(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<BarPtr> 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<uint8_t>(i * 100);
+ extra_bars[i] = Bar::New(base, base + 20, base + 40, type);
+ }
+
+ std::vector<uint8_t> data(10);
+ for (size_t i = 0; i < data.size(); ++i)
+ data[i] = static_cast<uint8_t>(data.size() - i);
+
+ std::vector<mojo::ScopedDataPipeConsumerHandle> input_streams(2);
+ std::vector<mojo::ScopedDataPipeProducerHandle> 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<std::vector<bool>> array_of_array_of_bools(2);
+ for (size_t i = 0; i < 2; ++i) {
+ std::vector<bool> 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<uint8_t>(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<uint8_t>(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 <typename H>
+void Print(int depth,
+ const char* name,
+ const mojo::ScopedHandleBase<H>& 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<int32_t>());
+ --depth;
+ }
+}
+
+template <typename T>
+void Print(int depth, const char* name, const std::vector<T>& 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 <typename T>
+void Print(int depth,
+ const char* name,
+ const base::Optional<std::vector<T>>& array) {
+ if (array)
+ Print(depth, name, *array);
+ else
+ Print(depth, name, std::vector<T>());
+}
+
+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<int32_t>(baz));
+ Print(depth, "port", port.get());
+ }
+ callback.Run(5);
+ }
+
+ void GetPort(mojo::InterfaceRequest<Port> 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<const uint8_t*>(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<mojo::MessageReceiver> 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 <stddef.h>
+#include <utility>
+
+#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<base::Optional<std::vector<ScopedHandle>>>
+CreateTestNestedHandleArray() {
+ std::vector<base::Optional<std::vector<ScopedHandle>>> array(2);
+ for (size_t i = 0; i < array.size(); ++i) {
+ std::vector<ScopedHandle> 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 <typename T>
+ 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<MojomType>(obj, &context));
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::Serialize<MojomType>(obj, &buf, &data, &context);
+
+ EXPECT_EQ(expected_warning, warning_observer_.last_warning());
+ }
+
+ template <typename MojomType, typename T>
+ 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<MojomType>(obj, &context));
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::Serialize<MojomType>(obj, &buf, &data, validate_params,
+ &context);
+
+ EXPECT_EQ(expected_warning, warning_observer_.last_warning());
+ }
+
+ template <typename T>
+ 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<MojomType>(obj, false, &context));
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::Serialize<MojomType>(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<ArrayDataView<ScopedHandle>>;
+ 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<MojomType>(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<MojomType>(
+ 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<MojomType>(
+ std::move(test_array),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ &validate_params_2);
+}
+
+TEST_F(SerializationWarningTest, ArrayOfStrings) {
+ using MojomType = ArrayDataView<StringDataView>;
+
+ std::vector<std::string> 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<MojomType>(std::move(test_array),
+ mojo::internal::VALIDATION_ERROR_NONE,
+ &validate_params_0);
+
+ std::vector<base::Optional<std::string>> optional_test_array(3);
+ ContainerValidateParams validate_params_1(
+ 0, false, new ContainerValidateParams(0, false, nullptr));
+ TestArrayWarning<MojomType>(
+ std::move(optional_test_array),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ &validate_params_1);
+
+ test_array = std::vector<std::string>(2);
+ ContainerValidateParams validate_params_2(
+ 3, true, new ContainerValidateParams(0, false, nullptr));
+ TestArrayWarning<MojomType>(
+ 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<test::SharedTypemappedRectDataView, test::SharedRect> {
+ 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 <typename T>
+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 <typename T>
+base::Callback<void(const T&)> ExpectResult(const T& r,
+ const base::Closure& callback) {
+ return base::Bind(&DoExpectResult<T>, r, callback);
+}
+
+template <typename T>
+void DoFail(const std::string& reason, const T&) {
+ EXPECT_TRUE(false) << reason;
+}
+
+template <typename T>
+base::Callback<void(const T&)> Fail(const std::string& reason) {
+ return base::Bind(&DoFail<T>, reason);
+}
+
+template <typename T>
+void ExpectError(InterfacePtr<T> *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<RectService>(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<blink::RectService>(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<MoveOnlyStructWithTraitsImpl> 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<int> e,
+ const EchoStructWithTraitsForUniquePtrCallback& callback) override {
+ callback.Run(std::move(e));
+ }
+
+ void EchoNullableStructWithTraitsForUniquePtr(
+ std::unique_ptr<int> e,
+ const EchoNullableStructWithTraitsForUniquePtrCallback& callback)
+ override {
+ callback.Run(std::move(e));
+ }
+
+ void EchoUnionWithTraits(
+ std::unique_ptr<test::UnionWithTraitsBase> u,
+ const EchoUnionWithTraitsCallback& callback) override {
+ callback.Run(std::move(u));
+ }
+
+ base::MessageLoop loop_;
+
+ ChromiumRectServiceImpl chromium_service_;
+ BindingSet<RectService> chromium_bindings_;
+
+ BlinkRectServiceImpl blink_service_;
+ BindingSet<blink::RectService> blink_bindings_;
+
+ BindingSet<TraitsTestService> 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<RectChromium>("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<uint32_t>(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<uint32_t>(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<MoveOnlyStructWithTraitsImpl>* storage,
+ const base::Closure& closure,
+ base::Optional<MoveOnlyStructWithTraitsImpl> passed) {
+ *storage = std::move(passed);
+ closure.Run();
+}
+
+TEST_F(StructTraitsTest, EchoNullableMoveOnlyStructWithTraits) {
+ base::RunLoop loop;
+ TraitsTestServicePtr proxy = GetTraitsTestProxy();
+
+ base::Optional<MoveOnlyStructWithTraitsImpl> 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<int> expected,
+ const base::Closure& closure,
+ std::unique_ptr<int> 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<int>(12345),
+ base::Bind(&ExpectUniquePtr, base::Passed(base::MakeUnique<int>(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<test::UnionWithTraitsBase> input(
+ new test::UnionWithTraitsInt32(1234));
+ base::RunLoop loop;
+ proxy->EchoUnionWithTraits(
+ std::move(input),
+ base::Bind(
+ [](const base::Closure& quit_closure,
+ std::unique_ptr<test::UnionWithTraitsBase> passed) {
+ ASSERT_EQ(test::UnionWithTraitsBase::Type::INT32, passed->type());
+ EXPECT_EQ(1234,
+ static_cast<test::UnionWithTraitsInt32*>(passed.get())
+ ->value());
+ quit_closure.Run();
+
+ },
+ loop.QuitClosure()));
+ loop.Run();
+ }
+
+ {
+ std::unique_ptr<test::UnionWithTraitsBase> input(
+ new test::UnionWithTraitsStruct(4321));
+ base::RunLoop loop;
+ proxy->EchoUnionWithTraits(
+ std::move(input),
+ base::Bind(
+ [](const base::Closure& quit_closure,
+ std::unique_ptr<test::UnionWithTraitsBase> passed) {
+ ASSERT_EQ(test::UnionWithTraitsBase::Type::STRUCT,
+ passed->type());
+ EXPECT_EQ(4321,
+ static_cast<test::UnionWithTraitsStruct*>(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 <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <utility>
+
+#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<int8_t>{10, 9, 8},
+ std::move(pipe.handle0), false, 42);
+}
+
+template <typename U, typename T>
+U SerializeAndDeserialize(T input) {
+ using InputMojomType = typename T::Struct::DataView;
+ using OutputMojomType = typename U::Struct::DataView;
+
+ using InputDataType =
+ typename mojo::internal::MojomTypeTraits<InputMojomType>::Data*;
+ using OutputDataType =
+ typename mojo::internal::MojomTypeTraits<OutputMojomType>::Data*;
+
+ mojo::internal::SerializationContext context;
+ size_t size =
+ mojo::internal::PrepareToSerialize<InputMojomType>(input, &context);
+ mojo::internal::FixedBufferForTesting buf(size + 32);
+ InputDataType data;
+ mojo::internal::Serialize<InputMojomType>(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<OutputDataType>(data);
+
+ U output;
+ mojo::internal::Deserialize<OutputMojomType>(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<RectDataView>(rect, nullptr);
+ EXPECT_EQ(8U + 16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::Rect_Data* data;
+ mojo::internal::Serialize<RectDataView>(rect, &buf, &data, nullptr);
+
+ RectPtr rect2;
+ mojo::internal::Deserialize<RectDataView>(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<RectPairDataView>(pair, nullptr);
+ EXPECT_EQ(8U + 16U + 2 * (8U + 16U), size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::RectPair_Data* data;
+ mojo::internal::Serialize<RectPairDataView>(pair, &buf, &data, nullptr);
+
+ RectPairPtr pair2;
+ mojo::internal::Deserialize<RectPairDataView>(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<RectPtr> rects;
+ for (size_t i = 0; i < 4; ++i)
+ rects.push_back(MakeRect(static_cast<int32_t>(i) + 1));
+
+ NamedRegionPtr region(
+ NamedRegion::New(std::string("region"), std::move(rects)));
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<NamedRegionDataView>(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<NamedRegionDataView>(region, &buf, &data, nullptr);
+
+ NamedRegionPtr region2;
+ mojo::internal::Deserialize<NamedRegionDataView>(data, &region2, 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<int32_t>(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<NamedRegionDataView>(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<NamedRegionDataView>(region, &buf, &data, nullptr);
+
+ NamedRegionPtr region2;
+ mojo::internal::Deserialize<NamedRegionDataView>(data, &region2, 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<MultiVersionStructPtr>(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<MultiVersionStructPtr>(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<MultiVersionStructPtr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MultiVersionStructV5Ptr input(MultiVersionStructV5::New(
+ 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8}));
+ MultiVersionStructPtr expected_output(MultiVersionStruct::New(
+ 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8}));
+
+ MultiVersionStructPtr output =
+ SerializeAndDeserialize<MultiVersionStructPtr>(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<int8_t>{10, 9, 8},
+ std::move(pipe.handle0), false));
+
+ MultiVersionStructPtr expected_output(MultiVersionStruct::New(
+ 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{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<MultiVersionStructPtr>(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<int8_t>{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<MultiVersionStructV7Ptr>(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<int8_t>{10, 9, 8}));
+
+ MultiVersionStructV5Ptr output =
+ SerializeAndDeserialize<MultiVersionStructV5Ptr>(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<MultiVersionStructV3Ptr>(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<MultiVersionStructV1Ptr>(std::move(input));
+ EXPECT_TRUE(output);
+ EXPECT_TRUE(output->Equals(*expected_output));
+ }
+
+ {
+ MultiVersionStructPtr input = MakeMultiVersionStruct();
+ MultiVersionStructV0Ptr expected_output(MultiVersionStructV0::New(123));
+
+ MultiVersionStructV0Ptr output =
+ SerializeAndDeserialize<MultiVersionStructV0Ptr>(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<NativeStructDataView>(
+ native, nullptr);
+ EXPECT_EQ(0u, size);
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ Data* data = nullptr;
+ mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf,
+ &data, nullptr);
+
+ EXPECT_EQ(nullptr, data);
+
+ NativeStructPtr output_native;
+ mojo::internal::Deserialize<NativeStructDataView>(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<NativeStructDataView>(
+ native, nullptr);
+ EXPECT_EQ(0u, size);
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ Data* data = nullptr;
+ mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf,
+ &data, nullptr);
+
+ EXPECT_EQ(nullptr, data);
+
+ NativeStructPtr output_native;
+ mojo::internal::Deserialize<NativeStructDataView>(data, &output_native,
+ nullptr);
+ EXPECT_TRUE(output_native.is_null());
+ }
+
+ {
+ NativeStructPtr native(NativeStruct::New());
+ native->data = std::vector<uint8_t>{'X', 'Y'};
+
+ size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>(
+ native, nullptr);
+ EXPECT_EQ(16u, size);
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ Data* data = nullptr;
+ mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf,
+ &data, nullptr);
+
+ EXPECT_NE(nullptr, data);
+
+ NativeStructPtr output_native;
+ mojo::internal::Deserialize<NativeStructDataView>(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<RectPtr> rects;
+ for (size_t i = 0; i < 3; ++i)
+ rects.push_back(MakeRect(static_cast<int32_t>(i) + 1));
+ NamedRegionPtr region(
+ NamedRegion::New(std::string("region"), std::move(rects)));
+
+ NamedRegionPtr cloned_region = region.Clone();
+ auto data = NamedRegion::Serialize(&region);
+
+ // 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<int>[move_only,nullable_is_same_type]",
+ "mojo.test.UnionWithTraits=std::unique_ptr<mojo::test::UnionWithTraitsBase>[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 <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#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<std::string>& get_string_array() const {
+ return string_array_;
+ }
+ std::vector<std::string>& get_mutable_string_array() { return string_array_; }
+
+ const std::set<std::string>& get_string_set() const {
+ return string_set_;
+ }
+ std::set<std::string>& get_mutable_string_set() { return string_set_; }
+
+ const NestedStructWithTraitsImpl& get_struct() const { return struct_; }
+ NestedStructWithTraitsImpl& get_mutable_struct() { return struct_; }
+
+ const std::vector<NestedStructWithTraitsImpl>& get_struct_array() const {
+ return struct_array_;
+ }
+ std::vector<NestedStructWithTraitsImpl>& get_mutable_struct_array() {
+ return struct_array_;
+ }
+
+ const std::map<std::string, NestedStructWithTraitsImpl>& get_struct_map()
+ const {
+ return struct_map_;
+ }
+ std::map<std::string, NestedStructWithTraitsImpl>& 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<std::string> string_array_;
+ std::set<std::string> string_set_;
+ NestedStructWithTraitsImpl struct_;
+ std::vector<NestedStructWithTraitsImpl> struct_array_;
+ std::map<std::string, NestedStructWithTraitsImpl> 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<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl>::
+ SetUpContext(const test::NestedStructWithTraitsImpl& input) {
+ Context* context = new Context;
+ context->value = input.value;
+ return context;
+}
+
+// static
+void StructTraits<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl>::
+ TearDownContext(const test::NestedStructWithTraitsImpl& input,
+ void* context) {
+ Context* context_obj = static_cast<Context*>(context);
+ CHECK_EQ(context_obj->value, input.value);
+ delete context_obj;
+}
+
+// static
+int32_t StructTraits<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl>::
+ value(const test::NestedStructWithTraitsImpl& input, void* context) {
+ Context* context_obj = static_cast<Context*>(context);
+ CHECK_EQ(context_obj->value, input.value);
+ return input.value;
+}
+
+// static
+bool StructTraits<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl>::
+ Read(test::NestedStructWithTraits::DataView data,
+ test::NestedStructWithTraitsImpl* output) {
+ output->value = data.value();
+ return true;
+}
+
+test::EnumWithTraits
+EnumTraits<test::EnumWithTraits, test::EnumWithTraitsImpl>::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<test::EnumWithTraits, test::EnumWithTraitsImpl>::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<test::StructWithTraitsDataView, test::StructWithTraitsImpl>::
+ 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<StringDataView> 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<test::MoveOnlyStructWithTraitsDataView,
+ test::MoveOnlyStructWithTraitsImpl>::
+ 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 <stdint.h>
+
+#include <string>
+#include <vector>
+
+#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<test::NestedStructWithTraitsDataView,
+ test::NestedStructWithTraitsImpl> {
+ 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<test::EnumWithTraits, test::EnumWithTraitsImpl> {
+ static test::EnumWithTraits ToMojom(test::EnumWithTraitsImpl input);
+ static bool FromMojom(test::EnumWithTraits input,
+ test::EnumWithTraitsImpl* output);
+};
+
+template <>
+struct StructTraits<test::StructWithTraitsDataView,
+ test::StructWithTraitsImpl> {
+ // 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<std::string>& f_string_array(
+ const test::StructWithTraitsImpl& value) {
+ return value.get_string_array();
+ }
+
+ static const std::set<std::string>& 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<test::NestedStructWithTraitsImpl>& f_struct_array(
+ const test::StructWithTraitsImpl& value) {
+ return value.get_struct_array();
+ }
+
+ static const std::map<std::string, test::NestedStructWithTraitsImpl>&
+ f_struct_map(const test::StructWithTraitsImpl& value) {
+ return value.get_struct_map();
+ }
+};
+
+template <>
+struct StructTraits<test::TrivialStructWithTraitsDataView,
+ test::TrivialStructWithTraitsImpl> {
+ // 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<test::MoveOnlyStructWithTraitsDataView,
+ test::MoveOnlyStructWithTraitsImpl> {
+ // 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<test::StructWithTraitsForUniquePtrDataView,
+ std::unique_ptr<int>> {
+ static bool IsNull(const std::unique_ptr<int>& data) { return !data; }
+ static void SetToNull(std::unique_ptr<int>* data) { data->reset(); }
+
+ static int f_int32(const std::unique_ptr<int>& data) { return *data; }
+
+ static bool Read(test::StructWithTraitsForUniquePtrDataView data,
+ std::unique_ptr<int>* out) {
+ out->reset(new int(data.f_int32()));
+ return true;
+ }
+};
+
+template <>
+struct UnionTraits<test::UnionWithTraitsDataView,
+ std::unique_ptr<test::UnionWithTraitsBase>> {
+ static bool IsNull(const std::unique_ptr<test::UnionWithTraitsBase>& data) {
+ return !data;
+ }
+ static void SetToNull(std::unique_ptr<test::UnionWithTraitsBase>* data) {
+ data->reset();
+ }
+
+ static test::UnionWithTraitsDataView::Tag GetTag(
+ const std::unique_ptr<test::UnionWithTraitsBase>& 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<test::UnionWithTraitsBase>& data) {
+ return static_cast<test::UnionWithTraitsInt32*>(data.get())->value();
+ }
+
+ static const test::NestedStructWithTraitsImpl& f_struct(
+ const std::unique_ptr<test::UnionWithTraitsBase>& data) {
+ return static_cast<test::UnionWithTraitsStruct*>(data.get())->get_struct();
+ }
+
+ static bool Read(test::UnionWithTraitsDataView data,
+ std::unique_ptr<test::UnionWithTraitsBase>* 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 <utility>
+
+#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 <typename... Args>
+struct LambdaBinder {
+ using CallbackType = base::Callback<void(Args...)>;
+
+ template <typename Func>
+ static void RunLambda(Func func, Args... args) {
+ func(std::move(args)...);
+ }
+
+ template <typename Func>
+ static CallbackType BindLambda(Func func) {
+ return base::Bind(&LambdaBinder::RunLambda<Func>, func);
+ }
+};
+
+class TestSyncCommonImpl {
+ public:
+ TestSyncCommonImpl() {}
+
+ using PingHandler = base::Callback<void(const base::Callback<void()>&)>;
+ using PingBinder = LambdaBinder<const base::Callback<void()>&>;
+ template <typename Func>
+ void set_ping_handler(Func handler) {
+ ping_handler_ = PingBinder::BindLambda(handler);
+ }
+
+ using EchoHandler =
+ base::Callback<void(int32_t, const base::Callback<void(int32_t)>&)>;
+ using EchoBinder =
+ LambdaBinder<int32_t, const base::Callback<void(int32_t)>&>;
+ template <typename Func>
+ void set_echo_handler(Func handler) {
+ echo_handler_ = EchoBinder::BindLambda(handler);
+ }
+
+ using AsyncEchoHandler =
+ base::Callback<void(int32_t, const base::Callback<void(int32_t)>&)>;
+ using AsyncEchoBinder =
+ LambdaBinder<int32_t, const base::Callback<void(int32_t)>&>;
+ template <typename Func>
+ void set_async_echo_handler(Func handler) {
+ async_echo_handler_ = AsyncEchoBinder::BindLambda(handler);
+ }
+
+ using SendInterfaceHandler = base::Callback<void(TestSyncAssociatedPtrInfo)>;
+ using SendInterfaceBinder = LambdaBinder<TestSyncAssociatedPtrInfo>;
+ template <typename Func>
+ void set_send_interface_handler(Func handler) {
+ send_interface_handler_ = SendInterfaceBinder::BindLambda(handler);
+ }
+
+ using SendRequestHandler = base::Callback<void(TestSyncAssociatedRequest)>;
+ using SendRequestBinder = LambdaBinder<TestSyncAssociatedRequest>;
+ template <typename Func>
+ void set_send_request_handler(Func handler) {
+ send_request_handler_ = SendRequestBinder::BindLambda(handler);
+ }
+
+ void PingImpl(const base::Callback<void()>& callback) {
+ if (ping_handler_.is_null()) {
+ callback.Run();
+ return;
+ }
+ ping_handler_.Run(callback);
+ }
+ void EchoImpl(int32_t value, const base::Callback<void(int32_t)>& callback) {
+ if (echo_handler_.is_null()) {
+ callback.Run(value);
+ return;
+ }
+ echo_handler_.Run(value, callback);
+ }
+ void AsyncEchoImpl(int32_t value,
+ const base::Callback<void(int32_t)>& 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<TestSync>* binding() { return &binding_; }
+
+ private:
+ Binding<TestSync> 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<TestSyncMaster>* binding() { return &binding_; }
+
+ private:
+ Binding<TestSyncMaster> 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<TestSync>* binding() { return &binding_; }
+
+ private:
+ AssociatedBinding<TestSync> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSyncAssociatedImpl);
+};
+
+template <typename Interface>
+struct ImplTraits;
+
+template <>
+struct ImplTraits<TestSync> {
+ using Type = TestSyncImpl;
+};
+
+template <>
+struct ImplTraits<TestSyncMaster> {
+ using Type = TestSyncMasterImpl;
+};
+
+template <typename Interface>
+using ImplTypeFor = typename ImplTraits<Interface>::Type;
+
+// A wrapper for either an InterfacePtr or scoped_refptr<ThreadSafeInterfacePtr>
+// that exposes the InterfacePtr interface.
+template <typename Interface>
+class PtrWrapper {
+ public:
+ explicit PtrWrapper(InterfacePtr<Interface> ptr) : ptr_(std::move(ptr)) {}
+
+ explicit PtrWrapper(
+ scoped_refptr<ThreadSafeInterfacePtr<Interface>> 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<Interface> ptr_;
+ scoped_refptr<ThreadSafeInterfacePtr<Interface>> 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 <typename InterfaceT, bool use_thread_safe_ptr>
+struct TestParams {
+ using Interface = InterfaceT;
+ static const bool kIsThreadSafeInterfacePtrTest = use_thread_safe_ptr;
+
+ static PtrWrapper<InterfaceT> Wrap(InterfacePtr<Interface> ptr) {
+ if (kIsThreadSafeInterfacePtrTest) {
+ return PtrWrapper<Interface>(
+ ThreadSafeInterfacePtr<Interface>::Create(std::move(ptr)));
+ } else {
+ return PtrWrapper<Interface>(std::move(ptr));
+ }
+ }
+};
+
+template <typename Interface>
+class TestSyncServiceThread {
+ public:
+ TestSyncServiceThread()
+ : thread_("TestSyncServiceThread"), ping_called_(false) {
+ thread_.Start();
+ }
+
+ void SetUp(InterfaceRequest<Interface> request) {
+ CHECK(thread_.task_runner()->BelongsToCurrentThread());
+ impl_.reset(new ImplTypeFor<Interface>(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<ImplTypeFor<Interface>> 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 <typename T>
+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<TestSyncMaster> master_ptr_;
+ std::unique_ptr<TestSyncMasterImpl> 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 <typename Func>
+void CallAsyncEchoCallback(Func func, int32_t value) {
+ func(value);
+}
+
+template <typename Func>
+TestSync::AsyncEchoCallback BindAsyncEchoCallback(Func func) {
+ return base::Bind(&CallAsyncEchoCallback<Func>, 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<TestSync, true>,
+ TestParams<TestSync, false>,
+ TestParams<TestSyncMaster, true>,
+ TestParams<TestSyncMaster, false>>;
+TYPED_TEST_CASE(SyncMethodCommonTest, InterfaceTypes);
+
+TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) {
+ using Interface = typename TypeParam::Interface;
+ InterfacePtr<Interface> interface_ptr;
+ ImplTypeFor<Interface> 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> interface_ptr;
+ InterfaceRequest<Interface> request = MakeRequest(&interface_ptr);
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+
+ TestSyncServiceThread<Interface> service_thread;
+ service_thread.thread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&TestSyncServiceThread<Interface>::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<Interface>::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> interface_ptr;
+ // The binding lives on the same thread as the interface pointer.
+ ImplTypeFor<Interface> 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> interface_ptr;
+ ImplTypeFor<Interface> 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> interface_ptr;
+ ImplTypeFor<Interface> 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> interface_ptr;
+ ImplTypeFor<Interface> 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> interface_ptr;
+ ImplTypeFor<Interface> 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> interface_ptr;
+ ImplTypeFor<Interface> 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> interface_ptr;
+ ImplTypeFor<Interface> 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<Interface> ptr;
+ ImplTypeFor<Interface> 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> interface_ptr;
+ interface_ptr.Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u));
+ auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+
+ MessagePipeHandle raw_binding_handle = pipe.handle1.get();
+ ImplTypeFor<Interface> impl(MakeRequest<Interface>(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 <stddef.h>
+#include <stdint.h>
+
+#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<RedmondRect> rects;
+};
+
+bool AreEqualRectArrays(const std::vector<test::RectPtr>& rects1,
+ const std::vector<test::RectPtr>& 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<test::RectPtr, RedmondRect> {
+ 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<RedmondRect, test::RectPtr> {
+ 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<test::NamedRegionPtr, RedmondNamedRegion> {
+ static test::NamedRegionPtr Convert(const RedmondNamedRegion& input) {
+ return test::NamedRegion::New(
+ input.name, ConvertTo<std::vector<test::RectPtr>>(input.rects));
+ }
+};
+
+template <>
+struct TypeConverter<RedmondNamedRegion, test::NamedRegionPtr> {
+ 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<RedmondRect>());
+ }
+ return region;
+ }
+};
+
+namespace test {
+namespace {
+
+TEST(TypeConversionTest, CustomTypeConverter) {
+ RectPtr rect(Rect::New(10, 20, 50, 45));
+
+ RedmondRect rr = rect.To<RedmondRect>();
+ 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<RectPtr> rects;
+
+ auto redmond_rects = ConvertTo<std::vector<RedmondRect>>(rects);
+
+ EXPECT_TRUE(redmond_rects.empty());
+}
+
+TEST(TypeConversionTest, CustomTypeConverter_Array) {
+ const RedmondRect kBase = {10, 20, 30, 40};
+
+ std::vector<RectPtr> rects(10);
+ for (size_t i = 0; i < rects.size(); ++i) {
+ RedmondRect rr = kBase;
+ rr.left += static_cast<int32_t>(i);
+ rr.top += static_cast<int32_t>(i);
+ rects[i] = Rect::From(rr);
+ }
+
+ auto redmond_rects = ConvertTo<std::vector<RedmondRect>>(rects);
+
+ auto rects2 = ConvertTo<std::vector<RectPtr>>(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<int32_t>(i);
+ rect.top += static_cast<int32_t>(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<RedmondNamedRegion>();
+
+ 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 <stddef.h>
+#include <stdint.h>
+#include <utility>
+#include <vector>
+
+#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<uint32_t>(15));
+ EXPECT_EQ(static_cast<uint32_t>(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<uint64_t>(17));
+ EXPECT_EQ(static_cast<uint64_t>(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<PodUnionDataView>(
+ pod1, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod1, &buf, &data, false,
+ &context);
+
+ PodUnionPtr pod2;
+ mojo::internal::Deserialize<PodUnionDataView>(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<PodUnionDataView>(
+ pod1, false, nullptr);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod1, &buf, &data, false,
+ nullptr);
+
+ PodUnionPtr pod2;
+ mojo::internal::Deserialize<PodUnionDataView>(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<PodUnionDataView>(pod, false, nullptr);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<PodUnionDataView>(pod, false, nullptr);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+ EXPECT_FALSE(data->is_null());
+}
+
+TEST(UnionTest, SerializeIsNullInlined) {
+ PodUnionPtr pod;
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(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<PodUnionDataView>(pod, &buf, &data, true, nullptr);
+ EXPECT_TRUE(data->is_null());
+
+ PodUnionPtr pod2;
+ mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, nullptr);
+ EXPECT_TRUE(pod2.is_null());
+}
+
+TEST(UnionTest, SerializeIsNullNotInlined) {
+ PodUnionPtr pod;
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
+ EXPECT_EQ(16U, size);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(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<char*>(raw_buf) + 1;
+
+ internal::PodUnion_Data* data =
+ reinterpret_cast<internal::PodUnion_Data*>(buf);
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<uint32_t>(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<internal::PodUnion_Data::PodUnion_Tag>(0xFFFFFF);
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<AnEnum>(0xFFFF));
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<AnExtensibleEnum>(0xFFFF));
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::PodUnion_Data* data = nullptr;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<ObjectUnionDataView>(
+ pod1, false, nullptr);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(pod1, &buf, &data, false,
+ nullptr);
+
+ ObjectUnionPtr pod2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(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<uint32_t>(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<uint32_t>(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<char*>(&data->data.f_f_string);
+ mojo::internal::ArrayHeader* array_header =
+ reinterpret_cast<mojo::internal::ArrayHeader*>(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<PodUnionPtr> 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<ArrayDataView<PodUnionDataView>>(
+ array, nullptr);
+ EXPECT_EQ(40U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ mojo::internal::Array_Data<internal::PodUnion_Data>* data;
+ mojo::internal::ContainerValidateParams validate_params(0, false, nullptr);
+ mojo::internal::Serialize<ArrayDataView<PodUnionDataView>>(
+ array, &buf, &data, &validate_params, nullptr);
+
+ std::vector<PodUnionPtr> array2;
+ mojo::internal::Deserialize<ArrayDataView<PodUnionDataView>>(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<PodUnionPtr> array(2);
+ array[0] = PodUnion::New();
+
+ array[0]->set_f_int8(10);
+ EXPECT_EQ(2U, array.size());
+
+ size_t size =
+ mojo::internal::PrepareToSerialize<ArrayDataView<PodUnionDataView>>(
+ array, nullptr);
+ EXPECT_EQ(40U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ mojo::internal::Array_Data<internal::PodUnion_Data>* data;
+ mojo::internal::ContainerValidateParams validate_params(0, true, nullptr);
+ mojo::internal::Serialize<ArrayDataView<PodUnionDataView>>(
+ array, &buf, &data, &validate_params, nullptr);
+
+ std::vector<PodUnionPtr> array2;
+ mojo::internal::Deserialize<ArrayDataView<PodUnionDataView>>(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<ObjectUnionPtr> 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<ArrayDataView<ObjectUnionDataView>>(
+ array, nullptr);
+ EXPECT_EQ(72U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ mojo::internal::Array_Data<internal::ObjectUnion_Data>* data;
+ mojo::internal::ContainerValidateParams validate_params(0, false, nullptr);
+ mojo::internal::Serialize<ArrayDataView<ObjectUnionDataView>>(
+ array, &buf, &data, &validate_params, nullptr);
+
+ std::vector<char> new_buf;
+ new_buf.resize(size);
+
+ void* raw_buf = buf.Leak();
+ memcpy(new_buf.data(), raw_buf, size);
+ free(raw_buf);
+
+ data =
+ reinterpret_cast<mojo::internal::Array_Data<internal::ObjectUnion_Data>*>(
+ new_buf.data());
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ ASSERT_TRUE(mojo::internal::Array_Data<internal::ObjectUnion_Data>::Validate(
+ data, &validation_context, &validate_params));
+
+ std::vector<ObjectUnionPtr> array2;
+ mojo::internal::Deserialize<ArrayDataView<ObjectUnionDataView>>(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<SmallStructDataView>(
+ small_struct, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
+ &context);
+
+ SmallStructPtr deserialized;
+ mojo::internal::Deserialize<SmallStructDataView>(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<SmallObjStructDataView>(
+ obj_struct, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallObjStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallObjStructDataView>(obj_struct, &buf, &data,
+ nullptr);
+
+ SmallObjStructPtr deserialized;
+ mojo::internal::Deserialize<SmallObjStructDataView>(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<SmallStructDataView>(
+ small_struct, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<SmallStructDataView>(
+ small_struct, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
+ &context);
+ data->pod_union.tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(100);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<SmallStructNonNullableUnionDataView>(
+ 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<uint32_t>(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<SmallStructDataView>(
+ small_struct, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::SmallStruct_Data* data = nullptr;
+ mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<StringDataView, PodUnionDataView>;
+
+ std::unordered_map<std::string, PodUnionPtr> 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<MojomType>(map, &context);
+ EXPECT_EQ(120U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::ContainerValidateParams validate_params(
+ new mojo::internal::ContainerValidateParams(0, false, nullptr),
+ new mojo::internal::ContainerValidateParams(0, false, nullptr));
+ mojo::internal::Serialize<MojomType>(map, &buf, &data, &validate_params,
+ &context);
+
+ std::unordered_map<std::string, PodUnionPtr> map2;
+ mojo::internal::Deserialize<MojomType>(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<StringDataView, PodUnionDataView>;
+
+ std::unordered_map<std::string, PodUnionPtr> 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<MojomType>(map, &context);
+ EXPECT_EQ(120U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::ContainerValidateParams validate_params(
+ new mojo::internal::ContainerValidateParams(0, false, nullptr),
+ new mojo::internal::ContainerValidateParams(0, true, nullptr));
+ mojo::internal::Serialize<MojomType>(map, &buf, &data, &validate_params,
+ &context);
+
+ std::unordered_map<std::string, PodUnionPtr> map2;
+ mojo::internal::Deserialize<MojomType>(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<ObjectUnionDataView>(
+ obj, false, nullptr);
+ EXPECT_EQ(32U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ ObjectUnionPtr obj2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(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<ObjectUnionDataView>(
+ obj, false, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<ObjectUnionDataView>(
+ obj, false, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<ObjectUnionDataView>(
+ obj, false, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+ EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, ArrayInUnionGetterSetter) {
+ std::vector<int8_t> 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<int8_t> 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<ObjectUnionDataView>(
+ obj, false, nullptr);
+ EXPECT_EQ(32U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ ObjectUnionPtr obj2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(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<int8_t> 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<ObjectUnionDataView>(
+ obj, false, nullptr);
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(size), 0, 0);
+
+ EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
+ raw_buf, &validation_context, false));
+ free(raw_buf);
+}
+
+TEST(UnionTest, MapInUnionGetterSetter) {
+ std::unordered_map<std::string, int8_t> 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<std::string, int8_t> 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<ObjectUnionDataView>(
+ obj, false, &context);
+ EXPECT_EQ(112U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ &context);
+
+ ObjectUnionPtr obj2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(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<std::string, int8_t> 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<ObjectUnionDataView>(
+ obj, false, &context);
+ EXPECT_EQ(112U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<ObjectUnionDataView>(
+ obj, false, nullptr);
+ EXPECT_EQ(32U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ ObjectUnionPtr obj2;
+ mojo::internal::Deserialize<ObjectUnionDataView>(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<ObjectUnionDataView>(
+ obj, false, nullptr);
+ EXPECT_EQ(32U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<ObjectUnionDataView>(
+ obj, false, nullptr);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::ObjectUnion_Data* data = nullptr;
+ mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
+ nullptr);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<HandleUnionDataView>(
+ handle, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::HandleUnion_Data* data = nullptr;
+ mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
+ &context);
+ EXPECT_EQ(1U, context.handles.size());
+
+ HandleUnionPtr handle2(HandleUnion::New());
+ mojo::internal::Deserialize<HandleUnionDataView>(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<HandleUnionDataView>(
+ handle, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::HandleUnion_Data* data = nullptr;
+ mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<HandleUnionDataView>(
+ handle, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::HandleUnion_Data* data = nullptr;
+ mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
+ &context);
+
+ void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(
+ data, static_cast<uint32_t>(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<SmallCache> 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<SmallCache> 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<HandleUnionDataView>(
+ handle, false, &context);
+ EXPECT_EQ(16U, size);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ internal::HandleUnion_Data* data = nullptr;
+ mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
+ &context);
+ EXPECT_EQ(1U, context.handles.size());
+
+ HandleUnionPtr handle2(HandleUnion::New());
+ mojo::internal::Deserialize<HandleUnionDataView>(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<UnionInterface> 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 <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#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<const void*>(ptr);
+}
+
+#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+TEST(ValidationContextTest, ConstructorRangeOverflow) {
+ {
+ // Test memory range overflow.
+ internal::ValidationContext context(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 5000, 0, 0);
+
+ EXPECT_FALSE(context.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1));
+ EXPECT_FALSE(context.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1));
+ }
+
+ if (sizeof(size_t) <= sizeof(uint32_t))
+ return;
+
+ {
+ // Test handle index range overflow.
+ size_t num_handles =
+ static_cast<size_t>(std::numeric_limits<uint32_t>::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<uint32_t>::max() - 1)));
+
+ EXPECT_TRUE(context.ClaimHandle(
+ Handle_Data(internal::kEncodedInvalidHandleValue)));
+ }
+
+ {
+ size_t num_associated_endpoint_handles =
+ static_cast<size_t>(std::numeric_limits<uint32_t>::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<uint32_t>::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<uintptr_t>::max() - 2000), 1000, 0, 0);
+
+ // Test overflow.
+ EXPECT_FALSE(context.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), 4000));
+ EXPECT_FALSE(context.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 1500),
+ std::numeric_limits<uint32_t>::max()));
+
+ // This should be fine.
+ EXPECT_TRUE(context.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::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<uint32_t>::max());
+ internal::ValidationContext context(
+ ToPtr(0), 0, std::numeric_limits<uint32_t>::max(), 0);
+
+ EXPECT_TRUE(context.ClaimHandle(
+ Handle_Data(std::numeric_limits<uint32_t>::max() - 1)));
+ EXPECT_FALSE(context.ClaimHandle(
+ Handle_Data(std::numeric_limits<uint32_t>::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<uint32_t>::max());
+ internal::ValidationContext context(ToPtr(0), 0, 0,
+ std::numeric_limits<uint32_t>::max());
+
+ EXPECT_TRUE(
+ context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data(
+ std::numeric_limits<uint32_t>::max() - 1)));
+ EXPECT_FALSE(
+ context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data(
+ std::numeric_limits<uint32_t>::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<uintptr_t>::max() - 1000), 500, 0, 0);
+
+ // Test overflow.
+ EXPECT_FALSE(context.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 750), 4000));
+ EXPECT_FALSE(
+ context.ClaimMemory(ToPtr(std::numeric_limits<uintptr_t>::max() - 750),
+ std::numeric_limits<uint32_t>::max()));
+
+ // This should be fine.
+ EXPECT_TRUE(context.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::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 <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <limits>
+#include <map>
+#include <set>
+#include <utility>
+
+#include "mojo/public/c/system/macros.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class ValidationTestInputParser {
+ public:
+ ValidationTestInputParser(const std::string& input,
+ std::vector<uint8_t>* data,
+ size_t* num_handles,
+ std::string* error_message);
+ ~ValidationTestInputParser();
+
+ bool Run();
+
+ private:
+ struct DataType;
+
+ typedef std::pair<const char*, const char*> 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 <typename T>
+ void AppendData(T data) {
+ size_t pos = data_->size();
+ data_->resize(pos + sizeof(T));
+ memcpy(&(*data_)[pos], &data, sizeof(T));
+ }
+
+ template <typename TargetType, typename InputType>
+ bool ConvertAndAppendData(InputType value) {
+ if (value > std::numeric_limits<TargetType>::max() ||
+ value < std::numeric_limits<TargetType>::min()) {
+ return false;
+ }
+ AppendData(static_cast<TargetType>(value));
+ return true;
+ }
+
+ template <typename TargetType, typename InputType>
+ bool ConvertAndFillData(size_t pos, InputType value) {
+ if (value > std::numeric_limits<TargetType>::max() ||
+ value < std::numeric_limits<TargetType>::min()) {
+ return false;
+ }
+ TargetType target_value = static_cast<TargetType>(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<uint8_t>* data_;
+ size_t* num_handles_;
+ std::string* error_message_;
+
+ std::map<std::string, PendingDistanceItem> pending_distance_items_;
+ std::set<std::string> 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<uint8_t>* 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<uint8_t>(value);
+ case 2:
+ return ConvertAndAppendData<uint16_t>(value);
+ case 4:
+ return ConvertAndAppendData<uint32_t>(value);
+ case 8:
+ return ConvertAndAppendData<uint64_t>(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<int8_t>(value);
+ case 2:
+ return ConvertAndAppendData<int16_t>(value);
+ case 4:
+ return ConvertAndAppendData<int32_t>(value);
+ case 8:
+ return ConvertAndAppendData<int64_t>(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<std::string, PendingDistanceItem>::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<uint32_t>(dist_item.pos, distance);
+ case 8:
+ return ConvertAndFillData<uint64_t>(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<size_t>::max())
+ return false;
+
+ *num_handles_ = static_cast<size_t>(value);
+ return true;
+}
+
+bool ValidationTestInputParser::StartsWith(const Range& range,
+ const char* prefix,
+ size_t prefix_length) {
+ if (static_cast<size_t>(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<uint8_t>* 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 <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+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 [<type>]<value>. 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<uint8_t>* 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 <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#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 <typename T>
+void Append(std::vector<uint8_t>* 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<uint8_t>& expected_data,
+ size_t expected_num_handles) {
+ std::vector<uint8_t> 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<std::string> GetMatchingTests(const std::vector<std::string>& names,
+ const std::string& prefix) {
+ const std::string suffix = ".data";
+ std::vector<std::string> 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<size_t>(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<uint8_t>* 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<uint8_t> data;
+ size_t num_handles;
+ if (!ReadAndParseDataFile(GetPath(test, ".data"), &data, &num_handles) ||
+ !ReadResultFile(GetPath(test, ".expected"), expected)) {
+ return false;
+ }
+
+ message->Initialize(static_cast<uint32_t>(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<std::string> names =
+ EnumerateSourceRootRelativeDirectory(GetPath("", ""));
+ std::vector<std::string> 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<uint8_t>());
+ }
+};
+
+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<char*>(&x)));
+ }
+ {
+ // Test empty input.
+ std::string input;
+ std::vector<uint8_t> 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<uint8_t> 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<uint8_t> expected;
+ Append(&expected, static_cast<uint8_t>(0x10));
+ Append(&expected, static_cast<uint16_t>(65535));
+ Append(&expected, static_cast<uint32_t>(65536));
+ Append(&expected, static_cast<uint64_t>(0xffffffffffffffff));
+ Append(&expected, static_cast<uint8_t>(0));
+ Append(&expected, static_cast<uint8_t>(0xff));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40";
+ std::vector<uint8_t> expected;
+ Append(&expected, -static_cast<int64_t>(0x800));
+ Append(&expected, static_cast<int8_t>(-128));
+ Append(&expected, static_cast<int16_t>(0));
+ Append(&expected, static_cast<int32_t>(-40));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[b]00001011 [b]10000000 // hello world\r [b]00000000";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint8_t>(11));
+ Append(&expected, static_cast<uint8_t>(128));
+ Append(&expected, static_cast<uint8_t>(0));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[f]+.3e9 [d]-10.03";
+ std::vector<uint8_t> 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<uint8_t> expected;
+ Append(&expected, static_cast<uint32_t>(14));
+ Append(&expected, static_cast<uint8_t>(0));
+ Append(&expected, static_cast<uint64_t>(9));
+ Append(&expected, static_cast<uint8_t>(0));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "// This message has handles! \n[handles]50 [u8]2";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint64_t>(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<uint8_t> 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<mojo::MessageHeaderValidator>();
+ validators.Append<ConformanceTestInterface::RequestValidator_>();
+
+ RunValidationTests("conformance_", &validators);
+}
+
+TEST_F(ValidationTest, AssociatedConformace) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<AssociatedConformanceTestInterface::RequestValidator_>();
+
+ 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<mojo::MessageHeaderValidator>();
+ validators.Append<BoundsCheckTestInterface::RequestValidator_>();
+
+ 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<mojo::MessageHeaderValidator>();
+ validators.Append<ConformanceTestInterface::ResponseValidator_>();
+
+ 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<mojo::MessageHeaderValidator>();
+ validators.Append<BoundsCheckTestInterface::ResponseValidator_>();
+
+ RunValidationTests("resp_boundscheck_", &validators);
+}
+
+// Test that InterfacePtr<X> applies the correct validators and they don't
+// conflict with each other:
+// - MessageHeaderValidator
+// - X::ResponseValidator_
+TEST_F(ValidationIntegrationTest, InterfacePtr) {
+ IntegrationTestInterfacePtr interface_ptr = MakeProxy(
+ InterfacePtrInfo<IntegrationTestInterface>(testee_endpoint(), 0u));
+ interface_ptr.internal_state()->EnableTestingMode();
+
+ RunValidationTests("integration_intf_resp", test_message_receiver());
+ RunValidationTests("integration_msghdr", test_message_receiver());
+}
+
+// Test that Binding<X> applies the correct validators and they don't
+// conflict with each other:
+// - MessageHeaderValidator
+// - X::RequestValidator_
+TEST_F(ValidationIntegrationTest, Binding) {
+ IntegrationTestInterfaceImpl interface_impl;
+ Binding<IntegrationTestInterface> binding(
+ &interface_impl,
+ MakeRequest<IntegrationTestInterface>(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<uint32_t>::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<BasicEnum>(-4)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(-3)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-2)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-1)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(0)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(1)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(2)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(9)));
+ // In the mojom, we represent this value as hex (0xa).
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(10)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(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<StructWithEnum::EnumWithin>(-1)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(0)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(1)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(2)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(3)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(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 <string.h>
+
+#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 <typename Interface0, typename Interface1>
+InterfaceRequest<Interface0> ConvertInterfaceRequest(
+ InterfaceRequest<Interface1> request) {
+ DCHECK_EQ(0, strcmp(Interface0::Name_, Interface1::Name_));
+ InterfaceRequest<Interface0> 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 <stddef.h>
+#include <stdint.h>
+
+#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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> returned_finger_print) {});
+ database_.WaitForIncomingResponse();
+ EXPECT_TRUE(database_.encountered_error());
+}
+
+TEST_F(VersioningApplicationTest, CallNonexistentMethod) {
+ EXPECT_EQ(0u, database_.version());
+
+ Array<uint8_t> 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<uint64_t> 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 <stdint.h>
+
+#include <map>
+#include <utility>
+
+#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<uint8_t> finger_print;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EmployeeInfo);
+};
+
+class HumanResourceDatabaseImpl : public HumanResourceDatabase {
+ public:
+ explicit HumanResourceDatabaseImpl(
+ InterfaceRequest<HumanResourceDatabase> 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<uint8_t>());
+ return;
+ }
+ callback.Run(employees_[id]->employee.Clone(),
+ retrieve_finger_print ? employees_[id]->finger_print.Clone()
+ : Array<uint8_t>());
+ }
+
+ void AttachFingerPrint(uint64_t id,
+ Array<uint8_t> 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<uint64_t, EmployeeInfo*> employees_;
+
+ StrongBinding<HumanResourceDatabase> strong_binding_;
+};
+
+class HumanResourceSystemServer
+ : public service_manager::Service,
+ public InterfaceFactory<HumanResourceDatabase> {
+ public:
+ HumanResourceSystemServer() {}
+
+ // service_manager::Service implementation.
+ bool OnConnect(Connection* connection) override {
+ connection->AddInterface<HumanResourceDatabase>(this);
+ return true;
+ }
+
+ // InterfaceFactory<HumanResourceDatabase> implementation.
+ void Create(Connection* connection,
+ InterfaceRequest<HumanResourceDatabase> 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<blink::TopLevelEnum>::Hash().hash(
+ blink::TopLevelEnum::E0),
+ WTF::DefaultHash<blink::TopLevelEnum>::Hash().hash(
+ blink::TopLevelEnum::E0));
+
+ // Nested in struct.
+ ASSERT_EQ(WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().hash(
+ blink::TestWTFStruct::NestedEnum::E0),
+ WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().hash(
+ blink::TestWTFStruct::NestedEnum::E0));
+
+ // Nested in interface.
+ ASSERT_EQ(WTF::DefaultHash<blink::TestWTF::NestedEnum>::Hash().hash(
+ blink::TestWTF::NestedEnum::E0),
+ WTF::DefaultHash<blink::TestWTF::NestedEnum>::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<blink::RectPtr, int32_t> 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<blink::ContainsHashablePtr, int32_t> 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<std::string>& str,
+ const EchoStringCallback& callback) override {
+ callback.Run(str);
+ }
+
+ void EchoStringArray(
+ const base::Optional<std::vector<base::Optional<std::string>>>& arr,
+ const EchoStringArrayCallback& callback) override {
+ callback.Run(std::move(arr));
+ }
+
+ void EchoStringMap(
+ const base::Optional<
+ std::unordered_map<std::string, base::Optional<std::string>>>&
+ str_map,
+ const EchoStringMapCallback& callback) override {
+ callback.Run(std::move(str_map));
+ }
+
+ private:
+ Binding<TestWTF> binding_;
+};
+
+class WTFTypesTest : public testing::Test {
+ public:
+ WTFTypesTest() {}
+
+ private:
+ base::MessageLoop loop_;
+};
+
+WTF::Vector<WTF::String> ConstructStringArray() {
+ WTF::Vector<WTF::String> 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<WTF::String, WTF::String> ConstructStringMap() {
+ WTF::HashMap<WTF::String, WTF::String> 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<WTF::Vector<WTF::String>>* expected_arr,
+ const base::Closure& closure,
+ const WTF::Optional<WTF::Vector<WTF::String>>& arr) {
+ EXPECT_EQ(*expected_arr, arr);
+ closure.Run();
+}
+
+void ExpectStringMap(
+ WTF::Optional<WTF::HashMap<WTF::String, WTF::String>>* expected_map,
+ const base::Closure& closure,
+ const WTF::Optional<WTF::HashMap<WTF::String, WTF::String>>& map) {
+ EXPECT_EQ(*expected_map, map);
+ closure.Run();
+}
+
+} // namespace
+
+TEST_F(WTFTypesTest, Serialization_WTFVectorToWTFVector) {
+ using MojomType = ArrayDataView<StringDataView>;
+
+ WTF::Vector<WTF::String> strs = ConstructStringArray();
+ auto cloned_strs = strs;
+
+ mojo::internal::SerializationContext context;
+ size_t size =
+ mojo::internal::PrepareToSerialize<MojomType>(cloned_strs, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::ContainerValidateParams validate_params(
+ 0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr));
+ mojo::internal::Serialize<MojomType>(cloned_strs, &buf, &data,
+ &validate_params, &context);
+
+ WTF::Vector<WTF::String> strs2;
+ mojo::internal::Deserialize<MojomType>(data, &strs2, &context);
+
+ EXPECT_EQ(strs, strs2);
+}
+
+TEST_F(WTFTypesTest, Serialization_WTFVectorToStlVector) {
+ using MojomType = ArrayDataView<StringDataView>;
+
+ WTF::Vector<WTF::String> strs = ConstructStringArray();
+ auto cloned_strs = strs;
+
+ mojo::internal::SerializationContext context;
+ size_t size =
+ mojo::internal::PrepareToSerialize<MojomType>(cloned_strs, &context);
+
+ mojo::internal::FixedBufferForTesting buf(size);
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ mojo::internal::ContainerValidateParams validate_params(
+ 0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr));
+ mojo::internal::Serialize<MojomType>(cloned_strs, &buf, &data,
+ &validate_params, &context);
+
+ std::vector<base::Optional<std::string>> strs2;
+ mojo::internal::Deserialize<MojomType>(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<TestWTF>(MakeRequest(&ptr)));
+
+ WTF::Vector<WTF::String> 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<std::string>;
+ // - 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<TestWTF>(MakeRequest(&ptr)));
+
+ WTF::Optional<WTF::Vector<WTF::String>> 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<WTF::Vector<WTF::String>> is unchanged after
+ // the following conversion:
+ // - serialized;
+ // - deserialized as
+ // base::Optional<std::vector<base::Optional<std::string>>>;
+ // - serialized;
+ // - deserialized as WTF::Optional<WTF::Vector<WTF::String>>.
+ 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<TestWTF>(MakeRequest(&ptr)));
+
+ WTF::Optional<WTF::HashMap<WTF::String, WTF::String>> 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<WTF::HashMap<WTF::String, WTF::String>> is
+ // unchanged after the following conversion:
+ // - serialized;
+ // - deserialized as base::Optional<
+ // std::unordered_map<std::string, base::Optional<std::string>>>;
+ // - serialized;
+ // - deserialized as WTF::Optional<WTF::HashMap<WTF::String,
+ // WTF::String>>.
+ ptr->EchoStringMap(maps[i],
+ base::Bind(&ExpectStringMap, base::Unretained(&maps[i]),
+ loop.QuitClosure()));
+ loop.Run();
+ }
+}
+
+} // namespace test
+} // namespace mojo