blob: d2620d503b3556f6a3e344303440b8c8ab0eac39 [file] [log] [blame]
// Copyright 2012 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 <stddef.h>
#include <stdint.h>
#include "base/bit_cast.h"
#include "base/metrics/field_trial.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/mock_entropy_provider.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "net/http/http_response_headers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_test_util.h"
#include "sync/internal_api/public/base/cancelation_signal.h"
#include "sync/internal_api/public/http_bridge.h"
#include "sync/internal_api/public/http_post_provider_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/zlib.h"
namespace syncer {
namespace {
// TODO(timsteele): Should use PathService here. See Chromium Issue 3113.
const base::FilePath::CharType kDocRoot[] =
FILE_PATH_LITERAL("chrome/test/data");
} // namespace
const char kUserAgent[] = "user-agent";
#if defined(OS_ANDROID)
#define MAYBE_SyncHttpBridgeTest DISABLED_SyncHttpBridgeTest
#else
#define MAYBE_SyncHttpBridgeTest SyncHttpBridgeTest
#endif // defined(OS_ANDROID)
class MAYBE_SyncHttpBridgeTest : public testing::Test {
public:
MAYBE_SyncHttpBridgeTest()
: fake_default_request_context_getter_(NULL),
bridge_for_race_test_(NULL),
io_thread_("IO thread") {
test_server_.AddDefaultHandlers(base::FilePath(kDocRoot));
}
void SetUp() override {
base::Thread::Options options;
options.message_loop_type = base::MessageLoop::TYPE_IO;
io_thread_.StartWithOptions(options);
}
void TearDown() override {
if (fake_default_request_context_getter_) {
GetIOThreadLoop()->ReleaseSoon(FROM_HERE,
fake_default_request_context_getter_);
fake_default_request_context_getter_ = NULL;
}
io_thread_.Stop();
}
HttpBridge* BuildBridge() {
if (!fake_default_request_context_getter_) {
fake_default_request_context_getter_ =
new net::TestURLRequestContextGetter(io_thread_.task_runner());
fake_default_request_context_getter_->AddRef();
}
HttpBridge* bridge =
new HttpBridge(kUserAgent, fake_default_request_context_getter_,
NetworkTimeUpdateCallback(), BindToTrackerCallback());
return bridge;
}
static void Abort(HttpBridge* bridge) {
bridge->Abort();
}
// Used by AbortAndReleaseBeforeFetchCompletes to test an interesting race
// condition.
void RunSyncThreadBridgeUseTest(base::WaitableEvent* signal_when_created,
base::WaitableEvent* signal_when_released);
static void TestSameHttpNetworkSession(base::MessageLoop* main_message_loop,
MAYBE_SyncHttpBridgeTest* test) {
scoped_refptr<HttpBridge> http_bridge(test->BuildBridge());
EXPECT_TRUE(test->GetTestRequestContextGetter());
net::HttpNetworkSession* test_session =
test->GetTestRequestContextGetter()->GetURLRequestContext()->
http_transaction_factory()->GetSession();
EXPECT_EQ(test_session,
http_bridge->GetRequestContextGetterForTest()->
GetURLRequestContext()->
http_transaction_factory()->GetSession());
main_message_loop->PostTask(FROM_HERE,
base::MessageLoop::QuitWhenIdleClosure());
}
base::MessageLoop* GetIOThreadLoop() { return io_thread_.message_loop(); }
// Note this is lazy created, so don't call this before your bridge.
net::TestURLRequestContextGetter* GetTestRequestContextGetter() {
return fake_default_request_context_getter_;
}
net::EmbeddedTestServer test_server_;
base::Thread* io_thread() { return &io_thread_; }
HttpBridge* bridge_for_race_test() { return bridge_for_race_test_; }
private:
// A make-believe "default" request context, as would be returned by
// Profile::GetDefaultRequestContext(). Created lazily by BuildBridge.
net::TestURLRequestContextGetter* fake_default_request_context_getter_;
HttpBridge* bridge_for_race_test_;
// Separate thread for IO used by the HttpBridge.
base::Thread io_thread_;
base::MessageLoop loop_;
};
// An HttpBridge that doesn't actually make network requests and just calls
// back with dummy response info.
// TODO(tim): Instead of inheriting here we should inject a component
// responsible for the MakeAsynchronousPost bit.
class ShuntedHttpBridge : public HttpBridge {
public:
// If |never_finishes| is true, the simulated request never actually
// returns.
ShuntedHttpBridge(net::URLRequestContextGetter* baseline_context_getter,
MAYBE_SyncHttpBridgeTest* test,
bool never_finishes)
: HttpBridge(kUserAgent,
baseline_context_getter,
NetworkTimeUpdateCallback(),
BindToTrackerCallback()),
test_(test),
never_finishes_(never_finishes) {}
protected:
void MakeAsynchronousPost() override {
ASSERT_TRUE(base::MessageLoop::current() == test_->GetIOThreadLoop());
if (never_finishes_)
return;
// We don't actually want to make a request for this test, so just callback
// as if it completed.
test_->GetIOThreadLoop()->PostTask(FROM_HERE,
base::Bind(&ShuntedHttpBridge::CallOnURLFetchComplete, this));
}
private:
~ShuntedHttpBridge() override {}
void CallOnURLFetchComplete() {
ASSERT_TRUE(base::MessageLoop::current() == test_->GetIOThreadLoop());
// We return no cookies and a dummy content response.
net::ResponseCookies cookies;
std::string response_content = "success!";
net::TestURLFetcher fetcher(0, GURL("http://www.google.com"), NULL);
scoped_refptr<net::HttpResponseHeaders> response_headers(
new net::HttpResponseHeaders(""));
fetcher.set_response_code(200);
fetcher.set_cookies(cookies);
fetcher.SetResponseString(response_content);
fetcher.set_response_headers(response_headers);
OnURLFetchComplete(&fetcher);
}
MAYBE_SyncHttpBridgeTest* test_;
bool never_finishes_;
};
void MAYBE_SyncHttpBridgeTest::RunSyncThreadBridgeUseTest(
base::WaitableEvent* signal_when_created,
base::WaitableEvent* signal_when_released) {
scoped_refptr<net::URLRequestContextGetter> ctx_getter(
new net::TestURLRequestContextGetter(io_thread_.task_runner()));
{
scoped_refptr<ShuntedHttpBridge> bridge(
new ShuntedHttpBridge(ctx_getter.get(), this, true));
bridge->SetURL("http://www.google.com", 9999);
bridge->SetPostPayload("text/plain", 2, " ");
bridge_for_race_test_ = bridge.get();
signal_when_created->Signal();
int os_error = 0;
int response_code = 0;
bridge->MakeSynchronousPost(&os_error, &response_code);
bridge_for_race_test_ = NULL;
}
signal_when_released->Signal();
}
TEST_F(MAYBE_SyncHttpBridgeTest, TestUsesSameHttpNetworkSession) {
// Run this test on the IO thread because we can only call
// URLRequestContextGetter::GetURLRequestContext on the IO thread.
io_thread()->message_loop()->PostTask(
FROM_HERE,
base::Bind(&MAYBE_SyncHttpBridgeTest::TestSameHttpNetworkSession,
base::MessageLoop::current(), this));
base::MessageLoop::current()->Run();
}
// Test the HttpBridge without actually making any network requests.
TEST_F(MAYBE_SyncHttpBridgeTest, TestMakeSynchronousPostShunted) {
scoped_refptr<net::URLRequestContextGetter> ctx_getter(
new net::TestURLRequestContextGetter(io_thread()->task_runner()));
scoped_refptr<HttpBridge> http_bridge(
new ShuntedHttpBridge(ctx_getter.get(), this, false));
http_bridge->SetURL("http://www.google.com", 9999);
http_bridge->SetPostPayload("text/plain", 2, " ");
int os_error = 0;
int response_code = 0;
bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
EXPECT_TRUE(success);
EXPECT_EQ(200, response_code);
EXPECT_EQ(0, os_error);
EXPECT_EQ(8, http_bridge->GetResponseContentLength());
EXPECT_EQ(std::string("success!"),
std::string(http_bridge->GetResponseContent()));
}
// Full round-trip test of the HttpBridge, using default UA string and
// no request cookies.
TEST_F(MAYBE_SyncHttpBridgeTest, TestMakeSynchronousPostLiveWithPayload) {
ASSERT_TRUE(test_server_.Start());
scoped_refptr<HttpBridge> http_bridge(BuildBridge());
std::string payload = "this should be echoed back";
GURL echo = test_server_.GetURL("/echo");
http_bridge->SetURL(echo.spec().c_str(), echo.IntPort());
http_bridge->SetPostPayload("application/x-www-form-urlencoded",
payload.length() + 1, payload.c_str());
int os_error = 0;
int response_code = 0;
bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
EXPECT_TRUE(success);
EXPECT_EQ(200, response_code);
EXPECT_EQ(0, os_error);
EXPECT_EQ(payload.length() + 1,
static_cast<size_t>(http_bridge->GetResponseContentLength()));
EXPECT_EQ(payload, std::string(http_bridge->GetResponseContent()));
}
// Full round-trip test of the HttpBridge with compression, check if header
// fields("Content-Encoding" ,"Accept-Encoding" and user agent) are set
// correctly.
TEST_F(MAYBE_SyncHttpBridgeTest, CompressedRequestHeaderCheck) {
ASSERT_TRUE(test_server_.Start());
scoped_refptr<HttpBridge> http_bridge(BuildBridge());
GURL echo_header = test_server_.GetURL("/echoall");
http_bridge->SetURL(echo_header.spec().c_str(), echo_header.IntPort());
std::string test_payload = "###TEST PAYLOAD###";
http_bridge->SetPostPayload("text/html", test_payload.length() + 1,
test_payload.c_str());
int os_error = 0;
int response_code = 0;
base::FieldTrialList field_trial_list(new base::MockEntropyProvider());
base::FieldTrialList::CreateFieldTrial("SyncHttpContentCompression",
"Enabled");
bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
EXPECT_TRUE(success);
EXPECT_EQ(200, response_code);
EXPECT_EQ(0, os_error);
std::string response(http_bridge->GetResponseContent(),
http_bridge->GetResponseContentLength());
EXPECT_NE(std::string::npos,
response.find(base::StringPrintf(
"%s: %s", net::HttpRequestHeaders::kAcceptEncoding,
"gzip, deflate")));
EXPECT_NE(std::string::npos,
response.find(base::StringPrintf(
"%s: %s", net::HttpRequestHeaders::kUserAgent, kUserAgent)));
}
// Full round-trip test of the HttpBridge.
TEST_F(MAYBE_SyncHttpBridgeTest, TestMakeSynchronousPostLiveComprehensive) {
ASSERT_TRUE(test_server_.Start());
scoped_refptr<HttpBridge> http_bridge(BuildBridge());
GURL echo_header = test_server_.GetURL("/echoall");
http_bridge->SetURL(echo_header.spec().c_str(), echo_header.IntPort());
std::string test_payload = "###TEST PAYLOAD###";
http_bridge->SetPostPayload("text/html", test_payload.length() + 1,
test_payload.c_str());
int os_error = 0;
int response_code = 0;
bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
EXPECT_TRUE(success);
EXPECT_EQ(200, response_code);
EXPECT_EQ(0, os_error);
std::string response(http_bridge->GetResponseContent(),
http_bridge->GetResponseContentLength());
EXPECT_EQ(std::string::npos, response.find("Cookie:"));
EXPECT_NE(std::string::npos,
response.find(base::StringPrintf(
"%s: %s", net::HttpRequestHeaders::kAcceptEncoding,
"deflate")));
EXPECT_NE(std::string::npos,
response.find(base::StringPrintf("%s: %s",
net::HttpRequestHeaders::kUserAgent, kUserAgent)));
EXPECT_NE(std::string::npos, response.find(test_payload.c_str()));
}
TEST_F(MAYBE_SyncHttpBridgeTest, TestExtraRequestHeaders) {
ASSERT_TRUE(test_server_.Start());
scoped_refptr<HttpBridge> http_bridge(BuildBridge());
GURL echo_header = test_server_.GetURL("/echoall");
http_bridge->SetURL(echo_header.spec().c_str(), echo_header.IntPort());
http_bridge->SetExtraRequestHeaders("test:fnord");
std::string test_payload = "###TEST PAYLOAD###";
http_bridge->SetPostPayload("text/html", test_payload.length() + 1,
test_payload.c_str());
int os_error = 0;
int response_code = 0;
bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
EXPECT_TRUE(success);
EXPECT_EQ(200, response_code);
EXPECT_EQ(0, os_error);
std::string response(http_bridge->GetResponseContent(),
http_bridge->GetResponseContentLength());
EXPECT_NE(std::string::npos, response.find("fnord"));
EXPECT_NE(std::string::npos, response.find(test_payload.c_str()));
}
TEST_F(MAYBE_SyncHttpBridgeTest, TestResponseHeader) {
ASSERT_TRUE(test_server_.Start());
scoped_refptr<HttpBridge> http_bridge(BuildBridge());
GURL echo_header = test_server_.GetURL("/echoall");
http_bridge->SetURL(echo_header.spec().c_str(), echo_header.IntPort());
std::string test_payload = "###TEST PAYLOAD###";
http_bridge->SetPostPayload("text/html", test_payload.length() + 1,
test_payload.c_str());
int os_error = 0;
int response_code = 0;
bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
EXPECT_TRUE(success);
EXPECT_EQ(200, response_code);
EXPECT_EQ(0, os_error);
EXPECT_EQ(http_bridge->GetResponseHeaderValue("Content-type"), "text/html");
EXPECT_TRUE(http_bridge->GetResponseHeaderValue("invalid-header").empty());
}
TEST_F(MAYBE_SyncHttpBridgeTest, Abort) {
scoped_refptr<net::URLRequestContextGetter> ctx_getter(
new net::TestURLRequestContextGetter(io_thread()->task_runner()));
scoped_refptr<ShuntedHttpBridge> http_bridge(
new ShuntedHttpBridge(ctx_getter.get(), this, true));
http_bridge->SetURL("http://www.google.com", 9999);
http_bridge->SetPostPayload("text/plain", 2, " ");
int os_error = 0;
int response_code = 0;
io_thread()->task_runner()->PostTask(
FROM_HERE, base::Bind(&MAYBE_SyncHttpBridgeTest::Abort,
base::RetainedRef(http_bridge)));
bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
EXPECT_FALSE(success);
EXPECT_EQ(net::ERR_ABORTED, os_error);
}
TEST_F(MAYBE_SyncHttpBridgeTest, AbortLate) {
scoped_refptr<net::URLRequestContextGetter> ctx_getter(
new net::TestURLRequestContextGetter(io_thread()->task_runner()));
scoped_refptr<ShuntedHttpBridge> http_bridge(
new ShuntedHttpBridge(ctx_getter.get(), this, false));
http_bridge->SetURL("http://www.google.com", 9999);
http_bridge->SetPostPayload("text/plain", 2, " ");
int os_error = 0;
int response_code = 0;
bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
ASSERT_TRUE(success);
http_bridge->Abort();
// Ensures no double-free of URLFetcher, etc.
}
// Tests an interesting case where code using the HttpBridge aborts the fetch
// and releases ownership before a pending fetch completed callback is issued by
// the underlying URLFetcher (and before that URLFetcher is destroyed, which
// would cancel the callback).
TEST_F(MAYBE_SyncHttpBridgeTest, AbortAndReleaseBeforeFetchComplete) {
base::Thread sync_thread("SyncThread");
sync_thread.Start();
// First, block the sync thread on the post.
base::WaitableEvent signal_when_created(false, false);
base::WaitableEvent signal_when_released(false, false);
sync_thread.message_loop()->PostTask(FROM_HERE,
base::Bind(&MAYBE_SyncHttpBridgeTest::RunSyncThreadBridgeUseTest,
base::Unretained(this),
&signal_when_created,
&signal_when_released));
// Stop IO so we can control order of operations.
base::WaitableEvent io_waiter(false, false);
ASSERT_TRUE(io_thread()->task_runner()->PostTask(
FROM_HERE,
base::Bind(&base::WaitableEvent::Wait, base::Unretained(&io_waiter))));
signal_when_created.Wait(); // Wait till we have a bridge to abort.
ASSERT_TRUE(bridge_for_race_test());
// Schedule the fetch completion callback (but don't run it yet). Don't take
// a reference to the bridge to mimic URLFetcher's handling of the delegate.
net::URLFetcherDelegate* delegate =
static_cast<net::URLFetcherDelegate*>(bridge_for_race_test());
net::ResponseCookies cookies;
std::string response_content = "success!";
net::TestURLFetcher fetcher(0, GURL("http://www.google.com"), NULL);
fetcher.set_response_code(200);
fetcher.set_cookies(cookies);
fetcher.SetResponseString(response_content);
ASSERT_TRUE(io_thread()->task_runner()->PostTask(
FROM_HERE,
base::Bind(&net::URLFetcherDelegate::OnURLFetchComplete,
base::Unretained(delegate), &fetcher)));
// Abort the fetch. This should be smart enough to handle the case where
// the bridge is destroyed before the callback scheduled above completes.
bridge_for_race_test()->Abort();
// Wait until the sync thread releases its ref on the bridge.
signal_when_released.Wait();
ASSERT_FALSE(bridge_for_race_test());
// Unleash the hounds. The fetch completion callback should fire first, and
// succeed even though we Release()d the bridge above because the call to
// Abort should have held a reference.
io_waiter.Signal();
// Done.
sync_thread.Stop();
io_thread()->Stop();
}
void HttpBridgeRunOnSyncThread(
net::URLRequestContextGetter* baseline_context_getter,
CancelationSignal* factory_cancelation_signal,
syncer::HttpPostProviderFactory** bridge_factory_out,
syncer::HttpPostProviderInterface** bridge_out,
base::WaitableEvent* signal_when_created,
base::WaitableEvent* wait_for_shutdown) {
std::unique_ptr<syncer::HttpBridgeFactory> bridge_factory(
new syncer::HttpBridgeFactory(baseline_context_getter,
NetworkTimeUpdateCallback(),
factory_cancelation_signal));
bridge_factory->Init("test", BindToTrackerCallback());
*bridge_factory_out = bridge_factory.get();
HttpPostProviderInterface* bridge = bridge_factory->Create();
*bridge_out = bridge;
signal_when_created->Signal();
wait_for_shutdown->Wait();
bridge_factory->Destroy(bridge);
}
void WaitOnIOThread(base::WaitableEvent* signal_wait_start,
base::WaitableEvent* wait_done) {
signal_wait_start->Signal();
wait_done->Wait();
}
// Tests RequestContextGetter is properly released on IO thread even when
// IO thread stops before sync thread.
TEST_F(MAYBE_SyncHttpBridgeTest, RequestContextGetterReleaseOrder) {
base::Thread sync_thread("SyncThread");
sync_thread.Start();
syncer::HttpPostProviderFactory* factory = NULL;
syncer::HttpPostProviderInterface* bridge = NULL;
scoped_refptr<net::URLRequestContextGetter> baseline_context_getter(
new net::TestURLRequestContextGetter(io_thread()->task_runner()));
base::WaitableEvent signal_when_created(false, false);
base::WaitableEvent wait_for_shutdown(false, false);
CancelationSignal release_request_context_signal;
// Create bridge factory and factory on sync thread and wait for the creation
// to finish.
sync_thread.message_loop()->PostTask(
FROM_HERE, base::Bind(&HttpBridgeRunOnSyncThread,
base::Unretained(baseline_context_getter.get()),
&release_request_context_signal, &factory, &bridge,
&signal_when_created, &wait_for_shutdown));
signal_when_created.Wait();
// Simulate sync shutdown by aborting bridge and shutting down factory on
// frontend.
bridge->Abort();
release_request_context_signal.Signal();
// Wait for sync's RequestContextGetter to be cleared on IO thread and
// check for reference count.
base::WaitableEvent signal_wait_start(false, false);
base::WaitableEvent wait_done(false, false);
io_thread()->message_loop()->PostTask(
FROM_HERE,
base::Bind(&WaitOnIOThread, &signal_wait_start, &wait_done));
signal_wait_start.Wait();
// |baseline_context_getter| should have only one reference from local
// variable.
EXPECT_TRUE(baseline_context_getter->HasOneRef());
baseline_context_getter = NULL;
// Unblock and stop IO thread before sync thread.
wait_done.Signal();
io_thread()->Stop();
// Unblock and stop sync thread.
wait_for_shutdown.Signal();
sync_thread.Stop();
}
// Attempt to release the URLRequestContextGetter before the HttpBridgeFactory
// is initialized.
TEST_F(MAYBE_SyncHttpBridgeTest, EarlyAbortFactory) {
// In a real scenario, the following would happen on many threads. For
// simplicity, this test uses only one thread.
scoped_refptr<net::URLRequestContextGetter> baseline_context_getter(
new net::TestURLRequestContextGetter(io_thread()->task_runner()));
CancelationSignal release_request_context_signal;
// UI Thread: Initialize the HttpBridgeFactory. The next step would be to
// post a task to SBH::Core to have it initialized.
std::unique_ptr<syncer::HttpBridgeFactory> factory(new HttpBridgeFactory(
baseline_context_getter.get(), NetworkTimeUpdateCallback(),
&release_request_context_signal));
// UI Thread: A very early shutdown request arrives and executes on the UI
// thread before the posted sync thread task is run.
release_request_context_signal.Signal();
// Sync thread: Finally run the posted task, only to find that our
// HttpBridgeFactory has been neutered. Should not crash.
factory->Init("TestUserAgent", BindToTrackerCallback());
// At this point, attempting to use the factory would trigger a crash. Both
// this test and the real world code should make sure this never happens.
}
} // namespace syncer