// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/heap/cppgc/incremental-marking-schedule.h"

#include "src/base/platform/time.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/stats-collector.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace cppgc {
namespace internal {

namespace {

class IncrementalMarkingScheduleTest : public ::testing::Test {
 public:
  static const size_t kObjectSize;
};

const size_t IncrementalMarkingScheduleTest::kObjectSize =
    100 * IncrementalMarkingSchedule::kMinimumMarkedBytesPerIncrementalStep;

}  // namespace

TEST_F(IncrementalMarkingScheduleTest, FirstStepReturnsDefaultDuration) {
  IncrementalMarkingSchedule schedule;
  schedule.NotifyIncrementalMarkingStart();
  schedule.SetElapsedTimeForTesting(0);
  EXPECT_EQ(IncrementalMarkingSchedule::kMinimumMarkedBytesPerIncrementalStep,
            schedule.GetNextIncrementalStepDuration(kObjectSize));
}

// If marking is not behind schedule and very small time passed between steps
// the oracle should return the minimum step duration.
TEST_F(IncrementalMarkingScheduleTest, NoTimePassedReturnsMinimumDuration) {
  IncrementalMarkingSchedule schedule;
  schedule.NotifyIncrementalMarkingStart();
  // Add incrementally marked bytes to tell oracle this is not the first step.
  schedule.UpdateMutatorThreadMarkedBytes(
      IncrementalMarkingSchedule::kMinimumMarkedBytesPerIncrementalStep);
  schedule.SetElapsedTimeForTesting(0);
  EXPECT_EQ(IncrementalMarkingSchedule::kMinimumMarkedBytesPerIncrementalStep,
            schedule.GetNextIncrementalStepDuration(kObjectSize));
}

TEST_F(IncrementalMarkingScheduleTest, OracleDoesntExccedMaximumStepDuration) {
  IncrementalMarkingSchedule schedule;
  schedule.NotifyIncrementalMarkingStart();
  // Add incrementally marked bytes to tell oracle this is not the first step.
  static constexpr size_t kMarkedBytes = 1;
  schedule.UpdateMutatorThreadMarkedBytes(kMarkedBytes);
  schedule.SetElapsedTimeForTesting(
      IncrementalMarkingSchedule::kEstimatedMarkingTimeMs);
  EXPECT_EQ(kObjectSize - kMarkedBytes,
            schedule.GetNextIncrementalStepDuration(kObjectSize));
}

TEST_F(IncrementalMarkingScheduleTest, AheadOfScheduleReturnsMinimumDuration) {
  IncrementalMarkingSchedule schedule;
  schedule.NotifyIncrementalMarkingStart();
  // Add incrementally marked bytes to tell oracle this is not the first step.
  schedule.UpdateMutatorThreadMarkedBytes(
      IncrementalMarkingSchedule::kMinimumMarkedBytesPerIncrementalStep);
  schedule.AddConcurrentlyMarkedBytes(0.6 * kObjectSize);
  schedule.SetElapsedTimeForTesting(
      0.5 * IncrementalMarkingSchedule::kEstimatedMarkingTimeMs);
  EXPECT_EQ(IncrementalMarkingSchedule::kMinimumMarkedBytesPerIncrementalStep,
            schedule.GetNextIncrementalStepDuration(kObjectSize));
}

TEST_F(IncrementalMarkingScheduleTest, BehindScheduleReturnsCorrectDuration) {
  IncrementalMarkingSchedule schedule;
  schedule.NotifyIncrementalMarkingStart();
  schedule.UpdateMutatorThreadMarkedBytes(0.1 * kObjectSize);
  schedule.AddConcurrentlyMarkedBytes(0.25 * kObjectSize);
  schedule.SetElapsedTimeForTesting(
      0.5 * IncrementalMarkingSchedule::kEstimatedMarkingTimeMs);
  EXPECT_EQ(0.15 * kObjectSize,
            schedule.GetNextIncrementalStepDuration(kObjectSize));
  schedule.AddConcurrentlyMarkedBytes(0.05 * kObjectSize);
  schedule.SetElapsedTimeForTesting(
      0.5 * IncrementalMarkingSchedule::kEstimatedMarkingTimeMs);
  EXPECT_EQ(0.1 * kObjectSize,
            schedule.GetNextIncrementalStepDuration(kObjectSize));
  schedule.AddConcurrentlyMarkedBytes(0.05 * kObjectSize);
  schedule.SetElapsedTimeForTesting(
      0.5 * IncrementalMarkingSchedule::kEstimatedMarkingTimeMs);
  EXPECT_EQ(0.05 * kObjectSize,
            schedule.GetNextIncrementalStepDuration(kObjectSize));
}

}  // namespace internal
}  // namespace cppgc
