Merge "Migrate CTS test users of UploadDataProvider to a single canonical impl."

This commit is contained in:
Dan Stahr
2023-03-30 07:48:09 +00:00
committed by Gerrit Code Review
3 changed files with 16 additions and 340 deletions

View File

@@ -34,13 +34,11 @@ import android.net.http.HttpEngine;
import android.net.http.HttpException; import android.net.http.HttpException;
import android.net.http.InlineExecutionProhibitedException; import android.net.http.InlineExecutionProhibitedException;
import android.net.http.UploadDataProvider; import android.net.http.UploadDataProvider;
import android.net.http.UploadDataSink;
import android.net.http.UrlRequest; import android.net.http.UrlRequest;
import android.net.http.UrlRequest.Status; import android.net.http.UrlRequest.Status;
import android.net.http.UrlResponseInfo; import android.net.http.UrlResponseInfo;
import android.net.http.cts.util.HttpCtsTestServer; import android.net.http.cts.util.HttpCtsTestServer;
import android.net.http.cts.util.TestStatusListener; 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;
import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep; import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
import android.net.http.cts.util.UploadDataProviders; import android.net.http.cts.util.UploadDataProviders;
@@ -140,14 +138,11 @@ public class UrlRequestTest {
} }
@Test @Test
public void testUrlRequestPost_EchoRequestBody() throws Exception { public void testUrlRequestPost_EchoRequestBody() {
String testData = "test"; String testData = "test";
UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoBodyUrl()); UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoBodyUrl());
TestUploadDataProvider dataProvider = UploadDataProvider dataProvider = UploadDataProviders.create(testData);
new TestUploadDataProvider(
TestUploadDataProvider.SuccessCallbackMode.SYNC, mCallback.getExecutor());
dataProvider.addRead(testData.getBytes());
builder.setUploadDataProvider(dataProvider, mCallback.getExecutor()); builder.setUploadDataProvider(dataProvider, mCallback.getExecutor());
builder.addHeader("Content-Type", "text/html"); builder.addHeader("Content-Type", "text/html");
builder.build().start(); builder.build().start();
@@ -155,7 +150,6 @@ public class UrlRequestTest {
assertOKStatusCode(mCallback.mResponseInfo); assertOKStatusCode(mCallback.mResponseInfo);
assertEquals(testData, mCallback.mResponseAsString); assertEquals(testData, mCallback.mResponseAsString);
dataProvider.assertClosed();
} }
@Test @Test
@@ -170,7 +164,7 @@ public class UrlRequestTest {
callback.setAllowDirectExecutor(true); callback.setAllowDirectExecutor(true);
UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder( UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback); mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test"); UploadDataProvider dataProvider = UploadDataProviders.create("test");
builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR); builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR);
builder.addHeader("Content-Type", "text/plain;charset=UTF-8"); builder.addHeader("Content-Type", "text/plain;charset=UTF-8");
builder.setDirectExecutorAllowed(true); builder.setDirectExecutorAllowed(true);
@@ -193,7 +187,7 @@ public class UrlRequestTest {
UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder( UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
mTestServer.getEchoBodyUrl(), Executors.newSingleThreadExecutor(), callback); mTestServer.getEchoBodyUrl(), Executors.newSingleThreadExecutor(), callback);
UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test"); UploadDataProvider dataProvider = UploadDataProviders.create("test");
builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR) builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR)
.addHeader("Content-Type", "text/plain;charset=UTF-8") .addHeader("Content-Type", "text/plain;charset=UTF-8")
@@ -213,7 +207,7 @@ public class UrlRequestTest {
UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder( UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback); mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test"); UploadDataProvider dataProvider = UploadDataProviders.create("test");
builder.setUploadDataProvider(dataProvider, Executors.newSingleThreadExecutor()) builder.setUploadDataProvider(dataProvider, Executors.newSingleThreadExecutor())
.addHeader("Content-Type", "text/plain;charset=UTF-8") .addHeader("Content-Type", "text/plain;charset=UTF-8")
@@ -415,39 +409,4 @@ public class UrlRequestTest {
throw new UnsupportedOperationException(); 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;
}
}
} }

View File

@@ -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");
}
}
}

View File

@@ -25,6 +25,7 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
/** /**
* Provides implementations of {@link UploadDataProvider} for common use cases. Corresponds to * 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); 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 { private interface FileChannelProvider {
FileChannel getChannel() throws IOException; FileChannel getChannel() throws IOException;
} }