// Copyright 2017 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/trace_event/memory_dump_scheduler.h" #include #include "base/bind.h" #include "base/synchronization/waitable_event.h" #include "base/task/single_thread_task_runner.h" #include "base/threading/thread.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::AtMost; using ::testing::Invoke; using ::testing::_; namespace base { namespace trace_event { namespace { // Wrapper to use gmock on a callback. struct CallbackWrapper { MOCK_METHOD1(OnTick, void(MemoryDumpLevelOfDetail)); }; } // namespace class MemoryDumpSchedulerTest : public testing::Test { public: MemoryDumpSchedulerTest() : testing::Test(), evt_(WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED), bg_thread_("MemoryDumpSchedulerTest Thread") { bg_thread_.Start(); } protected: MemoryDumpScheduler scheduler_; WaitableEvent evt_; CallbackWrapper on_tick_; Thread bg_thread_; }; TEST_F(MemoryDumpSchedulerTest, SingleTrigger) { const uint32_t kPeriodMs = 1; const auto kLevelOfDetail = MemoryDumpLevelOfDetail::DETAILED; const uint32_t kTicks = 5; MemoryDumpScheduler::Config config; config.triggers.push_back({kLevelOfDetail, kPeriodMs}); config.callback = BindRepeating(&CallbackWrapper::OnTick, Unretained(&on_tick_)); testing::InSequence sequence; EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1); EXPECT_CALL(on_tick_, OnTick(_)) .WillRepeatedly(Invoke( [this, kLevelOfDetail](MemoryDumpLevelOfDetail level_of_detail) { EXPECT_EQ(kLevelOfDetail, level_of_detail); this->evt_.Signal(); })); // Check that Stop() before Start() doesn't cause any error. scheduler_.Stop(); const TimeTicks tstart = TimeTicks::Now(); scheduler_.Start(config, bg_thread_.task_runner()); evt_.Wait(); const double time_ms = (TimeTicks::Now() - tstart).InMillisecondsF(); // It takes N-1 ms to perform N ticks of 1ms each. EXPECT_GE(time_ms, kPeriodMs * (kTicks - 1)); // Check that stopping twice doesn't cause any problems. scheduler_.Stop(); scheduler_.Stop(); } TEST_F(MemoryDumpSchedulerTest, MultipleTriggers) { const uint32_t kPeriodLightMs = 3; const uint32_t kPeriodDetailedMs = 9; MemoryDumpScheduler::Config config; const MemoryDumpLevelOfDetail kLight = MemoryDumpLevelOfDetail::LIGHT; const MemoryDumpLevelOfDetail kDetailed = MemoryDumpLevelOfDetail::DETAILED; config.triggers.push_back({kLight, kPeriodLightMs}); config.triggers.push_back({kDetailed, kPeriodDetailedMs}); config.callback = BindRepeating(&CallbackWrapper::OnTick, Unretained(&on_tick_)); TimeTicks t1, t2, t3; testing::InSequence sequence; EXPECT_CALL(on_tick_, OnTick(kDetailed)) .WillOnce( Invoke([&t1](MemoryDumpLevelOfDetail) { t1 = TimeTicks::Now(); })); EXPECT_CALL(on_tick_, OnTick(kLight)).Times(1); EXPECT_CALL(on_tick_, OnTick(kLight)).Times(1); EXPECT_CALL(on_tick_, OnTick(kDetailed)) .WillOnce( Invoke([&t2](MemoryDumpLevelOfDetail) { t2 = TimeTicks::Now(); })); EXPECT_CALL(on_tick_, OnTick(kLight)) .WillOnce( Invoke([&t3](MemoryDumpLevelOfDetail) { t3 = TimeTicks::Now(); })); // Rationale for WillRepeatedly and not just WillOnce: Extra ticks might // happen if the Stop() takes time. Not an interesting case, but we need to // avoid gmock to shout in that case. EXPECT_CALL(on_tick_, OnTick(_)) .WillRepeatedly( Invoke([this](MemoryDumpLevelOfDetail) { this->evt_.Signal(); })); scheduler_.Start(config, bg_thread_.task_runner()); evt_.Wait(); scheduler_.Stop(); EXPECT_GE((t2 - t1).InMillisecondsF(), kPeriodDetailedMs); EXPECT_GE((t3 - t2).InMillisecondsF(), kPeriodLightMs); } TEST_F(MemoryDumpSchedulerTest, StartStopQuickly) { const uint32_t kPeriodMs = 3; const uint32_t kQuickIterations = 5; const uint32_t kDetailedTicks = 10; MemoryDumpScheduler::Config light_config; light_config.triggers.push_back({MemoryDumpLevelOfDetail::LIGHT, kPeriodMs}); light_config.callback = BindRepeating(&CallbackWrapper::OnTick, Unretained(&on_tick_)); MemoryDumpScheduler::Config detailed_config; detailed_config.triggers.push_back( {MemoryDumpLevelOfDetail::DETAILED, kPeriodMs}); detailed_config.callback = BindRepeating(&CallbackWrapper::OnTick, Unretained(&on_tick_)); testing::InSequence sequence; EXPECT_CALL(on_tick_, OnTick(MemoryDumpLevelOfDetail::LIGHT)) .Times(AtMost(kQuickIterations)); EXPECT_CALL(on_tick_, OnTick(MemoryDumpLevelOfDetail::DETAILED)) .Times(kDetailedTicks - 1); EXPECT_CALL(on_tick_, OnTick(MemoryDumpLevelOfDetail::DETAILED)) .WillRepeatedly( Invoke([this](MemoryDumpLevelOfDetail) { this->evt_.Signal(); })); const TimeTicks tstart = TimeTicks::Now(); for (unsigned int i = 0; i < kQuickIterations; i++) { scheduler_.Start(light_config, bg_thread_.task_runner()); scheduler_.Stop(); } scheduler_.Start(detailed_config, bg_thread_.task_runner()); evt_.Wait(); const double time_ms = (TimeTicks::Now() - tstart).InMillisecondsF(); scheduler_.Stop(); // It takes N-1 ms to perform N ticks of 1ms each. EXPECT_GE(time_ms, kPeriodMs * (kDetailedTicks - 1)); } TEST_F(MemoryDumpSchedulerTest, StopAndStartOnAnotherThread) { const uint32_t kPeriodMs = 1; const uint32_t kTicks = 3; MemoryDumpScheduler::Config config; config.triggers.push_back({MemoryDumpLevelOfDetail::DETAILED, kPeriodMs}); config.callback = BindRepeating(&CallbackWrapper::OnTick, Unretained(&on_tick_)); auto expected_task_runner = bg_thread_.task_runner(); testing::InSequence sequence; EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1); EXPECT_CALL(on_tick_, OnTick(_)) .WillRepeatedly( Invoke([this, expected_task_runner](MemoryDumpLevelOfDetail) { EXPECT_TRUE(expected_task_runner->RunsTasksInCurrentSequence()); this->evt_.Signal(); })); scheduler_.Start(config, bg_thread_.task_runner()); evt_.Wait(); scheduler_.Stop(); bg_thread_.Stop(); Thread bg_thread_2("MemoryDumpSchedulerTest Thread 2"); bg_thread_2.Start(); evt_.Reset(); expected_task_runner = bg_thread_2.task_runner(); EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1); EXPECT_CALL(on_tick_, OnTick(_)) .WillRepeatedly( Invoke([this, expected_task_runner](MemoryDumpLevelOfDetail) { EXPECT_TRUE(expected_task_runner->RunsTasksInCurrentSequence()); this->evt_.Signal(); })); scheduler_.Start(config, bg_thread_2.task_runner()); evt_.Wait(); scheduler_.Stop(); } } // namespace trace_event } // namespace base