/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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 <Allocator.h>

#include <gtest/gtest.h>
#include <ScopedDisableMalloc.h>


std::function<void()> ScopedAlarm::func_;

class AllocatorTest : public testing::Test {
 protected:
  AllocatorTest() : heap(), disable_malloc_() {}
  virtual void SetUp() {
    heap_count = 0;
  }
  virtual void TearDown() {
    ASSERT_EQ(heap_count, 0);
    ASSERT_TRUE(heap.empty());
    ASSERT_FALSE(disable_malloc_.timed_out());
  }
  Heap heap;
 private:
  ScopedDisableMallocTimeout disable_malloc_;
};

TEST_F(AllocatorTest, simple) {
  Allocator<char[100]> allocator(heap);
  void *ptr = allocator.allocate();
  ASSERT_TRUE(ptr != NULL);
  allocator.deallocate(ptr);
}

TEST_F(AllocatorTest, multiple) {
  Allocator<char[100]> allocator(heap);
  void *ptr1 = allocator.allocate();
  ASSERT_TRUE(ptr1 != NULL);
  void *ptr2 = allocator.allocate();
  ASSERT_TRUE(ptr2 != NULL);
  ASSERT_NE(ptr1, ptr2);
  allocator.deallocate(ptr1);
  void *ptr3 = allocator.allocate();
  ASSERT_EQ(ptr1, ptr3);
  allocator.deallocate(ptr3);
  allocator.deallocate(ptr2);
}

TEST_F(AllocatorTest, many) {
  const int num = 4096;
  const int size = 128;
  Allocator<char[size]> allocator(heap);
  void *ptr[num];
  for (int i = 0; i < num; i++) {
    ptr[i] = allocator.allocate();
    memset(ptr[i], 0xaa, size);
    *(reinterpret_cast<unsigned char*>(ptr[i])) = i;
  }

  for (int i = 0; i < num; i++) {
    for (int j = 0; j < num; j++) {
      if (i != j) {
        ASSERT_NE(ptr[i], ptr[j]);
      }
    }
  }

  for (int i = 0; i < num; i++) {
    ASSERT_EQ(*(reinterpret_cast<unsigned char*>(ptr[i])), i & 0xFF);
    allocator.deallocate(ptr[i]);
  }
}

TEST_F(AllocatorTest, large) {
  const size_t size = 1024 * 1024;
  Allocator<char[size]> allocator(heap);
  void *ptr = allocator.allocate();
  memset(ptr, 0xaa, size);
  allocator.deallocate(ptr);
}

TEST_F(AllocatorTest, many_large) {
  const int num = 128;
  const int size = 1024 * 1024;
  Allocator<char[size]> allocator(heap);
  void *ptr[num];
  for (int i = 0; i < num; i++) {
    ptr[i] = allocator.allocate();
    memset(ptr[i], 0xaa, size);
    *(reinterpret_cast<unsigned char*>(ptr[i])) = i;
  }

  for (int i = 0; i < num; i++) {
    ASSERT_EQ(*(reinterpret_cast<unsigned char*>(ptr[i])), i & 0xFF);
    allocator.deallocate(ptr[i]);
  }
}

TEST_F(AllocatorTest, copy) {
  Allocator<char[100]> a(heap);
  Allocator<char[200]> b = a;
  Allocator<char[300]> c(b);
  Allocator<char[100]> d(a);
  Allocator<char[100]> e(heap);

  ASSERT_EQ(a, b);
  ASSERT_EQ(a, c);
  ASSERT_EQ(a, d);
  ASSERT_EQ(a, e);

  void* ptr1 = a.allocate();
  void* ptr2 = b.allocate();
  void* ptr3 = c.allocate();
  void* ptr4 = d.allocate();

  b.deallocate(ptr1);
  d.deallocate(ptr2);
  a.deallocate(ptr3);
  c.deallocate(ptr4);
}

TEST_F(AllocatorTest, stl_vector) {
  auto v = allocator::vector<int>(Allocator<int>(heap));
  for (int i = 0; i < 1024; i++) {
    v.push_back(i);
  }
  for (int i = 0; i < 1024; i++) {
    ASSERT_EQ(v[i], i);
  }
  v.clear();
}

TEST_F(AllocatorTest, stl_list) {
  auto v = allocator::list<int>(Allocator<int>(heap));
  for (int i = 0; i < 1024; i++) {
    v.push_back(i);
  }
  int i = 0;
  for (auto iter = v.begin(); iter != v.end(); iter++, i++) {
    ASSERT_EQ(*iter, i);
  }
  v.clear();
}

TEST_F(AllocatorTest, shared) {
  Allocator<int> allocator(heap);

  Allocator<int>::shared_ptr ptr = allocator.make_shared(0);
  {
    auto ptr2 = ptr;  // NOLINT, test copy of ptr
  }
  ASSERT_NE(ptr, nullptr);
}

TEST_F(AllocatorTest, unique) {
  Allocator<int> allocator(heap);

  Allocator<int>::unique_ptr ptr = allocator.make_unique(0);

  ASSERT_NE(ptr, nullptr);
}
