/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <gtest/gtest.h>
#include "ob_log_utils.h"       // current_time
#include "ob_small_arena.h"

#include "lib/allocator/ob_concurrent_fifo_allocator.h"   // ObConcurrentFIFOAllocator

#define ALLOC_AND_CHECK(size) ALLOC_ALIGN_AND_CHECK(sizeof(void*), size)

#define ALLOC_ALIGN_AND_CHECK(align_size, size) \
    do { \
      int64_t alloc_size = (size); \
      int64_t align = (align_size); \
      int64_t max_small_size = MAX_SMALL_ALLOC_SIZE(align); \
      void *ptr = sa.alloc_aligned(alloc_size, align); \
\
      ASSERT_TRUE(NULL != ptr); \
      EXPECT_EQ(0, reinterpret_cast<int64_t>(ptr) & (align - 1)); \
\
      if (alloc_size > max_small_size) { \
        large_alloc_count++; \
      } else { \
        small_alloc_count++; \
      } \
\
      EXPECT_EQ(small_alloc_count, sa.get_small_alloc_count()); \
      EXPECT_EQ(large_alloc_count, sa.get_large_alloc_count()); \
\
      ((char *)ptr)[alloc_size - 1] = 'a'; \
    } while (0)

#define MAX_SMALL_ALLOC_SIZE(align) (MAX_SMALL_ALLOC_SIZE_WITHOUT_ALIGN - align + 1)

namespace oceanbase
{
namespace liboblog
{

static const int64_t SMALL_ARENA_PAGE_SIZE = 1024;
static const int64_t MAX_SMALL_ALLOC_SIZE_WITHOUT_ALIGN = SMALL_ARENA_PAGE_SIZE - ObSmallArena::SMALL_PAGE_HEADER_SIZE;
static const int64_t PAGE_SIZE = 1024;

using namespace common;

// TODO: add multi thread test
class TestSmallArena : public ::testing::Test
{
public:
  TestSmallArena() {}
  ~TestSmallArena() {}

  virtual void SetUp();
  virtual void TearDown();

public:
  ObConcurrentFIFOAllocator large_allocator_;
  static const uint64_t tenant_id_ = 0;
};

void TestSmallArena::SetUp()
{
  const static int64_t LARGE_PAGE_SIZE = (1LL << 26);
  const static int64_t LARGE_TOTAL_LIMIT = (1LL << 34);
  const static int64_t LARGE_HOLD_LIMIT = LARGE_PAGE_SIZE;
  ASSERT_EQ(OB_SUCCESS, large_allocator_.init(LARGE_TOTAL_LIMIT, LARGE_HOLD_LIMIT, LARGE_PAGE_SIZE));

  srandom((unsigned int)get_timestamp());
}

void TestSmallArena::TearDown()
{
  large_allocator_.destroy();
}

TEST_F(TestSmallArena, smoke_test)
{
  ObSmallArena sa;
  int64_t small_alloc_count = 0;
  int64_t large_alloc_count = 0;

  sa.set_allocator(PAGE_SIZE, large_allocator_);

  ALLOC_AND_CHECK(8);
  ALLOC_AND_CHECK(16);
  ALLOC_AND_CHECK(256);
  ALLOC_AND_CHECK(512);
  ALLOC_AND_CHECK(17 + 8);
  ALLOC_AND_CHECK(17 + 16);
  ALLOC_AND_CHECK(17 + 256);
  ALLOC_AND_CHECK(17 + 512);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 8);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 16);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 256);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 512);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 17 + 8);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 17 + 16);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 17 + 256);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 17 + 512);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 8);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 16);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 256);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 512);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 8);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 16);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 256);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 512);

  sa.reset();
  small_alloc_count = 0;
  large_alloc_count = 0;

  ALLOC_AND_CHECK(8);
  ALLOC_AND_CHECK(16);
  ALLOC_AND_CHECK(256);
  ALLOC_AND_CHECK(512);
  ALLOC_AND_CHECK(17 + 8);
  ALLOC_AND_CHECK(17 + 16);
  ALLOC_AND_CHECK(17 + 256);
  ALLOC_AND_CHECK(17 + 512);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 8);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 16);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 256);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 512);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 17 + 8);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 17 + 16);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 17 + 256);
  ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE + 17 + 512);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 8);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 16);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 256);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 512);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 8);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 16);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 256);
  ALLOC_AND_CHECK((1<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 512);

  sa.reset();
  small_alloc_count = 0;
  large_alloc_count = 0;
}

