summaryrefslogtreecommitdiffstats
path: root/core/tests/utillib
diff options
context:
space:
mode:
authorNeal Nguyen <tommyn@google.com>2010-08-09 14:08:26 -0700
committerNeal Nguyen <tommyn@google.com>2010-09-08 17:02:53 -0700
commit5f53bca55b2c9e217dee12bff8ce55e168829783 (patch)
tree14f9203ef32283086d3fe6f1ac06d30804c1e2c9 /core/tests/utillib
parent3fa7d8af6560de07ef673f73308f7e51de64e4ec (diff)
downloadframeworks_base-5f53bca55b2c9e217dee12bff8ce55e168829783.zip
frameworks_base-5f53bca55b2c9e217dee12bff8ce55e168829783.tar.gz
frameworks_base-5f53bca55b2c9e217dee12bff8ce55e168829783.tar.bz2
Adding Download Manager Integration, stress, and hosts-based tests.
Change-Id: I97008f6cfd95ea9950db0b4e093da02528849b63
Diffstat (limited to 'core/tests/utillib')
-rw-r--r--core/tests/utillib/Android.mk27
-rw-r--r--core/tests/utillib/src/coretestutils/http/MockResponse.java239
-rw-r--r--core/tests/utillib/src/coretestutils/http/MockWebServer.java426
-rw-r--r--core/tests/utillib/src/coretestutils/http/RecordedRequest.java93
4 files changed, 785 insertions, 0 deletions
diff --git a/core/tests/utillib/Android.mk b/core/tests/utillib/Android.mk
new file mode 100644
index 0000000..299ea5a
--- /dev/null
+++ b/core/tests/utillib/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_MODULE := frameworks-core-util-lib
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/core/tests/utillib/src/coretestutils/http/MockResponse.java b/core/tests/utillib/src/coretestutils/http/MockResponse.java
new file mode 100644
index 0000000..5b03e5f
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/MockResponse.java
@@ -0,0 +1,239 @@
+/*
+ * 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 coretestutils.http;
+
+import static coretestutils.http.MockWebServer.ASCII;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.util.Log;
+
+/**
+ * A scripted response to be replayed by the mock web server.
+ */
+public class MockResponse {
+ private static final byte[] EMPTY_BODY = new byte[0];
+ static final String LOG_TAG = "coretestutils.http.MockResponse";
+
+ private String status = "HTTP/1.1 200 OK";
+ private Map<String, String> headers = new HashMap<String, String>();
+ private byte[] body = EMPTY_BODY;
+ private boolean closeConnectionAfter = false;
+ private String closeConnectionAfterHeader = null;
+ private int closeConnectionAfterXBytes = -1;
+ private int pauseConnectionAfterXBytes = -1;
+ private File bodyExternalFile = null;
+
+ public MockResponse() {
+ addHeader("Content-Length", 0);
+ }
+
+ /**
+ * Returns the HTTP response line, such as "HTTP/1.1 200 OK".
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ public MockResponse setResponseCode(int code) {
+ this.status = "HTTP/1.1 " + code + " OK";
+ return this;
+ }
+
+ /**
+ * Returns the HTTP headers, such as "Content-Length: 0".
+ */
+ public List<String> getHeaders() {
+ List<String> headerStrings = new ArrayList<String>();
+ for (String header : headers.keySet()) {
+ headerStrings.add(header + ": " + headers.get(header));
+ }
+ return headerStrings;
+ }
+
+ public MockResponse addHeader(String header, String value) {
+ headers.put(header.toLowerCase(), value);
+ return this;
+ }
+
+ public MockResponse addHeader(String header, long value) {
+ return addHeader(header, Long.toString(value));
+ }
+
+ public MockResponse removeHeader(String header) {
+ headers.remove(header.toLowerCase());
+ return this;
+ }
+
+ /**
+ * Returns true if the body should come from an external file, false otherwise.
+ */
+ private boolean bodyIsExternal() {
+ return bodyExternalFile != null;
+ }
+
+ /**
+ * Returns an input stream containing the raw HTTP payload.
+ */
+ public InputStream getBody() {
+ if (bodyIsExternal()) {
+ try {
+ return new FileInputStream(bodyExternalFile);
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, "File not found: " + bodyExternalFile.getAbsolutePath());
+ }
+ }
+ return new ByteArrayInputStream(this.body);
+ }
+
+ public MockResponse setBody(File body) {
+ addHeader("Content-Length", body.length());
+ this.bodyExternalFile = body;
+ return this;
+ }
+
+ public MockResponse setBody(byte[] body) {
+ addHeader("Content-Length", body.length);
+ this.body = body;
+ return this;
+ }
+
+ public MockResponse setBody(String body) {
+ try {
+ return setBody(body.getBytes(ASCII));
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Sets the body as chunked.
+ *
+ * Currently chunked body is not supported for external files as bodies.
+ */
+ public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException {
+ addHeader("Transfer-encoding", "chunked");
+
+ ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+ int pos = 0;
+ while (pos < body.length) {
+ int chunkSize = Math.min(body.length - pos, maxChunkSize);
+ bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII));
+ bytesOut.write("\r\n".getBytes(ASCII));
+ bytesOut.write(body, pos, chunkSize);
+ bytesOut.write("\r\n".getBytes(ASCII));
+ pos += chunkSize;
+ }
+ bytesOut.write("0\r\n".getBytes(ASCII));
+ this.body = bytesOut.toByteArray();
+ return this;
+ }
+
+ public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException {
+ return setChunkedBody(body.getBytes(ASCII), maxChunkSize);
+ }
+
+ @Override public String toString() {
+ return status;
+ }
+
+ public boolean shouldCloseConnectionAfter() {
+ return closeConnectionAfter;
+ }
+
+ public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) {
+ this.closeConnectionAfter = closeConnectionAfter;
+ return this;
+ }
+
+ /**
+ * Sets the header after which sending the server should close the connection.
+ */
+ public MockResponse setCloseConnectionAfterHeader(String header) {
+ closeConnectionAfterHeader = header;
+ setCloseConnectionAfter(true);
+ return this;
+ }
+
+ /**
+ * Returns the header after which sending the server should close the connection.
+ */
+ public String getCloseConnectionAfterHeader() {
+ return closeConnectionAfterHeader;
+ }
+
+ /**
+ * Sets the number of bytes in the body to send before which the server should close the
+ * connection. Set to -1 to unset and send the entire body (default).
+ */
+ public MockResponse setCloseConnectionAfterXBytes(int position) {
+ closeConnectionAfterXBytes = position;
+ setCloseConnectionAfter(true);
+ return this;
+ }
+
+ /**
+ * Returns the number of bytes in the body to send before which the server should close the
+ * connection. Returns -1 if the entire body should be sent (default).
+ */
+ public int getCloseConnectionAfterXBytes() {
+ return closeConnectionAfterXBytes;
+ }
+
+ /**
+ * Sets the number of bytes in the body to send before which the server should pause the
+ * connection (stalls in sending data). Only one pause per response is supported.
+ * Set to -1 to unset pausing (default).
+ */
+ public MockResponse setPauseConnectionAfterXBytes(int position) {
+ pauseConnectionAfterXBytes = position;
+ return this;
+ }
+
+ /**
+ * Returns the number of bytes in the body to send before which the server should pause the
+ * connection (stalls in sending data). (Returns -1 if it should not pause).
+ */
+ public int getPauseConnectionAfterXBytes() {
+ return pauseConnectionAfterXBytes;
+ }
+
+ /**
+ * Returns true if this response is flagged to pause the connection mid-stream, false otherwise
+ */
+ public boolean getShouldPause() {
+ return (pauseConnectionAfterXBytes != -1);
+ }
+
+ /**
+ * Returns true if this response is flagged to close the connection mid-stream, false otherwise
+ */
+ public boolean getShouldClose() {
+ return (closeConnectionAfterXBytes != -1);
+ }
+}
diff --git a/core/tests/utillib/src/coretestutils/http/MockWebServer.java b/core/tests/utillib/src/coretestutils/http/MockWebServer.java
new file mode 100644
index 0000000..c329ffa
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/MockWebServer.java
@@ -0,0 +1,426 @@
+/*
+ * 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 coretestutils.http;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import android.util.Log;
+
+/**
+ * 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";
+ static final String LOG_TAG = "coretestutils.http.MockWebServer";
+
+ private final BlockingQueue<RecordedRequest> requestQueue
+ = new LinkedBlockingQueue<RecordedRequest>();
+ private final BlockingQueue<MockResponse> responseQueue
+ = new LinkedBlockingQueue<MockResponse>();
+ private int bodyLimit = Integer.MAX_VALUE;
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+ // keep Futures around so we can rethrow any exceptions thrown by Callables
+ private final Queue<Future<?>> futures = new LinkedList<Future<?>>();
+ private final Object downloadPauseLock = new Object();
+ // global flag to signal when downloads should resume on the server
+ private volatile boolean downloadResume = false;
+
+ private int port = -1;
+
+ public int getPort() {
+ if (port == -1) {
+ throw new IllegalStateException("Cannot retrieve port before calling play()");
+ }
+ return port;
+ }
+
+ /**
+ * Returns a URL for connecting to this server.
+ *
+ * @param path the request path, such as "/".
+ */
+ public URL getUrl(String path) throws MalformedURLException {
+ return new URL("http://localhost:" + getPort() + path);
+ }
+
+ /**
+ * Sets the number of bytes of the POST body to keep in memory to the given
+ * limit.
+ */
+ public void setBodyLimit(int maxBodyLength) {
+ this.bodyLimit = maxBodyLength;
+ }
+
+ public void enqueue(MockResponse response) {
+ responseQueue.add(response);
+ }
+
+ /**
+ * Awaits the next HTTP request, removes it, and returns it. Callers should
+ * use this to verify the request sent was as intended.
+ */
+ public RecordedRequest takeRequest() throws InterruptedException {
+ 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 using the default (server-assigned) port.
+ */
+ public void play() throws IOException {
+ play(0);
+ }
+
+ /**
+ * Starts the server, serves all enqueued requests, and shuts the server
+ * down.
+ *
+ * @param port The port number to use to listen to connections on; pass in 0 to have the
+ * server automatically assign a free port
+ */
+ public void play(int portNumber) throws IOException {
+ final ServerSocket ss = new ServerSocket(portNumber);
+ ss.setReuseAddress(true);
+ port = ss.getLocalPort();
+ submitCallable(new Callable<Void>() {
+ public Void call() throws Exception {
+ int count = 0;
+ while (true) {
+ if (count > 0 && responseQueue.isEmpty()) {
+ ss.close();
+ executor.shutdown();
+ return null;
+ }
+
+ serveConnection(ss.accept());
+ count++;
+ }
+ }
+ });
+ }
+
+ private void serveConnection(final Socket s) {
+ submitCallable(new Callable<Void>() {
+ public Void call() throws Exception {
+ InputStream in = new BufferedInputStream(s.getInputStream());
+ OutputStream out = new BufferedOutputStream(s.getOutputStream());
+
+ int sequenceNumber = 0;
+ while (true) {
+ RecordedRequest request = readRequest(in, sequenceNumber);
+ if (request == null) {
+ if (sequenceNumber == 0) {
+ throw new IllegalStateException("Connection without any request!");
+ } else {
+ break;
+ }
+ }
+ requestQueue.add(request);
+ MockResponse response = computeResponse(request);
+ writeResponse(out, response);
+ if (response.shouldCloseConnectionAfter()) {
+ break;
+ }
+ sequenceNumber++;
+ }
+
+ in.close();
+ out.close();
+ return null;
+ }
+ });
+ }
+
+ private void submitCallable(Callable<?> callable) {
+ Future<?> future = executor.submit(callable);
+ futures.add(future);
+ }
+
+ /**
+ * Check for and raise any exceptions that have been thrown by child threads. Will not block on
+ * children still running.
+ * @throws ExecutionException for the first child thread that threw an exception
+ */
+ public void checkForExceptions() throws ExecutionException, InterruptedException {
+ final int originalSize = futures.size();
+ for (int i = 0; i < originalSize; i++) {
+ Future<?> future = futures.remove();
+ try {
+ future.get(0, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ futures.add(future); // still running
+ }
+ }
+ }
+
+ /**
+ * @param sequenceNumber the index of this request on this connection.
+ */
+ private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
+ String request = readAsciiUntilCrlf(in);
+ if (request.equals("")) {
+ return null; // end of data; no more requests
+ }
+
+ List<String> headers = new ArrayList<String>();
+ int contentLength = -1;
+ boolean chunked = false;
+ String header;
+ while (!(header = readAsciiUntilCrlf(in)).equals("")) {
+ headers.add(header);
+ String lowercaseHeader = header.toLowerCase();
+ if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
+ contentLength = Integer.parseInt(header.substring(15).trim());
+ }
+ if (lowercaseHeader.startsWith("transfer-encoding:") &&
+ lowercaseHeader.substring(18).trim().equals("chunked")) {
+ chunked = true;
+ }
+ }
+
+ boolean hasBody = false;
+ TruncatingOutputStream requestBody = new TruncatingOutputStream();
+ List<Integer> chunkSizes = new ArrayList<Integer>();
+ if (contentLength != -1) {
+ hasBody = true;
+ transfer(contentLength, in, requestBody);
+ } else if (chunked) {
+ hasBody = true;
+ while (true) {
+ int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
+ if (chunkSize == 0) {
+ readEmptyLine(in);
+ break;
+ }
+ chunkSizes.add(chunkSize);
+ transfer(chunkSize, in, requestBody);
+ readEmptyLine(in);
+ }
+ }
+
+ if (request.startsWith("GET ")) {
+ if (hasBody) {
+ throw new IllegalArgumentException("GET requests should not have a body!");
+ }
+ } else if (request.startsWith("POST ")) {
+ if (!hasBody) {
+ throw new IllegalArgumentException("POST requests must have a body!");
+ }
+ } else {
+ throw new UnsupportedOperationException("Unexpected method: " + request);
+ }
+
+ return new RecordedRequest(request, headers, chunkSizes,
+ requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
+ }
+
+ /**
+ * Returns a response to satisfy {@code request}.
+ */
+ private MockResponse computeResponse(RecordedRequest request) throws InterruptedException {
+ if (responseQueue.isEmpty()) {
+ throw new IllegalStateException("Unexpected request: " + request);
+ }
+ return responseQueue.take();
+ }
+
+ private void writeResponse(OutputStream out, MockResponse response) throws IOException {
+ out.write((response.getStatus() + "\r\n").getBytes(ASCII));
+ boolean doCloseConnectionAfterHeader = (response.getCloseConnectionAfterHeader() != null);
+
+ // Send headers
+ String closeConnectionAfterHeader = response.getCloseConnectionAfterHeader();
+ for (String header : response.getHeaders()) {
+ out.write((header + "\r\n").getBytes(ASCII));
+
+ if (doCloseConnectionAfterHeader && header.startsWith(closeConnectionAfterHeader)) {
+ Log.i(LOG_TAG, "Closing connection after header" + header);
+ break;
+ }
+ }
+
+ // Send actual body data
+ if (!doCloseConnectionAfterHeader) {
+ out.write(("\r\n").getBytes(ASCII));
+
+ InputStream body = response.getBody();
+ final int READ_BLOCK_SIZE = 10000; // process blocks this size
+ byte[] currentBlock = new byte[READ_BLOCK_SIZE];
+ int currentBlockSize = 0;
+ int writtenSoFar = 0;
+
+ boolean shouldPause = response.getShouldPause();
+ boolean shouldClose = response.getShouldClose();
+ int pause = response.getPauseConnectionAfterXBytes();
+ int close = response.getCloseConnectionAfterXBytes();
+
+ // Don't bother pausing if it's set to pause -after- the connection should be dropped
+ if (shouldPause && shouldClose && (pause > close)) {
+ shouldPause = false;
+ }
+
+ // Process each block we read in...
+ while ((currentBlockSize = body.read(currentBlock)) != -1) {
+ int startIndex = 0;
+ int writeLength = currentBlockSize;
+
+ // handle the case of pausing
+ if (shouldPause && (writtenSoFar + currentBlockSize >= pause)) {
+ writeLength = pause - writtenSoFar;
+ out.write(currentBlock, 0, writeLength);
+ out.flush();
+ writtenSoFar += writeLength;
+
+ // now pause...
+ try {
+ Log.i(LOG_TAG, "Pausing connection after " + pause + " bytes");
+ // Wait until someone tells us to resume sending...
+ synchronized(downloadPauseLock) {
+ while (!downloadResume) {
+ downloadPauseLock.wait();
+ }
+ // reset resume back to false
+ downloadResume = false;
+ }
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Server was interrupted during pause in download.");
+ }
+
+ startIndex = writeLength;
+ writeLength = currentBlockSize - writeLength;
+ }
+
+ // handle the case of closing the connection
+ if (shouldClose && (writtenSoFar + writeLength > close)) {
+ writeLength = close - writtenSoFar;
+ out.write(currentBlock, startIndex, writeLength);
+ writtenSoFar += writeLength;
+ Log.i(LOG_TAG, "Closing connection after " + close + " bytes");
+ break;
+ }
+ out.write(currentBlock, startIndex, writeLength);
+ writtenSoFar += writeLength;
+ }
+ }
+ out.flush();
+ }
+
+ /**
+ * Transfer bytes from {@code in} to {@code out} until either {@code length}
+ * bytes have been transferred or {@code in} is exhausted.
+ */
+ private void transfer(int length, InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[1024];
+ while (length > 0) {
+ int count = in.read(buffer, 0, Math.min(buffer.length, length));
+ if (count == -1) {
+ return;
+ }
+ out.write(buffer, 0, count);
+ length -= count;
+ }
+ }
+
+ /**
+ * Returns the text from {@code in} until the next "\r\n", or null if
+ * {@code in} is exhausted.
+ */
+ private String readAsciiUntilCrlf(InputStream in) throws IOException {
+ StringBuilder builder = new StringBuilder();
+ while (true) {
+ int c = in.read();
+ if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.toString();
+ } else if (c == -1) {
+ return builder.toString();
+ } else {
+ builder.append((char) c);
+ }
+ }
+ }
+
+ private void readEmptyLine(InputStream in) throws IOException {
+ String line = readAsciiUntilCrlf(in);
+ if (!line.equals("")) {
+ throw new IllegalStateException("Expected empty but was: " + line);
+ }
+ }
+
+ /**
+ * An output stream that drops data after bodyLimit bytes.
+ */
+ private class TruncatingOutputStream extends ByteArrayOutputStream {
+ private int numBytesReceived = 0;
+ @Override public void write(byte[] buffer, int offset, int len) {
+ numBytesReceived += len;
+ super.write(buffer, offset, Math.min(len, bodyLimit - count));
+ }
+ @Override public void write(int oneByte) {
+ numBytesReceived++;
+ if (count < bodyLimit) {
+ super.write(oneByte);
+ }
+ }
+ }
+
+ /**
+ * Trigger the server to resume sending the download
+ */
+ public void doResumeDownload() {
+ synchronized (downloadPauseLock) {
+ downloadResume = true;
+ downloadPauseLock.notifyAll();
+ }
+ }
+}
diff --git a/core/tests/utillib/src/coretestutils/http/RecordedRequest.java b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
new file mode 100644
index 0000000..293ff80
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
@@ -0,0 +1,93 @@
+/*
+ * 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 coretestutils.http;
+
+import java.util.List;
+
+/**
+ * An HTTP request that came into the mock web server.
+ */
+public final class RecordedRequest {
+ private final String requestLine;
+ private final List<String> headers;
+ private final List<Integer> chunkSizes;
+ private final int bodySize;
+ private final byte[] body;
+ private final int sequenceNumber;
+
+ RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes,
+ int bodySize, byte[] body, int sequenceNumber) {
+ this.requestLine = requestLine;
+ this.headers = headers;
+ this.chunkSizes = chunkSizes;
+ this.bodySize = bodySize;
+ this.body = body;
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ public String getRequestLine() {
+ return requestLine;
+ }
+
+ public List<String> getHeaders() {
+ return headers;
+ }
+
+ /**
+ * Returns the sizes of the chunks of this request's body, or an empty list
+ * if the request's body was empty or unchunked.
+ */
+ public List<Integer> getChunkSizes() {
+ return chunkSizes;
+ }
+
+ /**
+ * Returns the total size of the body of this POST request (before
+ * truncation).
+ */
+ public int getBodySize() {
+ return bodySize;
+ }
+
+ /**
+ * Returns the body of this POST request. This may be truncated.
+ */
+ public byte[] getBody() {
+ return body;
+ }
+
+ /**
+ * Returns the index of this request on its HTTP connection. Since a single
+ * HTTP connection may serve multiple requests, each request is assigned its
+ * own sequence number.
+ */
+ public int getSequenceNumber() {
+ return sequenceNumber;
+ }
+
+ @Override public String toString() {
+ return requestLine;
+ }
+
+ public String getMethod() {
+ return getRequestLine().split(" ")[0];
+ }
+
+ public String getPath() {
+ return getRequestLine().split(" ")[1];
+ }
+}