// Copyright 2021 The Chromium 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 "base/message_loop/timer_slack.h"

#include "build/build_config.h"

#if BUILDFLAG(IS_MAC)
#include "base/message_loop/message_pump_kqueue.h"
#include "base/message_loop/message_pump_mac.h"
#endif  // BUILDFLAG(IS_MAC)
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace {

#if BUILDFLAG(IS_MAC)
class TestMessagePumpKqueue : public MessagePumpKqueue {
 public:
  size_t set_wakeup_timer_event_calls() {
    return set_wakeup_timer_event_calls_;
  }
  const kevent64_s& last_timer_event() { return last_timer_event_; }

 protected:
  void SetWakeupTimerEvent(const base::TimeTicks& wakeup_time,
                           bool use_slack,
                           kevent64_s* timer_event) override;

 private:
  size_t set_wakeup_timer_event_calls_{0};
  kevent64_s last_timer_event_;
};

void TestMessagePumpKqueue::SetWakeupTimerEvent(
    const base::TimeTicks& wakeup_time,
    bool use_slack,
    kevent64_s* timer_event) {
  // Call through to the super class, then persist what it set.
  MessagePumpKqueue::SetWakeupTimerEvent(wakeup_time, use_slack, timer_event);

  ++set_wakeup_timer_event_calls_;
  last_timer_event_ = *timer_event;
}
#endif  // BUILDFLAG(IS_MAC)

TEST(TimerSlackTest, LudicrousTimerSlackDefaultsOff) {
  EXPECT_FALSE(IsLudicrousTimerSlackEnabled());
  EXPECT_EQ(base::Milliseconds(1500), GetLudicrousTimerSlack());

#if BUILDFLAG(IS_MAC)
  MessagePumpCFRunLoop message_pump_cf_run_loop;
  EXPECT_EQ(
      MessagePumpCFRunLoop::LudicrousSlackSetting::kLudicrousSlackUninitialized,
      message_pump_cf_run_loop.GetLudicrousSlackStateForTesting());

  // Tickle the delay work path.
  const base::TimeTicks now = TimeTicks::Now();
  message_pump_cf_run_loop.ScheduleDelayedWork({now, now});
  EXPECT_EQ(MessagePumpCFRunLoop::LudicrousSlackSetting::kLudicrousSlackOff,
            message_pump_cf_run_loop.GetLudicrousSlackStateForTesting());

  TestMessagePumpKqueue message_pump_kqueue;
  // Tickle the delay work path.
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(now);
  EXPECT_EQ(1u, message_pump_kqueue.set_wakeup_timer_event_calls());
  EXPECT_FALSE(message_pump_kqueue.last_timer_event().fflags & NOTE_LEEWAY);
  EXPECT_FALSE(message_pump_kqueue
                   .GetIsLudicrousTimerSlackEnabledAndNotSuspendedForTesting());
#endif  // BUILDFLAG(IS_MAC)
}

TEST(TimerSlackTest, LudicrousTimerSlackObservesFeature) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      base::features::kLudicrousTimerSlack);

  EXPECT_TRUE(IsLudicrousTimerSlackEnabled());
  EXPECT_EQ(base::Milliseconds(1500), GetLudicrousTimerSlack());

#if BUILDFLAG(IS_MAC)
  MessagePumpCFRunLoop message_pump_cf_run_loop;
  EXPECT_EQ(
      MessagePumpCFRunLoop::LudicrousSlackSetting::kLudicrousSlackUninitialized,
      message_pump_cf_run_loop.GetLudicrousSlackStateForTesting());

  // Tickle the delay work path.
  const base::TimeTicks now = TimeTicks::Now();
  message_pump_cf_run_loop.ScheduleDelayedWork({now, now});
  EXPECT_EQ(MessagePumpCFRunLoop::LudicrousSlackSetting::kLudicrousSlackOn,
            message_pump_cf_run_loop.GetLudicrousSlackStateForTesting());

  // Validate that suspend works for the CF message loop.
  SuspendLudicrousTimerSlack();
  EXPECT_EQ(
      MessagePumpCFRunLoop::LudicrousSlackSetting::kLudicrousSlackSuspended,
      message_pump_cf_run_loop.GetLudicrousSlackStateForTesting());
  ResumeLudicrousTimerSlack();

  TestMessagePumpKqueue message_pump_kqueue;
  EXPECT_TRUE(message_pump_kqueue
                  .GetIsLudicrousTimerSlackEnabledAndNotSuspendedForTesting());
#endif  // BUILDFLAG(IS_MAC)
}

