blob: 74f01100aff1bb24d07f2292a2fa30098ea9296c [file] [log] [blame]
// Copyright 2019 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/callback_helpers.h"
#include "base/check.h"
#include "base/macros.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:
// mojom::TestService:
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) {
// Verifies that only mojom interface messages expect to be acknowledged and
// control messages do not. This means that control messages cannot trigger
// idle notifications.
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) {
// Verifies that basic idle tracking works when sending one-off messages with
// reply and no attached PendingReceivers. Such messages can cause the
// receiver to re-signal idle as soon as they're dispatched.
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) {
// Verifies that any reply-expecting messages sent to an idle-tracking
// receiver will keep that receiver from idling until their reply is sent.
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([&] {
// The PingPong message should have been acked by now, but we still should
// not have seen the idle handler invoked because the PingPong reply is
// still pending.
EXPECT_EQ(0u, remote.GetNumUnackedMessagesForTesting());
EXPECT_FALSE(replied);
loop.Quit();
}));
loop.Run();
}
// For good measure, also confirm that other one-off messages do not trigger
// an idle notification while the above reply is still pending.
remote->Ping();
// We RunUntilIdle because we want to ensure that no asynchronous side-effects
// of the above operations result in the idle handler being invoked.
base::RunLoop().RunUntilIdle();
{
// Let the impl send its PingPong reply. This should allow the idle handler
// to be invoked (*after* the reply is received).
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) {
// Verifies that the existence of other receivers bound through an
// idle-tracking receiver will keep that receiver from signaling idle as long
// as they remain bound.
Remote<mojom::TestService> remote;
TestServiceImpl impl(remote.BindNewPipeAndPassReceiver());
// First see that we can bind another receiver and that the Remote does not
// invoke its idle handler even though its number of unacked messages goes to
// zero.
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();
// We RunUntilIdle because we want to ensure that no asynchronous side-effects
// of the above operations result in the idle handler being invoked.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, remote.GetNumUnackedMessagesForTesting());
// Now we can reset the KeepAlive, and we expect the TestService Remote to
// subsequently report that its receiver is idle.
base::RunLoop loop;
remote.set_idle_handler(base::TimeDelta(), loop.QuitClosure());
keepalive.reset();
loop.Run();
}
TEST_P(IdleTrackingTest, NonZeroTimeout) {
// Verifies that a non-zero idle timeout will not signal idle for at least as
// long as the specified duration.
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) {
// Verifies that hierarchical ConnectionGroup references work properly, such
// that a main interface with an idle timeout can bind a subinterface with its
// own idle timeout, and the subinterface (and all subinterfaces it binds
// transitively) will still keep the main interface from timing out.
Remote<mojom::TestService> remote;
TestServiceImpl impl(remote.BindNewPipeAndPassReceiver());
// First see that we can bind another receiver and that the Remote does not
// invoke its idle handler even though its number of unacked messages goes to
// zero.
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();
// We RunUntilIdle because we want to ensure that no asynchronous side-effects
// of the above operations result in the idle handler being invoked.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, remote.GetNumUnackedMessagesForTesting());
// Now we set an idle handler on the KeepAlive itself, and we expect the
// TestService Remote to NOT invoke its idle handler.
keepalive.set_idle_handler(base::TimeDelta(), base::DoNothing());
// We use RunUntilIdle() again because we want to ensure there are no
// asynchronous side-effects of the above operation that lead to idling on the
// main interface. If the main interface idles, it will hit an assertion
// before this call returns.
base::RunLoop().RunUntilIdle();
// Finally verify that the main interface does still go idle once we reset the
// keepalive connection.
base::RunLoop loop;
remote.set_idle_handler(base::TimeDelta(), loop.QuitClosure());
remote.FlushForTesting();
keepalive.reset();
loop.Run();
}
INSTANTIATE_MOJO_BINDINGS_TEST_SUITE_P(IdleTrackingTest);
} // namespace idle_tracking_unittest
} // namespace test
} // namespace mojo