普通文本  |  520行  |  19.23 KB

// 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 "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "net/test/spawned_test_server/spawned_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 "testing/gtest/include/gtest/gtest.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");
}

class SyncHttpBridgeTest : public testing::Test {
 public:
  SyncHttpBridgeTest()
      : test_server_(net::SpawnedTestServer::TYPE_HTTP,
                     net::SpawnedTestServer::kLocalhost,
                     base::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 = base::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 net::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"),
        NetworkTimeUpdateCallback());
    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,
                                         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::QuitClosure());
  }

  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::SpawnedTestServer 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,
                    SyncHttpBridgeTest* test, bool never_finishes)
      : HttpBridge(
          new HttpBridge::RequestContextGetter(
              baseline_context_getter, "user agent"),
          NetworkTimeUpdateCallback()),
        test_(test), never_finishes_(never_finishes) { }
 protected:
  virtual 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:
  virtual ~ShuntedHttpBridge() {}

  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(), 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 net::TestURLRequestContextGetter(io_thread_.message_loop_proxy()));
  {
    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(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,
                            base::MessageLoop::current(),
                            this));
  base::MessageLoop::current()->Run();
}

// Test the HttpBridge without actually making any network requests.
TEST_F(SyncHttpBridgeTest, TestMakeSynchronousPostShunted) {
  scoped_refptr<net::URLRequestContextGetter> ctx_getter(
      new net::TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
  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(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 net::TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
  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()->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 net::TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
  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(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();
}

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) {
  scoped_ptr<syncer::HttpBridgeFactory> bridge_factory(
      new syncer::HttpBridgeFactory(baseline_context_getter,
                                    NetworkTimeUpdateCallback(),
                                    factory_cancelation_signal));
  bridge_factory->Init("test");
  *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(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()->message_loop_proxy()));

  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(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()->message_loop_proxy()));
  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.
  scoped_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");

  // 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