blob: 9796dc7c3bcf6427c02c981df81cdc4f6c975e35 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// 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 android.os.ConditionVariable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.Executor;
/**
* An UploadDataProvider that allows tests to invoke {@code onReadSucceeded}
* and {@code onRewindSucceeded} on the UploadDataSink directly.
* Chunked mode is not supported here, since the main interest is to test
* different order of init/read/rewind calls.
*/
class TestDrivenDataProvider extends UploadDataProvider {
private final Executor mExecutor;
private final List<byte[]> mReads;
private final ConditionVariable mWaitForReadRequest = new ConditionVariable();
private final ConditionVariable mWaitForRewindRequest = new ConditionVariable();
// Lock used to synchronize access to mReadPending and mRewindPending.
private final Object mLock = new Object();
private int mNextRead;
// Only accessible when holding mLock.
private boolean mReadPending;
private boolean mRewindPending;
private int mNumRewindCalls;
private int mNumReadCalls;
/**
* Constructor.
* @param Executor executor. Executor to run callbacks of UploadDataSink.
* @param List<byte[]> reads. Results to be returned by successful read
* requests. Returned bytes must all fit within the read buffer
* provided by Cronet. After a rewind, if there is one, all reads
* will be repeated.
*/
TestDrivenDataProvider(Executor executor, List<byte[]> reads) {
mExecutor = executor;
mReads = reads;
}
// Called by UploadDataSink on the main thread.
@Override
public long getLength() {
long length = 0;
for (byte[] read : mReads) {
length += read.length;
}
return length;
}
// Called by UploadDataSink on the executor thread.
@Override
public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteBuffer)
throws IOException {
synchronized (mLock) {
++mNumReadCalls;
assertIdle();
mReadPending = true;
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 {
throw new IllegalStateException("Too many reads: " + mNextRead);
}
mWaitForReadRequest.open();
}
}
// Called by UploadDataSink on the executor thread.
@Override
public void rewind(final UploadDataSink uploadDataSink) throws IOException {
synchronized (mLock) {
++mNumRewindCalls;
assertIdle();
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;
mWaitForRewindRequest.open();
}
}
// Called by test fixture on the main thread.
public void onReadSucceeded(final UploadDataSink uploadDataSink) {
Runnable completeRunnable =
new Runnable() {
@Override
public void run() {
synchronized (mLock) {
if (!mReadPending) {
throw new IllegalStateException("No read pending.");
}
mReadPending = false;
uploadDataSink.onReadSucceeded(false);
}
}
};
mExecutor.execute(completeRunnable);
}
// Called by test fixture on the main thread.
public void onRewindSucceeded(final UploadDataSink uploadDataSink) {
Runnable completeRunnable =
new Runnable() {
@Override
public void run() {
synchronized (mLock) {
if (!mRewindPending) {
throw new IllegalStateException("No rewind pending.");
}
mRewindPending = false;
uploadDataSink.onRewindSucceeded();
}
}
};
mExecutor.execute(completeRunnable);
}
// Called by test fixture on the main thread.
public int getNumReadCalls() {
synchronized (mLock) {
return mNumReadCalls;
}
}
// Called by test fixture on the main thread.
public int getNumRewindCalls() {
synchronized (mLock) {
return mNumRewindCalls;
}
}
// Called by test fixture on the main thread.
public void waitForReadRequest() {
mWaitForReadRequest.block();
}
// Called by test fixture on the main thread.
public void resetWaitForReadRequest() {
mWaitForReadRequest.close();
}
// Called by test fixture on the main thread.
public void waitForRewindRequest() {
mWaitForRewindRequest.block();
}
// Called by test fixture on the main thread.
public void assertReadNotPending() {
synchronized (mLock) {
if (mReadPending) {
throw new IllegalStateException("Read is pending.");
}
}
}
// Called by test fixture on the main thread.
public void assertRewindNotPending() {
synchronized (mLock) {
if (mRewindPending) {
throw new IllegalStateException("Rewind is pending.");
}
}
}
/** Helper method to ensure no read or rewind is in progress. */
private void assertIdle() {
assertReadNotPending();
assertRewindNotPending();
}
}