diff options
author | Luis Hector Chavez <lhchavez@google.com> | 2016-12-28 10:56:26 -0800 |
---|---|---|
committer | Luis Hector Chavez <lhchavez@google.com> | 2016-12-28 10:56:26 -0800 |
commit | 645501c2ab19a559ce82a1d5a29ced159a4c30fb (patch) | |
tree | b3d9637e908d074f905d3dacbeb1db8361fafb8a /mojo/public/cpp/bindings/tests/binding_unittest.cc | |
parent | 3d72bec9be57b7d199d2cbd7139308bb3c3c34bb (diff) | |
download | libmojo-645501c2ab19a559ce82a1d5a29ced159a4c30fb.tar.gz |
Initial import of libmojo r405848
This brings libmojo in sync with libchrome.
Bug: 27569341
Test: mma -j32 libmojo
Change-Id: Ia7cb877e46dd3f86f18888b5d8d80bef5468b266
Diffstat (limited to 'mojo/public/cpp/bindings/tests/binding_unittest.cc')
-rw-r--r-- | mojo/public/cpp/bindings/tests/binding_unittest.cc | 414 |
1 files changed, 414 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 0000000..4838ee8 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/binding_unittest.cc @@ -0,0 +1,414 @@ +// 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/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/strong_binding.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; + 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 = GetProxy(&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 = GetProxy(&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, GetProxy(&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, GetProxy(&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(), + GetProxy(&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, GetProxy(&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 = GetProxy(&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 = GetProxy(&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); +} + +// 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; + ServiceImpl impl(&was_deleted); + sample::ServicePtr ptr; + auto request = GetProxy(&ptr); + ptr.set_connection_error_handler( + SetFlagAndRunClosure(&encountered_error, run_loop.QuitClosure())); + bool called = false; + base::RunLoop run_loop2; + { + StrongBinding<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 StrongBinding is out of scope we should detect an error on the + // other end of the pipe. + run_loop.Run(); + EXPECT_TRUE(encountered_error); + // But destroying the StrongBinding doesn't destroy the object. + ASSERT_FALSE(was_deleted); +} + +class ServiceImplWithStrongBinding : public ServiceImpl { + public: + ServiceImplWithStrongBinding(bool* was_deleted, + InterfaceRequest<sample::Service> request) + : ServiceImpl(was_deleted), binding_(this, std::move(request)) {} + + StrongBinding<sample::Service>& binding() { return binding_; } + + private: + StrongBinding<sample::Service> binding_; + + DISALLOW_COPY_AND_ASSIGN(ServiceImplWithStrongBinding); +}; + +// 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(), + GetProxy(&ptr)); + + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(was_deleted); + + ptr.reset(); + EXPECT_FALSE(was_deleted); + run_loop.Run(); + EXPECT_TRUE(was_deleted); +} + +// Tests that even when the implementation object owns the StrongBinding, that +// the implementation can still be deleted (which should result in the message +// pipe being closed). Also checks that the connection error handler doesn't get +// called. +TEST_F(StrongBindingTest, ExplicitDeleteImpl) { + bool ptr_error_handler_called = false; + sample::ServicePtr ptr; + auto request = GetProxy(&ptr); + base::RunLoop run_loop; + ptr.set_connection_error_handler( + SetFlagAndRunClosure(&ptr_error_handler_called, run_loop.QuitClosure())); + bool was_deleted = false; + ServiceImplWithStrongBinding* impl = + new ServiceImplWithStrongBinding(&was_deleted, std::move(request)); + bool binding_error_handler_called = false; + impl->binding().set_connection_error_handler( + SetFlagAndRunClosure(&binding_error_handler_called)); + + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(ptr_error_handler_called); + EXPECT_FALSE(was_deleted); + + delete impl; + EXPECT_FALSE(ptr_error_handler_called); + EXPECT_TRUE(was_deleted); + was_deleted = false; // It shouldn't be double-deleted! + run_loop.Run(); + EXPECT_TRUE(ptr_error_handler_called); + EXPECT_FALSE(was_deleted); + + EXPECT_FALSE(binding_error_handler_called); +} + +} // namespace +} // mojo |