#include "base/check.h"
#include "base/functional/callback_helpers.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/cpp/bindings/tests/idle_tracking_unittest.test-mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace mojo {
namespace test {
namespace idle_tracking_unittest {
using IdleTrackingTest = BindingsTestBase;
class TestServiceImpl : public mojom::TestService, public mojom::KeepAlive {
public:
explicit TestServiceImpl(PendingReceiver<mojom::TestService> receiver)
: receiver_(this, std::move(receiver)) {}
TestServiceImpl(const TestServiceImpl&) = delete;
TestServiceImpl& operator=(const TestServiceImpl&) = delete;
~TestServiceImpl() override = default;
void HoldNextPingPong() { hold_next_ping_pong_ = true; }
void ReplyLastPingPong() {
DCHECK(last_ping_pong_reply_);
std::move(last_ping_pong_reply_).Run();
}
private:
void Ping() override {}
void PingPong(PingPongCallback callback) override {
if (hold_next_ping_pong_) {
hold_next_ping_pong_ = false;
last_ping_pong_reply_ = std::move(callback);
} else {
std::move(callback).Run();
}
}
void BindKeepAlive(PendingReceiver<mojom::KeepAlive> receiver) override {
keepalive_receivers_.Add(this, std::move(receiver));
}
Receiver<mojom::TestService> receiver_;
ReceiverSet<mojom::KeepAlive> keepalive_receivers_;
bool hold_next_ping_pong_ = false;
base::OnceClosure last_ping_pong_reply_;
};
TEST_P(IdleTrackingTest, ControlMessagesDontExpectAck) {
Remote<mojom::TestService> remote;
TestServiceImpl impl(remote.BindNewPipeAndPassReceiver());
base::RunLoop loop;
remote.set_idle_handler(base::TimeDelta(),
base::BindRepeating([] { NOTREACHED(); }));
remote.FlushAsyncForTesting(loop.QuitClosure());
EXPECT_EQ(0u, remote.GetNumUnackedMessagesForTesting());
loop.Run();
EXPECT_EQ(0u, remote.GetNumUnackedMessagesForTesting());
}
TEST_P(IdleTrackingTest, BasicTracking) {
Remote<mojom::TestService> remote;
TestServiceImpl impl(remote.BindNewPipeAndPassReceiver());
constexpr size_t kNumPings = 5;
for (size_t i = 0; i < kNumPings; ++i) {
base::RunLoop loop;
remote.set_idle_handler(base::TimeDelta(), loop.QuitClosure());
remote->Ping();
EXPECT_EQ(1u, remote.GetNumUnackedMessagesForTesting());
loop.Run();
EXPECT_EQ(0u, remote.GetNumUnackedMessagesForTesting());
}
}
TEST_P(IdleTrackingTest, PendingRepliesPreventIdling) {
Remote<mojom::TestService> remote;
TestServiceImpl impl(remote.BindNewPipeAndPassReceiver());
impl.HoldNextPingPong();
remote.set_idle_handler(base::TimeDelta(),
base::BindRepeating([] { NOTREACHED(); }));
bool idle = false;
bool replied = false;
{
base::RunLoop loop;
remote->PingPong(base::BindLambdaForTesting([&] {
EXPECT_FALSE(idle);
replied = true;
}));
remote.FlushAsyncForTesting(base::BindLambdaForTesting([&] {
EXPECT_EQ(0u, remote.GetNumUnackedMessagesForTesting());
EXPECT_FALSE(replied);
loop.Quit();
}));
loop.Run();
}
remote->Ping();
base::RunLoop().RunUntilIdle();
{
impl.ReplyLastPingPong();
base::RunLoop loop;
remote.set_idle_handler(base::TimeDelta(), base::BindLambdaForTesting([&] {
EXPECT_TRUE(replied);
EXPECT_FALSE(idle);
idle = true;
loop.Quit();
}));
loop.Run();
}
}
TEST_P(IdleTrackingTest, OtherBoundReceiversPreventIdling) {
Remote<mojom::TestService> remote;
TestServiceImpl impl(remote.BindNewPipeAndPassReceiver());
remote.set_idle_handler(base::TimeDelta(),
base::BindRepeating([] { NOTREACHED(); }));
Remote<mojom::KeepAlive> keepalive;
remote->BindKeepAlive(keepalive.BindNewPipeAndPassReceiver());
EXPECT_EQ(1u, remote.GetNumUnackedMessagesForTesting());
keepalive.FlushForTesting();
remote.FlushForTesting();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, remote.GetNumUnackedMessagesForTesting());
base::RunLoop loop;
remote.set_idle_handler(base::TimeDelta(), loop.QuitClosure());
keepalive.reset();
loop.Run();
}
TEST_P(IdleTrackingTest, NonZeroTimeout) {
Remote<mojom::TestService> remote;
TestServiceImpl impl(remote.BindNewPipeAndPassReceiver());
constexpr auto kTimeout = base::Milliseconds(500);
base::ElapsedTimer timer;
base::RunLoop loop;
remote.set_idle_handler(kTimeout, base::BindLambdaForTesting([&] {
EXPECT_GE(timer.Elapsed(), kTimeout);
loop.Quit();
}));
remote->Ping();
loop.Run();
}
TEST_P(IdleTrackingTest, SubInterfacesCanIdleSeparately) {
Remote<mojom::TestService> remote;
TestServiceImpl impl(remote.BindNewPipeAndPassReceiver());
remote.set_idle_handler(base::TimeDelta(),
base::BindRepeating([] { NOTREACHED(); }));
Remote<mojom::KeepAlive> keepalive;
remote->BindKeepAlive(keepalive.BindNewPipeAndPassReceiver());
EXPECT_EQ(1u, remote.GetNumUnackedMessagesForTesting());
keepalive.FlushForTesting();
remote.FlushForTesting();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, remote.GetNumUnackedMessagesForTesting());
keepalive.set_idle_handler(base::TimeDelta(), base::DoNothing());
base::RunLoop().RunUntilIdle();
base::RunLoop loop;
remote.set_idle_handler(base::TimeDelta(), loop.QuitClosure());
remote.FlushForTesting();
keepalive.reset();
loop.Run();
}
INSTANTIATE_MOJO_BINDINGS_TEST_SUITE_P(IdleTrackingTest);
}
}
}