| // 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 |