summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tests/Android.mk15
-rw-r--r--tests/AndroidManifest.xml34
-rw-r--r--tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java395
-rw-r--r--tests/src/com/android/providers/downloads/FakeIConnectivityManager.java149
-rw-r--r--tests/src/tests/http/MockResponse.java44
-rw-r--r--tests/src/tests/http/MockWebServer.java30
-rw-r--r--tests/src/tests/http/RecordedRequest.java8
7 files changed, 648 insertions, 27 deletions
diff --git a/tests/Android.mk b/tests/Android.mk
index e9e3a87..80a1c76 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -1,10 +1,17 @@
LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
-########################
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
-include $(CLEAR_VARS)
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_INSTRUMENTATION_FOR := DownloadProvider
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := DownloadProviderTests
+LOCAL_CERTIFICATE := media
-# no tests to build for now
+include $(BUILD_PACKAGE)
# additionally, build sub-tests in a separate .apk
-include $(call all-makefiles-under,$(LOCAL_PATH)) \ No newline at end of file
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..4d971db
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2009 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.providers.downloads.tests"
+ android:sharedUserId="android.media">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!--
+ The test declared in this instrumentation can be run via this command
+ "adb shell am instrument -w com.android.providers.downloads/android.test.InstrumentationTestRunner"
+ -->
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.providers.downloads"
+ android:label="Tests for Download Manager"/>
+
+</manifest>
diff --git a/tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java b/tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java
new file mode 100644
index 0000000..76b3d58
--- /dev/null
+++ b/tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2010 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 com.android.providers.downloads;
+
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.provider.Downloads;
+import android.test.RenamingDelegatingContext;
+import android.test.ServiceTestCase;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import tests.http.MockResponse;
+import tests.http.MockWebServer;
+import tests.http.RecordedRequest;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This test exercises the entire download manager working together -- it requests downloads through
+ * the {@link DownloadProvider}, just like a normal client would, and runs the
+ * {@link DownloadService} with start intents. It sets up a {@link MockWebServer} running on the
+ * device to serve downloads.
+ */
+@LargeTest
+public class DownloadManagerFunctionalTest extends ServiceTestCase<DownloadService> {
+ private static final int HTTP_PARTIAL_CONTENT = 206;
+ private static final String PROVIDER_AUTHORITY = "downloads";
+ private static final int HTTP_OK = 200;
+ private static final String LOG_TAG = "DownloadManagerFunctionalTest";
+ private static final int HTTP_NOT_FOUND = 404;
+ private static final String FILE_CONTENT = "hello world";
+ private static final long REQUEST_TIMEOUT_MILLIS = 5000;
+
+ private MockWebServer mServer;
+ // resolves requests to the DownloadProvider we set up
+ private MockContentResolver mResolver;
+ private TestContext mTestContext;
+
+ /**
+ * Context passed to the provider and the service. Allows most methods to pass through to the
+ * real Context (this is a LargeTest), with a few exceptions, including renaming file operations
+ * to avoid file and DB conflicts (via RenamingDelegatingContext).
+ */
+ private static class TestContext extends RenamingDelegatingContext {
+ private static final String FILENAME_PREFIX = "test.";
+
+ private Context mRealContext;
+ private Set<String> mAllowedSystemServices;
+ private ContentResolver mResolver;
+
+ boolean mHasServiceBeenStarted = false;
+ FakeIConnectivityManager mFakeIConnectivityManager;
+
+ public TestContext(Context realContext) {
+ super(realContext, FILENAME_PREFIX);
+ mRealContext = realContext;
+ mAllowedSystemServices = new HashSet<String>(Arrays.asList(new String[] {
+ Context.NOTIFICATION_SERVICE,
+ Context.POWER_SERVICE,
+ }));
+ mFakeIConnectivityManager = new FakeIConnectivityManager();
+ }
+
+ public void setResolver(ContentResolver resolver) {
+ mResolver = resolver;
+ }
+
+ /**
+ * Direct DownloadService to our test instance of DownloadProvider.
+ */
+ @Override
+ public ContentResolver getContentResolver() {
+ assert mResolver != null;
+ return mResolver;
+ }
+
+ /**
+ * Stub some system services, allow access to others, and block the rest.
+ */
+ @Override
+ public Object getSystemService(String name) {
+ if (name.equals(Context.CONNECTIVITY_SERVICE)) {
+ return new ConnectivityManager(mFakeIConnectivityManager);
+ }
+ if (mAllowedSystemServices.contains(name)) {
+ return mRealContext.getSystemService(name);
+ }
+ return super.getSystemService(name);
+ }
+
+ /**
+ * Record when DownloadProvider starts DownloadService.
+ */
+ @Override
+ public ComponentName startService(Intent service) {
+ if (service.getComponent().getClassName().equals(DownloadService.class.getName())) {
+ mHasServiceBeenStarted = true;
+ return service.getComponent();
+ }
+ throw new UnsupportedOperationException("Unexpected service: " + service);
+ }
+ }
+
+ public DownloadManagerFunctionalTest() {
+ super(DownloadService.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Context realContext = getContext();
+ mTestContext = new TestContext(realContext);
+ setupProviderAndResolver();
+ assert isDatabaseEmpty(); // ensure we're not messing with real data
+
+ mTestContext.setResolver(mResolver);
+ setContext(mTestContext);
+
+ mServer = new MockWebServer();
+ mServer.play();
+ }
+
+ private void setupProviderAndResolver() {
+ ContentProvider provider = new DownloadProvider();
+ provider.attachInfo(mTestContext, null);
+ mResolver = new MockContentResolver();
+ mResolver.addProvider(PROVIDER_AUTHORITY, provider);
+ }
+
+ private boolean isDatabaseEmpty() {
+ Cursor cursor = mResolver.query(Downloads.CONTENT_URI, null, null, null, null);
+ try {
+ return cursor.getCount() == 0;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ cleanUpDownloads();
+ super.tearDown();
+ }
+
+ /**
+ * Remove any downloaded files and delete any lingering downloads.
+ */
+ private void cleanUpDownloads() {
+ if (mResolver == null) {
+ return;
+ }
+ String[] columns = new String[] {Downloads._DATA};
+ Cursor cursor = mResolver.query(Downloads.CONTENT_URI, columns, null, null, null);
+ try {
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ String filePath = cursor.getString(0);
+ if (filePath == null) continue;
+ Log.d(LOG_TAG, "Deleting " + filePath);
+ new File(filePath).delete();
+ }
+ } finally {
+ cursor.close();
+ }
+ mResolver.delete(Downloads.CONTENT_URI, null, null);
+ }
+
+ public void testBasicRequest() throws Exception {
+ enqueueResponse(HTTP_OK, FILE_CONTENT);
+
+ String path = "/download_manager_test_path";
+ Uri downloadUri = requestDownload(path);
+ assertEquals(Downloads.STATUS_PENDING, getDownloadStatus(downloadUri));
+ assertTrue(mTestContext.mHasServiceBeenStarted);
+
+ startService(null);
+
+ RecordedRequest request = takeRequest();
+ assertEquals("GET", request.getMethod());
+ assertEquals(path, request.getPath());
+
+ waitForDownloadToStop(downloadUri, Downloads.STATUS_SUCCESS);
+ assertEquals(FILE_CONTENT, getDownloadContents(downloadUri));
+ checkForUnexpectedRequests();
+ }
+
+ public void testFileNotFound() throws Exception {
+ enqueueEmptyResponse(HTTP_NOT_FOUND);
+ Uri downloadUri = requestDownload("/nonexistent_path");
+ assertEquals(Downloads.STATUS_PENDING, getDownloadStatus(downloadUri));
+
+ startService(null);
+ takeRequest();
+ waitForDownloadToStop(downloadUri, HTTP_NOT_FOUND);
+ checkForUnexpectedRequests();
+ }
+
+ public void testBasicConnectivityChanges() throws Exception {
+ enqueueResponse(HTTP_OK, FILE_CONTENT);
+ Uri downloadUri = requestDownload("/path");
+
+ // without connectivity, download immediately pauses
+ mTestContext.mFakeIConnectivityManager.setNetworkState(NetworkInfo.State.DISCONNECTED);
+ startService(null);
+ waitForDownloadToStop(downloadUri, Downloads.STATUS_RUNNING_PAUSED);
+ checkForUnexpectedRequests();
+
+ // connecting should start the download
+ mTestContext.mFakeIConnectivityManager.setNetworkState(NetworkInfo.State.CONNECTED);
+ startService(null); // normally done by DownloadReceiver
+ takeRequest();
+ waitForDownloadToStop(downloadUri, Downloads.STATUS_SUCCESS);
+ checkForUnexpectedRequests();
+ }
+
+ // disabled due to excessive sleep
+ public void disabledTestInterruptedDownload() throws Exception {
+ int initialLength = 5;
+ String etag = "my_etag";
+ int totalLength = FILE_CONTENT.length();
+ // the first response has normal headers but unexpectedly closes after initialLength bytes
+ enqueueResponse(HTTP_OK, FILE_CONTENT.substring(0, initialLength))
+ .addHeader("Content-length", totalLength)
+ .addHeader("Etag", etag)
+ .setCloseConnectionAfter(true);
+ Uri downloadUri = requestDownload("/path");
+
+ startService(null);
+ takeRequest();
+ waitForDownloadToStop(downloadUri, Downloads.STATUS_RUNNING_PAUSED);
+
+ Thread.sleep(61 * 1000); // TODO: avoid this by stubbing the system clock
+ mServer.drainRequests();
+ // the second response returns partial content for the rest of the data
+ enqueueResponse(HTTP_PARTIAL_CONTENT, FILE_CONTENT.substring(initialLength))
+ .addHeader("Content-range",
+ "bytes " + initialLength + "-" + totalLength + "/" + totalLength)
+ .addHeader("Etag", etag);
+ // TODO: ideally we wouldn't need to call startService again, but there's a bug where the
+ // service won't retry a download until an intent comes in
+ startService(null);
+ waitForDownloadToStop(downloadUri, Downloads.STATUS_SUCCESS);
+
+ RecordedRequest request = takeRequest();
+ List<String> headers = request.getHeaders();
+ assertTrue("No Range header: " + headers,
+ headers.contains("Range: bytes=" + initialLength + "-"));
+ assertTrue("No ETag header: " + headers, headers.contains("If-Match: " + etag));
+
+ assertEquals(FILE_CONTENT, getDownloadContents(downloadUri));
+ checkForUnexpectedRequests();
+ }
+
+ /**
+ * Enqueue a response from the MockWebServer.
+ */
+ private MockResponse enqueueResponse(int status, String body) {
+ MockResponse response = new MockResponse()
+ .setResponseCode(status)
+ .setBody(body)
+ .addHeader("Content-type", "text/plain");
+ mServer.enqueue(response);
+ return response;
+ }
+
+ private void enqueueEmptyResponse(int status) {
+ enqueueResponse(status, "");
+ }
+
+ /**
+ * Wait for a request to come to the MockWebServer and return it.
+ */
+ private RecordedRequest takeRequest() throws InterruptedException {
+ RecordedRequest request = mServer.takeRequestWithTimeout(REQUEST_TIMEOUT_MILLIS);
+ assertNotNull("Timed out waiting for request", request);
+ return request;
+ }
+
+ /**
+ * Read a downloaded file from disk.
+ */
+ private String getDownloadContents(Uri downloadUri) throws Exception {
+ InputStream inputStream = mResolver.openInputStream(downloadUri);
+ try {
+ return readStream(inputStream);
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ /**
+ * Wait for a download to given a given status, with a timeout. Fails if the download reaches
+ * any other final status.
+ */
+ private void waitForDownloadToStop(Uri downloadUri, int expectedStatus) {
+ // TODO(showard): find a better way to accomplish this
+ long startTimeMillis = System.currentTimeMillis();
+ int status = getDownloadStatus(downloadUri);
+ while (status != expectedStatus) {
+ if (!Downloads.isStatusInformational(status)) {
+ fail("Download completed with unexpected status: " + status);
+ }
+ if (System.currentTimeMillis() > startTimeMillis + REQUEST_TIMEOUT_MILLIS) {
+ fail("Download timed out with status " + status);
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException exc) {
+ // no problem
+ }
+ status = getDownloadStatus(downloadUri);
+ }
+
+ long delta = System.currentTimeMillis() - startTimeMillis;
+ Log.d(LOG_TAG, "Status " + status + " reached after " + delta + "ms");
+ }
+
+ private int getDownloadStatus(Uri downloadUri) {
+ final String[] columns = new String[] {Downloads.COLUMN_STATUS};
+ Cursor cursor = mResolver.query(downloadUri, columns, null, null, null);
+ try {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Request a download from the Download Manager.
+ */
+ private Uri requestDownload(String path) throws MalformedURLException {
+ ContentValues values = new ContentValues();
+ values.put(Downloads.COLUMN_URI, mServer.getUrl(path).toString());
+ values.put(Downloads.COLUMN_DESTINATION, Downloads.DESTINATION_EXTERNAL);
+ return mResolver.insert(Downloads.CONTENT_URI, values);
+ }
+
+ private String readStream(InputStream inputStream) throws IOException {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ try {
+ char[] buffer = new char[1024];
+ int length = reader.read(buffer);
+ assertTrue("Failed to read anything from input stream", length > -1);
+ return String.valueOf(buffer, 0, length);
+ } finally {
+ reader.close();
+ }
+ }
+
+ /**
+ * Check for any extra requests made to the MockWebServer that weren't consumed with
+ * {@link #takeRequest()}.
+ */
+ private void checkForUnexpectedRequests() {
+ if (mServer == null) {
+ return;
+ }
+ List<RecordedRequest> extraRequests = mServer.drainRequests();
+ assertEquals("Invalid requests: " + extraRequests.toString(), 0, extraRequests.size());
+ }
+}
diff --git a/tests/src/com/android/providers/downloads/FakeIConnectivityManager.java b/tests/src/com/android/providers/downloads/FakeIConnectivityManager.java
new file mode 100644
index 0000000..0e92bd5
--- /dev/null
+++ b/tests/src/com/android/providers/downloads/FakeIConnectivityManager.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2010 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 com.android.providers.downloads;
+
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+class FakeIConnectivityManager implements IConnectivityManager {
+ private static class MockNetworkInfo extends NetworkInfo {
+ private State mState;
+
+ @SuppressWarnings("deprecation")
+ public MockNetworkInfo(State state) {
+ super(0);
+ mState = state;
+ }
+
+ @Override
+ public State getState() {
+ return mState;
+ }
+
+ @Override
+ public int getType() {
+ return ConnectivityManager.TYPE_MOBILE;
+ }
+ }
+
+ private State mCurrentState = State.CONNECTED;
+
+ public void setNetworkState(State state) {
+ mCurrentState = state;
+ }
+
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ public NetworkInfo getActiveNetworkInfo() throws RemoteException {
+ return new MockNetworkInfo(mCurrentState);
+ }
+
+ public NetworkInfo[] getAllNetworkInfo() throws RemoteException {
+ return new NetworkInfo[] {getActiveNetworkInfo()};
+ }
+
+ public boolean getBackgroundDataSetting() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getLastTetherError(String iface) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean getMobileDataEnabled() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public NetworkInfo getNetworkInfo(int networkType) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getNetworkPreference() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public String[] getTetherableIfaces() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public String[] getTetherableUsbRegexs() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public String[] getTetherableWifiRegexs() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public String[] getTetheredIfaces() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public String[] getTetheringErroredIfaces() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isTetheringSupported() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean requestRouteToHost(int networkType, int hostAddress) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setBackgroundDataSetting(boolean allowBackgroundData) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setMobileDataEnabled(boolean enabled) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setNetworkPreference(int pref) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean setRadio(int networkType, boolean turnOn) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean setRadios(boolean onOff) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public int startUsingNetworkFeature(int networkType, String feature, IBinder binder)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public int stopUsingNetworkFeature(int networkType, String feature) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public int tether(String iface) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ public int untether(String iface) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/tests/src/tests/http/MockResponse.java b/tests/src/tests/http/MockResponse.java
index 9893e2f..2139701 100644
--- a/tests/src/tests/http/MockResponse.java
+++ b/tests/src/tests/http/MockResponse.java
@@ -16,28 +16,29 @@
package tests.http;
+import static tests.http.MockWebServer.ASCII;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
-
-import static tests.http.MockWebServer.ASCII;
+import java.util.Map;
/**
* A scripted response to be replayed by the mock web server.
*/
public class MockResponse {
- private static final String EMPTY_BODY_HEADER = "Content-Length: 0";
- private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked";
private static final byte[] EMPTY_BODY = new byte[0];
private String status = "HTTP/1.1 200 OK";
- private List<String> headers = new ArrayList<String>();
+ private Map<String, String> headers = new HashMap<String, String>();
private byte[] body = EMPTY_BODY;
+ private boolean closeConnectionAfter = false;
public MockResponse() {
- headers.add(EMPTY_BODY_HEADER);
+ addHeader("Content-Length", 0);
}
/**
@@ -56,14 +57,22 @@ public class MockResponse {
* Returns the HTTP headers, such as "Content-Length: 0".
*/
public List<String> getHeaders() {
- return headers;
+ List<String> headerStrings = new ArrayList<String>();
+ for (String header : headers.keySet()) {
+ headerStrings.add(header + ": " + headers.get(header));
+ }
+ return headerStrings;
}
- public MockResponse addHeader(String header) {
- headers.add(header);
+ public MockResponse addHeader(String header, String value) {
+ headers.put(header.toLowerCase(), value);
return this;
}
+ public MockResponse addHeader(String header, int value) {
+ return addHeader(header, Integer.toString(value));
+ }
+
/**
* Returns an input stream containing the raw HTTP payload.
*/
@@ -72,10 +81,7 @@ public class MockResponse {
}
public MockResponse setBody(byte[] body) {
- if (this.body == EMPTY_BODY) {
- headers.remove(EMPTY_BODY_HEADER);
- }
- this.headers.add("Content-Length: " + body.length);
+ addHeader("Content-Length", body.length);
this.body = body;
return this;
}
@@ -89,8 +95,7 @@ public class MockResponse {
}
public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException {
- headers.remove(EMPTY_BODY_HEADER);
- headers.add(CHUNKED_BODY_HEADER);
+ addHeader("Transfer-encoding", "chunked");
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
int pos = 0;
@@ -114,4 +119,13 @@ public class MockResponse {
@Override public String toString() {
return status;
}
+
+ public boolean shouldCloseConnectionAfter() {
+ return closeConnectionAfter;
+ }
+
+ public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) {
+ this.closeConnectionAfter = closeConnectionAfter;
+ return this;
+ }
}
diff --git a/tests/src/tests/http/MockWebServer.java b/tests/src/tests/http/MockWebServer.java
index e3df2e8..b2cb8d7 100644
--- a/tests/src/tests/http/MockWebServer.java
+++ b/tests/src/tests/http/MockWebServer.java
@@ -32,21 +32,22 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
/**
* A scriptable web server. Callers supply canned responses and the server
* replays them upon request in sequence.
+ *
+ * TODO: merge with the version from libcore/support/src/tests/java once it's in.
*/
public final class MockWebServer {
-
static final String ASCII = "US-ASCII";
private final BlockingQueue<RecordedRequest> requestQueue
= new LinkedBlockingQueue<RecordedRequest>();
private final BlockingQueue<MockResponse> responseQueue
- = new LinkedBlockingDeque<MockResponse>();
+ = new LinkedBlockingQueue<MockResponse>();
private int bodyLimit = Integer.MAX_VALUE;
private final ExecutorService executor = Executors.newCachedThreadPool();
@@ -88,6 +89,16 @@ public final class MockWebServer {
return requestQueue.take();
}
+ public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException {
+ return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+
+ public List<RecordedRequest> drainRequests() {
+ List<RecordedRequest> requests = new ArrayList<RecordedRequest>();
+ requestQueue.drainTo(requests);
+ return requests;
+ }
+
/**
* Starts the server, serves all enqueued requests, and shuts the server
* down.
@@ -130,7 +141,11 @@ public final class MockWebServer {
}
}
requestQueue.add(request);
- writeResponse(out, computeResponse(request));
+ MockResponse response = computeResponse(request);
+ writeResponse(out, response);
+ if (response.shouldCloseConnectionAfter()) {
+ break;
+ }
sequenceNumber++;
}
@@ -146,7 +161,7 @@ public final class MockWebServer {
*/
private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
String request = readAsciiUntilCrlf(in);
- if (request.isEmpty()) {
+ if (request.equals("")) {
return null; // end of data; no more requests
}
@@ -154,7 +169,7 @@ public final class MockWebServer {
int contentLength = -1;
boolean chunked = false;
String header;
- while (!(header = readAsciiUntilCrlf(in)).isEmpty()) {
+ while (!(header = readAsciiUntilCrlf(in)).equals("")) {
headers.add(header);
String lowercaseHeader = header.toLowerCase();
if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
@@ -219,7 +234,6 @@ public final class MockWebServer {
}
out.write(("\r\n").getBytes(ASCII));
out.write(response.getBody());
- out.write(("\r\n").getBytes(ASCII));
out.flush();
}
@@ -260,7 +274,7 @@ public final class MockWebServer {
private void readEmptyLine(InputStream in) throws IOException {
String line = readAsciiUntilCrlf(in);
- if (!line.isEmpty()) {
+ if (!line.equals("")) {
throw new IllegalStateException("Expected empty but was: " + line);
}
}
diff --git a/tests/src/tests/http/RecordedRequest.java b/tests/src/tests/http/RecordedRequest.java
index c805006..6b67af2 100644
--- a/tests/src/tests/http/RecordedRequest.java
+++ b/tests/src/tests/http/RecordedRequest.java
@@ -82,4 +82,12 @@ public final class RecordedRequest {
@Override public String toString() {
return requestLine;
}
+
+ public String getMethod() {
+ return getRequestLine().split(" ")[0];
+ }
+
+ public String getPath() {
+ return getRequestLine().split(" ")[1];
+ }
}