// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "testing/gtest/include/gtest/gtest.h"

#include "base/allocator/dispatcher/configuration.h"
#include "base/allocator/dispatcher/initializer.h"
#include "base/allocator/dispatcher/testing/observer_mock.h"
#include "base/allocator/dispatcher/testing/tools.h"

#include <functional>
#include <map>
#include <tuple>

namespace base::allocator::dispatcher {
namespace testing {

// A mock Dispatcher for testing. Since Initializer and Dispatcher rely on
// templating, we can't employ GoogleMocks for mocking. The mock dispatcher
// records the number of invocations of Initialize for a given tuple of
// observers.
struct Dispatcher {
  Dispatcher() = default;

  ~Dispatcher() {
    for (const auto& reset_data : reseter_) {
      reset_data.second();
    }
  }

  template <typename... Observers>
  void Initialize(const std::tuple<Observers*...>& observers) {
    ++total_number_of_inits_;
    ++(GetInitCounterForObservers(observers));
  }

  size_t GetTotalInitCounter() const { return total_number_of_inits_; }

  template <typename... Observers>
  size_t& GetInitCounterForObservers(
      const std::tuple<Observers*...>& observers) {
    static std::map<std::tuple<Observers*...>, size_t>
        observer_init_counter_map;
    reseter_[&observer_init_counter_map] = []() {
      observer_init_counter_map.clear();
    };
    return observer_init_counter_map[observers];
  }

