| Index: components/cronet/android/test/javatests/src/org/chromium/net/TestDataProvider.java
 | 
| diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/TestDataProvider.java b/components/cronet/android/test/javatests/src/org/chromium/net/TestDataProvider.java
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..7175fbe2ce7a259e97f0be3c4a4efe2e8f3ccd2b
 | 
| --- /dev/null
 | 
| +++ b/components/cronet/android/test/javatests/src/org/chromium/net/TestDataProvider.java
 | 
| @@ -0,0 +1,245 @@
 | 
| +// Copyright 2015 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.net;
 | 
| +
 | 
| +import java.io.IOException;
 | 
| +import java.nio.ByteBuffer;
 | 
| +import java.util.ArrayList;
 | 
| +import java.util.concurrent.Executor;
 | 
| +
 | 
| +/**
 | 
| + * An UploadDataProvider implementation used in tests.
 | 
| + */
 | 
| +class TestUploadDataProvider implements UploadDataProvider {
 | 
| +    // Indicates whether all success callbacks are synchronous or asynchronous.
 | 
| +    // Doesn't apply to errors.
 | 
| +    enum SuccessCallbackMode {
 | 
| +        SYNC,
 | 
| +        ASYNC
 | 
| +    };
 | 
| +
 | 
| +    // Indicates whether failures should throw exceptions, invoke callbacks
 | 
| +    // synchronously, or invoke callback asynchronously.
 | 
| +    enum FailMode {
 | 
| +        NONE,
 | 
| +        THROWN,
 | 
| +        CALLBACK_SYNC,
 | 
| +        CALLBACK_ASYNC
 | 
| +    };
 | 
| +
 | 
| +    private ArrayList<byte[]> mReads = new ArrayList<byte[]>();
 | 
| +    private final SuccessCallbackMode mSuccessCallbackMode;
 | 
| +    private final Executor mExecutor;
 | 
| +
 | 
| +    private boolean mChunked = false;
 | 
| +
 | 
| +    // Index of read to fail on.
 | 
| +    private int mReadFailIndex = -1;
 | 
| +    // Indicates how to fail on a read.
 | 
| +    private FailMode mReadFailMode = FailMode.NONE;
 | 
| +
 | 
| +    private FailMode mRewindFailMode = FailMode.NONE;
 | 
| +
 | 
| +    private int mNumReadCalls = 0;
 | 
| +    private int mNumRewindCalls = 0;
 | 
| +
 | 
| +    private int mNextRead = 0;
 | 
| +    private boolean mStarted = false;
 | 
| +    private boolean mReadPending = false;
 | 
| +    private boolean mRewindPending = false;
 | 
| +    // Used to ensure there are no read/rewind requests after a failure.
 | 
| +    private boolean mFailed = false;
 | 
| +
 | 
| +    TestUploadDataProvider(SuccessCallbackMode successCallbackMode,
 | 
| +            Executor executor) {
 | 
| +        mSuccessCallbackMode = successCallbackMode;
 | 
| +        mExecutor = executor;
 | 
| +    }
 | 
| +
 | 
| +    // Adds the result to be returned by a successful read request.  The
 | 
| +    // returned bytes must all fit within the read buffer provided by Cronet.
 | 
| +    // After a rewind, if there is one, all reads will be repeated.
 | 
| +    public void addRead(byte[] read) {
 | 
| +        if (mStarted) {
 | 
| +            throw new IllegalStateException("Adding bytes after read");
 | 
| +        }
 | 
| +        mReads.add(read);
 | 
| +    }
 | 
| +
 | 
| +    public void setReadFailure(int readFailIndex, FailMode readFailMode) {
 | 
| +        mReadFailIndex = readFailIndex;
 | 
| +        mReadFailMode = readFailMode;
 | 
| +    }
 | 
| +
 | 
| +    public void setRewindFailure(FailMode rewindFailMode) {
 | 
| +        mRewindFailMode = rewindFailMode;
 | 
| +    }
 | 
| +
 | 
| +    public void setChunked(boolean chunked) {
 | 
| +        mChunked = chunked;
 | 
| +    }
 | 
| +
 | 
| +    public int getNumReadCalls() {
 | 
| +        return mNumReadCalls;
 | 
| +    }
 | 
| +
 | 
| +    public int getNumRewindCalls() {
 | 
| +        return mNumRewindCalls;
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * Returns the cumulative length of all data added by calls to addRead.
 | 
| +     */
 | 
| +    @Override
 | 
| +    public long getLength() {
 | 
| +        if (mChunked) {
 | 
| +            return -1;
 | 
| +        }
 | 
| +        long length = 0;
 | 
| +        for (byte[] read : mReads) {
 | 
| +            length += read.length;
 | 
| +        }
 | 
| +        return length;
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void read(final UploadDataSink uploadDataSink,
 | 
| +            final ByteBuffer byteBuffer) throws IOException {
 | 
| +        int currentReadCall = mNumReadCalls;
 | 
| +        ++mNumReadCalls;
 | 
| +        assertIdle();
 | 
| +
 | 
| +        if (maybeFailRead(currentReadCall, uploadDataSink)) {
 | 
| +            mFailed = true;
 | 
| +            return;
 | 
| +        }
 | 
| +
 | 
| +        mReadPending = true;
 | 
| +        mStarted = true;
 | 
| +
 | 
| +        final boolean finalChunk = (mChunked && mNextRead == mReads.size());
 | 
| +        if (mNextRead < mReads.size()) {
 | 
| +            if ((byteBuffer.limit() - byteBuffer.position())
 | 
| +                    < mReads.get(mNextRead).length) {
 | 
| +                throw new IllegalStateException(
 | 
| +                        "Read buffer smaller than expected.");
 | 
| +            }
 | 
| +            byteBuffer.put(mReads.get(mNextRead));
 | 
| +            ++mNextRead;
 | 
| +        } else if (!finalChunk) {
 | 
| +            throw new IllegalStateException(
 | 
| +                    "Too many reads: " + mNextRead);
 | 
| +        }
 | 
| +
 | 
| +        Runnable completeRunnable = new Runnable() {
 | 
| +            @Override
 | 
| +            public void run() {
 | 
| +                mReadPending = false;
 | 
| +                uploadDataSink.onReadSucceeded(finalChunk);
 | 
| +            }
 | 
| +        };
 | 
| +        if (mSuccessCallbackMode == SuccessCallbackMode.SYNC) {
 | 
| +            completeRunnable.run();
 | 
| +        } else {
 | 
| +            mExecutor.execute(completeRunnable);
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    public void rewind(final UploadDataSink uploadDataSink) throws IOException {
 | 
| +        ++mNumRewindCalls;
 | 
| +        assertIdle();
 | 
| +
 | 
| +        if (maybeFailRewind(uploadDataSink)) {
 | 
| +            mFailed = true;
 | 
| +            return;
 | 
| +        }
 | 
| +
 | 
| +        if (mNextRead == 0) {
 | 
| +            // Should never try and rewind when rewinding does nothing.
 | 
| +            throw new IllegalStateException(
 | 
| +                    "Unexpected rewind when already at beginning");
 | 
| +        }
 | 
| +
 | 
| +        mRewindPending = true;
 | 
| +        mNextRead = 0;
 | 
| +
 | 
| +        Runnable completeRunnable = new Runnable() {
 | 
| +            @Override
 | 
| +            public void run() {
 | 
| +                mRewindPending = false;
 | 
| +                uploadDataSink.onRewindSucceeded();
 | 
| +            }
 | 
| +        };
 | 
| +        if (mSuccessCallbackMode == SuccessCallbackMode.SYNC) {
 | 
| +            completeRunnable.run();
 | 
| +        } else {
 | 
| +            mExecutor.execute(completeRunnable);
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    private void assertIdle() {
 | 
| +        if (mReadPending) {
 | 
| +            throw new IllegalStateException("Unexpected operation during read");
 | 
| +        }
 | 
| +        if (mRewindPending) {
 | 
| +            throw new IllegalStateException(
 | 
| +                    "Unexpected operation during rewind");
 | 
| +        }
 | 
| +        if (mFailed) {
 | 
| +            throw new IllegalStateException(
 | 
| +                    "Unexpected operation after failure");
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    private boolean maybeFailRead(int readIndex,
 | 
| +            final UploadDataSink uploadDataSink) {
 | 
| +        if (readIndex != mReadFailIndex)
 | 
| +            return false;
 | 
| +
 | 
| +        switch (mReadFailMode) {
 | 
| +            case THROWN:
 | 
| +                throw new IllegalStateException("Thrown read failure");
 | 
| +            case CALLBACK_SYNC:
 | 
| +                uploadDataSink.onReadError(
 | 
| +                        new IllegalStateException("Sync read failure"));
 | 
| +                return true;
 | 
| +            case CALLBACK_ASYNC:
 | 
| +                Runnable errorRunnable = new Runnable() {
 | 
| +                    @Override
 | 
| +                    public void run() {
 | 
| +                        uploadDataSink.onReadError(
 | 
| +                                new IllegalStateException("Async read failure"));
 | 
| +                    }
 | 
| +                };
 | 
| +                mExecutor.execute(errorRunnable);
 | 
| +                return true;
 | 
| +            default:
 | 
| +                return false;
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    private boolean maybeFailRewind(final UploadDataSink uploadDataSink) {
 | 
| +        switch (mRewindFailMode) {
 | 
| +            case THROWN:
 | 
| +                throw new IllegalStateException("Thrown rewind failure");
 | 
| +            case CALLBACK_SYNC:
 | 
| +                uploadDataSink.onRewindError(
 | 
| +                        new IllegalStateException("Sync rewind failure"));
 | 
| +                return true;
 | 
| +            case CALLBACK_ASYNC:
 | 
| +                Runnable errorRunnable = new Runnable() {
 | 
| +                    @Override
 | 
| +                    public void run() {
 | 
| +                        uploadDataSink.onRewindError(new IllegalStateException(
 | 
| +                                "Async rewind failure"));
 | 
| +                    }
 | 
| +                };
 | 
| +                mExecutor.execute(errorRunnable);
 | 
| +                return true;
 | 
| +            default:
 | 
| +                return false;
 | 
| +        }
 | 
| +    }
 | 
| +}
 | 
| 
 |