summaryrefslogtreecommitdiff
path: root/mojo/public/cpp/bindings/tests/binding_unittest.cc
diff options
context:
space:
mode:
authorHidehiko Abe <hidehiko@google.com>2018-04-23 19:57:41 -0700
committerandroid-build-merger <android-build-merger@google.com>2018-04-23 19:57:41 -0700
commit0ab20ac2283987e63b0e7c1318db2a5cf7c668d2 (patch)
treebd2d04362f66c36d4279f7a9735ba21ea3a2a021 /mojo/public/cpp/bindings/tests/binding_unittest.cc
parente389a13ad8648d89ac670ca06f68c9b32976351c (diff)
parentb268b43ac6fdbc4f3a2ed1429b99ace424906090 (diff)
downloadlibchrome-0ab20ac2283987e63b0e7c1318db2a5cf7c668d2.tar.gz
Migrate libmojo repository into libchrome, part 2.
am: b268b43ac6 Change-Id: I7b2c3d7cfed03673c33eb497be20e238bd0aac33
Diffstat (limited to 'mojo/public/cpp/bindings/tests/binding_unittest.cc')
-rw-r--r--mojo/public/cpp/bindings/tests/binding_unittest.cc611
1 files changed, 611 insertions, 0 deletions
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