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