Files
platform-external-webrtc/webrtc/base/httpbase_unittest.cc
Tommi 0eefb4d5c3 Detach base/logging.* from base/stream.*.
This is being done in preparation of moving base/logging.* to rtc_base_approved. base/stream.* has libjingle dependencies that webrtc can't use, so logging.* can't depend on streams. It does look like stream.* isn't used much, so cleaning that up as well as cleaning up usage of the actual stream support (now LogStream) in the logging code, is in order, but I'll leave that to another cl.

BUG=
R=pthatcher@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/54529004

Cr-Commit-Position: refs/heads/master@{#9269}
2015-05-23 07:54:19 +00:00

524 lines
14 KiB
C++

/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#include "webrtc/base/gunit.h"
#include "webrtc/base/httpbase.h"
#include "webrtc/base/testutils.h"
namespace rtc {
const char* const kHttpResponse =
"HTTP/1.1 200\r\n"
"Connection: Keep-Alive\r\n"
"Content-Type: text/plain\r\n"
"Proxy-Authorization: 42\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"00000008\r\n"
"Goodbye!\r\n"
"0\r\n\r\n";
const char* const kHttpEmptyResponse =
"HTTP/1.1 200\r\n"
"Connection: Keep-Alive\r\n"
"Content-Length: 0\r\n"
"Proxy-Authorization: 42\r\n"
"\r\n";
const char* const kHttpResponsePrefix =
"HTTP/1.1 200\r\n"
"Connection: Keep-Alive\r\n"
"Content-Type: text/plain\r\n"
"Proxy-Authorization: 42\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"8\r\n"
"Goodbye!\r\n";
class HttpBaseTest : public testing::Test, public IHttpNotify {
public:
enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED };
struct Event {
EventType event;
bool chunked;
size_t data_size;
HttpMode mode;
HttpError err;
};
HttpBaseTest() : mem(NULL), obtain_stream(false), http_stream(NULL) { }
virtual void SetUp() { }
virtual void TearDown() {
delete http_stream;
// Avoid an ASSERT, in case a test doesn't clean up properly
base.abort(HE_NONE);
}
virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) {
LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size;
Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE};
events.push_back(e);
if (obtain_stream) {
ObtainDocumentStream();
}
return HE_NONE;
}
virtual void onHttpComplete(HttpMode mode, HttpError err) {
LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err;
Event e = { E_COMPLETE, false, 0, mode, err };
events.push_back(e);
}
virtual void onHttpClosed(HttpError err) {
LOG_F(LS_VERBOSE) << "err: " << err;
Event e = { E_CLOSED, false, 0, HM_NONE, err };
events.push_back(e);
}
void SetupSource(const char* response);
void VerifyHeaderComplete(size_t event_count, bool empty_doc);
void VerifyDocumentContents(const char* expected_data,
size_t expected_length = SIZE_UNKNOWN);
void ObtainDocumentStream();
void VerifyDocumentStreamIsOpening();
void VerifyDocumentStreamOpenEvent();
void ReadDocumentStreamData(const char* expected_data);
void VerifyDocumentStreamIsEOS();
void SetupDocument(const char* response);
void VerifySourceContents(const char* expected_data,
size_t expected_length = SIZE_UNKNOWN);
void VerifyTransferComplete(HttpMode mode, HttpError error);
HttpBase base;
MemoryStream* mem;
HttpResponseData data;
// The source of http data, and source events
testing::StreamSource src;
std::vector<Event> events;
// Document stream, and stream events
bool obtain_stream;
StreamInterface* http_stream;
testing::StreamSink sink;
};
void HttpBaseTest::SetupSource(const char* http_data) {
LOG_F(LS_VERBOSE) << "Enter";
src.SetState(SS_OPENING);
src.QueueString(http_data);
base.notify(this);
base.attach(&src);
EXPECT_TRUE(events.empty());
src.SetState(SS_OPEN);
ASSERT_EQ(1U, events.size());
EXPECT_EQ(E_COMPLETE, events[0].event);
EXPECT_EQ(HM_CONNECT, events[0].mode);
EXPECT_EQ(HE_NONE, events[0].err);
events.clear();
mem = new MemoryStream;
data.document.reset(mem);
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_EQ(event_count, events.size());
EXPECT_EQ(E_HEADER_COMPLETE, events[0].event);
std::string header;
EXPECT_EQ(HVER_1_1, data.version);
EXPECT_EQ(static_cast<uint32>(HC_OK), data.scode);
EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header));
EXPECT_EQ("42", header);
EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header));
EXPECT_EQ("Keep-Alive", header);
if (empty_doc) {
EXPECT_FALSE(events[0].chunked);
EXPECT_EQ(0U, events[0].data_size);
EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header));
EXPECT_EQ("0", header);
} else {
EXPECT_TRUE(events[0].chunked);
EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size);
EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header));
EXPECT_EQ("text/plain", header);
EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header));
EXPECT_EQ("chunked", header);
}
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyDocumentContents(const char* expected_data,
size_t expected_length) {
LOG_F(LS_VERBOSE) << "Enter";
if (SIZE_UNKNOWN == expected_length) {
expected_length = strlen(expected_data);
}
EXPECT_EQ(mem, data.document.get());
size_t length;
mem->GetSize(&length);
EXPECT_EQ(expected_length, length);
EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length));
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::ObtainDocumentStream() {
LOG_F(LS_VERBOSE) << "Enter";
EXPECT_FALSE(http_stream);
http_stream = base.GetDocumentStream();
ASSERT_TRUE(NULL != http_stream);
sink.Monitor(http_stream);
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyDocumentStreamIsOpening() {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_TRUE(NULL != http_stream);
EXPECT_EQ(0, sink.Events(http_stream));
EXPECT_EQ(SS_OPENING, http_stream->GetState());
size_t read = 0;
char buffer[5] = { 0 };
EXPECT_EQ(SR_BLOCK, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyDocumentStreamOpenEvent() {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_TRUE(NULL != http_stream);
EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream));
EXPECT_EQ(SS_OPEN, http_stream->GetState());
// HTTP headers haven't arrived yet
EXPECT_EQ(0U, events.size());
EXPECT_EQ(static_cast<uint32>(HC_INTERNAL_SERVER_ERROR), data.scode);
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_TRUE(NULL != http_stream);
EXPECT_EQ(SS_OPEN, http_stream->GetState());
// Pump the HTTP I/O using Read, and verify the results.
size_t verified_length = 0;
const size_t expected_length = strlen(expected_data);
while (verified_length < expected_length) {
size_t read = 0;
char buffer[5] = { 0 };
size_t amt_to_read =
std::min(expected_length - verified_length, sizeof(buffer));
EXPECT_EQ(SR_SUCCESS, http_stream->Read(buffer, amt_to_read, &read, NULL));
EXPECT_EQ(amt_to_read, read);
EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read));
verified_length += read;
}
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyDocumentStreamIsEOS() {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_TRUE(NULL != http_stream);
size_t read = 0;
char buffer[5] = { 0 };
EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
EXPECT_EQ(SS_CLOSED, http_stream->GetState());
// When EOS is caused by Read, we don't expect SE_CLOSE
EXPECT_EQ(0, sink.Events(http_stream));
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::SetupDocument(const char* document_data) {
LOG_F(LS_VERBOSE) << "Enter";
src.SetState(SS_OPEN);
base.notify(this);
base.attach(&src);
EXPECT_TRUE(events.empty());
if (document_data) {
// Note: we could just call data.set_success("text/plain", mem), but that
// won't allow us to use the chunked transfer encoding.
mem = new MemoryStream(document_data);
data.document.reset(mem);
data.setHeader(HH_CONTENT_TYPE, "text/plain");
data.setHeader(HH_TRANSFER_ENCODING, "chunked");
} else {
data.setHeader(HH_CONTENT_LENGTH, "0");
}
data.scode = HC_OK;
data.setHeader(HH_PROXY_AUTHORIZATION, "42");
data.setHeader(HH_CONNECTION, "Keep-Alive");
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifySourceContents(const char* expected_data,
size_t expected_length) {
LOG_F(LS_VERBOSE) << "Enter";
if (SIZE_UNKNOWN == expected_length) {
expected_length = strlen(expected_data);
}
std::string contents = src.ReadData();
EXPECT_EQ(expected_length, contents.length());
EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length));
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) {
LOG_F(LS_VERBOSE) << "Enter";
// Verify that http operation has completed
ASSERT_TRUE(events.size() > 0);
size_t last_event = events.size() - 1;
EXPECT_EQ(E_COMPLETE, events[last_event].event);
EXPECT_EQ(mode, events[last_event].mode);
EXPECT_EQ(error, events[last_event].err);
LOG_F(LS_VERBOSE) << "Exit";
}
//
// Tests
//
TEST_F(HttpBaseTest, SupportsSend) {
// Queue response document
SetupDocument("Goodbye!");
// Begin send
base.send(&data);
// Send completed successfully
VerifyTransferComplete(HM_SEND, HE_NONE);
VerifySourceContents(kHttpResponse);
}
TEST_F(HttpBaseTest, SupportsSendNoDocument) {
// Queue response document
SetupDocument(NULL);
// Begin send
base.send(&data);
// Send completed successfully
VerifyTransferComplete(HM_SEND, HE_NONE);
VerifySourceContents(kHttpEmptyResponse);
}
TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) {
// This test is attempting to expose a bug that occurs when a particular
// base objects is used for receiving, and then used for sending. In
// particular, the HttpParser state is different after receiving. Simulate
// that here.
SetupSource(kHttpResponse);
base.recv(&data);
VerifyTransferComplete(HM_RECV, HE_NONE);
src.Clear();
data.clear(true);
events.clear();
base.detach();
// Queue response document
SetupDocument("Goodbye!");
// Prevent entire response from being sent
const size_t kInterruptedLength = strlen(kHttpResponse) - 1;
src.SetWriteBlock(kInterruptedLength);
// Begin send
base.send(&data);
// Document is mostly complete, but no completion signal yet.
EXPECT_TRUE(events.empty());
VerifySourceContents(kHttpResponse, kInterruptedLength);
src.SetState(SS_CLOSED);
// Send completed with disconnect error, and no additional data.
VerifyTransferComplete(HM_SEND, HE_DISCONNECTED);
EXPECT_TRUE(src.ReadData().empty());
}
TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) {
// Queue response document
SetupSource(kHttpResponse);
// Begin receive
base.recv(&data);
// Document completed successfully
VerifyHeaderComplete(2, false);
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("Goodbye!");
}
TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) {
// Switch to pull mode
ObtainDocumentStream();
VerifyDocumentStreamIsOpening();
// Queue response document
SetupSource(kHttpResponse);
VerifyDocumentStreamIsOpening();
// Begin receive
base.recv(&data);
// Pull document data
VerifyDocumentStreamOpenEvent();
ReadDocumentStreamData("Goodbye!");
VerifyDocumentStreamIsEOS();
// Document completed successfully
VerifyHeaderComplete(2, false);
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("");
}
TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) {
// TODO: Remove extra logging once test failure is understood
LoggingSeverity old_sev = rtc::LogMessage::GetLogToDebug();
rtc::LogMessage::LogToDebug(LS_VERBOSE);
// Switch to pull mode
ObtainDocumentStream();
VerifyDocumentStreamIsOpening();
// Queue response document
SetupSource(kHttpResponse);
VerifyDocumentStreamIsOpening();
// Begin receive
base.recv(&data);
// Pull some of the data
VerifyDocumentStreamOpenEvent();
ReadDocumentStreamData("Goodb");
// We've seen the header by now
VerifyHeaderComplete(1, false);
// Close the pull stream, this will transition back to push I/O.
http_stream->Close();
Thread::Current()->ProcessMessages(0);
// Remainder of document completed successfully
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("ye!");
rtc::LogMessage::LogToDebug(old_sev);
}
TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) {
// Queue response document
SetupSource(kHttpResponse);
// Switch to pull mode in response to header arrival
obtain_stream = true;
// Begin receive
base.recv(&data);
// We've already seen the header, but not data has arrived
VerifyHeaderComplete(1, false);
VerifyDocumentContents("");
// Pull the document data
ReadDocumentStreamData("Goodbye!");
VerifyDocumentStreamIsEOS();
// Document completed successfully
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("");
}
TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) {
// Queue empty response document
SetupSource(kHttpEmptyResponse);
// Switch to pull mode in response to header arrival
obtain_stream = true;
// Begin receive
base.recv(&data);
// We've already seen the header, but not data has arrived
VerifyHeaderComplete(1, true);
VerifyDocumentContents("");
// The document is still open, until we attempt to read
ASSERT_TRUE(NULL != http_stream);
EXPECT_EQ(SS_OPEN, http_stream->GetState());
// Attempt to read data, and discover EOS
VerifyDocumentStreamIsEOS();
// Document completed successfully
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("");
}
TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) {
// Switch to pull mode
ObtainDocumentStream();
VerifyDocumentStreamIsOpening();
// Queue response document
SetupSource(kHttpResponsePrefix);
VerifyDocumentStreamIsOpening();
// Begin receive
base.recv(&data);
// Pull document data
VerifyDocumentStreamOpenEvent();
ReadDocumentStreamData("Goodbye!");
// Simulate unexpected close
src.SetState(SS_CLOSED);
// Observe error event on document stream
EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream));
// Future reads give an error
int error = 0;
char buffer[5] = { 0 };
EXPECT_EQ(SR_ERROR, http_stream->Read(buffer, sizeof(buffer), NULL, &error));
EXPECT_EQ(HE_DISCONNECTED, error);
// Document completed with error
VerifyHeaderComplete(2, false);
VerifyTransferComplete(HM_RECV, HE_DISCONNECTED);
VerifyDocumentContents("");
}
} // namespace rtc