#include "base/message_loop/message_pump.h"
#include <type_traits>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/message_loop/message_pump_for_io.h"
#include "base/message_loop/message_pump_for_ui.h"
#include "base/message_loop/message_pump_type.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_executor.h"
#include "base/test/bind.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
#include "base/message_loop/message_pump_libevent.h"
#endif
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtMost;
using ::testing::Invoke;
using ::testing::Return;
namespace base {
namespace {
constexpr bool ChromeControlsNativeEventProcessing(MessagePumpType pump_type) {
#if BUILDFLAG(IS_MAC)
return pump_type != MessagePumpType::UI;
#elif BUILDFLAG(IS_IOS)
return false;
#else
return true;
#endif
}
class MockMessagePumpDelegate : public MessagePump::Delegate {
public:
explicit MockMessagePumpDelegate(MessagePumpType pump_type)
: check_work_items_(ChromeControlsNativeEventProcessing(pump_type)),
native_work_item_accounting_is_on_(
!ChromeControlsNativeEventProcessing(pump_type)) {}
~MockMessagePumpDelegate() override { ValidateNoOpenWorkItems(); }
MockMessagePumpDelegate(const MockMessagePumpDelegate&) = delete;
MockMessagePumpDelegate& operator=(const MockMessagePumpDelegate&) = delete;
void BeforeWait() override {}
MOCK_METHOD0(DoWork, MessagePump::Delegate::NextWorkInfo());
MOCK_METHOD0(DoIdleWork, bool());
void OnBeginWorkItem() override {
any_work_begun_ = true;
if (check_work_items_) {
MockOnBeginWorkItem();
}
++work_item_count_;
}
void OnEndWorkItem(int run_level_depth) override {
if (check_work_items_) {
MockOnEndWorkItem(run_level_depth);
}
EXPECT_EQ(run_level_depth, work_item_count_);
--work_item_count_;
EXPECT_GE(work_item_count_, 0);
}
int RunDepth() override { return work_item_count_; }
void ValidateNoOpenWorkItems() {
EXPECT_EQ(work_item_count_, 0);
if (native_work_item_accounting_is_on_) {
#if !BUILDFLAG(IS_IOS)
EXPECT_TRUE(any_work_begun_);
#endif
}
}
MOCK_METHOD0(MockOnBeginWorkItem, void(void));
MOCK_METHOD1(MockOnEndWorkItem, void(int));
const bool check_work_items_;
const bool native_work_item_accounting_is_on_;
int work_item_count_ = 0;
bool any_work_begun_ = false;
};
class MessagePumpTest : public ::testing::TestWithParam<MessagePumpType> {
public:
MessagePumpTest() : message_pump_(MessagePump::Create(GetParam())) {}
protected:
#if defined(USE_GLIB)
std::map<MessagePump::Delegate*, int> do_work_counts;
#endif
void AddPreDoWorkExpectations(
testing::StrictMock<MockMessagePumpDelegate>& delegate) {
#if BUILDFLAG(IS_WIN)
if (GetParam() == MessagePumpType::UI) {
EXPECT_CALL(delegate, MockOnBeginWorkItem);
EXPECT_CALL(delegate, MockOnEndWorkItem);
EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
}
#endif
#if defined(USE_GLIB)
do_work_counts.try_emplace(&delegate, 0);
if (GetParam() == MessagePumpType::UI) {
if (++do_work_counts[&delegate] % 2) {
EXPECT_CALL(delegate, MockOnBeginWorkItem);
EXPECT_CALL(delegate, MockOnEndWorkItem);
}
}
#endif
}
void AddPostDoWorkExpectations(
testing::StrictMock<MockMessagePumpDelegate>& delegate) {
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
#endif
#if defined(USE_GLIB)
if (GetParam() == MessagePumpType::UI) {
EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
}
#endif
}
std::unique_ptr<MessagePump> message_pump_;
};
}
TEST_P(MessagePumpTest, QuitStopsWork) {
testing::InSequence sequence;
testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
AddPreDoWorkExpectations(delegate);
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
#if defined(USE_GLIB)
if (GetParam() == MessagePumpType::UI) {
AddPostDoWorkExpectations(delegate);
}
#endif
EXPECT_CALL(delegate, DoIdleWork()).Times(0);
message_pump_->ScheduleWork();
message_pump_->Run(&delegate);
}
TEST_P(MessagePumpTest, QuitStopsWorkWithNestedRunLoop) {
testing::InSequence sequence;
testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
testing::StrictMock<MockMessagePumpDelegate> nested_delegate(GetParam());
AddPreDoWorkExpectations(delegate);
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([&] {
message_pump_->Run(&nested_delegate);
return MessagePump::Delegate::NextWorkInfo();
}));
AddPreDoWorkExpectations(nested_delegate);
EXPECT_CALL(nested_delegate, DoWork).WillOnce(Invoke([&] {
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
AddPostDoWorkExpectations(nested_delegate);
AddPostDoWorkExpectations(delegate);
AddPreDoWorkExpectations(delegate);
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
message_pump_->ScheduleWork();
message_pump_->Run(&delegate);
}
TEST_P(MessagePumpTest, YieldToNativeRequestedSmokeTest) {
testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
testing::InSequence sequence;
AddPreDoWorkExpectations(delegate);
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([] {
return MessagePump::Delegate::NextWorkInfo{TimeTicks(), TimeTicks(),
true};
}));
AddPostDoWorkExpectations(delegate);
AddPreDoWorkExpectations(delegate);
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
message_pump_->Quit();
auto now = TimeTicks::Now();
return MessagePump::Delegate::NextWorkInfo{now + Milliseconds(1), now,
true};
}));
EXPECT_CALL(delegate, DoIdleWork()).Times(AnyNumber());
message_pump_->ScheduleWork();
message_pump_->Run(&delegate);
}
namespace {
class TimerSlackTestDelegate : public MessagePump::Delegate {
public:
TimerSlackTestDelegate(MessagePump* message_pump)
: message_pump_(message_pump) {
message_pump_->SetTimerSlack(TIMER_SLACK_MAXIMUM);
const TimeTicks now = TimeTicks::Now();
message_pump_->ScheduleDelayedWork({now + Hours(1), now});
action_.store(NONE);
}
void OnBeginWorkItem() override {}
void OnEndWorkItem(int run_level_depth) override {}
int RunDepth() override { return 0; }
void BeforeWait() override {}
MessagePump::Delegate::NextWorkInfo DoWork() override {
switch (action_.load()) {
case NONE:
break;
case SCHEDULE_DELAYED_WORK: {
action_.store(QUIT);
TimeTicks now = TimeTicks::Now();
return {now + Milliseconds(50), now};
}
case QUIT:
message_pump_->Quit();
break;
}
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}
bool DoIdleWork() override { return false; }
void WakeUpFromOtherThread() {
action_.store(SCHEDULE_DELAYED_WORK);
message_pump_->ScheduleWork();
}
private:
enum Action {
NONE,
SCHEDULE_DELAYED_WORK,
QUIT,
};
const raw_ptr<MessagePump> message_pump_;
std::atomic<Action> action_;
};
}
TEST_P(MessagePumpTest, TimerSlackWithLongDelays) {
TimerSlackTestDelegate delegate(message_pump_.get());
Thread thread("Waking thread");
thread.StartAndWaitForTesting();
thread.task_runner()->PostDelayedTask(
FROM_HERE,
BindLambdaForTesting([&delegate] { delegate.WakeUpFromOtherThread(); }),
Seconds(2));
message_pump_->Run(&delegate);
}
TEST_P(MessagePumpTest, RunWithoutScheduleWorkInvokesDoWork) {
testing::InSequence sequence;
testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
AddPreDoWorkExpectations(delegate);
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
AddPostDoWorkExpectations(delegate);
#if BUILDFLAG(IS_IOS)
EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
#endif
message_pump_->Run(&delegate);
}
TEST_P(MessagePumpTest, NestedRunWithoutScheduleWorkInvokesDoWork) {
testing::InSequence sequence;
testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
testing::StrictMock<MockMessagePumpDelegate> nested_delegate(GetParam());
AddPreDoWorkExpectations(delegate);
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this, &nested_delegate] {
message_pump_->Run(&nested_delegate);
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
AddPreDoWorkExpectations(nested_delegate);
EXPECT_CALL(nested_delegate, DoWork).WillOnce(Invoke([this] {
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
AddPostDoWorkExpectations(nested_delegate);
AddPostDoWorkExpectations(delegate);
#if BUILDFLAG(IS_IOS)
EXPECT_CALL(nested_delegate, DoIdleWork).Times(AnyNumber());
EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
#endif
message_pump_->Run(&delegate);
}
INSTANTIATE_TEST_SUITE_P(All,
MessagePumpTest,
::testing::Values(MessagePumpType::DEFAULT,
MessagePumpType::UI,
MessagePumpType::IO));
}