| // 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 <utility> |
| |
| #include "mojo/public/cpp/bindings/binding.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "mojo/public/cpp/utility/run_loop.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 { |
| |
| template <typename Method, typename Class> |
| class RunnableImpl { |
| public: |
| RunnableImpl(Method method, Class instance) |
| : method_(method), instance_(instance) {} |
| template <typename... Args> |
| void Run(Args... args) const { |
| (instance_->*method_)(args...); |
| } |
| |
| private: |
| Method method_; |
| Class instance_; |
| }; |
| |
| template <typename Method, typename Class> |
| RunnableImpl<Method, Class> MakeRunnable(Method method, Class object) { |
| return RunnableImpl<Method, Class>(method, object); |
| } |
| |
| typedef mojo::Callback<void(double)> CalcCallback; |
| |
| class MathCalculatorImpl : public math::Calculator { |
| public: |
| explicit MathCalculatorImpl(InterfaceRequest<math::Calculator> request) |
| : total_(0.0), binding_(this, request.Pass()) {} |
| ~MathCalculatorImpl() override {} |
| |
| void CloseMessagePipe() { binding_.Close(); } |
| |
| void WaitForIncomingMethodCall() { binding_.WaitForIncomingMethodCall(); } |
| |
| 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_); |
| } |
| |
| private: |
| double total_; |
| Binding<math::Calculator> binding_; |
| }; |
| |
| class MathCalculatorUI { |
| public: |
| explicit MathCalculatorUI(math::CalculatorPtr calculator) |
| : calculator_(calculator.Pass()), |
| output_(0.0), |
| callback_(MakeRunnable(&MathCalculatorUI::Output, this)) {} |
| |
| bool WaitForIncomingResponse() { |
| return calculator_.WaitForIncomingResponse(); |
| } |
| |
| bool WaitForIncomingResponseWithTimeout(MojoDeadline deadline) { |
| return calculator_.WaitForIncomingResponseWithTimeout(deadline); |
| } |
| |
| bool encountered_error() const { return calculator_.encountered_error(); } |
| |
| void Add(double value) { calculator_->Add(value, callback_); } |
| |
| void Subtract(double value) { calculator_->Add(-value, callback_); } |
| |
| void Multiply(double value) { calculator_->Multiply(value, callback_); } |
| |
| void Divide(double value) { calculator_->Multiply(1.0 / value, callback_); } |
| |
| double GetOutput() const { return output_; } |
| |
| private: |
| void Output(double output) { output_ = output; } |
| |
| math::CalculatorPtr calculator_; |
| double output_; |
| Callback<void(double)> callback_; |
| }; |
| |
| class SelfDestructingMathCalculatorUI { |
| public: |
| explicit SelfDestructingMathCalculatorUI(math::CalculatorPtr calculator) |
| : calculator_(calculator.Pass()), nesting_level_(0) { |
| ++num_instances_; |
| } |
| |
| void BeginTest(bool nested) { |
| nesting_level_ = nested ? 2 : 1; |
| calculator_->Add( |
| 1.0, MakeRunnable(&SelfDestructingMathCalculatorUI::Output, this)); |
| } |
| |
| static int num_instances() { return num_instances_; } |
| |
| void Output(double value) { |
| if (--nesting_level_ > 0) { |
| // Add some more and wait for re-entrant call to Output! |
| calculator_->Add( |
| 1.0, MakeRunnable(&SelfDestructingMathCalculatorUI::Output, this)); |
| RunLoop::current()->RunUntilIdle(); |
| } else { |
| 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, request.Pass()) {} |
| |
| int max_call_depth() { return max_call_depth_; } |
| |
| void Frobinate(sample::FooPtr foo, |
| sample::Service::BazOptions baz, |
| mojo::InterfaceHandle<sample::Port> 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_; } |
| |
| 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; } |
| |
| int64_t integer_; |
| }; |
| |
| class InterfacePtrTest : public testing::Test { |
| public: |
| ~InterfacePtrTest() override { loop_.RunUntilIdle(); } |
| |
| void PumpMessages() { loop_.RunUntilIdle(); } |
| |
| private: |
| RunLoop loop_; |
| }; |
| |
| TEST_F(InterfacePtrTest, IsBound) { |
| math::CalculatorPtr calc; |
| EXPECT_FALSE(calc.is_bound()); |
| EXPECT_FALSE(calc); |
| MathCalculatorImpl calc_impl(GetProxy(&calc)); |
| EXPECT_TRUE(calc.is_bound()); |
| EXPECT_TRUE(calc); |
| } |
| |
| TEST_F(InterfacePtrTest, EndToEnd) { |
| math::CalculatorPtr calc; |
| MathCalculatorImpl calc_impl(GetProxy(&calc)); |
| |
| // Suppose this is instantiated in a process that has pipe1_. |
| MathCalculatorUI calculator_ui(calc.Pass()); |
| |
| calculator_ui.Add(2.0); |
| calculator_ui.Multiply(5.0); |
| |
| PumpMessages(); |
| |
| EXPECT_EQ(10.0, calculator_ui.GetOutput()); |
| } |
| |
| TEST_F(InterfacePtrTest, EndToEnd_Synchronous) { |
| math::CalculatorPtr calc; |
| MathCalculatorImpl calc_impl(GetProxy(&calc)); |
| |
| // Suppose this is instantiated in a process that has pipe1_. |
| MathCalculatorUI calculator_ui(calc.Pass()); |
| |
| EXPECT_EQ(0.0, calculator_ui.GetOutput()); |
| |
| calculator_ui.Add(2.0); |
| EXPECT_EQ(0.0, calculator_ui.GetOutput()); |
| calc_impl.WaitForIncomingMethodCall(); |
| calculator_ui.WaitForIncomingResponse(); |
| EXPECT_EQ(2.0, calculator_ui.GetOutput()); |
| |
| calculator_ui.Multiply(5.0); |
| EXPECT_EQ(2.0, calculator_ui.GetOutput()); |
| calc_impl.WaitForIncomingMethodCall(); |
| calculator_ui.WaitForIncomingResponse(); |
| EXPECT_EQ(10.0, calculator_ui.GetOutput()); |
| |
| EXPECT_FALSE(calculator_ui.WaitForIncomingResponseWithTimeout(0)); |
| EXPECT_FALSE(calculator_ui.encountered_error()); |
| calculator_ui.Multiply(3.0); |
| calc_impl.WaitForIncomingMethodCall(); |
| EXPECT_TRUE(calculator_ui.WaitForIncomingResponseWithTimeout(0)); |
| EXPECT_EQ(30.0, calculator_ui.GetOutput()); |
| } |
| |
| TEST_F(InterfacePtrTest, Movable) { |
| math::CalculatorPtr a; |
| math::CalculatorPtr b; |
| MathCalculatorImpl calc_impl(GetProxy(&b)); |
| |
| EXPECT_TRUE(!a); |
| EXPECT_FALSE(!b); |
| |
| a = b.Pass(); |
| |
| 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 = math::CalculatorPtr::Create( |
| InterfaceHandle<math::Calculator>(std::move(pipe.handle0), 0u)); |
| |
| EXPECT_FALSE(!a); |
| |
| a.reset(); |
| |
| EXPECT_TRUE(!a); |
| EXPECT_FALSE(a.internal_state()->router_for_testing()); |
| |
| // 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(InterfaceHandle<math::Calculator>()); |
| EXPECT_FALSE(ptr.get()); |
| EXPECT_FALSE(ptr); |
| } |
| |
| TEST_F(InterfacePtrTest, EncounteredError) { |
| math::CalculatorPtr proxy; |
| MathCalculatorImpl calc_impl(GetProxy(&proxy)); |
| |
| MathCalculatorUI calculator_ui(proxy.Pass()); |
| |
| calculator_ui.Add(2.0); |
| PumpMessages(); |
| EXPECT_EQ(2.0, calculator_ui.GetOutput()); |
| EXPECT_FALSE(calculator_ui.encountered_error()); |
| |
| calculator_ui.Multiply(5.0); |
| EXPECT_FALSE(calculator_ui.encountered_error()); |
| |
| // Close the server. |
| calc_impl.CloseMessagePipe(); |
| |
| // The state change isn't picked up locally yet. |
| EXPECT_FALSE(calculator_ui.encountered_error()); |
| |
| PumpMessages(); |
| |
| // OK, now we see the error. |
| EXPECT_TRUE(calculator_ui.encountered_error()); |
| } |
| |
| TEST_F(InterfacePtrTest, EncounteredErrorCallback) { |
| math::CalculatorPtr proxy; |
| MathCalculatorImpl calc_impl(GetProxy(&proxy)); |
| |
| bool encountered_error = false; |
| proxy.set_connection_error_handler( |
| [&encountered_error]() { encountered_error = true; }); |
| |
| MathCalculatorUI calculator_ui(proxy.Pass()); |
| |
| calculator_ui.Add(2.0); |
| PumpMessages(); |
| EXPECT_EQ(2.0, calculator_ui.GetOutput()); |
| EXPECT_FALSE(calculator_ui.encountered_error()); |
| |
| calculator_ui.Multiply(5.0); |
| EXPECT_FALSE(calculator_ui.encountered_error()); |
| |
| // Close the server. |
| calc_impl.CloseMessagePipe(); |
| |
| // The state change isn't picked up locally yet. |
| EXPECT_FALSE(calculator_ui.encountered_error()); |
| |
| PumpMessages(); |
| |
| // 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(GetProxy(&proxy)); |
| |
| EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); |
| |
| SelfDestructingMathCalculatorUI* impl = |
| new SelfDestructingMathCalculatorUI(proxy.Pass()); |
| impl->BeginTest(false); |
| |
| PumpMessages(); |
| |
| EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); |
| } |
| |
| TEST_F(InterfacePtrTest, NestedDestroyInterfacePtrOnMethodResponse) { |
| math::CalculatorPtr proxy; |
| MathCalculatorImpl calc_impl(GetProxy(&proxy)); |
| |
| EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); |
| |
| SelfDestructingMathCalculatorUI* impl = |
| new SelfDestructingMathCalculatorUI(proxy.Pass()); |
| impl->BeginTest(true); |
| |
| PumpMessages(); |
| |
| EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); |
| } |
| |
| TEST_F(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) { |
| sample::ServicePtr proxy; |
| ReentrantServiceImpl impl(GetProxy(&proxy)); |
| |
| proxy->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| sample::Service::FrobinateCallback()); |
| proxy->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| sample::Service::FrobinateCallback()); |
| |
| PumpMessages(); |
| |
| EXPECT_EQ(2, impl.max_call_depth()); |
| } |
| |
| TEST_F(InterfacePtrTest, QueryVersion) { |
| IntegerAccessorImpl impl; |
| sample::IntegerAccessorPtr ptr; |
| Binding<sample::IntegerAccessor> binding(&impl, GetProxy(&ptr)); |
| |
| EXPECT_EQ(0u, ptr.version()); |
| |
| auto callback = [](uint32_t version) { EXPECT_EQ(3u, version); }; |
| ptr.QueryVersion(callback); |
| |
| PumpMessages(); |
| |
| EXPECT_EQ(3u, ptr.version()); |
| } |
| |
| TEST_F(InterfacePtrTest, RequireVersion) { |
| IntegerAccessorImpl impl; |
| sample::IntegerAccessorPtr ptr; |
| Binding<sample::IntegerAccessor> binding(&impl, GetProxy(&ptr)); |
| |
| EXPECT_EQ(0u, ptr.version()); |
| |
| ptr.RequireVersion(1u); |
| EXPECT_EQ(1u, ptr.version()); |
| ptr->SetInteger(123, sample::Enum::VALUE); |
| PumpMessages(); |
| EXPECT_FALSE(ptr.encountered_error()); |
| EXPECT_EQ(123, impl.integer()); |
| |
| ptr.RequireVersion(3u); |
| EXPECT_EQ(3u, ptr.version()); |
| ptr->SetInteger(456, sample::Enum::VALUE); |
| PumpMessages(); |
| 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()); |
| ptr->SetInteger(789, sample::Enum::VALUE); |
| PumpMessages(); |
| 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(ScopedMessagePipeHandle handle, |
| bool* error_received, |
| bool* destroyed) |
| : error_received_(error_received), |
| destroyed_(destroyed), |
| binding_(this, handle.Pass()) { |
| binding_.set_connection_error_handler( |
| [this]() { *error_received_ = true; }); |
| } |
| ~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* error_received_; |
| bool* destroyed_; |
| |
| StrongBinding<math::Calculator> binding_; |
| }; |
| |
| TEST(StrongConnectorTest, Math) { |
| RunLoop loop; |
| |
| bool error_received = false; |
| bool destroyed = false; |
| MessagePipe pipe; |
| new StrongMathCalculatorImpl(pipe.handle0.Pass(), &error_received, |
| &destroyed); |
| |
| math::CalculatorPtr calc; |
| calc.Bind(InterfaceHandle<math::Calculator>(pipe.handle1.Pass(), 0u)); |
| |
| { |
| // Suppose this is instantiated in a process that has the other end of the |
| // message pipe. |
| MathCalculatorUI calculator_ui(calc.Pass()); |
| |
| calculator_ui.Add(2.0); |
| calculator_ui.Multiply(5.0); |
| |
| loop.RunUntilIdle(); |
| |
| 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. |
| |
| loop.RunUntilIdle(); |
| EXPECT_TRUE(error_received); |
| EXPECT_TRUE(destroyed); |
| } |
| |
| class WeakMathCalculatorImpl : public math::Calculator { |
| public: |
| WeakMathCalculatorImpl(ScopedMessagePipeHandle handle, |
| bool* error_received, |
| bool* destroyed) |
| : error_received_(error_received), |
| destroyed_(destroyed), |
| binding_(this, handle.Pass()) { |
| binding_.set_connection_error_handler( |
| [this]() { *error_received_ = true; }); |
| } |
| ~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_; |
| |
| Binding<math::Calculator> binding_; |
| }; |
| |
| TEST(WeakConnectorTest, Math) { |
| RunLoop loop; |
| |
| bool error_received = false; |
| bool destroyed = false; |
| MessagePipe pipe; |
| WeakMathCalculatorImpl impl(pipe.handle0.Pass(), &error_received, &destroyed); |
| |
| math::CalculatorPtr calc; |
| calc.Bind(InterfaceHandle<math::Calculator>(pipe.handle1.Pass(), 0u)); |
| |
| { |
| // Suppose this is instantiated in a process that has the other end of the |
| // message pipe. |
| MathCalculatorUI calculator_ui(calc.Pass()); |
| |
| calculator_ui.Add(2.0); |
| calculator_ui.Multiply(5.0); |
| |
| loop.RunUntilIdle(); |
| |
| 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. |
| } |
| |
| loop.RunUntilIdle(); |
| EXPECT_TRUE(error_received); |
| EXPECT_FALSE(destroyed); |
| } |
| |
| class CImpl : public C { |
| public: |
| CImpl(bool* d_called, InterfaceRequest<C> request) |
| : d_called_(d_called), |
| binding_(this, request.Pass()) {} |
| ~CImpl() override {} |
| |
| private: |
| void D() override { |
| *d_called_ = true; |
| } |
| |
| bool* d_called_; |
| StrongBinding<C> binding_; |
| }; |
| |
| class BImpl : public B { |
| public: |
| BImpl(bool* d_called, InterfaceRequest<B> request) |
| : d_called_(d_called), |
| binding_(this, request.Pass()) {} |
| ~BImpl() override {} |
| |
| private: |
| void GetC(InterfaceRequest<C> c) override { |
| new CImpl(d_called_, c.Pass()); |
| } |
| |
| bool* d_called_; |
| StrongBinding<B> binding_; |
| }; |
| |
| class AImpl : public A { |
| public: |
| explicit AImpl(InterfaceRequest<A> request) |
| : d_called_(false), |
| binding_(this, request.Pass()) {} |
| ~AImpl() override {} |
| |
| bool d_called() const { return d_called_; } |
| |
| private: |
| void GetB(InterfaceRequest<B> b) override { |
| new BImpl(&d_called_, b.Pass()); |
| } |
| |
| bool d_called_; |
| Binding<A> binding_; |
| }; |
| |
| TEST_F(InterfacePtrTest, Scoping) { |
| APtr a; |
| AImpl a_impl(GetProxy(&a)); |
| |
| EXPECT_FALSE(a_impl.d_called()); |
| |
| { |
| BPtr b; |
| a->GetB(GetProxy(&b)); |
| CPtr c; |
| b->GetC(GetProxy(&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()); |
| PumpMessages(); |
| EXPECT_TRUE(a_impl.d_called()); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace mojo |