blob: 72b837d0b66daa92f6180061c1b19cbaa52eda19 [file] [log] [blame]
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <vector>
#include "base/message_loop.h"
#include "chrome/browser/renderer_security_policy.h"
#include "chrome/browser/resource_dispatcher_host.h"
#include "chrome/common/render_messages.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_job.h"
#include "net/url_request/url_request_test_job.h"
#include "testing/gtest/include/gtest/gtest.h"
static int RequestIDForMessage(const IPC::Message& msg) {
int request_id = -1;
switch (msg.type()) {
case ViewMsg_Resource_UploadProgress::ID:
case ViewMsg_Resource_ReceivedResponse::ID:
case ViewMsg_Resource_ReceivedRedirect::ID:
case ViewMsg_Resource_DataReceived::ID:
case ViewMsg_Resource_RequestComplete::ID:
request_id = IPC::MessageIterator(msg).NextInt();
break;
}
return request_id;
}
static ViewHostMsg_Resource_Request CreateResourceRequest(const char* method,
const GURL& url) {
ViewHostMsg_Resource_Request request;
request.method = std::string(method);
request.url = url;
request.policy_url = url; // bypass third-party cookie blocking
// init the rest to default values to prevent getting UMR.
request.load_flags = 0;
request.origin_pid = 0;
request.resource_type = ResourceType::SUB_RESOURCE;
request.mixed_content = false;
return request;
}
// We may want to move this to a shared space if it is useful for something else
class ResourceIPCAccumulator {
public:
void AddMessage(const IPC::Message& msg) {
messages_.push_back(msg);
}
// This groups the messages by their request ID. The groups will be in order
// that the first message for each request ID was received, and the messages
// within the groups will be in the order that they appeared.
typedef std::vector< std::vector<IPC::Message> > ClassifiedMessages;
void GetClassifiedMessages(ClassifiedMessages* msgs);
std::vector<IPC::Message> messages_;
};
// This is very inefficient as a result of repeatedly extracting the ID, use
// only for tests!
void ResourceIPCAccumulator::GetClassifiedMessages(ClassifiedMessages* msgs) {
while (!messages_.empty()) {
std::vector<IPC::Message> cur_requests;
cur_requests.push_back(messages_[0]);
int cur_id = RequestIDForMessage(messages_[0]);
// find all other messages with this ID
for (int i = 1; i < static_cast<int>(messages_.size()); i++) {
int id = RequestIDForMessage(messages_[i]);
if (id == cur_id) {
cur_requests.push_back(messages_[i]);
messages_.erase(messages_.begin() + i);
i --;
}
}
messages_.erase(messages_.begin());
msgs->push_back(cur_requests);
}
}
class ResourceDispatcherHostTest : public testing::Test,
public ResourceDispatcherHost::Receiver {
public:
ResourceDispatcherHostTest() : host_(NULL) {
}
// ResourceDispatcherHost::Delegate implementation
virtual bool Send(IPC::Message* msg) {
accum_.AddMessage(*msg);
delete msg;
return true;
}
protected:
// testing::Test
virtual void SetUp() {
RendererSecurityPolicy::GetInstance()->Add(0);
URLRequest::RegisterProtocolFactory("test", &URLRequestTestJob::Factory);
EnsureTestSchemeIsAllowed();
}
virtual void TearDown() {
URLRequest::RegisterProtocolFactory("test", NULL);
RendererSecurityPolicy::GetInstance()->Remove(0);
// Flush the message loop to make Purify happy.
message_loop_.RunAllPending();
}
void MakeTestRequest(int request_id, const GURL& url);
void MakeCancelRequest(int request_id);
void EnsureTestSchemeIsAllowed() {
static bool have_white_listed_test_scheme = false;
if (!have_white_listed_test_scheme) {
RendererSecurityPolicy::GetInstance()->RegisterWebSafeScheme("test");
have_white_listed_test_scheme = true;
}
}
MessageLoopForIO message_loop_;
ResourceDispatcherHost host_;
ResourceIPCAccumulator accum_;
};
// Spin up the message loop to kick off the request.
static void KickOffRequest() {
MessageLoop::current()->RunAllPending();
}
void ResourceDispatcherHostTest::MakeTestRequest(int request_id,
const GURL& url) {
ViewHostMsg_Resource_Request request = CreateResourceRequest("GET", url);
host_.BeginRequest(this, GetCurrentProcess(), 0, MSG_ROUTING_NONE,
request_id, request, NULL, NULL);
KickOffRequest();
}
void ResourceDispatcherHostTest::MakeCancelRequest(int request_id) {
host_.CancelRequest(0, request_id, false);
}
void CheckSuccessfulRequest(const std::vector<IPC::Message>& messages,
const std::string& reference_data) {
// A successful request will have received 4 messages:
// ReceivedResponse (indicates headers received)
// DataReceived (data)
// XXX DataReceived (0 bytes remaining from a read)
// RequestComplete (request is done)
//
// This function verifies that we received 4 messages and that they
// are appropriate.
ASSERT_EQ(messages.size(), 3);
// The first messages should be received response
ASSERT_EQ(ViewMsg_Resource_ReceivedResponse::ID, messages[0].type());
// followed by the data, currently we only do the data in one chunk, but
// should probably test multiple chunks later
ASSERT_EQ(ViewMsg_Resource_DataReceived::ID, messages[1].type());
void* iter = NULL;
int request_id;
ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &request_id));
SharedMemoryHandle shm_handle;
ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &shm_handle));
int data_len;
ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &data_len));
ASSERT_EQ(reference_data.size(), data_len);
SharedMemory shared_mem(shm_handle, true); // read only
shared_mem.Map(data_len);
const char* data = static_cast<char*>(shared_mem.memory());
ASSERT_EQ(0, memcmp(reference_data.c_str(), data, data_len));
// followed by a 0-byte read
//ASSERT_EQ(ViewMsg_Resource_DataReceived::ID, messages[2].type());
// the last message should be all data received
ASSERT_EQ(ViewMsg_Resource_RequestComplete::ID, messages[2].type());
}
// Tests whether many messages get dispatched properly.
TEST_F(ResourceDispatcherHostTest, TestMany) {
MakeTestRequest(1, URLRequestTestJob::test_url_1());
MakeTestRequest(2, URLRequestTestJob::test_url_2());
MakeTestRequest(3, URLRequestTestJob::test_url_3());
// flush all the pending requests
while (URLRequestTestJob::ProcessOnePendingMessage());
// sorts out all the messages we saw by request
ResourceIPCAccumulator::ClassifiedMessages msgs;
accum_.GetClassifiedMessages(&msgs);
// there are three requests, so we should have gotten them classified as such
ASSERT_EQ(3, msgs.size());
CheckSuccessfulRequest(msgs[0], URLRequestTestJob::test_data_1());
CheckSuccessfulRequest(msgs[1], URLRequestTestJob::test_data_2());
CheckSuccessfulRequest(msgs[2], URLRequestTestJob::test_data_3());
}
// Tests whether messages get canceled properly. We issue three requests,
// cancel one of them, and make sure that each sent the proper notifications.
TEST_F(ResourceDispatcherHostTest, Cancel) {
ResourceDispatcherHost host(NULL);
MakeTestRequest(1, URLRequestTestJob::test_url_1());
MakeTestRequest(2, URLRequestTestJob::test_url_2());
MakeTestRequest(3, URLRequestTestJob::test_url_3());
MakeCancelRequest(2);
// flush all the pending requests
while (URLRequestTestJob::ProcessOnePendingMessage());
ResourceIPCAccumulator::ClassifiedMessages msgs;
accum_.GetClassifiedMessages(&msgs);
// there are three requests, so we should have gotten them classified as such
ASSERT_EQ(3, msgs.size());
CheckSuccessfulRequest(msgs[0], URLRequestTestJob::test_data_1());
CheckSuccessfulRequest(msgs[2], URLRequestTestJob::test_data_3());
// Check that request 2 got canceled before it finished reading, which gives
// us 1 ReceivedResponse message.
ASSERT_EQ(1, msgs[1].size());
ASSERT_EQ(ViewMsg_Resource_ReceivedResponse::ID, msgs[1][0].type());
// TODO(mbelshe):
// Now that the async IO path is in place, the IO always completes on the
// initial call; so the cancel doesn't arrive until after we finished.
// This basically means the test doesn't work.
#if 0
int request_id;
URLRequestStatus status;
// The message should be all data received with an error.
ASSERT_EQ(ViewMsg_Resource_RequestComplete::ID, msgs[1][2].type());
void* iter = NULL;
ASSERT_TRUE(IPC::ReadParam(&msgs[1][2], &iter, &request_id));
ASSERT_TRUE(IPC::ReadParam(&msgs[1][2], &iter, &status));
EXPECT_EQ(URLRequestStatus::CANCELED, status.status());
#endif
}
// Tests CancelRequestsForProcess
TEST_F(ResourceDispatcherHostTest, TestProcessCancel) {
// the host delegate acts as a second one so we can have some requests
// pending and some canceled
class TestReceiver : public ResourceDispatcherHost::Receiver {
public:
TestReceiver() : has_canceled_(false), received_after_canceled_(0) {
}
virtual bool Send(IPC::Message* msg) {
// no messages should be received when the process has been canceled
if (has_canceled_)
received_after_canceled_ ++;
delete msg;
return true;
}
bool has_canceled_;
int received_after_canceled_;
};
TestReceiver test_receiver;
// request 1 goes to the test delegate
ViewHostMsg_Resource_Request request =
CreateResourceRequest("GET", URLRequestTestJob::test_url_1());
host_.BeginRequest(&test_receiver, GetCurrentProcess(), 0, MSG_ROUTING_NONE,
1, request, NULL, NULL);
KickOffRequest();
// request 2 goes to us
MakeTestRequest(2, URLRequestTestJob::test_url_2());
// request 3 goes to the test delegate
request.url = URLRequestTestJob::test_url_3();
host_.BeginRequest(&test_receiver, GetCurrentProcess(), 0, MSG_ROUTING_NONE,
3, request, NULL, NULL);
KickOffRequest();
// TODO: mbelshe
// Now that the async IO path is in place, the IO always completes on the
// initial call; so the requests have already completed. This basically
// breaks the whole test.
//EXPECT_EQ(3, host_.pending_requests());
// process each request for one level so one callback is called
for (int i = 0; i < 3; i++)
EXPECT_TRUE(URLRequestTestJob::ProcessOnePendingMessage());
// cancel the requests to the test process
host_.CancelRequestsForProcess(0);
test_receiver.has_canceled_ = true;
// flush all the pending requests
while (URLRequestTestJob::ProcessOnePendingMessage());
EXPECT_EQ(0, host_.pending_requests());
// the test delegate should not have gotten any messages after being canceled
ASSERT_EQ(0, test_receiver.received_after_canceled_);
// we should have gotten exactly one result
ResourceIPCAccumulator::ClassifiedMessages msgs;
accum_.GetClassifiedMessages(&msgs);
ASSERT_EQ(1, msgs.size());
CheckSuccessfulRequest(msgs[0], URLRequestTestJob::test_data_2());
}