// 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.system.impl;

import android.test.suitebuilder.annotation.SmallTest;

import org.chromium.mojo.MojoTestCase;
import org.chromium.mojo.system.AsyncWaiter;
import org.chromium.mojo.system.AsyncWaiter.Callback;
import org.chromium.mojo.system.AsyncWaiter.Cancellable;
import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.Core.WaitManyResult;
import org.chromium.mojo.system.DataPipe;
import org.chromium.mojo.system.Handle;
import org.chromium.mojo.system.InvalidHandle;
import org.chromium.mojo.system.MessagePipeHandle;
import org.chromium.mojo.system.MojoException;
import org.chromium.mojo.system.MojoResult;
import org.chromium.mojo.system.Pair;
import org.chromium.mojo.system.SharedBufferHandle;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Testing the core API.
 */
public class CoreImplTest extends MojoTestCase {

    private static final long RUN_LOOP_TIMEOUT_MS = 5;

    private static final ScheduledExecutorService WORKER =
            Executors.newSingleThreadScheduledExecutor();

    private List<Handle> mHandlesToClose = new ArrayList<Handle>();

    /**
     * @see MojoTestCase#tearDown()
     */
    @Override
    protected void tearDown() throws Exception {
        MojoException toThrow = null;
        for (Handle handle : mHandlesToClose) {
            try {
                handle.close();
            } catch (MojoException e) {
                if (toThrow == null) {
                    toThrow = e;
                }
            }
        }
        if (toThrow != null) {
            throw toThrow;
        }
        super.tearDown();
    }

    private void addHandleToClose(Handle handle) {
        mHandlesToClose.add(handle);
    }

    private void addHandlePairToClose(Pair<? extends Handle, ? extends Handle> handles) {
        mHandlesToClose.add(handles.first);
        mHandlesToClose.add(handles.second);
    }

    /**
     * Runnable that will close the given handle.
     */
    private static class CloseHandle implements Runnable {
        private Handle mHandle;

        CloseHandle(Handle handle) {
            mHandle = handle;
        }

        @Override
        public void run() {
            mHandle.close();
        }
    }

    private static void checkSendingMessage(MessagePipeHandle in, MessagePipeHandle out) {
        Random random = new Random();

        // Writing a random 8 bytes message.
        byte[] bytes = new byte[8];
        random.nextBytes(bytes);
        ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
        buffer.put(bytes);
        in.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE);

