diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Android.mk | 15 | ||||
-rw-r--r-- | tests/AndroidManifest.xml | 34 | ||||
-rw-r--r-- | tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java | 395 | ||||
-rw-r--r-- | tests/src/com/android/providers/downloads/FakeIConnectivityManager.java | 149 | ||||
-rw-r--r-- | tests/src/tests/http/MockResponse.java | 44 | ||||
-rw-r--r-- | tests/src/tests/http/MockWebServer.java | 30 | ||||
-rw-r--r-- | tests/src/tests/http/RecordedRequest.java | 8 |
7 files changed, 648 insertions, 27 deletions
diff --git a/tests/Android.mk b/tests/Android.mk index e9e3a87a..80a1c761 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 00000000..4d971db1 --- /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 00000000..76b3d589 --- /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 00000000..0e92bd5f --- /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 9893e2fc..21397019 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 e3df2e83..b2cb8d7b 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 c8050065..6b67af20 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]; + } } |