// 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 "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h" #include "base/synchronization/waitable_event.h" #include "base/test/test_timeouts.h" #include "base/threading/sequenced_worker_pool.h" #include "base/timer/elapsed_timer.h" #include "base/timer/timer.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" #include "net/proxy/mock_proxy_script_fetcher.h" #include "net/proxy/proxy_script_fetcher_impl.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { const char kPacUrl[] = "http://pacserver/script.pac"; // In net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc there are a few // tests that exercise DhcpProxyScriptAdapterFetcher end-to-end along with // DhcpProxyScriptFetcherWin, i.e. it tests the end-to-end usage of Win32 // APIs and the network. In this file we test only by stubbing out // functionality. // Version of DhcpProxyScriptAdapterFetcher that mocks out dependencies // to allow unit testing. class MockDhcpProxyScriptAdapterFetcher : public DhcpProxyScriptAdapterFetcher { public: explicit MockDhcpProxyScriptAdapterFetcher( URLRequestContext* context, scoped_refptr task_runner) : DhcpProxyScriptAdapterFetcher(context, task_runner), dhcp_delay_(base::TimeDelta::FromMilliseconds(1)), timeout_(TestTimeouts::action_timeout()), configured_url_(kPacUrl), fetcher_delay_ms_(1), fetcher_result_(OK), pac_script_("bingo") { } void Cancel() override { DhcpProxyScriptAdapterFetcher::Cancel(); fetcher_ = NULL; } ProxyScriptFetcher* ImplCreateScriptFetcher() override { // We don't maintain ownership of the fetcher, it is transferred to // the caller. fetcher_ = new MockProxyScriptFetcher(); if (fetcher_delay_ms_ != -1) { fetcher_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(fetcher_delay_ms_), this, &MockDhcpProxyScriptAdapterFetcher::OnFetcherTimer); } return fetcher_; } class DelayingDhcpQuery : public DhcpQuery { public: explicit DelayingDhcpQuery() : DhcpQuery(), test_finished_event_(true, false) { } std::string ImplGetPacURLFromDhcp( const std::string& adapter_name) override { base::ElapsedTimer timer; test_finished_event_.TimedWait(dhcp_delay_); return configured_url_; } base::WaitableEvent test_finished_event_; base::TimeDelta dhcp_delay_; std::string configured_url_; private: ~DelayingDhcpQuery() override {} }; DhcpQuery* ImplCreateDhcpQuery() override { dhcp_query_ = new DelayingDhcpQuery(); dhcp_query_->dhcp_delay_ = dhcp_delay_; dhcp_query_->configured_url_ = configured_url_; return dhcp_query_.get(); } // Use a shorter timeout so tests can finish more quickly. base::TimeDelta ImplGetTimeout() const override { return timeout_; } void OnFetcherTimer() { // Note that there is an assumption by this mock implementation that // DhcpProxyScriptAdapterFetcher::Fetch will call ImplCreateScriptFetcher // and call Fetch on the fetcher before the message loop is re-entered. // This holds true today, but if you hit this DCHECK the problem can // possibly be resolved by having a separate subclass of // MockProxyScriptFetcher that adds the delay internally (instead of // the simple approach currently used in ImplCreateScriptFetcher above). DCHECK(fetcher_ && fetcher_->has_pending_request()); fetcher_->NotifyFetchCompletion(fetcher_result_, pac_script_); fetcher_ = NULL; } bool IsWaitingForFetcher() const { return state() == STATE_WAIT_URL; } bool WasCancelled() const { return state() == STATE_CANCEL; } void FinishTest() { DCHECK(dhcp_query_.get()); dhcp_query_->test_finished_event_.Signal(); } base::TimeDelta dhcp_delay_; base::TimeDelta timeout_; std::string configured_url_; int fetcher_delay_ms_; int fetcher_result_; std::string pac_script_; MockProxyScriptFetcher* fetcher_; base::OneShotTimer fetcher_timer_; scoped_refptr dhcp_query_; }; class FetcherClient { public: FetcherClient() : url_request_context_(new TestURLRequestContext()), worker_pool_( new base::SequencedWorkerPool(4, "DhcpAdapterFetcherTest")), fetcher_(new MockDhcpProxyScriptAdapterFetcher( url_request_context_.get(), worker_pool_->GetTaskRunnerWithShutdownBehavior( base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN))) { } ~FetcherClient() { worker_pool_->Shutdown(); } void WaitForResult(int expected_error) { EXPECT_EQ(expected_error, callback_.WaitForResult()); } void RunTest() { fetcher_->Fetch("adapter name", callback_.callback()); } void FinishTestAllowCleanup() { fetcher_->FinishTest(); base::MessageLoop::current()->RunUntilIdle(); } TestCompletionCallback callback_; scoped_ptr url_request_context_; scoped_refptr worker_pool_; scoped_ptr fetcher_; base::string16 pac_text_; }; TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLNotInDhcp) { FetcherClient client; client.fetcher_->configured_url_ = ""; client.RunTest(); client.WaitForResult(ERR_PAC_NOT_IN_DHCP); ASSERT_TRUE(client.fetcher_->DidFinish()); EXPECT_EQ(ERR_PAC_NOT_IN_DHCP, client.fetcher_->GetResult()); EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); } TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLInDhcp) { FetcherClient client; client.RunTest(); client.WaitForResult(OK); ASSERT_TRUE(client.fetcher_->DidFinish()); EXPECT_EQ(OK, client.fetcher_->GetResult()); EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript()); EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); } TEST(DhcpProxyScriptAdapterFetcher, TimeoutDuringDhcp) { // Does a Fetch() with a long enough delay on accessing DHCP that the // fetcher should time out. This is to test a case manual testing found, // where under certain circumstances (e.g. adapter enabled for DHCP and // needs to retrieve its configuration from DHCP, but no DHCP server // present on the network) accessing DHCP can take on the order of tens // of seconds. FetcherClient client; client.fetcher_->dhcp_delay_ = TestTimeouts::action_max_timeout(); client.fetcher_->timeout_ = base::TimeDelta::FromMilliseconds(25); base::ElapsedTimer timer; client.RunTest(); // An error different from this would be received if the timeout didn't // kick in. client.WaitForResult(ERR_TIMED_OUT); ASSERT_TRUE(client.fetcher_->DidFinish()); EXPECT_EQ(ERR_TIMED_OUT, client.fetcher_->GetResult()); EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); client.FinishTestAllowCleanup(); } TEST(DhcpProxyScriptAdapterFetcher, CancelWhileDhcp) { FetcherClient client; client.RunTest(); client.fetcher_->Cancel(); base::MessageLoop::current()->RunUntilIdle(); ASSERT_FALSE(client.fetcher_->DidFinish()); ASSERT_TRUE(client.fetcher_->WasCancelled()); EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult()); EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); client.FinishTestAllowCleanup(); } TEST(DhcpProxyScriptAdapterFetcher, CancelWhileFetcher) { FetcherClient client; // This causes the mock fetcher not to pretend the // fetcher finishes after a timeout. client.fetcher_->fetcher_delay_ms_ = -1; client.RunTest(); int max_loops = 4; while (!client.fetcher_->IsWaitingForFetcher() && max_loops--) { base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); base::MessageLoop::current()->RunUntilIdle(); } client.fetcher_->Cancel(); base::MessageLoop::current()->RunUntilIdle(); ASSERT_FALSE(client.fetcher_->DidFinish()); ASSERT_TRUE(client.fetcher_->WasCancelled()); EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult()); EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); // GetPacURL() still returns the URL fetched in this case. EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); client.FinishTestAllowCleanup(); } TEST(DhcpProxyScriptAdapterFetcher, CancelAtCompletion) { FetcherClient client; client.RunTest(); client.WaitForResult(OK); client.fetcher_->Cancel(); // Canceling after you're done should have no effect, so these // are identical expectations to the NormalCaseURLInDhcp test. ASSERT_TRUE(client.fetcher_->DidFinish()); EXPECT_EQ(OK, client.fetcher_->GetResult()); EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript()); EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); client.FinishTestAllowCleanup(); } // Does a real fetch on a mock DHCP configuration. class MockDhcpRealFetchProxyScriptAdapterFetcher : public MockDhcpProxyScriptAdapterFetcher { public: explicit MockDhcpRealFetchProxyScriptAdapterFetcher( URLRequestContext* context, scoped_refptr task_runner) : MockDhcpProxyScriptAdapterFetcher(context, task_runner), url_request_context_(context) { } // Returns a real proxy script fetcher. ProxyScriptFetcher* ImplCreateScriptFetcher() override { ProxyScriptFetcher* fetcher = new ProxyScriptFetcherImpl(url_request_context_); return fetcher; } URLRequestContext* url_request_context_; }; TEST(DhcpProxyScriptAdapterFetcher, MockDhcpRealFetch) { EmbeddedTestServer test_server; test_server.ServeFilesFromSourceDirectory( "net/data/proxy_script_fetcher_unittest"); ASSERT_TRUE(test_server.Start()); GURL configured_url = test_server.GetURL("/downloadable.pac"); FetcherClient client; TestURLRequestContext url_request_context; scoped_refptr runner = client.worker_pool_->GetTaskRunnerWithShutdownBehavior( base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); client.fetcher_.reset( new MockDhcpRealFetchProxyScriptAdapterFetcher( &url_request_context, runner)); client.fetcher_->configured_url_ = configured_url.spec(); client.RunTest(); client.WaitForResult(OK); ASSERT_TRUE(client.fetcher_->DidFinish()); EXPECT_EQ(OK, client.fetcher_->GetResult()); EXPECT_EQ(base::string16(L"-downloadable.pac-\n"), client.fetcher_->GetPacScript()); EXPECT_EQ(configured_url, client.fetcher_->GetPacURL()); } #define BASE_URL "http://corpserver/proxy.pac" TEST(DhcpProxyScriptAdapterFetcher, SanitizeDhcpApiString) { const size_t kBaseUrlLen = strlen(BASE_URL); // Default case. EXPECT_EQ(BASE_URL, DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( BASE_URL, kBaseUrlLen)); // Trailing \n and no null-termination. EXPECT_EQ(BASE_URL, DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( BASE_URL "\nblablabla", kBaseUrlLen + 1)); // Embedded NULLs. EXPECT_EQ(BASE_URL, DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( BASE_URL "\0foo\0blat", kBaseUrlLen + 9)); } #undef BASE_URL } // namespace } // namespace net