// 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/command_line.h" #include "base/location.h" #include "base/memory/ref_counted.h" #include "base/single_thread_task_runner.h" #include "base/synchronization/waitable_event.h" #include "base/thread_task_runner_handle.h" #include "base/threading/thread.h" #include "base/values.h" #include "chrome/service/cloud_print/cloud_print_url_fetcher.h" #include "chrome/service/service_process.h" #include "net/test/spawned_test_server/spawned_test_server.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_status.h" #include "net/url_request/url_request_test_util.h" #include "net/url_request/url_request_throttler_manager.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" using base::Time; using base::TimeDelta; namespace cloud_print { const base::FilePath::CharType kDocRoot[] = FILE_PATH_LITERAL("chrome/test/data"); int g_request_context_getter_instances = 0; class TrackingTestURLRequestContextGetter : public net::TestURLRequestContextGetter { public: explicit TrackingTestURLRequestContextGetter( base::SingleThreadTaskRunner* io_task_runner, net::URLRequestThrottlerManager* throttler_manager) : TestURLRequestContextGetter(io_task_runner), throttler_manager_(throttler_manager) { g_request_context_getter_instances++; } net::TestURLRequestContext* GetURLRequestContext() override { if (!context_.get()) { context_.reset(new net::TestURLRequestContext(true)); context_->set_throttler_manager(throttler_manager_); context_->Init(); } return context_.get(); } protected: ~TrackingTestURLRequestContextGetter() override { g_request_context_getter_instances--; } private: // Not owned here. net::URLRequestThrottlerManager* throttler_manager_; scoped_ptr context_; }; class TestCloudPrintURLFetcher : public CloudPrintURLFetcher { public: explicit TestCloudPrintURLFetcher( base::SingleThreadTaskRunner* io_task_runner) : io_task_runner_(io_task_runner) {} net::URLRequestContextGetter* GetRequestContextGetter() override { return new TrackingTestURLRequestContextGetter(io_task_runner_.get(), throttler_manager()); } net::URLRequestThrottlerManager* throttler_manager() { return &throttler_manager_; } private: ~TestCloudPrintURLFetcher() override {} scoped_refptr io_task_runner_; // We set this as the throttler manager for the // TestURLRequestContext we create. net::URLRequestThrottlerManager throttler_manager_; }; class CloudPrintURLFetcherTest : public testing::Test, public CloudPrintURLFetcherDelegate { public: CloudPrintURLFetcherTest() : max_retries_(0), fetcher_(NULL) { } // Creates a URLFetcher, using the program's main thread to do IO. virtual void CreateFetcher(const GURL& url, int max_retries); // CloudPrintURLFetcher::Delegate CloudPrintURLFetcher::ResponseAction HandleRawResponse( const net::URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const net::ResponseCookies& cookies, const std::string& data) override; CloudPrintURLFetcher::ResponseAction OnRequestAuthError() override { ADD_FAILURE(); return CloudPrintURLFetcher::STOP_PROCESSING; } std::string GetAuthHeader() override { return std::string(); } scoped_refptr io_task_runner() { return io_task_runner_; } protected: void SetUp() override { testing::Test::SetUp(); io_task_runner_ = base::ThreadTaskRunnerHandle::Get(); } void TearDown() override { fetcher_ = NULL; // Deleting the fetcher causes a task to be posted to the IO thread to // release references to the URLRequestContextGetter. We need to run all // pending tasks to execute that (this is the IO thread). base::MessageLoop::current()->RunUntilIdle(); EXPECT_EQ(0, g_request_context_getter_instances); } // URLFetcher is designed to run on the main UI thread, but in our tests // we assume that the current thread is the IO thread where the URLFetcher // dispatches its requests to. When we wish to simulate being used from // a UI thread, we dispatch a worker thread to do so. base::MessageLoopForIO io_loop_; scoped_refptr io_task_runner_; int max_retries_; Time start_time_; scoped_refptr fetcher_; }; class CloudPrintURLFetcherBasicTest : public CloudPrintURLFetcherTest { public: CloudPrintURLFetcherBasicTest() : handle_raw_response_(false), handle_raw_data_(false) { } // CloudPrintURLFetcher::Delegate CloudPrintURLFetcher::ResponseAction HandleRawResponse( const net::URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const net::ResponseCookies& cookies, const std::string& data) override; CloudPrintURLFetcher::ResponseAction HandleRawData( const net::URLFetcher* source, const GURL& url, const std::string& data) override; CloudPrintURLFetcher::ResponseAction HandleJSONData( const net::URLFetcher* source, const GURL& url, base::DictionaryValue* json_data, bool succeeded) override; void SetHandleRawResponse(bool handle_raw_response) { handle_raw_response_ = handle_raw_response; } void SetHandleRawData(bool handle_raw_data) { handle_raw_data_ = handle_raw_data; } private: bool handle_raw_response_; bool handle_raw_data_; }; // Version of CloudPrintURLFetcherTest that tests overload protection. class CloudPrintURLFetcherOverloadTest : public CloudPrintURLFetcherTest { public: CloudPrintURLFetcherOverloadTest() : response_count_(0) { } // CloudPrintURLFetcher::Delegate CloudPrintURLFetcher::ResponseAction HandleRawData( const net::URLFetcher* source, const GURL& url, const std::string& data) override; private: int response_count_; }; // Version of CloudPrintURLFetcherTest that tests backoff protection. class CloudPrintURLFetcherRetryBackoffTest : public CloudPrintURLFetcherTest { public: CloudPrintURLFetcherRetryBackoffTest() : response_count_(0) { } // CloudPrintURLFetcher::Delegate CloudPrintURLFetcher::ResponseAction HandleRawData( const net::URLFetcher* source, const GURL& url, const std::string& data) override; void OnRequestGiveUp() override; private: int response_count_; }; void CloudPrintURLFetcherTest::CreateFetcher(const GURL& url, int max_retries) { fetcher_ = new TestCloudPrintURLFetcher(io_task_runner().get()); // Registers an entry for test url. It only allows 3 requests to be sent // in 200 milliseconds. scoped_refptr entry(new net::URLRequestThrottlerEntry( fetcher_->throttler_manager(), std::string(), 200, 3, 1, 2.0, 0.0, 256)); fetcher_->throttler_manager()->OverrideEntryForTests(url, entry.get()); max_retries_ = max_retries; start_time_ = Time::Now(); fetcher_->StartGetRequest(CloudPrintURLFetcher::REQUEST_MAX, url, this, max_retries_, std::string()); } CloudPrintURLFetcher::ResponseAction CloudPrintURLFetcherTest::HandleRawResponse( const net::URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const net::ResponseCookies& cookies, const std::string& data) { EXPECT_TRUE(status.is_success()); EXPECT_EQ(200, response_code); // HTTP OK EXPECT_FALSE(data.empty()); return CloudPrintURLFetcher::CONTINUE_PROCESSING; } CloudPrintURLFetcher::ResponseAction CloudPrintURLFetcherBasicTest::HandleRawResponse( const net::URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const net::ResponseCookies& cookies, const std::string& data) { EXPECT_TRUE(status.is_success()); EXPECT_EQ(200, response_code); // HTTP OK EXPECT_FALSE(data.empty()); if (handle_raw_response_) { // If the current message loop is not the IO loop, it will be shut down when // the main loop returns and this thread subsequently goes out of scope. io_task_runner()->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); return CloudPrintURLFetcher::STOP_PROCESSING; } return CloudPrintURLFetcher::CONTINUE_PROCESSING; } CloudPrintURLFetcher::ResponseAction CloudPrintURLFetcherBasicTest::HandleRawData( const net::URLFetcher* source, const GURL& url, const std::string& data) { // We should never get here if we returned true in HandleRawResponse EXPECT_FALSE(handle_raw_response_); if (handle_raw_data_) { io_task_runner()->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); return CloudPrintURLFetcher::STOP_PROCESSING; } return CloudPrintURLFetcher::CONTINUE_PROCESSING; } CloudPrintURLFetcher::ResponseAction CloudPrintURLFetcherBasicTest::HandleJSONData( const net::URLFetcher* source, const GURL& url, base::DictionaryValue* json_data, bool succeeded) { // We should never get here if we returned true in one of the above methods. EXPECT_FALSE(handle_raw_response_); EXPECT_FALSE(handle_raw_data_); io_task_runner()->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); return CloudPrintURLFetcher::STOP_PROCESSING; } CloudPrintURLFetcher::ResponseAction CloudPrintURLFetcherOverloadTest::HandleRawData( const net::URLFetcher* source, const GURL& url, const std::string& data) { const TimeDelta one_second = TimeDelta::FromMilliseconds(1000); response_count_++; if (response_count_ < 20) { fetcher_->StartGetRequest(CloudPrintURLFetcher::REQUEST_MAX, url, this, max_retries_, std::string()); } else { // We have already sent 20 requests continuously. And we expect that // it takes more than 1 second due to the overload protection settings. EXPECT_TRUE(Time::Now() - start_time_ >= one_second); io_task_runner()->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); } return CloudPrintURLFetcher::STOP_PROCESSING; } CloudPrintURLFetcher::ResponseAction CloudPrintURLFetcherRetryBackoffTest::HandleRawData( const net::URLFetcher* source, const GURL& url, const std::string& data) { response_count_++; // First attempt + 11 retries = 12 total responses. EXPECT_LE(response_count_, 12); return CloudPrintURLFetcher::RETRY_REQUEST; } void CloudPrintURLFetcherRetryBackoffTest::OnRequestGiveUp() { // It takes more than 200 ms to finish all 11 requests. EXPECT_TRUE(Time::Now() - start_time_ >= TimeDelta::FromMilliseconds(200)); io_task_runner()->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); } TEST_F(CloudPrintURLFetcherBasicTest, HandleRawResponse) { net::SpawnedTestServer test_server(net::SpawnedTestServer::TYPE_HTTP, net::SpawnedTestServer::kLocalhost, base::FilePath(kDocRoot)); ASSERT_TRUE(test_server.Start()); SetHandleRawResponse(true); CreateFetcher(test_server.GetURL("echo"), 0); base::MessageLoop::current()->Run(); } TEST_F(CloudPrintURLFetcherBasicTest, HandleRawData) { net::SpawnedTestServer test_server(net::SpawnedTestServer::TYPE_HTTP, net::SpawnedTestServer::kLocalhost, base::FilePath(kDocRoot)); ASSERT_TRUE(test_server.Start()); SetHandleRawData(true); CreateFetcher(test_server.GetURL("echo"), 0); base::MessageLoop::current()->Run(); } TEST_F(CloudPrintURLFetcherOverloadTest, Protect) { net::SpawnedTestServer test_server(net::SpawnedTestServer::TYPE_HTTP, net::SpawnedTestServer::kLocalhost, base::FilePath(kDocRoot)); ASSERT_TRUE(test_server.Start()); GURL url(test_server.GetURL("defaultresponse")); CreateFetcher(url, 11); base::MessageLoop::current()->Run(); } TEST_F(CloudPrintURLFetcherRetryBackoffTest, GiveUp) { net::SpawnedTestServer test_server(net::SpawnedTestServer::TYPE_HTTP, net::SpawnedTestServer::kLocalhost, base::FilePath(kDocRoot)); ASSERT_TRUE(test_server.Start()); GURL url(test_server.GetURL("defaultresponse")); CreateFetcher(url, 11); base::MessageLoop::current()->Run(); } } // namespace cloud_print