TEST_F(TestSmallArena, alloc_small)
{
  static const int64_t TEST_COUNT = 10;
  int64_t max_alloc_size = MAX_SMALL_ALLOC_SIZE(8);
  int64_t small_alloc_count = 0;
  int64_t large_alloc_count = 0;
  ObSmallArena sa;

  sa.set_allocator(PAGE_SIZE, large_allocator_);

  for (int i = 0; i < TEST_COUNT; i++) {
    ALLOC_AND_CHECK(8);
    ALLOC_AND_CHECK(16);
    ALLOC_AND_CHECK(256);
    ALLOC_AND_CHECK(512);
    ALLOC_AND_CHECK(17 + 8);
    ALLOC_AND_CHECK(17 + 16);
    ALLOC_AND_CHECK(17 + 256);
    ALLOC_AND_CHECK(17 + 512);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 15);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 16);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 17);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 64);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 67);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 128);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 129);

    ALLOC_AND_CHECK(random() % (max_alloc_size + 1));
  }

  sa.reset();
  small_alloc_count = 0;
  large_alloc_count = 0;

  for (int i = 0; i < TEST_COUNT; i++) {
    ALLOC_AND_CHECK(8);
    ALLOC_AND_CHECK(16);
    ALLOC_AND_CHECK(256);
    ALLOC_AND_CHECK(512);
    ALLOC_AND_CHECK(17 + 8);
    ALLOC_AND_CHECK(17 + 16);
    ALLOC_AND_CHECK(17 + 256);
    ALLOC_AND_CHECK(17 + 512);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 15);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 16);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 17);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 64);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 67);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 128);
    ALLOC_AND_CHECK(SMALL_ARENA_PAGE_SIZE - 129);

    ALLOC_AND_CHECK(random() % (max_alloc_size + 1));
  }

  sa.reset();
  small_alloc_count = 0;
  large_alloc_count = 0;
}