        // Try to read into a small buffer.
        ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length / 2);
        MessagePipeHandle.ReadMessageResult result = out.readMessage(
                receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE);
        assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult());
        assertEquals(bytes.length, result.getMessageSize());
        assertEquals(0, result.getHandlesCount());

        // Read into a correct buffer.
        receiveBuffer = ByteBuffer.allocateDirect(bytes.length);
        result = out.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE);
        assertEquals(MojoResult.OK, result.getMojoResult());
        assertEquals(bytes.length, result.getMessageSize());
        assertEquals(0, result.getHandlesCount());
        assertEquals(0, receiveBuffer.position());
        assertEquals(result.getMessageSize(), receiveBuffer.limit());
        byte[] receivedBytes = new byte[result.getMessageSize()];
        receiveBuffer.get(receivedBytes);
        assertTrue(Arrays.equals(bytes, receivedBytes));

    }

    private static void checkSendingData(DataPipe.ProducerHandle in, DataPipe.ConsumerHandle out) {
        Random random = new Random();

        // Writing a random 8 bytes message.
        byte[] bytes = new byte[8];
        random.nextBytes(bytes);
        ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
        buffer.put(bytes);
        int result = in.writeData(buffer, DataPipe.WriteFlags.NONE);
        assertEquals(bytes.length, result);

        // Query number of bytes available.
        result = out.readData(null, DataPipe.ReadFlags.none().query(true));
        assertEquals(bytes.length, result);

        // Read into a buffer.
        ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length);
        result = out.readData(receiveBuffer, DataPipe.ReadFlags.NONE);
        assertEquals(bytes.length, result);
        assertEquals(0, receiveBuffer.position());
        assertEquals(bytes.length, receiveBuffer.limit());
        byte[] receivedBytes = new byte[bytes.length];
        receiveBuffer.get(receivedBytes);
        assertTrue(Arrays.equals(bytes, receivedBytes));
    }

    private static void checkSharing(SharedBufferHandle in, SharedBufferHandle out) {
        Random random = new Random();

        ByteBuffer buffer1 = in.map(0, 8, SharedBufferHandle.MapFlags.NONE);
        assertEquals(8, buffer1.capacity());
        ByteBuffer buffer2 = out.map(0, 8, SharedBufferHandle.MapFlags.NONE);
        assertEquals(8, buffer2.capacity());

        byte[] bytes = new byte[8];
        random.nextBytes(bytes);
        buffer1.put(bytes);

        byte[] receivedBytes = new byte[bytes.length];
        buffer2.get(receivedBytes);

        assertTrue(Arrays.equals(bytes, receivedBytes));

        in.unmap(buffer1);
        out.unmap(buffer2);
    }

    /**
     * Testing {@link Core#waitMany(List, long)}.
     */
    @SmallTest
    public void testWaitMany() {
        Core core = CoreImpl.getInstance();
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        List<Pair<Handle, Core.HandleSignals>> handlesToWaitOn = new ArrayList<
                Pair<Handle, Core.HandleSignals>>();
        handlesToWaitOn.add(
                new Pair<Handle, Core.HandleSignals>(handles.second, Core.HandleSignals.READABLE));
        handlesToWaitOn.add(
                new Pair<Handle, Core.HandleSignals>(handles.first, Core.HandleSignals.WRITABLE));
        WaitManyResult result = core.waitMany(handlesToWaitOn, 0);
        assertEquals(MojoResult.OK, result.getMojoResult());
        assertEquals(1, result.getHandleIndex());

        handlesToWaitOn.clear();
        handlesToWaitOn.add(
                new Pair<Handle, Core.HandleSignals>(handles.first, Core.HandleSignals.WRITABLE));
        handlesToWaitOn.add(
                new Pair<Handle, Core.HandleSignals>(handles.second, Core.HandleSignals.READABLE));
        result = core.waitMany(handlesToWaitOn, 0);
        assertEquals(MojoResult.OK, result.getMojoResult());
        assertEquals(0, result.getHandleIndex());
    }

    /**
     * Testing that Core can be retrieved from a handle.
     */
    @SmallTest
    public void testGetCore() {
        Core core = CoreImpl.getInstance();

        Pair<? extends Handle, ? extends Handle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);
        assertEquals(core, handles.first.getCore());
        assertEquals(core, handles.second.getCore());

        handles = core.createDataPipe(null);
        addHandlePairToClose(handles);
        assertEquals(core, handles.first.getCore());
        assertEquals(core, handles.second.getCore());

        SharedBufferHandle handle = core.createSharedBuffer(null, 100);
        SharedBufferHandle handle2 = handle.duplicate(null);
        addHandleToClose(handle);
        addHandleToClose(handle2);
        assertEquals(core, handle.getCore());
        assertEquals(core, handle2.getCore());
    }

    private static void createAndCloseMessagePipe(MessagePipeHandle.CreateOptions options) {
        Core core = CoreImpl.getInstance();
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(options);
        handles.first.close();
        handles.second.close();
    }

    /**
     * Testing {@link MessagePipeHandle} creation.
     */
    @SmallTest
    public void testMessagePipeCreation() {
        // Test creation with null options.
        createAndCloseMessagePipe(null);
        // Test creation with default options.
        createAndCloseMessagePipe(new MessagePipeHandle.CreateOptions());
    }

    /**
     * Testing {@link MessagePipeHandle}.
     */
    @SmallTest
    public void testMessagePipeEmpty() {
        Core core = CoreImpl.getInstance();
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);
        // Testing wait.
        assertEquals(MojoResult.OK,
                handles.first.wait(Core.HandleSignals.none().setReadable(true).setWritable(true),
                        0));
        assertEquals(MojoResult.OK, handles.first.wait(Core.HandleSignals.WRITABLE, 0));
        assertEquals(MojoResult.DEADLINE_EXCEEDED,
                handles.first.wait(Core.HandleSignals.READABLE, 0));

        // Testing read on an empty pipe.
        MessagePipeHandle.ReadMessageResult result = handles.first.readMessage(null, 0,
                MessagePipeHandle.ReadFlags.NONE);
        assertEquals(MojoResult.SHOULD_WAIT, result.getMojoResult());

        // Closing a pipe while waiting.
        WORKER.schedule(new CloseHandle(handles.first), 10, TimeUnit.MILLISECONDS);
        assertEquals(MojoResult.CANCELLED,
                handles.first.wait(Core.HandleSignals.READABLE, 1000000L));

        handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        // Closing the other pipe while waiting.
        WORKER.schedule(new CloseHandle(handles.first), 10, TimeUnit.MILLISECONDS);
        assertEquals(MojoResult.FAILED_PRECONDITION,
                handles.second.wait(Core.HandleSignals.READABLE, 1000000L));

        // Waiting on a closed pipe.
        assertEquals(MojoResult.FAILED_PRECONDITION,
                handles.second.wait(Core.HandleSignals.READABLE, 0));
        assertEquals(MojoResult.FAILED_PRECONDITION,
                handles.second.wait(Core.HandleSignals.WRITABLE, 0));
    }

    /**
     * Testing {@link MessagePipeHandle}.
     */
    @SmallTest
    public void testMessagePipeSend() {
        Core core = CoreImpl.getInstance();
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        checkSendingMessage(handles.first, handles.second);
        checkSendingMessage(handles.second, handles.first);
    }

    /**
     * Testing {@link MessagePipeHandle}.
     */
    @SmallTest
    public void testMessagePipeReceiveOnSmallBuffer() {
        Random random = new Random();
        Core core = CoreImpl.getInstance();
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        // Writing a random 8 bytes message.
        byte[] bytes = new byte[8];
        random.nextBytes(bytes);
        ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
        buffer.put(bytes);
        handles.first.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE);

        ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(1);
        MessagePipeHandle.ReadMessageResult result = handles.second
                .readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE);
        assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult());
        assertEquals(bytes.length, result.getMessageSize());
        assertEquals(0, result.getHandlesCount());
    }

    /**
     * Testing {@link MessagePipeHandle}.
     */
    @SmallTest
    public void testMessagePipeSendHandles() {
        Core core = CoreImpl.getInstance();
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        Pair<MessagePipeHandle, MessagePipeHandle> handlesToShare = core.createMessagePipe(null);
        addHandlePairToClose(handles);
        addHandlePairToClose(handlesToShare);

        handles.first.writeMessage(null,
                Collections.<Handle> singletonList(handlesToShare.second),
                MessagePipeHandle.WriteFlags.NONE);
        assertFalse(handlesToShare.second.isValid());
        MessagePipeHandle.ReadMessageResult readMessageResult =
                handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE);
        assertEquals(1, readMessageResult.getHandlesCount());
        MessagePipeHandle newHandle = readMessageResult.getHandles().get(0)
                .toMessagePipeHandle();
        addHandleToClose(newHandle);
        assertTrue(newHandle.isValid());
        checkSendingMessage(handlesToShare.first, newHandle);
        checkSendingMessage(newHandle, handlesToShare.first);
    }

    private static void createAndCloseDataPipe(DataPipe.CreateOptions options) {
        Core core = CoreImpl.getInstance();
        Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(
                options);
        handles.first.close();
        handles.second.close();
    }

    /**
     * Testing {@link DataPipe}.
     */
    @SmallTest
    public void testDataPipeCreation() {
        // Create datapipe with null options.
        createAndCloseDataPipe(null);
        DataPipe.CreateOptions options = new DataPipe.CreateOptions();
        // Create datapipe with element size set.
        options.setElementNumBytes(24);
        createAndCloseDataPipe(options);
        // Create datapipe with a flag set.
        options.getFlags().setMayDiscard(true);
        createAndCloseDataPipe(options);
        // Create datapipe with capacity set.
        options.setCapacityNumBytes(1024 * options.getElementNumBytes());
        createAndCloseDataPipe(options);
    }

    /**
     * Testing {@link DataPipe}.
     */
    @SmallTest
    public void testDataPipeSend() {
        Core core = CoreImpl.getInstance();

        Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
        addHandlePairToClose(handles);

        checkSendingData(handles.first, handles.second);
    }

    /**
     * Testing {@link DataPipe}.
     */
    @SmallTest
    public void testDataPipeTwoPhaseSend() {
        Random random = new Random();
        Core core = CoreImpl.getInstance();
        Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
        addHandlePairToClose(handles);

        // Writing a random 8 bytes message.
        byte[] bytes = new byte[8];
        random.nextBytes(bytes);
        ByteBuffer buffer = handles.first.beginWriteData(bytes.length,
                DataPipe.WriteFlags.NONE);
        assertTrue(buffer.capacity() >= bytes.length);
        buffer.put(bytes);
        handles.first.endWriteData(bytes.length);

        // Read into a buffer.
        ByteBuffer receiveBuffer = handles.second.beginReadData(bytes.length,
                DataPipe.ReadFlags.NONE);
        assertEquals(0, receiveBuffer.position());
        assertEquals(bytes.length, receiveBuffer.limit());
        byte[] receivedBytes = new byte[bytes.length];
        receiveBuffer.get(receivedBytes);
        assertTrue(Arrays.equals(bytes, receivedBytes));
        handles.second.endReadData(bytes.length);
    }

    /**
     * Testing {@link DataPipe}.
     */
    @SmallTest
    public void testDataPipeDiscard() {
        Random random = new Random();
        Core core = CoreImpl.getInstance();
        Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
        addHandlePairToClose(handles);

        // Writing a random 8 bytes message.
        byte[] bytes = new byte[8];
        random.nextBytes(bytes);
        ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
        buffer.put(bytes);
        int result = handles.first.writeData(buffer, DataPipe.WriteFlags.NONE);
        assertEquals(bytes.length, result);

        // Discard bytes.
        final int nbBytesToDiscard = 4;
        assertEquals(nbBytesToDiscard,
                handles.second.discardData(nbBytesToDiscard, DataPipe.ReadFlags.NONE));

        // Read into a buffer.
        ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length - nbBytesToDiscard);
        result = handles.second.readData(receiveBuffer, DataPipe.ReadFlags.NONE);
        assertEquals(bytes.length - nbBytesToDiscard, result);
        assertEquals(0, receiveBuffer.position());
        assertEquals(bytes.length - nbBytesToDiscard, receiveBuffer.limit());
        byte[] receivedBytes = new byte[bytes.length - nbBytesToDiscard];
        receiveBuffer.get(receivedBytes);
        assertTrue(Arrays.equals(Arrays.copyOfRange(bytes, nbBytesToDiscard, bytes.length),
                receivedBytes));
    }

    /**
     * Testing {@link SharedBufferHandle}.
     */
    @SmallTest
    public void testSharedBufferCreation() {
        Core core = CoreImpl.getInstance();
        // Test creation with empty options.
        core.createSharedBuffer(null, 8).close();
        // Test creation with default options.
        core.createSharedBuffer(new SharedBufferHandle.CreateOptions(), 8).close();
    }

    /**
     * Testing {@link SharedBufferHandle}.
     */
    @SmallTest
    public void testSharedBufferDuplication() {
        Core core = CoreImpl.getInstance();
        SharedBufferHandle handle = core.createSharedBuffer(null, 8);
        addHandleToClose(handle);

        // Test duplication with empty options.
        handle.duplicate(null).close();
        // Test creation with default options.
        handle.duplicate(new SharedBufferHandle.DuplicateOptions()).close();
    }

    /**
     * Testing {@link SharedBufferHandle}.
     */
    @SmallTest
    public void testSharedBufferSending() {
        Core core = CoreImpl.getInstance();
        SharedBufferHandle handle = core.createSharedBuffer(null, 8);
        addHandleToClose(handle);
        SharedBufferHandle newHandle = handle.duplicate(null);
        addHandleToClose(newHandle);

        checkSharing(handle, newHandle);
        checkSharing(newHandle, handle);
    }

    /**
     * Testing that invalid handle can be used with this implementation.
     */
    @SmallTest
    public void testInvalidHandle() {
        Core core = CoreImpl.getInstance();
        Handle handle = InvalidHandle.INSTANCE;

        // Checking wait.
        boolean exception = false;
        try {
            core.wait(handle, Core.HandleSignals.WRITABLE, 0);
        } catch (MojoException e) {
            assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult());
            exception = true;
        }
        assertTrue(exception);

        // Checking waitMany.
        exception = false;
        try {
            List<Pair<Handle, Core.HandleSignals>> handles = new ArrayList<
                    Pair<Handle, Core.HandleSignals>>();
            handles.add(Pair.create(handle, Core.HandleSignals.WRITABLE));
            core.waitMany(handles, 0);
        } catch (MojoException e) {
            assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult());
            exception = true;
        }
        assertTrue(exception);

        // Checking sending an invalid handle.
        // Until the behavior is changed on the C++ side, handle gracefully 2 different use case:
        // - Receive a INVALID_ARGUMENT exception
        // - Receive an invalid handle on the other side.
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);
        try {
            handles.first.writeMessage(null, Collections.<Handle> singletonList(handle),
                    MessagePipeHandle.WriteFlags.NONE);
            MessagePipeHandle.ReadMessageResult readMessageResult =
                    handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE);
            assertEquals(1, readMessageResult.getHandlesCount());
            assertFalse(readMessageResult.getHandles().get(0).isValid());
        } catch (MojoException e) {
            assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult());
        }
    }

    private static class AsyncWaiterResult implements Callback {
        private int mResult = Integer.MIN_VALUE;
        private MojoException mException = null;

        /**
         * @see Callback#onResult(int)
         */
        @Override
        public void onResult(int result) {
            this.mResult = result;
        }

        /**
         * @see Callback#onError(MojoException)
         */
        @Override
        public void onError(MojoException exception) {
            this.mException = exception;
        }

        /**
         * @return the result
         */
        public int getResult() {
            return mResult;
        }

        /**
         * @return the exception
         */
        public MojoException getException() {
            return mException;
        }

    }

    /**
     * Testing core {@link AsyncWaiter} implementation.
     */
    @SmallTest
    public void testAsyncWaiterCorrectResult() {
        Core core = CoreImpl.getInstance();

        // Checking a correct result.
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);
        final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        core.getDefaultAsyncWaiter().asyncWait(handles.first, Core.HandleSignals.READABLE,
                Core.DEADLINE_INFINITE, asyncWaiterResult);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        handles.second.writeMessage(ByteBuffer.allocateDirect(1), null,
                MessagePipeHandle.WriteFlags.NONE);
        nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
        assertNull(asyncWaiterResult.getException());
        assertEquals(MojoResult.OK, asyncWaiterResult.getResult());
    }

    /**
     * Testing core {@link AsyncWaiter} implementation.
     */
    @SmallTest
    public void testAsyncWaiterClosingPeerHandle() {
        Core core = CoreImpl.getInstance();

        // Closing the peer handle.
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        core.getDefaultAsyncWaiter().asyncWait(handles.first, Core.HandleSignals.READABLE,
                Core.DEADLINE_INFINITE, asyncWaiterResult);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        handles.second.close();
        nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
        assertNull(asyncWaiterResult.getException());
        assertEquals(MojoResult.FAILED_PRECONDITION, asyncWaiterResult.getResult());
    }

    /**
     * Testing core {@link AsyncWaiter} implementation.
     */
    @SmallTest
    public void testAsyncWaiterClosingWaitingHandle() {
        Core core = CoreImpl.getInstance();

        // Closing the peer handle.
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        Cancellable cancellable = core.getDefaultAsyncWaiter().asyncWait(handles.first,
                Core.HandleSignals.READABLE, Core.DEADLINE_INFINITE, asyncWaiterResult);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        cancellable.cancel();
        nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
        // TODO(qsr) Re-enable when MojoWaitMany handles it correctly.
        // assertNull(asyncWaiterResult.getException());
        // assertEquals(MojoResult.CANCELLED, asyncWaiterResult.getResult());
    }

    /**
     * Testing core {@link AsyncWaiter} implementation.
     */
    @SmallTest
    public void testAsyncWaiterWaitingWithTimeout() {
        Core core = CoreImpl.getInstance();

        // Closing the peer handle.
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        core.getDefaultAsyncWaiter().asyncWait(handles.first, Core.HandleSignals.READABLE,
                RUN_LOOP_TIMEOUT_MS, asyncWaiterResult);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        nativeRunLoop(10 * RUN_LOOP_TIMEOUT_MS);
        assertNull(asyncWaiterResult.getException());
        assertEquals(MojoResult.DEADLINE_EXCEEDED, asyncWaiterResult.getResult());
    }

    /**
     * Testing core {@link AsyncWaiter} implementation.
     */
    @SmallTest
    public void testAsyncWaiterCancelWaiting() {
        Core core = CoreImpl.getInstance();

        // Closing the peer handle.
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        Cancellable cancellable = core.getDefaultAsyncWaiter().asyncWait(handles.first,
                Core.HandleSignals.READABLE, Core.DEADLINE_INFINITE, asyncWaiterResult);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        cancellable.cancel();
        nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        handles.second.writeMessage(ByteBuffer.allocateDirect(1), null,
                MessagePipeHandle.WriteFlags.NONE);
        nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());
    }

    /**
     * Testing core {@link AsyncWaiter} implementation.
     */
    @SmallTest
    public void testAsyncWaiterImmediateCancelOnInvalidHandle() {
        Core core = CoreImpl.getInstance();

        // Closing the peer handle.
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
        handles.first.close();
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());

        Cancellable cancellable = core.getDefaultAsyncWaiter().asyncWait(handles.first,
                Core.HandleSignals.READABLE, Core.DEADLINE_INFINITE, asyncWaiterResult);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());
        cancellable.cancel();

        nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
        assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
        assertEquals(null, asyncWaiterResult.getException());
    }

    /**
     * Testing the pass method on message pipes.
     */
    @SmallTest
    public void testMessagePipeHandlePass() {
        Core core = CoreImpl.getInstance();
        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
        addHandlePairToClose(handles);

        assertTrue(handles.first.isValid());
        MessagePipeHandle handleClone = handles.first.pass();

        addHandleToClose(handleClone);

        assertFalse(handles.first.isValid());
        assertTrue(handleClone.isValid());
        checkSendingMessage(handleClone, handles.second);
        checkSendingMessage(handles.second, handleClone);
    }

    /**
     * Testing the pass method on data pipes.
     */
    @SmallTest
    public void testDataPipeHandlePass() {
        Core core = CoreImpl.getInstance();
        Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
        addHandlePairToClose(handles);

        DataPipe.ProducerHandle producerClone = handles.first.pass();
        DataPipe.ConsumerHandle consumerClone = handles.second.pass();

        addHandleToClose(producerClone);
        addHandleToClose(consumerClone);

        assertFalse(handles.first.isValid());
        assertFalse(handles.second.isValid());
        assertTrue(producerClone.isValid());
        assertTrue(consumerClone.isValid());
        checkSendingData(producerClone, consumerClone);
    }

    /**
     * Testing the pass method on shared buffers.
     */
    @SmallTest
    public void testSharedBufferPass() {
        Core core = CoreImpl.getInstance();
        SharedBufferHandle handle = core.createSharedBuffer(null, 8);
        addHandleToClose(handle);
        SharedBufferHandle newHandle = handle.duplicate(null);
        addHandleToClose(newHandle);

        SharedBufferHandle handleClone = handle.pass();
        SharedBufferHandle newHandleClone = newHandle.pass();

        addHandleToClose(handleClone);
        addHandleToClose(newHandleClone);

        assertFalse(handle.isValid());
        assertTrue(handleClone.isValid());
        checkSharing(handleClone, newHandleClone);
        checkSharing(newHandleClone, handleClone);
    }

}
