| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/timer/wall_clock_timer.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/test/mock_callback.h" |
| #include "base/test/power_monitor_test.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/task_environment.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| |
| class WallClockTimerTest : public ::testing::Test { |
| protected: |
| // Fast-forwards virtual time by |delta|. If |with_power| is true, both |
| // |clock_| and |task_environment_| time will be fast-forwarded. Otherwise, |
| // only |clock_| time will be changed to mimic the behavior when machine is |
| // suspended. |
| // Power event will be triggered if |with_power| is set to false. |
| void FastForwardBy(base::TimeDelta delay, bool with_power = true) { |
| if (!with_power) |
| fake_power_monitor_source_.Suspend(); |
| |
| clock_.Advance(delay); |
| |
| if (with_power) { |
| task_environment_.FastForwardBy(delay); |
| } else { |
| fake_power_monitor_source_.Resume(); |
| task_environment_.RunUntilIdle(); |
| } |
| } |
| |
| base::test::ScopedPowerMonitorTestSource fake_power_monitor_source_; |
| base::test::SingleThreadTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| base::SimpleTestClock clock_; |
| }; |
| |
| TEST_F(WallClockTimerTest, PowerResume) { |
| ::testing::StrictMock<base::MockOnceClosure> callback; |
| // Set up a WallClockTimer that will fire in one minute. |
| WallClockTimer wall_clock_timer(&clock_, |
| task_environment_.GetMockTickClock()); |
| constexpr auto delay = base::Minutes(1); |
| const auto start_time = base::Time::Now(); |
| const auto run_time = start_time + delay; |
| clock_.SetNow(start_time); |
| wall_clock_timer.Start(FROM_HERE, run_time, callback.Get()); |
| EXPECT_EQ(wall_clock_timer.desired_run_time(), start_time + delay); |
| |
| // Pretend that time jumps forward 30 seconds while the machine is suspended. |
| constexpr auto past_time = base::Seconds(30); |
| FastForwardBy(past_time, /*with_power=*/false); |
| // Ensure that the timer has not yet fired. |
| ::testing::Mock::VerifyAndClearExpectations(&callback); |
| EXPECT_EQ(wall_clock_timer.desired_run_time(), start_time + delay); |
| |
| // Expect that the timer fires at the desired run time. |
| EXPECT_CALL(callback, Run()); |
| // Both Time::Now() and |task_environment_| MockTickClock::Now() |
| // go forward by (|delay| - |past_time|): |
| FastForwardBy(delay - past_time); |
| ::testing::Mock::VerifyAndClearExpectations(&callback); |
| EXPECT_FALSE(wall_clock_timer.IsRunning()); |
| } |
| |
| TEST_F(WallClockTimerTest, UseTimerTwiceInRow) { |
| ::testing::StrictMock<base::MockOnceClosure> first_callback; |
| ::testing::StrictMock<base::MockOnceClosure> second_callback; |
| const auto start_time = base::Time::Now(); |
| clock_.SetNow(start_time); |
| |
| // Set up a WallClockTimer that will invoke |first_callback| in one minute. |
| // Once it's done, it will invoke |second_callback| after the other minute. |
| WallClockTimer wall_clock_timer(&clock_, |
| task_environment_.GetMockTickClock()); |
| constexpr auto delay = base::Minutes(1); |
| wall_clock_timer.Start(FROM_HERE, clock_.Now() + delay, first_callback.Get()); |
| EXPECT_CALL(first_callback, Run()) |
| .WillOnce(::testing::InvokeWithoutArgs( |
| [this, &wall_clock_timer, &second_callback, delay]() { |
| wall_clock_timer.Start(FROM_HERE, clock_.Now() + delay, |
| second_callback.Get()); |
| })); |
| |
| FastForwardBy(delay); |
| ::testing::Mock::VerifyAndClearExpectations(&first_callback); |
| ::testing::Mock::VerifyAndClearExpectations(&second_callback); |
| |
| // When the |wall_clock_time| is used for the second time, it can still handle |
| // power suspension properly. |
| constexpr auto past_time = base::Seconds(30); |
| FastForwardBy(past_time, /*with_power=*/false); |
| ::testing::Mock::VerifyAndClearExpectations(&second_callback); |
| |
| EXPECT_CALL(second_callback, Run()); |
| FastForwardBy(delay - past_time); |
| ::testing::Mock::VerifyAndClearExpectations(&second_callback); |
| } |
| |
| TEST_F(WallClockTimerTest, Stop) { |
| ::testing::StrictMock<base::MockOnceClosure> callback; |
| clock_.SetNow(base::Time::Now()); |
| |
| // Set up a WallClockTimer. |
| WallClockTimer wall_clock_timer(&clock_, |
| task_environment_.GetMockTickClock()); |
| constexpr auto delay = base::Minutes(1); |
| wall_clock_timer.Start(FROM_HERE, clock_.Now() + delay, callback.Get()); |
| |
| // After 20 seconds, timer is stopped. |
| constexpr auto past_time = base::Seconds(20); |
| FastForwardBy(past_time); |
| EXPECT_TRUE(wall_clock_timer.IsRunning()); |
| wall_clock_timer.Stop(); |
| EXPECT_FALSE(wall_clock_timer.IsRunning()); |
| |
| // When power is suspends and resumed, timer won't be resumed. |
| FastForwardBy(past_time, /*with_power=*/false); |
| EXPECT_FALSE(wall_clock_timer.IsRunning()); |
| |
| // Timer won't fire when desired run time is reached. |
| FastForwardBy(delay - past_time * 2); |
| ::testing::Mock::VerifyAndClearExpectations(&callback); |
| } |
| |
| TEST_F(WallClockTimerTest, RestartRunningTimer) { |
| ::testing::StrictMock<base::MockOnceClosure> first_callback; |
| ::testing::StrictMock<base::MockOnceClosure> second_callback; |
| constexpr auto delay = base::Minutes(1); |
| |
| // Set up a WallClockTimer that will invoke |first_callback| in one minute. |
| clock_.SetNow(base::Time::Now()); |
| WallClockTimer wall_clock_timer(&clock_, |
| task_environment_.GetMockTickClock()); |
| wall_clock_timer.Start(FROM_HERE, clock_.Now() + delay, first_callback.Get()); |
| |
| // After 30 seconds, replace the timer with |second_callback| with new one |
| // minute delay. |
| constexpr auto past_time = delay / 2; |
| FastForwardBy(past_time); |
| wall_clock_timer.Start(FROM_HERE, clock_.Now() + delay, |
| second_callback.Get()); |
| |
| // |first_callback| is due but it won't be called because it's replaced. |
| FastForwardBy(past_time); |
| ::testing::Mock::VerifyAndClearExpectations(&first_callback); |
| ::testing::Mock::VerifyAndClearExpectations(&second_callback); |
| |
| // Timer invokes the |second_callback|. |
| EXPECT_CALL(second_callback, Run()); |
| FastForwardBy(past_time); |
| ::testing::Mock::VerifyAndClearExpectations(&first_callback); |
| ::testing::Mock::VerifyAndClearExpectations(&second_callback); |
| } |
| |
| TEST_F(WallClockTimerTest, DoubleStop) { |
| ::testing::StrictMock<base::MockOnceClosure> callback; |
| clock_.SetNow(base::Time::Now()); |
| |
| // Set up a WallClockTimer. |
| WallClockTimer wall_clock_timer(&clock_, |
| task_environment_.GetMockTickClock()); |
| constexpr auto delay = base::Minutes(1); |
| wall_clock_timer.Start(FROM_HERE, clock_.Now() + delay, callback.Get()); |
| |
| // After 15 seconds, timer is stopped. |
| constexpr auto past_time = delay / 4; |
| FastForwardBy(past_time); |
| EXPECT_TRUE(wall_clock_timer.IsRunning()); |
| wall_clock_timer.Stop(); |
| EXPECT_FALSE(wall_clock_timer.IsRunning()); |
| |
| // And timer is stopped again later. The second stop should be a no-op. |
| FastForwardBy(past_time); |
| EXPECT_FALSE(wall_clock_timer.IsRunning()); |
| wall_clock_timer.Stop(); |
| EXPECT_FALSE(wall_clock_timer.IsRunning()); |
| |
| // Timer won't fire after stop. |
| FastForwardBy(past_time, /*with_power=*/false); |
| FastForwardBy(delay - past_time * 3); |
| ::testing::Mock::VerifyAndClearExpectations(&callback); |
| } |
| |
| // On some platforms, TickClock will never freeze. WallClockTimer are still |
| // supported on those platforms. |
| TEST_F(WallClockTimerTest, NonStopTickClock) { |
| ::testing::StrictMock<base::MockOnceClosure> callback; |
| // Set up a WallClockTimer that will fire in one minute. |
| WallClockTimer wall_clock_timer(&clock_, |
| task_environment_.GetMockTickClock()); |
| constexpr auto delay = base::Minutes(1); |
| const auto start_time = base::Time::Now(); |
| const auto run_time = start_time + delay; |
| clock_.SetNow(start_time); |
| wall_clock_timer.Start(FROM_HERE, run_time, callback.Get()); |
| EXPECT_EQ(wall_clock_timer.desired_run_time(), start_time + delay); |
| |
| // Pretend that time jumps forward 30 seconds while the machine is suspended. |
| constexpr auto past_time = base::Seconds(30); |
| |
| // Fastword with both clocks even the power is suspended. |
| fake_power_monitor_source_.Suspend(); |
| clock_.SetNow(clock_.Now() + past_time); |
| task_environment_.FastForwardBy(past_time); |
| fake_power_monitor_source_.Resume(); |
| |
| // Ensure that the timer has not yet fired. |
| ::testing::Mock::VerifyAndClearExpectations(&callback); |
| EXPECT_EQ(wall_clock_timer.desired_run_time(), start_time + delay); |
| |
| // Expect that the timer fires at the desired run time. |
| EXPECT_CALL(callback, Run()); |
| // Both Time::Now() and |task_environment_| MockTickClock::Now() |
| // go forward by (|delay| - |past_time|): |
| FastForwardBy(delay - past_time); |
| ::testing::Mock::VerifyAndClearExpectations(&callback); |
| EXPECT_FALSE(wall_clock_timer.IsRunning()); |
| } |
| |
| TEST_F(WallClockTimerTest, NonStopTickClockWithLongPause) { |
| ::testing::StrictMock<base::MockOnceClosure> callback; |
| // Set up a WallClockTimer that will fire in one minute. |
| WallClockTimer wall_clock_timer(&clock_, |
| task_environment_.GetMockTickClock()); |
| constexpr auto delay = base::Minutes(1); |
| const auto start_time = base::Time::Now(); |
| const auto run_time = start_time + delay; |
| clock_.SetNow(start_time); |
| wall_clock_timer.Start(FROM_HERE, run_time, callback.Get()); |
| EXPECT_EQ(wall_clock_timer.desired_run_time(), start_time + delay); |
| |
| // Pretend that time jumps forward 60 seconds while the machine is suspended. |
| constexpr auto past_time = base::Seconds(60); |
| |
| // Fastword with both clocks even the power is suspended. Timer fires at the |
| // moment of power resume. |
| EXPECT_CALL(callback, Run()); |
| fake_power_monitor_source_.Suspend(); |
| clock_.SetNow(clock_.Now() + past_time); |
| task_environment_.FastForwardBy(past_time); |
| fake_power_monitor_source_.Resume(); |
| |
| ::testing::Mock::VerifyAndClearExpectations(&callback); |
| EXPECT_FALSE(wall_clock_timer.IsRunning()); |
| } |
| |
| } // namespace base |