// 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