summaryrefslogtreecommitdiffstats
path: root/sync/internal_api/http_bridge_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sync/internal_api/http_bridge_unittest.cc')
-rw-r--r--sync/internal_api/http_bridge_unittest.cc404
1 files changed, 404 insertions, 0 deletions
diff --git a/sync/internal_api/http_bridge_unittest.cc b/sync/internal_api/http_bridge_unittest.cc
new file mode 100644
index 0000000..a4a4ceb
--- /dev/null
+++ b/sync/internal_api/http_bridge_unittest.cc
@@ -0,0 +1,404 @@
+// Copyright (c) 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 "base/message_loop_proxy.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "net/test/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/http_bridge.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using browser_sync::HttpBridge;
+
+namespace {
+// TODO(timsteele): Should use PathService here. See Chromium Issue 3113.
+const FilePath::CharType kDocRoot[] = FILE_PATH_LITERAL("chrome/test/data");
+}
+
+class SyncHttpBridgeTest : public testing::Test {
+ public:
+ SyncHttpBridgeTest()
+ : test_server_(net::TestServer::TYPE_HTTP,
+ net::TestServer::kLocalhost,
+ FilePath(kDocRoot)),
+ fake_default_request_context_getter_(NULL),
+ bridge_for_race_test_(NULL),
+ io_thread_("IO thread") {
+ }
+
+ virtual void SetUp() {
+ base::Thread::Options options;
+ options.message_loop_type = MessageLoop::TYPE_IO;
+ io_thread_.StartWithOptions(options);
+ }
+
+ virtual void TearDown() {
+ 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 TestURLRequestContextGetter(io_thread_.message_loop_proxy());
+ fake_default_request_context_getter_->AddRef();
+ }
+ HttpBridge* bridge = new HttpBridge(
+ new HttpBridge::RequestContextGetter(
+ fake_default_request_context_getter_,
+ "user agent"));
+ 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(MessageLoop* main_message_loop,
+ 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->GetRequestContextGetter()->
+ GetURLRequestContext()->
+ http_transaction_factory()->GetSession());
+ main_message_loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ }
+
+ MessageLoop* GetIOThreadLoop() {
+ return io_thread_.message_loop();
+ }
+
+ // Note this is lazy created, so don't call this before your bridge.
+ TestURLRequestContextGetter* GetTestRequestContextGetter() {
+ return fake_default_request_context_getter_;
+ }
+
+ net::TestServer 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.
+ TestURLRequestContextGetter* fake_default_request_context_getter_;
+
+ HttpBridge* bridge_for_race_test_;
+
+ // Separate thread for IO used by the HttpBridge.
+ base::Thread io_thread_;
+ 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,
+ SyncHttpBridgeTest* test, bool never_finishes)
+ : HttpBridge(
+ new HttpBridge::RequestContextGetter(
+ baseline_context_getter, "user agent")),
+ test_(test), never_finishes_(never_finishes) { }
+ protected:
+ virtual void MakeAsynchronousPost() {
+ ASSERT_TRUE(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() {}
+
+ void CallOnURLFetchComplete() {
+ ASSERT_TRUE(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(), NULL);
+ fetcher.set_url(GURL("www.google.com"));
+ fetcher.set_response_code(200);
+ fetcher.set_cookies(cookies);
+ fetcher.SetResponseString(response_content);
+ OnURLFetchComplete(&fetcher);
+ }
+ SyncHttpBridgeTest* test_;
+ bool never_finishes_;
+};
+
+void SyncHttpBridgeTest::RunSyncThreadBridgeUseTest(
+ base::WaitableEvent* signal_when_created,
+ base::WaitableEvent* signal_when_released) {
+ scoped_refptr<net::URLRequestContextGetter> ctx_getter(
+ new TestURLRequestContextGetter(io_thread_.message_loop_proxy()));
+ {
+ scoped_refptr<ShuntedHttpBridge> bridge(new ShuntedHttpBridge(
+ ctx_getter, this, true));
+ bridge->SetURL("http://www.google.com", 9999);
+ bridge->SetPostPayload("text/plain", 2, " ");
+ bridge_for_race_test_ = bridge;
+ 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(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(&SyncHttpBridgeTest::TestSameHttpNetworkSession,
+ MessageLoop::current(), this));
+ MessageLoop::current()->Run();
+}
+
+// Test the HttpBridge without actually making any network requests.
+TEST_F(SyncHttpBridgeTest, TestMakeSynchronousPostShunted) {
+ scoped_refptr<net::URLRequestContextGetter> ctx_getter(
+ new TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
+ scoped_refptr<HttpBridge> http_bridge(new ShuntedHttpBridge(
+ ctx_getter, 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(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.
+TEST_F(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("User-Agent: user agent"));
+ EXPECT_NE(std::string::npos, response.find(test_payload.c_str()));
+}
+
+TEST_F(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(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(SyncHttpBridgeTest, Abort) {
+ scoped_refptr<net::URLRequestContextGetter> ctx_getter(
+ new TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
+ scoped_refptr<ShuntedHttpBridge> http_bridge(new ShuntedHttpBridge(
+ ctx_getter, 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()->message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&SyncHttpBridgeTest::Abort, http_bridge));
+ bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
+ EXPECT_FALSE(success);
+ EXPECT_EQ(net::ERR_ABORTED, os_error);
+}
+
+TEST_F(SyncHttpBridgeTest, AbortLate) {
+ scoped_refptr<net::URLRequestContextGetter> ctx_getter(
+ new TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
+ scoped_refptr<ShuntedHttpBridge> http_bridge(new ShuntedHttpBridge(
+ ctx_getter, 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(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(&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()->message_loop_proxy()->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(), NULL);
+ fetcher.set_url(GURL("www.google.com"));
+ fetcher.set_response_code(200);
+ fetcher.set_cookies(cookies);
+ fetcher.SetResponseString(response_content);
+ ASSERT_TRUE(io_thread()->message_loop_proxy()->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();
+}