TEST_F(TestSmallArena, alloc_large)
{
  static const int64_t TEST_COUNT = 10;
  int64_t max_alloc_size = (1 << 22);
  int64_t min_alloc_size = MAX_SMALL_ALLOC_SIZE(8) + 1;
  int64_t small_alloc_count = 0;
  int64_t large_alloc_count = 0;
  ObSmallArena sa;

  sa.set_allocator(PAGE_SIZE, large_allocator_);

  for (int i = 0; i < TEST_COUNT; i++) {
    ALLOC_AND_CHECK(min_alloc_size + 0);
    ALLOC_AND_CHECK(min_alloc_size + 1);
    ALLOC_AND_CHECK(min_alloc_size + 2);
    ALLOC_AND_CHECK(min_alloc_size + 4);
    ALLOC_AND_CHECK(min_alloc_size + 8);
    ALLOC_AND_CHECK(min_alloc_size + 16);
    ALLOC_AND_CHECK(min_alloc_size + 256);
    ALLOC_AND_CHECK(min_alloc_size + 512);
    ALLOC_AND_CHECK(min_alloc_size + 17 + 8);
    ALLOC_AND_CHECK(min_alloc_size + 17 + 16);
    ALLOC_AND_CHECK(min_alloc_size + 17 + 256);
    ALLOC_AND_CHECK(min_alloc_size + 17 + 512);
    ALLOC_AND_CHECK(min_alloc_size + 1 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 2 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 3 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 4 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 5 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 6 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 7 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK((1LL << 10) + 11);
    ALLOC_AND_CHECK((1LL << 12) + 13);
    ALLOC_AND_CHECK((1LL << 18) + 17);
    ALLOC_AND_CHECK((1LL << 19) + 19);
    ALLOC_AND_CHECK((1LL << 20) + 7);
    ALLOC_AND_CHECK((1LL << 21) + 3);

    ALLOC_AND_CHECK((random() % (max_alloc_size)) + min_alloc_size);
  }

  sa.reset();
  small_alloc_count = 0;
  large_alloc_count = 0;

  for (int i = 0; i < TEST_COUNT; i++) {
    ALLOC_AND_CHECK(min_alloc_size + 0);
    ALLOC_AND_CHECK(min_alloc_size + 1);
    ALLOC_AND_CHECK(min_alloc_size + 2);
    ALLOC_AND_CHECK(min_alloc_size + 4);
    ALLOC_AND_CHECK(min_alloc_size + 8);
    ALLOC_AND_CHECK(min_alloc_size + 16);
    ALLOC_AND_CHECK(min_alloc_size + 256);
    ALLOC_AND_CHECK(min_alloc_size + 512);
    ALLOC_AND_CHECK(min_alloc_size + 17 + 8);
    ALLOC_AND_CHECK(min_alloc_size + 17 + 16);
    ALLOC_AND_CHECK(min_alloc_size + 17 + 256);
    ALLOC_AND_CHECK(min_alloc_size + 17 + 512);
    ALLOC_AND_CHECK(min_alloc_size + 1 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 2 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 3 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 4 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 5 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 6 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK(min_alloc_size + 7 * SMALL_ARENA_PAGE_SIZE);
    ALLOC_AND_CHECK((1LL << 10) + 11);
    ALLOC_AND_CHECK((1LL << 12) + 13);
    ALLOC_AND_CHECK((1LL << 18) + 17);
    ALLOC_AND_CHECK((1LL << 19) + 19);
    ALLOC_AND_CHECK((1LL << 20) + 7);
    ALLOC_AND_CHECK((1LL << 21) + 3);

    ALLOC_AND_CHECK((random() % (max_alloc_size)) + min_alloc_size);
  }

  sa.reset();
  small_alloc_count = 0;
  large_alloc_count = 0;
}

TEST_F(TestSmallArena, alloc_align)
{
  int64_t small_alloc_count = 0;
  int64_t large_alloc_count = 0;
  ObSmallArena sa;

  sa.set_allocator(PAGE_SIZE, large_allocator_);

  ALLOC_ALIGN_AND_CHECK(1, 4);
  ALLOC_ALIGN_AND_CHECK(16, 8);
  ALLOC_ALIGN_AND_CHECK(32, 16);
  ALLOC_ALIGN_AND_CHECK(64, 256);
  ALLOC_ALIGN_AND_CHECK(128, 512);
  ALLOC_ALIGN_AND_CHECK(16, 17 + 8);
  ALLOC_ALIGN_AND_CHECK(32, 17 + 16);
  ALLOC_ALIGN_AND_CHECK(64, 17 + 256);
  ALLOC_ALIGN_AND_CHECK(128, 17 + 512);
  ALLOC_ALIGN_AND_CHECK(16, SMALL_ARENA_PAGE_SIZE + 8);
  ALLOC_ALIGN_AND_CHECK(32, SMALL_ARENA_PAGE_SIZE + 16);
  ALLOC_ALIGN_AND_CHECK(64, SMALL_ARENA_PAGE_SIZE + 256);
  ALLOC_ALIGN_AND_CHECK(128, SMALL_ARENA_PAGE_SIZE + 512);
  ALLOC_ALIGN_AND_CHECK(16, SMALL_ARENA_PAGE_SIZE + 17 + 8);
  ALLOC_ALIGN_AND_CHECK(32, SMALL_ARENA_PAGE_SIZE + 17 + 16);
  ALLOC_ALIGN_AND_CHECK(64, SMALL_ARENA_PAGE_SIZE + 17 + 256);
  ALLOC_ALIGN_AND_CHECK(128, SMALL_ARENA_PAGE_SIZE + 17 + 512);
  ALLOC_ALIGN_AND_CHECK(16, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 8);
  ALLOC_ALIGN_AND_CHECK(32, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 16);
  ALLOC_ALIGN_AND_CHECK(64, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 256);
  ALLOC_ALIGN_AND_CHECK(128, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 512);
  ALLOC_ALIGN_AND_CHECK(16, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 8);
  ALLOC_ALIGN_AND_CHECK(32, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 16);
  ALLOC_ALIGN_AND_CHECK(64, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 256);
  ALLOC_ALIGN_AND_CHECK(128, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 512);

  sa.reset();
  small_alloc_count = 0;
  large_alloc_count = 0;

  ALLOC_ALIGN_AND_CHECK(1, 4);
  ALLOC_ALIGN_AND_CHECK(16, 8);
  ALLOC_ALIGN_AND_CHECK(32, 16);
  ALLOC_ALIGN_AND_CHECK(64, 256);
  ALLOC_ALIGN_AND_CHECK(128, 512);
  ALLOC_ALIGN_AND_CHECK(16, 17 + 8);
  ALLOC_ALIGN_AND_CHECK(32, 17 + 16);
  ALLOC_ALIGN_AND_CHECK(64, 17 + 256);
  ALLOC_ALIGN_AND_CHECK(128, 17 + 512);
  ALLOC_ALIGN_AND_CHECK(16, SMALL_ARENA_PAGE_SIZE + 8);
  ALLOC_ALIGN_AND_CHECK(32, SMALL_ARENA_PAGE_SIZE + 16);
  ALLOC_ALIGN_AND_CHECK(64, SMALL_ARENA_PAGE_SIZE + 256);
  ALLOC_ALIGN_AND_CHECK(128, SMALL_ARENA_PAGE_SIZE + 512);
  ALLOC_ALIGN_AND_CHECK(16, SMALL_ARENA_PAGE_SIZE + 17 + 8);
  ALLOC_ALIGN_AND_CHECK(32, SMALL_ARENA_PAGE_SIZE + 17 + 16);
  ALLOC_ALIGN_AND_CHECK(64, SMALL_ARENA_PAGE_SIZE + 17 + 256);
  ALLOC_ALIGN_AND_CHECK(128, SMALL_ARENA_PAGE_SIZE + 17 + 512);
  ALLOC_ALIGN_AND_CHECK(16, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 8);
  ALLOC_ALIGN_AND_CHECK(32, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 16);
  ALLOC_ALIGN_AND_CHECK(64, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 256);
  ALLOC_ALIGN_AND_CHECK(128, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 512);
  ALLOC_ALIGN_AND_CHECK(16, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 8);
  ALLOC_ALIGN_AND_CHECK(32, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 16);
  ALLOC_ALIGN_AND_CHECK(64, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 256);
  ALLOC_ALIGN_AND_CHECK(128, (1LL<<21) + SMALL_ARENA_PAGE_SIZE + 17 + 512);

  sa.reset();
  small_alloc_count = 0;
  large_alloc_count = 0;
}

TEST_F(TestSmallArena, init_err)
{
  void *ptr = NULL;
  ObSmallArena sa;
  ptr = sa.alloc(8); EXPECT_TRUE(NULL == ptr);

  sa.set_allocator(-1, large_allocator_);
  ptr = sa.alloc(8); EXPECT_TRUE(NULL == ptr);

  sa.reset();
}

TEST_F(TestSmallArena, invalid_args)
{
  void *ptr = NULL;
  ObSmallArena sa;
  sa.set_allocator(PAGE_SIZE, large_allocator_);
  ptr = sa.alloc(-1); EXPECT_TRUE(NULL == ptr);
  ptr = sa.alloc_aligned(1,3); EXPECT_TRUE(NULL == ptr);
  ptr = sa.alloc_aligned(1, 1024); EXPECT_TRUE(NULL == ptr);
  sa.reset();
}

} // ns liboblog
} // ns oceanbase

int main(int argc, char **argv)
{
  oceanbase::common::ObLogger::get_logger().set_log_level("DEBUG");
  OB_LOGGER.set_log_level("DEBUG");
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
