// Copyright 2015 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <gtest/gtest.h>

#include "./allocator_testing_utils.h"
#include "rcutils/allocator.h"
#include "rcutils/testing/fault_injection.h"

#include "osrf_testing_tools_cpp/memory_tools/memory_tools.hpp"
#include "osrf_testing_tools_cpp/scope_exit.hpp"

using osrf_testing_tools_cpp::memory_tools::disable_monitoring_in_all_threads;
using osrf_testing_tools_cpp::memory_tools::enable_monitoring_in_all_threads;
using osrf_testing_tools_cpp::memory_tools::on_unexpected_calloc;
using osrf_testing_tools_cpp::memory_tools::on_unexpected_free;
using osrf_testing_tools_cpp::memory_tools::on_unexpected_malloc;
using osrf_testing_tools_cpp::memory_tools::on_unexpected_realloc;

class TestAllocatorFixture : public ::testing::Test
{
public:
  TestAllocatorFixture() {}

  void SetUp()
  {
    osrf_testing_tools_cpp::memory_tools::initialize();
    enable_monitoring_in_all_threads();
  }

  void TearDown()
  {
    disable_monitoring_in_all_threads();
    osrf_testing_tools_cpp::memory_tools::uninitialize();
  }
};

/* Tests the default allocator.
 */
TEST_F(TestAllocatorFixture, test_default_allocator_normal)
{
  size_t mallocs = 0;
  size_t reallocs = 0;
  size_t callocs = 0;
  size_t frees = 0;
  on_unexpected_malloc([&mallocs]() {mallocs++;});
  on_unexpected_realloc([&reallocs]() {reallocs++;});
  on_unexpected_calloc([&callocs]() {callocs++;});
  on_unexpected_free([&frees]() {frees++;});

  rcutils_allocator_t allocator;
  EXPECT_NO_MEMORY_OPERATIONS(
  {
    allocator = rcutils_get_default_allocator();
  });
  EXPECT_EQ(0u, mallocs);
  EXPECT_EQ(0u, reallocs);
  EXPECT_EQ(0u, callocs);
  EXPECT_EQ(0u, frees);

  void * allocated_memory = nullptr;
  EXPECT_NO_MEMORY_OPERATIONS(
  {
    allocated_memory = allocator.allocate(1024, allocator.state);
  });
  EXPECT_EQ(1u, mallocs);
  EXPECT_NE(nullptr, allocated_memory);
  EXPECT_NO_MEMORY_OPERATIONS(
  {
    allocated_memory = allocator.reallocate(allocated_memory, 2048, allocator.state);
  });
  EXPECT_EQ(1u, reallocs);
  EXPECT_NE(nullptr, allocated_memory);
  EXPECT_NO_MEMORY_OPERATIONS(
  {
    allocator.deallocate(allocated_memory, allocator.state);
    allocated_memory = allocator.zero_allocate(1024, sizeof(void *), allocator.state);
  });
  EXPECT_EQ(1u, callocs);
  EXPECT_NE(nullptr, allocated_memory);
  EXPECT_NO_MEMORY_OPERATIONS(
  {
    allocator.deallocate(allocated_memory, allocator.state);
  });
  EXPECT_EQ(1u, mallocs);
  EXPECT_EQ(1u, reallocs);
  EXPECT_EQ(1u, callocs);
  EXPECT_EQ(2u, frees);
}

TEST(test_allocator, realloc_failing_allocators) {
  void * allocated_memory = nullptr;
  EXPECT_EQ(nullptr, rcutils_reallocf(allocated_memory, 1024, nullptr));

  auto failing_allocator = get_failing_allocator();
  EXPECT_EQ(nullptr, rcutils_reallocf(allocated_memory, 1024, &failing_allocator));
}

TEST(test_allocator, default_allocator_maybe_fail) {
  rcutils_allocator_t allocator = rcutils_get_default_allocator();

  // Check allocate
  rcutils_fault_injection_set_count(RCUTILS_FAULT_INJECTION_FAIL_NOW);
  EXPECT_EQ(nullptr, allocator.allocate(1u, allocator.state));
  EXPECT_EQ(RCUTILS_FAULT_INJECTION_NEVER_FAIL, rcutils_fault_injection_get_count());

  void * pointer = allocator.allocate(1u, allocator.state);
  ASSERT_NE(nullptr, pointer);
  OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
  {
    allocator.deallocate(pointer, allocator.state);
  });

  // Check reallocate
  rcutils_fault_injection_set_count(RCUTILS_FAULT_INJECTION_FAIL_NOW);
  EXPECT_EQ(nullptr, allocator.reallocate(pointer, 1u, allocator.state));
  EXPECT_EQ(RCUTILS_FAULT_INJECTION_NEVER_FAIL, rcutils_fault_injection_get_count());

  // Check zero_allocate
  rcutils_fault_injection_set_count(RCUTILS_FAULT_INJECTION_FAIL_NOW);
  EXPECT_EQ(nullptr, allocator.zero_allocate(1u, 1u, allocator.state));
  EXPECT_EQ(RCUTILS_FAULT_INJECTION_NEVER_FAIL, rcutils_fault_injection_get_count());
}
