| // Copyright 2014 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. |
| |
| package org.chromium.mojo.bindings; |
| |
| import android.support.test.filters.SmallTest; |
| |
| import org.chromium.base.annotations.SuppressFBWarnings; |
| import org.chromium.mojo.MojoTestCase; |
| import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; |
| import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiverWithResponder; |
| import org.chromium.mojo.system.Core; |
| import org.chromium.mojo.system.Core.HandleSignals; |
| import org.chromium.mojo.system.Handle; |
| import org.chromium.mojo.system.MessagePipeHandle; |
| import org.chromium.mojo.system.MojoResult; |
| import org.chromium.mojo.system.Pair; |
| import org.chromium.mojo.system.ResultAnd; |
| import org.chromium.mojo.system.impl.CoreImpl; |
| |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| |
| /** |
| * Testing {@link Router} |
| */ |
| public class RouterTest extends MojoTestCase { |
| |
| private MessagePipeHandle mHandle; |
| private Router mRouter; |
| private RecordingMessageReceiverWithResponder mReceiver; |
| private CapturingErrorHandler mErrorHandler; |
| |
| /** |
| * @see MojoTestCase#setUp() |
| */ |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| Core core = CoreImpl.getInstance(); |
| Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); |
| mHandle = handles.first; |
| mRouter = new RouterImpl(handles.second); |
| mReceiver = new RecordingMessageReceiverWithResponder(); |
| mRouter.setIncomingMessageReceiver(mReceiver); |
| mErrorHandler = new CapturingErrorHandler(); |
| mRouter.setErrorHandler(mErrorHandler); |
| mRouter.start(); |
| } |
| |
| /** |
| * Testing sending a message via the router that expected a response. |
| */ |
| @SmallTest |
| public void testSendingToRouterWithResponse() { |
| final int requestMessageType = 0xdead; |
| final int responseMessageType = 0xbeaf; |
| |
| // Sending a message expecting a response. |
| MessageHeader header = new MessageHeader(requestMessageType, |
| MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0); |
| Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); |
| header.encode(encoder); |
| mRouter.acceptWithResponder(encoder.getMessage(), mReceiver); |
| ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(header.getSize()); |
| ResultAnd<MessagePipeHandle.ReadMessageResult> result = |
| mHandle.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); |
| |
| assertEquals(MojoResult.OK, result.getMojoResult()); |
| MessageHeader receivedHeader = new Message( |
| receiveBuffer, new ArrayList<Handle>()).asServiceMessage().getHeader(); |
| |
| assertEquals(header.getType(), receivedHeader.getType()); |
| assertEquals(header.getFlags(), receivedHeader.getFlags()); |
| assertTrue(receivedHeader.getRequestId() != 0); |
| |
| // Sending the response. |
| MessageHeader responseHeader = new MessageHeader(responseMessageType, |
| MessageHeader.MESSAGE_IS_RESPONSE_FLAG, receivedHeader.getRequestId()); |
| encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); |
| responseHeader.encode(encoder); |
| Message responseMessage = encoder.getMessage(); |
| mHandle.writeMessage(responseMessage.getData(), new ArrayList<Handle>(), |
| MessagePipeHandle.WriteFlags.NONE); |
| runLoopUntilIdle(); |
| |
| assertEquals(1, mReceiver.messages.size()); |
| ServiceMessage receivedResponseMessage = mReceiver.messages.get(0).asServiceMessage(); |
| assertEquals(MessageHeader.MESSAGE_IS_RESPONSE_FLAG, |
| receivedResponseMessage.getHeader().getFlags()); |
| assertEquals(responseMessage.getData(), receivedResponseMessage.getData()); |
| } |
| |
| /** |
| * Sends a message to the Router. |
| * |
| * @param messageIndex Used when sending multiple messages to indicate the index of this |
| * message. |
| * @param requestMessageType The message type to use in the header of the sent message. |
| * @param requestId The requestId to use in the header of the sent message. |
| */ |
| private void sendMessageToRouter(int messageIndex, int requestMessageType, int requestId) { |
| MessageHeader header = new MessageHeader( |
| requestMessageType, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, requestId); |
| Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); |
| header.encode(encoder); |
| Message headerMessage = encoder.getMessage(); |
| mHandle.writeMessage(headerMessage.getData(), new ArrayList<Handle>(), |
| MessagePipeHandle.WriteFlags.NONE); |
| runLoopUntilIdle(); |
| |
| assertEquals(messageIndex + 1, mReceiver.messagesWithReceivers.size()); |
| Pair<Message, MessageReceiver> receivedMessage = |
| mReceiver.messagesWithReceivers.get(messageIndex); |
| assertEquals(headerMessage.getData(), receivedMessage.first.getData()); |
| } |
| |
| /** |
| * Sends a response message from the Router. |
| * |
| * @param messageIndex Used when sending responses to multiple messages to indicate the index |
| * of the message that this message is a response to. |
| * @param responseMessageType The message type to use in the header of the response message. |
| */ |
| private void sendResponseFromRouter(int messageIndex, int responseMessageType) { |
| Pair<Message, MessageReceiver> receivedMessage = |
| mReceiver.messagesWithReceivers.get(messageIndex); |
| |
| long requestId = receivedMessage.first.asServiceMessage().getHeader().getRequestId(); |
| |
| MessageHeader responseHeader = new MessageHeader( |
| responseMessageType, MessageHeader.MESSAGE_IS_RESPONSE_FLAG, requestId); |
| Encoder encoder = new Encoder(CoreImpl.getInstance(), responseHeader.getSize()); |
| responseHeader.encode(encoder); |
| Message message = encoder.getMessage(); |
| receivedMessage.second.accept(message); |
| |
| ByteBuffer receivedResponseMessage = ByteBuffer.allocateDirect(responseHeader.getSize()); |
| ResultAnd<MessagePipeHandle.ReadMessageResult> result = |
| mHandle.readMessage(receivedResponseMessage, 0, MessagePipeHandle.ReadFlags.NONE); |
| |
| assertEquals(MojoResult.OK, result.getMojoResult()); |
| assertEquals(message.getData(), receivedResponseMessage); |
| } |
| |
| /** |
| * Clears {@code mReceiver.messagesWithReceivers} allowing all message receivers to be |
| * finalized. |
| * <p> |
| * Since there is no way to force the Garbage Collector to actually call finalize and we want to |
| * test the effects of the finalize() method, we explicitly call finalize() on all of the |
| * message receivers. We do this in a custom thread to better approximate what the JVM does. |
| */ |
| private void clearAllMessageReceivers() { |
| Thread myFinalizerThread = new Thread() { |
| @Override |
| @SuppressFBWarnings("FI_EXPLICIT_INVOCATION") |
| public void run() { |
| for (Pair<Message, MessageReceiver> receivedMessage : |
| mReceiver.messagesWithReceivers) { |
| RouterImpl.ResponderThunk thunk = |
| (RouterImpl.ResponderThunk) receivedMessage.second; |
| try { |
| thunk.finalize(); |
| } catch (Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| }; |
| myFinalizerThread.start(); |
| try { |
| myFinalizerThread.join(); |
| } catch (InterruptedException e) { |
| // ignore. |
| } |
| mReceiver.messagesWithReceivers.clear(); |
| } |
| |
| /** |
| * Testing receiving a message via the router that expected a response. |
| */ |
| @SmallTest |
| public void testReceivingViaRouterWithResponse() { |
| final int requestMessageType = 0xdead; |
| final int responseMessageType = 0xbeef; |
| final int requestId = 0xdeadbeaf; |
| |
| // Send a message expecting a response. |
| sendMessageToRouter(0, requestMessageType, requestId); |
| |
| // Sending the response. |
| sendResponseFromRouter(0, responseMessageType); |
| } |
| |
| /** |
| * Tests that if a callback is dropped (i.e. becomes unreachable and is finalized |
| * without being used), then the message pipe will be closed. |
| */ |
| @SmallTest |
| public void testDroppingReceiverWithoutUsingIt() { |
| // Send 10 messages to the router without sending a response. |
| for (int i = 0; i < 10; i++) { |
| sendMessageToRouter(i, i, i); |
| } |
| |
| // Now send the 10 responses. This should work fine. |
| for (int i = 0; i < 10; i++) { |
| sendResponseFromRouter(i, i); |
| } |
| |
| // Clear all MessageRecievers so that the ResponderThunks will |
| // be finalized. |
| clearAllMessageReceivers(); |
| |
| // Send another message to the router without sending a response. |
| sendMessageToRouter(0, 0, 0); |
| |
| // Clear the MessageReciever so that the ResponderThunk will |
| // be finalized. Since the RespondeThunk was never used, this |
| // should close the pipe. |
| clearAllMessageReceivers(); |
| // The close() occurs asynchronously on this thread. |
| runLoopUntilIdle(); |
| |
| // Confirm that the pipe was closed on the Router side. |
| HandleSignals closedFlag = HandleSignals.none().setPeerClosed(true); |
| assertEquals(closedFlag, mHandle.querySignalsState().getSatisfiedSignals()); |
| } |
| } |