// Copyright 2016 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 "content/child/memory/child_memory_coordinator_impl.h"

#if defined(OS_ANDROID)
#include "content/child/memory/child_memory_coordinator_impl_android.h"
#endif  // defined(OS_ANDROID)

#include <memory>

#include "base/memory/memory_coordinator_client_registry.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

class MockMemoryCoordinatorHandle : public mojom::MemoryCoordinatorHandle {
 public:
  MockMemoryCoordinatorHandle() : binding_(this) {}

  void AddChild(mojom::ChildMemoryCoordinatorPtr child) override {
    child_ = std::move(child);
  }

  mojom::MemoryCoordinatorHandlePtr Bind() {
    DCHECK(!binding_.is_bound());
    return binding_.CreateInterfacePtrAndBind();
  }

  mojom::ChildMemoryCoordinatorPtr& child() { return child_; }

 private:
  mojo::Binding<mojom::MemoryCoordinatorHandle> binding_;
  mojom::ChildMemoryCoordinatorPtr child_ = nullptr;
};

class ChildMemoryCoordinatorImplTest : public testing::Test,
                                       public ChildMemoryCoordinatorDelegate {
 public:
  ChildMemoryCoordinatorImplTest()
      : message_loop_(new base::MessageLoop) {
    auto parent = coordinator_handle_.Bind();
    coordinator_impl_ = CreateChildMemoryCoordinator(std::move(parent), this);
    // Needs to run loop to initalize mojo pointers including |child_| in
    // MockMemoryCoordinatorHandle.
    base::RunLoop loop;
    loop.RunUntilIdle();
  }

  void RegisterClient(base::MemoryCoordinatorClient* client) {
    base::MemoryCoordinatorClientRegistry::GetInstance()->Register(client);
  }

  void UnregisterClient(base::MemoryCoordinatorClient* client) {
    base::MemoryCoordinatorClientRegistry::GetInstance()->Unregister(client);
  }

  mojom::ChildMemoryCoordinatorPtr& coordinator() {
    return coordinator_handle_.child();
  }

  ChildMemoryCoordinatorImpl& coordinator_impl() {
    return *coordinator_impl_.get();
  }

  void ChangeState(mojom::MemoryState state) {
    base::RunLoop loop;
    coordinator()->OnStateChange(state);
    loop.RunUntilIdle();
  }

  // ChildMemoryCoordinatorDelegate implementation:
  void OnTrimMemoryImmediately() override {
    on_trim_memory_called_ = true;
  }

 protected:
  bool on_trim_memory_called_ = false;

 private:
  std::unique_ptr<base::MessageLoop> message_loop_;
  MockMemoryCoordinatorHandle coordinator_handle_;
  std::unique_ptr<ChildMemoryCoordinatorImpl> coordinator_impl_;

  DISALLOW_COPY_AND_ASSIGN(ChildMemoryCoordinatorImplTest);
};

namespace {

class MockMemoryCoordinatorClient final : public base::MemoryCoordinatorClient {
public:
  void OnMemoryStateChange(base::MemoryState state) override {
    last_state_ = state;
  }

  base::MemoryState last_state() const { return last_state_; }

 private:
  base::MemoryState last_state_ = base::MemoryState::UNKNOWN;
};

class MemoryCoordinatorTestThread : public base::Thread,
                                    public base::MemoryCoordinatorClient {
 public:
  MemoryCoordinatorTestThread(
      const std::string& name)
      : Thread(name) {}
  ~MemoryCoordinatorTestThread() override { Stop(); }

  void Init() override {
    base::MemoryCoordinatorClientRegistry::GetInstance()->Register(this);
  }

  void OnMemoryStateChange(base::MemoryState state) override {
    EXPECT_EQ(message_loop(), base::MessageLoop::current());
    last_state_ = state;
  }

  void CheckLastState(base::MemoryState state) {
    task_runner()->PostTask(
        FROM_HERE,
        base::Bind(&MemoryCoordinatorTestThread::CheckLastStateInternal,
                   base::Unretained(this), state));
  }

 private:
  void CheckLastStateInternal(base::MemoryState state) {
    base::RunLoop loop;
    loop.RunUntilIdle();
    EXPECT_EQ(state, last_state_);
  }

  base::MemoryState last_state_ = base::MemoryState::UNKNOWN;
};

TEST_F(ChildMemoryCoordinatorImplTest, SingleClient) {
  MockMemoryCoordinatorClient client;
  RegisterClient(&client);

  ChangeState(mojom::MemoryState::THROTTLED);
  EXPECT_EQ(base::MemoryState::THROTTLED, client.last_state());

  ChangeState(mojom::MemoryState::NORMAL);
  EXPECT_EQ(base::MemoryState::NORMAL, client.last_state());

  UnregisterClient(&client);
  ChangeState(mojom::MemoryState::THROTTLED);
  EXPECT_TRUE(base::MemoryState::THROTTLED != client.last_state());
}

TEST_F(ChildMemoryCoordinatorImplTest, MultipleClients) {
  MemoryCoordinatorTestThread t1("thread 1");
  MemoryCoordinatorTestThread t2("thread 2");

  t1.StartAndWaitForTesting();
  t2.StartAndWaitForTesting();

  ChangeState(mojom::MemoryState::THROTTLED);
  t1.CheckLastState(base::MemoryState::THROTTLED);
  t2.CheckLastState(base::MemoryState::THROTTLED);

  ChangeState(mojom::MemoryState::NORMAL);
  t1.CheckLastState(base::MemoryState::NORMAL);
  t2.CheckLastState(base::MemoryState::NORMAL);

  t1.Stop();
  t2.Stop();
}

#if defined(OS_ANDROID)
TEST_F(ChildMemoryCoordinatorImplTest, OnTrimMemoryImmediately) {
  // TRIM_MEMORY_COMPLETE defined in ComponentCallbacks2.
  static const int kTrimMemoryComplete = 80;

  ChildMemoryCoordinatorImplAndroid& coordinator_android =
      static_cast<ChildMemoryCoordinatorImplAndroid&>(coordinator_impl());
  coordinator_android.OnTrimMemory(kTrimMemoryComplete);
  EXPECT_EQ(true, on_trim_memory_called_);
}
#endif  // defined(OS_ANDROID)

}  // namespace

}  // namespace content
