//===-- common_test.cpp -----------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "internal_defs.h"
#include "tests/scudo_unit_test.h"

#include "common.h"
#include "mem_map.h"

#include <errno.h>
#include <string.h>
#include <sys/mman.h>

#include <algorithm>
#include <vector>

namespace scudo {

TEST(ScudoCommonTest, VerifyGetResidentPages) {
  if (!SCUDO_LINUX)
    TEST_SKIP("Only valid on linux systems.");

  constexpr uptr NumPages = 512;
  const uptr SizeBytes = NumPages * getPageSizeCached();

  MemMapT MemMap;
  ASSERT_TRUE(MemMap.map(/*Addr=*/0U, SizeBytes, "ResidentMemorySize"));
  ASSERT_NE(MemMap.getBase(), 0U);

  // Only android seems to properly detect when single pages are touched.
#if SCUDO_ANDROID
  // Verify nothing should be mapped in right after the map is created.
  EXPECT_EQ(0U, MemMap.getResidentPages(MemMap.getBase(), SizeBytes));

  // Touch a page.
  u8 *Data = reinterpret_cast<u8 *>(MemMap.getBase());
  Data[0] = 1;
  EXPECT_EQ(1U, MemMap.getResidentPages(MemMap.getBase(), SizeBytes));

  // Touch a non-consective page.
  Data[getPageSizeCached() * 2] = 1;
  EXPECT_EQ(2U, MemMap.getResidentPages(MemMap.getBase(), SizeBytes));

  // Touch a page far enough that the function has to make multiple calls
  // to mincore.
  Data[getPageSizeCached() * 300] = 1;
  EXPECT_EQ(3U, MemMap.getResidentPages(MemMap.getBase(), SizeBytes));

  // Touch another page in the same range to make sure the second
  // read is working.
  Data[getPageSizeCached() * 400] = 1;
  EXPECT_EQ(4U, MemMap.getResidentPages(MemMap.getBase(), SizeBytes));
#endif

  // Now write the whole thing.
  memset(reinterpret_cast<void *>(MemMap.getBase()), 1, SizeBytes);
  s64 ResidentPages = MemMap.getResidentPages(MemMap.getBase(), SizeBytes);
  EXPECT_EQ(NumPages, static_cast<uintptr_t>(ResidentPages));

  MemMap.unmap();
}

TEST(ScudoCommonTest, VerifyReleasePagesToOS) {
  if (!SCUDO_LINUX)
    TEST_SKIP("Only valid on linux systems.");

  constexpr uptr NumPages = 1000;
  const uptr SizeBytes = NumPages * getPageSizeCached();

  MemMapT MemMap;
  ASSERT_TRUE(MemMap.map(/*Addr=*/0U, SizeBytes, "ResidentMemorySize"));
  ASSERT_NE(MemMap.getBase(), 0U);

  void *P = reinterpret_cast<void *>(MemMap.getBase());
  EXPECT_EQ(0U, MemMap.getResidentPages(MemMap.getBase(), SizeBytes));

  // Make the entire map resident.
  memset(P, 1, SizeBytes);
  s64 ResidentPages = MemMap.getResidentPages(MemMap.getBase(), SizeBytes);
  if (ResidentPages >= 0)
    EXPECT_EQ(NumPages, static_cast<uintptr_t>(ResidentPages));

  // Should release the memory to the kernel immediately.
  MemMap.releasePagesToOS(MemMap.getBase(), SizeBytes);
  EXPECT_EQ(0U, MemMap.getResidentPages(MemMap.getBase(), SizeBytes));

  // Make the entire map resident again.
  memset(P, 1, SizeBytes);
  ResidentPages = MemMap.getResidentPages(MemMap.getBase(), SizeBytes);
  EXPECT_EQ(NumPages, static_cast<uintptr_t>(ResidentPages));

  MemMap.unmap();
}

TEST(ScudoCommonTest, Zeros) {
  const uptr Size = 1ull << 20;

  MemMapT MemMap;
  ASSERT_TRUE(MemMap.map(/*Addr=*/0U, Size, "Zeros"));
  ASSERT_NE(MemMap.getBase(), 0U);
  uptr *P = reinterpret_cast<uptr *>(MemMap.getBase());
  const ptrdiff_t N = Size / sizeof(uptr);
  EXPECT_EQ(std::count(P, P + N, 0), N);

  memset(P, 1, Size);
  EXPECT_EQ(std::count(P, P + N, 0), 0);

  MemMap.releasePagesToOS(MemMap.getBase(), Size);
  EXPECT_EQ(std::count(P, P + N, 0), N);

  MemMap.unmap();
}

} // namespace scudo