#if BUILDFLAG(IS_MAC)
TEST(TimerSlackTest, LudicrousTimerSlackResetsTimerOnSuspendResume) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      base::features::kLudicrousTimerSlack);

  TestMessagePumpKqueue message_pump_kqueue;
  // Tickle the delay work path with the same |now| each time.
  const base::TimeTicks now = TimeTicks::Now();
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(now);
  EXPECT_EQ(1u, message_pump_kqueue.set_wakeup_timer_event_calls());
  EXPECT_TRUE(message_pump_kqueue.last_timer_event().fflags & NOTE_LEEWAY);
  EXPECT_TRUE(message_pump_kqueue
                  .GetIsLudicrousTimerSlackEnabledAndNotSuspendedForTesting());
  // A second tickle should not set the timer again.
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(now);
  EXPECT_EQ(1u, message_pump_kqueue.set_wakeup_timer_event_calls());

  // Now suspend ludicrous slack and verify that the timer is reset.
  SuspendLudicrousTimerSlack();
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(now);
  EXPECT_EQ(2u, message_pump_kqueue.set_wakeup_timer_event_calls());
  EXPECT_FALSE(message_pump_kqueue.last_timer_event().fflags & NOTE_LEEWAY);
  EXPECT_FALSE(message_pump_kqueue
                   .GetIsLudicrousTimerSlackEnabledAndNotSuspendedForTesting());
  // A second tickle should not set the timer again.
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(now);
  EXPECT_EQ(2u, message_pump_kqueue.set_wakeup_timer_event_calls());

  // Resume and validate again.
  ResumeLudicrousTimerSlack();
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(now);
  EXPECT_EQ(3u, message_pump_kqueue.set_wakeup_timer_event_calls());
  EXPECT_TRUE(message_pump_kqueue.last_timer_event().fflags & NOTE_LEEWAY);
  EXPECT_TRUE(message_pump_kqueue
                  .GetIsLudicrousTimerSlackEnabledAndNotSuspendedForTesting());
}

TEST(TimerSlackTest, LudicrousTimerSlackDoesntDoubleCancelOnSuspendToggle) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      base::features::kLudicrousTimerSlack);

  TestMessagePumpKqueue message_pump_kqueue;
  // Tickle the delay work path with the same |now| each time.
  const base::TimeTicks now = TimeTicks::Now();
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(now);
  EXPECT_EQ(1u, message_pump_kqueue.set_wakeup_timer_event_calls());
  EXPECT_TRUE(message_pump_kqueue.last_timer_event().fflags & NOTE_LEEWAY);
  EXPECT_TRUE(message_pump_kqueue
                  .GetIsLudicrousTimerSlackEnabledAndNotSuspendedForTesting());

  // Cancel the timed work.
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(base::TimeTicks::Max());
  EXPECT_EQ(2u, message_pump_kqueue.set_wakeup_timer_event_calls());

  // Now suspend ludicrous slack and verify that the timer is not reset again.
  SuspendLudicrousTimerSlack();
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(base::TimeTicks::Max());
  EXPECT_EQ(2u, message_pump_kqueue.set_wakeup_timer_event_calls());

  // Resume and validate again.
  ResumeLudicrousTimerSlack();
  message_pump_kqueue.MaybeUpdateWakeupTimerForTesting(base::TimeTicks::Max());
  EXPECT_EQ(2u, message_pump_kqueue.set_wakeup_timer_event_calls());
}
#endif  // BUILDFLAG(IS_MAC)

TEST(TimerSlackTest, LudicrousTimerSlackSlackObservesFeatureParam) {
  base::test::ScopedFeatureList scoped_feature_list;
  base::FieldTrialParams parameters;
  parameters["slack_ms"] = "12345ms";
  scoped_feature_list.InitAndEnableFeatureWithParameters(
      base::features::kLudicrousTimerSlack, parameters);

  EXPECT_EQ(base::Milliseconds(12345), GetLudicrousTimerSlack());
}

TEST(TimerSlackTest, LudicrousTimerSlackSlackSuspendResume) {
  EXPECT_FALSE(base::IsLudicrousTimerSlackSuspended());

  base::SuspendLudicrousTimerSlack();
  EXPECT_TRUE(base::IsLudicrousTimerSlackSuspended());
  base::SuspendLudicrousTimerSlack();
  EXPECT_TRUE(base::IsLudicrousTimerSlackSuspended());
  base::ResumeLudicrousTimerSlack();
  EXPECT_TRUE(base::IsLudicrousTimerSlackSuspended());
  base::ResumeLudicrousTimerSlack();
  EXPECT_FALSE(base::IsLudicrousTimerSlackSuspended());
}

}  // namespace
}  // namespace base