  size_t total_number_of_inits_ = 0;
  std::map<void*, std::function<void()>> reseter_;
};
}  // namespace testing

using testing::ObserverMock;

struct BaseAllocatorDispatcherInitializerTest : public ::testing::Test {};

TEST_F(BaseAllocatorDispatcherInitializerTest, VerifyEmptyInitializer) {
  const auto initializer = CreateInitializer();

  EXPECT_EQ(initializer.GetOptionalObservers(), std::make_tuple());
  EXPECT_EQ(initializer.GetMandatoryObservers(), std::make_tuple());
}

TEST_F(BaseAllocatorDispatcherInitializerTest, VerifySettingOptionalObservers) {
  ObserverMock<int> optional_observer_1;
  ObserverMock<float> optional_observer_2;
  ObserverMock<size_t> optional_observer_3;

  auto initializer_1 = CreateInitializer().SetOptionalObservers(
      &optional_observer_1, &optional_observer_2);
  EXPECT_EQ(initializer_1.GetOptionalObservers(),
            std::make_tuple(&optional_observer_1, &optional_observer_2));
  EXPECT_EQ(initializer_1.GetMandatoryObservers(), std::make_tuple());

  auto initializer_2 = initializer_1.SetOptionalObservers(&optional_observer_3);
  EXPECT_EQ(initializer_2.GetOptionalObservers(),
            std::make_tuple(&optional_observer_3));
  EXPECT_EQ(initializer_2.GetMandatoryObservers(), std::make_tuple());

  auto initializer_3 = initializer_2.SetOptionalObservers();
  EXPECT_EQ(initializer_3.GetOptionalObservers(), std::make_tuple());
  EXPECT_EQ(initializer_3.GetMandatoryObservers(), std::make_tuple());
}

TEST_F(BaseAllocatorDispatcherInitializerTest, VerifyAddingOptionalObservers) {
  ObserverMock<int> optional_observer_1;
  ObserverMock<float> optional_observer_2;
  ObserverMock<size_t> optional_observer_3;

  auto initializer_1 = CreateInitializer().AddOptionalObservers(
      &optional_observer_1, &optional_observer_2);
  EXPECT_EQ(initializer_1.GetOptionalObservers(),
            std::make_tuple(&optional_observer_1, &optional_observer_2));
  EXPECT_EQ(initializer_1.GetMandatoryObservers(), std::make_tuple());

  auto initializer_2 = initializer_1.AddOptionalObservers(&optional_observer_3);
  EXPECT_EQ(initializer_2.GetOptionalObservers(),
            std::make_tuple(&optional_observer_1, &optional_observer_2,
                            &optional_observer_3));
  EXPECT_EQ(initializer_2.GetMandatoryObservers(), std::make_tuple());

  auto initializer_3 = initializer_2.AddOptionalObservers();
  EXPECT_EQ(initializer_3.GetOptionalObservers(),
            std::make_tuple(&optional_observer_1, &optional_observer_2,
                            &optional_observer_3));
  EXPECT_EQ(initializer_3.GetMandatoryObservers(), std::make_tuple());

  auto initializer_4 = initializer_3.SetOptionalObservers();
  EXPECT_EQ(initializer_4.GetOptionalObservers(), std::make_tuple());
  EXPECT_EQ(initializer_4.GetMandatoryObservers(), std::make_tuple());
}

TEST_F(BaseAllocatorDispatcherInitializerTest,
       VerifySettingMandatoryObservers) {
  ObserverMock<int> mandatory_observer_1;
  ObserverMock<float> mandatory_observer_2;
  ObserverMock<size_t> mandatory_observer_3;

  auto initializer_1 = CreateInitializer().SetMandatoryObservers(
      &mandatory_observer_1, &mandatory_observer_2);
  EXPECT_EQ(initializer_1.GetMandatoryObservers(),
            std::make_tuple(&mandatory_observer_1, &mandatory_observer_2));
  EXPECT_EQ(initializer_1.GetOptionalObservers(), std::make_tuple());

  auto initializer_2 =
      initializer_1.SetMandatoryObservers(&mandatory_observer_3);
  EXPECT_EQ(initializer_2.GetMandatoryObservers(),
            std::make_tuple(&mandatory_observer_3));
  EXPECT_EQ(initializer_2.GetOptionalObservers(), std::make_tuple());

  auto initializer_3 = initializer_2.SetMandatoryObservers();
  EXPECT_EQ(initializer_3.GetMandatoryObservers(), std::make_tuple());
  EXPECT_EQ(initializer_3.GetOptionalObservers(), std::make_tuple());
}

TEST_F(BaseAllocatorDispatcherInitializerTest, VerifyAddingMandatoryObservers) {
  ObserverMock<int> mandatory_observer_1;
  ObserverMock<float> mandatory_observer_2;
  ObserverMock<size_t> mandatory_observer_3;

  auto initializer_1 = CreateInitializer().AddMandatoryObservers(
      &mandatory_observer_1, &mandatory_observer_2);
  EXPECT_EQ(initializer_1.GetMandatoryObservers(),
            std::make_tuple(&mandatory_observer_1, &mandatory_observer_2));
  EXPECT_EQ(initializer_1.GetOptionalObservers(), std::make_tuple());

  auto initializer_2 =
      initializer_1.AddMandatoryObservers(&mandatory_observer_3);
  EXPECT_EQ(initializer_2.GetMandatoryObservers(),
            std::make_tuple(&mandatory_observer_1, &mandatory_observer_2,
                            &mandatory_observer_3));
  EXPECT_EQ(initializer_2.GetOptionalObservers(), std::make_tuple());

  auto initializer_3 = initializer_2.AddMandatoryObservers();
  EXPECT_EQ(initializer_3.GetMandatoryObservers(),
            std::make_tuple(&mandatory_observer_1, &mandatory_observer_2,
                            &mandatory_observer_3));
  EXPECT_EQ(initializer_3.GetOptionalObservers(), std::make_tuple());

  auto initializer_4 = initializer_3.SetMandatoryObservers();
  EXPECT_EQ(initializer_4.GetMandatoryObservers(), std::make_tuple());
  EXPECT_EQ(initializer_4.GetOptionalObservers(), std::make_tuple());
}

TEST_F(BaseAllocatorDispatcherInitializerTest, VerifyBasicInitialization) {
  ObserverMock<int> optional_observer_1;
  ObserverMock<float> optional_observer_2;
  ObserverMock<size_t> mandatory_observer_1;
  ObserverMock<double> mandatory_observer_2;

  testing::Dispatcher test_dispatcher;

  CreateInitializer()
      .SetMandatoryObservers(&mandatory_observer_1, &mandatory_observer_2)
      .SetOptionalObservers(&optional_observer_1, &optional_observer_2)
      .DoInitialize(test_dispatcher);

  const auto observer_ptrs =
      std::make_tuple(&mandatory_observer_1, &mandatory_observer_2,
                      &optional_observer_1, &optional_observer_2);

  EXPECT_EQ(1ul, test_dispatcher.GetInitCounterForObservers(observer_ptrs));
}

TEST_F(BaseAllocatorDispatcherInitializerTest,
       VerifyInitializationWithMandatoryNullObservers) {
  ObserverMock<int> optional_observer_1;
  ObserverMock<float> optional_observer_2;
  ObserverMock<size_t> mandatory_observer;
  ObserverMock<double>* mandatory_null_observer = nullptr;

  testing::Dispatcher test_dispatcher;

  CreateInitializer()
      .SetMandatoryObservers(&mandatory_observer, mandatory_null_observer)
      .SetOptionalObservers(&optional_observer_1, &optional_observer_2)
      .DoInitialize(test_dispatcher);

  // For mandatory observers being null we expect them to be passed straight
  // down to the dispatcher, which will then perform a check of ALL observers.
  const auto valid_observer_ptrs =
      std::make_tuple(&mandatory_observer, mandatory_null_observer,
                      &optional_observer_1, &optional_observer_2);

  EXPECT_EQ(1ul, test_dispatcher.GetTotalInitCounter());
  EXPECT_EQ(1ul,
            test_dispatcher.GetInitCounterForObservers(valid_observer_ptrs));
}

TEST_F(BaseAllocatorDispatcherInitializerTest,
       VerifyInitializationWithOptionalNullObservers) {
  ObserverMock<int> optional_observer;
  ObserverMock<float>* optional_null_observer = nullptr;
  ObserverMock<size_t> mandatory_observer_1;
  ObserverMock<double> mandatory_observer_2;

  testing::Dispatcher test_dispatcher;

  CreateInitializer()
      .SetMandatoryObservers(&mandatory_observer_1, &mandatory_observer_2)
      .SetOptionalObservers(&optional_observer, optional_null_observer)
      .DoInitialize(test_dispatcher);

  const auto valid_observer_ptrs = std::make_tuple(
      &mandatory_observer_1, &mandatory_observer_2, &optional_observer);

  EXPECT_EQ(1ul, test_dispatcher.GetTotalInitCounter());
  EXPECT_EQ(1ul,
            test_dispatcher.GetInitCounterForObservers(valid_observer_ptrs));
}

}  // namespace base::allocator::dispatcher