Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1112)

Unified Diff: net/http/http_stream_parser_unittest.cc

Issue 921453003: Allow URLRequest::AppendChunkToUpload to take 0-byte final chunks. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Reduce tests, refactor out some of the boilerplate, merge some lines Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | net/url_request/url_request.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/http/http_stream_parser_unittest.cc
diff --git a/net/http/http_stream_parser_unittest.cc b/net/http/http_stream_parser_unittest.cc
index 01d4e120ab1b583bae90aef127db07654e16224b..a30cb0091cdb225e21ede3fc1df05a43a7e60008 100644
--- a/net/http/http_stream_parser_unittest.cc
+++ b/net/http/http_stream_parser_unittest.cc
@@ -40,6 +40,24 @@ const size_t kOutputSize = 1024; // Just large enough for this test.
const size_t kMaxPayloadSize =
kOutputSize - HttpStreamParser::kChunkHeaderFooterSize;
+// Helper method to create a connected ClientSocketHandle using |data|.
+// Modifies |data|.
+scoped_ptr<ClientSocketHandle> CreateConnectedSocketHandle(
+ DeterministicSocketData* data) {
+ data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+
+ scoped_ptr<DeterministicMockTCPClientSocket> transport(
+ new DeterministicMockTCPClientSocket(nullptr, data));
+ data->set_delegate(transport->AsWeakPtr());
+
+ TestCompletionCallback callback;
+ EXPECT_EQ(OK, transport->Connect(callback.callback()));
+
+ scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle);
+ socket_handle->SetSocket(transport.Pass());
+ return socket_handle.Pass();
+}
+
// The empty payload is how the last chunk is encoded.
TEST(HttpStreamParser, EncodeChunk_EmptyPayload) {
char output[kOutputSize];
@@ -184,11 +202,174 @@ TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_LargeBodyInMemory) {
}
// Test to ensure the HttpStreamParser state machine does not get confused
+// when sending a request with a chunked body with only one chunk that becomes
+// available asynchronously.
+TEST(HttpStreamParser, AsyncSingleChunkAndAsyncSocket) {
+ static const char kChunk[] = "Chunk";
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "GET /one.html HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n\r\n"),
+ MockWrite(ASYNC, 1, "5\r\nChunk\r\n"),
+ MockWrite(ASYNC, 2, "0\r\n\r\n"),
+ };
+
+ // The size of the response body, as reflected in the Content-Length of the
+ // MockRead below.
+ static const int kBodySize = 8;
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 3, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 4, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 5, "one.html"),
+ MockRead(SYNCHRONOUS, 0, 6), // EOF
+ };
+
+ ChunkedUploadDataStream upload_stream(0);
+ ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback()));
+
+ DeterministicSocketData data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ scoped_ptr<ClientSocketHandle> socket_handle =
+ CreateConnectedSocketHandle(&data);
+
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("http://localhost");
+ request_info.upload_data_stream = &upload_stream;
+
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer);
+ HttpStreamParser parser(socket_handle.get(), &request_info, read_buffer.get(),
+ BoundNetLog());
+
+ HttpRequestHeaders request_headers;
+ request_headers.SetHeader("Transfer-Encoding", "chunked");
+
+ HttpResponseInfo response_info;
+ TestCompletionCallback callback;
+ // This will attempt to Write() the initial request and headers, which will
+ // complete asynchronously.
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers,
+ &response_info, callback.callback()));
+
+ // Complete the initial request write.
+ data.RunFor(1);
+ ASSERT_FALSE(callback.have_result());
+
+ // Now append the only chunk.
+ upload_stream.AppendData(kChunk, arraysize(kChunk) - 1, true);
+ // Write the chunk.
+ data.RunFor(1);
+ ASSERT_FALSE(callback.have_result());
+
+ // Write the trailer.
+ data.RunFor(1);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_EQ(OK, callback.WaitForResult());
+
+ // Attempt to read the response status and the response headers.
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback()));
+ data.RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_GT(callback.WaitForResult(), 0);
+
+ // Finally, attempt to read the response body.
+ scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize));
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.ReadResponseBody(body_buffer.get(), kBodySize,
+ callback.callback()));
+ data.RunFor(1);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_EQ(kBodySize, callback.WaitForResult());
+}
+
+// Test to ensure the HttpStreamParser state machine does not get confused
+// when sending a request with a chunked body with only one chunk that is
+// available synchronously.
+TEST(HttpStreamParser, SyncSingleChunkAndAsyncSocket) {
+ static const char kChunk[] = "Chunk";
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "GET /one.html HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n\r\n"),
+ MockWrite(ASYNC, 1, "5\r\nChunk\r\n"),
+ MockWrite(ASYNC, 2, "0\r\n\r\n"),
+ };
+
+ // The size of the response body, as reflected in the Content-Length of the
+ // MockRead below.
+ static const int kBodySize = 8;
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 3, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 4, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 5, "one.html"),
+ MockRead(SYNCHRONOUS, 0, 6), // EOF
+ };
+
+ ChunkedUploadDataStream upload_stream(0);
+ ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback()));
+ // Append the only chunk.
+ upload_stream.AppendData(kChunk, arraysize(kChunk) - 1, true);
+
+ DeterministicSocketData data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ scoped_ptr<ClientSocketHandle> socket_handle =
+ CreateConnectedSocketHandle(&data);
+
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("http://localhost");
+ request_info.upload_data_stream = &upload_stream;
+
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer);
+ HttpStreamParser parser(socket_handle.get(), &request_info, read_buffer.get(),
+ BoundNetLog());
+
+ HttpRequestHeaders request_headers;
+ request_headers.SetHeader("Transfer-Encoding", "chunked");
+
+ HttpResponseInfo response_info;
+ TestCompletionCallback callback;
+ // This will attempt to Write() the initial request and headers, which will
+ // complete asynchronously.
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers,
+ &response_info, callback.callback()));
+
+ // Write the request and the only chunk.
+ data.RunFor(2);
+
+ // Write the trailer.
+ data.RunFor(1);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_EQ(OK, callback.WaitForResult());
+
+ // Attempt to read the response status and the response headers.
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback()));
+ data.RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_GT(callback.WaitForResult(), 0);
+
+ // Finally, attempt to read the response body.
+ scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize));
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.ReadResponseBody(body_buffer.get(), kBodySize,
+ callback.callback()));
+ data.RunFor(1);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_EQ(kBodySize, callback.WaitForResult());
+}
+
+// Test to ensure the HttpStreamParser state machine does not get confused
// when sending a request with a chunked body, where chunks become available
// asynchronously, over a socket where writes may also complete
// asynchronously.
// This is a regression test for http://crbug.com/132243
-TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) {
+TEST(HttpStreamParser, AsyncChunkAndAsyncSocketWithMultipleChunks) {
// The chunks that will be written in the request, as reflected in the
// MockWrites below.
static const char kChunk1[] = "Chunk 1";
@@ -196,15 +377,13 @@ TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) {
static const char kChunk3[] = "Test 3";
MockWrite writes[] = {
- MockWrite(ASYNC, 0,
- "GET /one.html HTTP/1.1\r\n"
- "Host: localhost\r\n"
- "Transfer-Encoding: chunked\r\n"
- "Connection: keep-alive\r\n\r\n"),
- MockWrite(ASYNC, 1, "7\r\nChunk 1\r\n"),
- MockWrite(ASYNC, 2, "8\r\nChunky 2\r\n"),
- MockWrite(ASYNC, 3, "6\r\nTest 3\r\n"),
- MockWrite(ASYNC, 4, "0\r\n\r\n"),
+ MockWrite(ASYNC, 0,
+ "GET /one.html HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n\r\n"),
+ MockWrite(ASYNC, 1, "7\r\nChunk 1\r\n"),
+ MockWrite(ASYNC, 2, "8\r\nChunky 2\r\n"),
+ MockWrite(ASYNC, 3, "6\r\nTest 3\r\n"),
+ MockWrite(ASYNC, 4, "0\r\n\r\n"),
};
// The size of the response body, as reflected in the Content-Length of the
@@ -222,26 +401,14 @@ TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) {
upload_stream.AppendData(kChunk1, arraysize(kChunk1) - 1, false);
ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback()));
- DeterministicSocketData data(reads, arraysize(reads),
- writes, arraysize(writes));
- data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
-
- scoped_ptr<DeterministicMockTCPClientSocket> transport(
- new DeterministicMockTCPClientSocket(NULL, &data));
- data.set_delegate(transport->AsWeakPtr());
-
- TestCompletionCallback callback;
- int rv = transport->Connect(callback.callback());
- rv = callback.GetResult(rv);
- ASSERT_EQ(OK, rv);
-
- scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle);
- socket_handle->SetSocket(transport.Pass());
+ DeterministicSocketData data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ scoped_ptr<ClientSocketHandle> socket_handle =
+ CreateConnectedSocketHandle(&data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://localhost");
- request_info.load_flags = LOAD_NORMAL;
request_info.upload_data_stream = &upload_stream;
scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer);
@@ -249,16 +416,15 @@ TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) {
socket_handle.get(), &request_info, read_buffer.get(), BoundNetLog());
HttpRequestHeaders request_headers;
- request_headers.SetHeader("Host", "localhost");
request_headers.SetHeader("Transfer-Encoding", "chunked");
- request_headers.SetHeader("Connection", "keep-alive");
HttpResponseInfo response_info;
+ TestCompletionCallback callback;
// This will attempt to Write() the initial request and headers, which will
// complete asynchronously.
- rv = parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers,
- &response_info, callback.callback());
- ASSERT_EQ(ERR_IO_PENDING, rv);
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers,
+ &response_info, callback.callback()));
// Complete the initial request write. Additionally, this should enqueue the
// first chunk.
@@ -294,32 +460,174 @@ TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) {
// Finalize writing the trailer.
data.RunFor(1);
ASSERT_TRUE(callback.have_result());
-
- // Warning: This will hang if the callback doesn't already have a result,
- // due to the deterministic socket provider. Do not remove the above
- // ASSERT_TRUE, which will avoid this hang.
- rv = callback.WaitForResult();
- ASSERT_EQ(OK, rv);
+ ASSERT_EQ(OK, callback.WaitForResult());
// Attempt to read the response status and the response headers.
- rv = parser.ReadResponseHeaders(callback.callback());
- ASSERT_EQ(ERR_IO_PENDING, rv);
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback()));
data.RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_GT(callback.WaitForResult(), 0);
+
+ // Finally, attempt to read the response body.
+ scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize));
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.ReadResponseBody(body_buffer.get(), kBodySize,
+ callback.callback()));
+ data.RunFor(1);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_EQ(kBodySize, callback.WaitForResult());
+}
+
+// Test to ensure the HttpStreamParser state machine does not get confused
+// when there's only one "chunk" with 0 bytes, and is received from the
+// UploadStream only after sending the request headers successfully.
+TEST(HttpStreamParser, AsyncEmptyChunkedUpload) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "GET /one.html HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n\r\n"),
+ MockWrite(ASYNC, 1, "0\r\n\r\n"),
+ };
+
+ // The size of the response body, as reflected in the Content-Length of the
+ // MockRead below.
+ const int kBodySize = 8;
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 4, "one.html"),
+ MockRead(SYNCHRONOUS, 0, 5), // EOF
+ };
+
+ ChunkedUploadDataStream upload_stream(0);
+ ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback()));
+
+ DeterministicSocketData data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ scoped_ptr<ClientSocketHandle> socket_handle =
+ CreateConnectedSocketHandle(&data);
+
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("http://localhost");
+ request_info.upload_data_stream = &upload_stream;
+
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer);
+ HttpStreamParser parser(socket_handle.get(), &request_info, read_buffer.get(),
+ BoundNetLog());
+
+ HttpRequestHeaders request_headers;
+ request_headers.SetHeader("Transfer-Encoding", "chunked");
+
+ HttpResponseInfo response_info;
+ TestCompletionCallback callback;
+ // This will attempt to Write() the initial request and headers, which will
+ // complete asynchronously.
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers,
+ &response_info, callback.callback()));
+
+ // Complete writing the request headers.
+ data.RunFor(1);
+ ASSERT_FALSE(callback.have_result());
+
+ // Now append the terminal 0-byte "chunk".
+ upload_stream.AppendData(nullptr, 0, true);
+ ASSERT_FALSE(callback.have_result());
+
+ // Finalize writing the trailer.
+ data.RunFor(1);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_EQ(OK, callback.WaitForResult());
+ // Attempt to read the response status and the response headers.
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback()));
+ data.RunFor(2);
ASSERT_TRUE(callback.have_result());
- rv = callback.WaitForResult();
- ASSERT_GT(rv, 0);
+ ASSERT_GT(callback.WaitForResult(), 0);
// Finally, attempt to read the response body.
scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize));
- rv = parser.ReadResponseBody(
- body_buffer.get(), kBodySize, callback.callback());
- ASSERT_EQ(ERR_IO_PENDING, rv);
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.ReadResponseBody(body_buffer.get(), kBodySize,
+ callback.callback()));
data.RunFor(1);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_EQ(kBodySize, callback.WaitForResult());
+}
+
+// Test to ensure the HttpStreamParser state machine does not get confused
+// when there's only one "chunk" with 0 bytes, which was already appended before
+// the request was started.
+TEST(HttpStreamParser, SyncEmptyChunkedUpload) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "GET /one.html HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n\r\n"),
+ MockWrite(ASYNC, 1, "0\r\n\r\n"),
+ };
+
+ // The size of the response body, as reflected in the Content-Length of the
+ // MockRead below.
+ const int kBodySize = 8;
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 4, "one.html"),
+ MockRead(SYNCHRONOUS, 0, 5), // EOF
+ };
+
+ ChunkedUploadDataStream upload_stream(0);
+ ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback()));
+ // Append final empty chunk.
+ upload_stream.AppendData(nullptr, 0, true);
+ DeterministicSocketData data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ scoped_ptr<ClientSocketHandle> socket_handle =
+ CreateConnectedSocketHandle(&data);
+
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("http://localhost");
+ request_info.upload_data_stream = &upload_stream;
+
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer);
+ HttpStreamParser parser(socket_handle.get(), &request_info, read_buffer.get(),
+ BoundNetLog());
+
+ HttpRequestHeaders request_headers;
+ request_headers.SetHeader("Transfer-Encoding", "chunked");
+
+ HttpResponseInfo response_info;
+ TestCompletionCallback callback;
+ // This will attempt to Write() the initial request and headers, which will
+ // complete asynchronously.
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers,
+ &response_info, callback.callback()));
+
+ // Complete writing the request headers and body.
+ data.RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_EQ(OK, callback.WaitForResult());
+
+ // Attempt to read the response status and the response headers.
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback()));
+ data.RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ ASSERT_GT(callback.WaitForResult(), 0);
+
+ // Finally, attempt to read the response body.
+ scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize));
+ ASSERT_EQ(ERR_IO_PENDING,
+ parser.ReadResponseBody(body_buffer.get(), kBodySize,
+ callback.callback()));
+ data.RunFor(1);
ASSERT_TRUE(callback.have_result());
- rv = callback.WaitForResult();
- ASSERT_EQ(kBodySize, rv);
+ ASSERT_EQ(kBodySize, callback.WaitForResult());
}
TEST(HttpStreamParser, TruncatedHeaders) {
@@ -387,7 +695,6 @@ TEST(HttpStreamParser, TruncatedHeaders) {
TestCompletionCallback callback;
int rv = transport->Connect(callback.callback());
- rv = callback.GetResult(rv);
ASSERT_EQ(OK, rv);
scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle);
@@ -456,7 +763,6 @@ TEST(HttpStreamParser, Websocket101Response) {
TestCompletionCallback callback;
int rv = transport->Connect(callback.callback());
- rv = callback.GetResult(rv);
ASSERT_EQ(OK, rv);
scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle);
« no previous file with comments | « no previous file | net/url_request/url_request.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698