Merge "Migrate CTS test users of UploadDataProvider to a single canonical impl."
This commit is contained in:
@@ -34,13 +34,11 @@ import android.net.http.HttpEngine;
|
||||
import android.net.http.HttpException;
|
||||
import android.net.http.InlineExecutionProhibitedException;
|
||||
import android.net.http.UploadDataProvider;
|
||||
import android.net.http.UploadDataSink;
|
||||
import android.net.http.UrlRequest;
|
||||
import android.net.http.UrlRequest.Status;
|
||||
import android.net.http.UrlResponseInfo;
|
||||
import android.net.http.cts.util.HttpCtsTestServer;
|
||||
import android.net.http.cts.util.TestStatusListener;
|
||||
import android.net.http.cts.util.TestUploadDataProvider;
|
||||
import android.net.http.cts.util.TestUrlRequestCallback;
|
||||
import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
|
||||
import android.net.http.cts.util.UploadDataProviders;
|
||||
@@ -140,14 +138,11 @@ public class UrlRequestTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUrlRequestPost_EchoRequestBody() throws Exception {
|
||||
public void testUrlRequestPost_EchoRequestBody() {
|
||||
String testData = "test";
|
||||
UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoBodyUrl());
|
||||
|
||||
TestUploadDataProvider dataProvider =
|
||||
new TestUploadDataProvider(
|
||||
TestUploadDataProvider.SuccessCallbackMode.SYNC, mCallback.getExecutor());
|
||||
dataProvider.addRead(testData.getBytes());
|
||||
UploadDataProvider dataProvider = UploadDataProviders.create(testData);
|
||||
builder.setUploadDataProvider(dataProvider, mCallback.getExecutor());
|
||||
builder.addHeader("Content-Type", "text/html");
|
||||
builder.build().start();
|
||||
@@ -155,7 +150,6 @@ public class UrlRequestTest {
|
||||
|
||||
assertOKStatusCode(mCallback.mResponseInfo);
|
||||
assertEquals(testData, mCallback.mResponseAsString);
|
||||
dataProvider.assertClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,7 +164,7 @@ public class UrlRequestTest {
|
||||
callback.setAllowDirectExecutor(true);
|
||||
UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
|
||||
mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
|
||||
UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test");
|
||||
UploadDataProvider dataProvider = UploadDataProviders.create("test");
|
||||
builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR);
|
||||
builder.addHeader("Content-Type", "text/plain;charset=UTF-8");
|
||||
builder.setDirectExecutorAllowed(true);
|
||||
@@ -193,7 +187,7 @@ public class UrlRequestTest {
|
||||
|
||||
UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
|
||||
mTestServer.getEchoBodyUrl(), Executors.newSingleThreadExecutor(), callback);
|
||||
UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test");
|
||||
UploadDataProvider dataProvider = UploadDataProviders.create("test");
|
||||
|
||||
builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR)
|
||||
.addHeader("Content-Type", "text/plain;charset=UTF-8")
|
||||
@@ -213,7 +207,7 @@ public class UrlRequestTest {
|
||||
|
||||
UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
|
||||
mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
|
||||
UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test");
|
||||
UploadDataProvider dataProvider = UploadDataProviders.create("test");
|
||||
|
||||
builder.setUploadDataProvider(dataProvider, Executors.newSingleThreadExecutor())
|
||||
.addHeader("Content-Type", "text/plain;charset=UTF-8")
|
||||
@@ -415,39 +409,4 @@ public class UrlRequestTest {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private static class InMemoryUploadDataProvider extends UploadDataProvider {
|
||||
private final byte[] mBody;
|
||||
private int mNextChunkStartIndex = 0;
|
||||
|
||||
private InMemoryUploadDataProvider(byte[] body) {
|
||||
this.mBody = body;
|
||||
}
|
||||
|
||||
static InMemoryUploadDataProvider fromUtf8String(String body) {
|
||||
return new InMemoryUploadDataProvider(body.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength() {
|
||||
return mBody.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) {
|
||||
if (mNextChunkStartIndex >= getLength()) {
|
||||
throw new IllegalStateException("Body of known length is exhausted");
|
||||
}
|
||||
int nextChunkSize =
|
||||
Math.min(mBody.length - mNextChunkStartIndex, byteBuffer.remaining());
|
||||
byteBuffer.put(mBody, mNextChunkStartIndex, nextChunkSize);
|
||||
mNextChunkStartIndex += nextChunkSize;
|
||||
uploadDataSink.onReadSucceeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rewind(UploadDataSink uploadDataSink) {
|
||||
mNextChunkStartIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.http.cts.util;
|
||||
|
||||
import android.net.http.UploadDataProvider;
|
||||
import android.net.http.UploadDataSink;
|
||||
import android.os.ConditionVariable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** An UploadDataProvider implementation used in tests. */
|
||||
public class TestUploadDataProvider extends UploadDataProvider {
|
||||
// Indicates whether all success callbacks are synchronous or asynchronous.
|
||||
// Doesn't apply to errors.
|
||||
public enum SuccessCallbackMode {
|
||||
SYNC,
|
||||
ASYNC
|
||||
}
|
||||
|
||||
// Indicates whether failures should throw exceptions, invoke callbacks
|
||||
// synchronously, or invoke callback asynchronously.
|
||||
public enum FailMode {
|
||||
NONE,
|
||||
THROWN,
|
||||
CALLBACK_SYNC,
|
||||
CALLBACK_ASYNC
|
||||
}
|
||||
|
||||
private final ArrayList<byte[]> mReads = new ArrayList<byte[]>();
|
||||
private final SuccessCallbackMode mSuccessCallbackMode;
|
||||
private final Executor mExecutor;
|
||||
|
||||
private boolean mChunked;
|
||||
|
||||
// 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 FailMode mLengthFailMode = FailMode.NONE;
|
||||
|
||||
private int mNumReadCalls;
|
||||
private int mNumRewindCalls;
|
||||
|
||||
private int mNextRead;
|
||||
private boolean mStarted;
|
||||
private boolean mReadPending;
|
||||
private boolean mRewindPending;
|
||||
// Used to ensure there are no read/rewind requests after a failure.
|
||||
private boolean mFailed;
|
||||
|
||||
private final AtomicBoolean mClosed = new AtomicBoolean(false);
|
||||
private final ConditionVariable mAwaitingClose = new ConditionVariable(false);
|
||||
|
||||
public TestUploadDataProvider(
|
||||
SuccessCallbackMode successCallbackMode, final 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 setLengthFailure() {
|
||||
mLengthFailMode = FailMode.THROWN;
|
||||
}
|
||||
|
||||
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() throws IOException {
|
||||
if (mClosed.get()) {
|
||||
throw new ClosedChannelException();
|
||||
}
|
||||
if (mLengthFailMode == FailMode.THROWN) {
|
||||
throw new IllegalStateException("Sync length failure");
|
||||
}
|
||||
return getUploadedLength();
|
||||
}
|
||||
|
||||
public long getUploadedLength() {
|
||||
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;
|
||||
if (mClosed.get()) {
|
||||
throw new ClosedChannelException();
|
||||
}
|
||||
assertIdle();
|
||||
|
||||
if (maybeFailRead(currentReadCall, uploadDataSink)) {
|
||||
mFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
mReadPending = true;
|
||||
mStarted = true;
|
||||
|
||||
final boolean finalChunk = (mChunked && mNextRead == mReads.size() - 1);
|
||||
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);
|
||||
}
|
||||
|
||||
Runnable completeRunnable =
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mReadPending = false;
|
||||
uploadDataSink.onReadSucceeded(finalChunk);
|
||||
}
|
||||
};
|
||||
if (mSuccessCallbackMode == SuccessCallbackMode.SYNC) {
|
||||
completeRunnable.run();
|
||||
} else {
|
||||
mExecutor.execute(completeRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rewind(final UploadDataSink uploadDataSink) throws IOException {
|
||||
++mNumRewindCalls;
|
||||
if (mClosed.get()) {
|
||||
throw new ClosedChannelException();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!mClosed.compareAndSet(false, true)) {
|
||||
throw new AssertionError("Closed twice");
|
||||
}
|
||||
mAwaitingClose.open();
|
||||
}
|
||||
|
||||
public void assertClosed() {
|
||||
mAwaitingClose.block(5000);
|
||||
if (!mClosed.get()) {
|
||||
throw new AssertionError("Was not closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Provides implementations of {@link UploadDataProvider} for common use cases. Corresponds to
|
||||
@@ -91,6 +92,16 @@ public final class UploadDataProviders {
|
||||
return create(data, 0, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the UTF-8 representation of {@code data}
|
||||
*
|
||||
* @param data String containing data to upload
|
||||
* @return A new UploadDataProvider for the given data
|
||||
*/
|
||||
public static UploadDataProvider create(String data) {
|
||||
return create(data.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private interface FileChannelProvider {
|
||||
FileChannel getChannel() throws IOException;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user