| // 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_resolution/dhcp_pac_file_adapter_fetcher_win.h" |
| |
| #include "base/run_loop.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/task/post_task.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/threading/thread_restrictions.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_resolution/mock_pac_file_fetcher.h" |
| #include "net/proxy_resolution/pac_file_fetcher_impl.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/gtest_util.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| |
| namespace net { |
| |
| namespace { |
| |
| const char kPacUrl[] = "http://pacserver/script.pac"; |
| |
| // In net/proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc there are |
| // a few tests that exercise DhcpPacFileAdapterFetcher end-to-end along with |
| // DhcpPacFileFetcherWin, 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 DhcpPacFileAdapterFetcher that mocks out dependencies |
| // to allow unit testing. |
| class MockDhcpPacFileAdapterFetcher : public DhcpPacFileAdapterFetcher { |
| public: |
| explicit MockDhcpPacFileAdapterFetcher( |
| URLRequestContext* context, |
| scoped_refptr<base::TaskRunner> task_runner) |
| : DhcpPacFileAdapterFetcher(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 { |
| DhcpPacFileAdapterFetcher::Cancel(); |
| fetcher_ = NULL; |
| } |
| |
| std::unique_ptr<PacFileFetcher> ImplCreateScriptFetcher() override { |
| // We don't maintain ownership of the fetcher, it is transferred to |
| // the caller. |
| fetcher_ = new MockPacFileFetcher(); |
| if (fetcher_delay_ms_ != -1) { |
| fetcher_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromMilliseconds(fetcher_delay_ms_), this, |
| &MockDhcpPacFileAdapterFetcher::OnFetcherTimer); |
| } |
| return base::WrapUnique(fetcher_); |
| } |
| |
| class DelayingDhcpQuery : public DhcpQuery { |
| public: |
| explicit DelayingDhcpQuery() |
| : DhcpQuery(), |
| test_finished_event_( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED) {} |
| |
| std::string ImplGetPacURLFromDhcp( |
| const std::string& adapter_name) override { |
| base::ElapsedTimer timer; |
| { |
| base::ScopedAllowBaseSyncPrimitivesForTesting |
| scoped_allow_base_sync_primitives; |
| 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 |
| // DhcpPacFileAdapterFetcher::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 |
| // MockPacFileFetcher 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_; |
| MockPacFileFetcher* fetcher_; |
| base::OneShotTimer fetcher_timer_; |
| scoped_refptr<DelayingDhcpQuery> dhcp_query_; |
| }; |
| |
| class FetcherClient { |
| public: |
| FetcherClient() |
| : url_request_context_(new TestURLRequestContext()), |
| fetcher_(new MockDhcpPacFileAdapterFetcher( |
| url_request_context_.get(), |
| base::CreateSequencedTaskRunnerWithTraits( |
| {base::MayBlock(), |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}))) {} |
| |
| void WaitForResult(int expected_error) { |
| EXPECT_EQ(expected_error, callback_.WaitForResult()); |
| } |
| |
| void RunTest() { |
| fetcher_->Fetch("adapter name", callback_.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| } |
| |
| void FinishTestAllowCleanup() { |
| fetcher_->FinishTest(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TestCompletionCallback callback_; |
| std::unique_ptr<URLRequestContext> url_request_context_; |
| std::unique_ptr<MockDhcpPacFileAdapterFetcher> fetcher_; |
| base::string16 pac_text_; |
| }; |
| |
| TEST(DhcpPacFileAdapterFetcher, NormalCaseURLNotInDhcp) { |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| |
| FetcherClient client; |
| client.fetcher_->configured_url_ = ""; |
| client.RunTest(); |
| client.WaitForResult(ERR_PAC_NOT_IN_DHCP); |
| ASSERT_TRUE(client.fetcher_->DidFinish()); |
| EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_PAC_NOT_IN_DHCP)); |
| EXPECT_EQ(base::string16(), client.fetcher_->GetPacScript()); |
| } |
| |
| TEST(DhcpPacFileAdapterFetcher, NormalCaseURLInDhcp) { |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| |
| FetcherClient client; |
| client.RunTest(); |
| client.WaitForResult(OK); |
| ASSERT_TRUE(client.fetcher_->DidFinish()); |
| EXPECT_THAT(client.fetcher_->GetResult(), IsOk()); |
| EXPECT_EQ(base::string16(STRING16_LITERAL("bingo")), |
| client.fetcher_->GetPacScript()); |
| EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); |
| } |
| |
| TEST(DhcpPacFileAdapterFetcher, TimeoutDuringDhcp) { |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| |
| // 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_THAT(client.fetcher_->GetResult(), IsError(ERR_TIMED_OUT)); |
| EXPECT_EQ(base::string16(), client.fetcher_->GetPacScript()); |
| EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); |
| client.FinishTestAllowCleanup(); |
| } |
| |
| TEST(DhcpPacFileAdapterFetcher, CancelWhileDhcp) { |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| |
| FetcherClient client; |
| client.RunTest(); |
| client.fetcher_->Cancel(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(client.fetcher_->DidFinish()); |
| ASSERT_TRUE(client.fetcher_->WasCancelled()); |
| EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_ABORTED)); |
| EXPECT_EQ(base::string16(), client.fetcher_->GetPacScript()); |
| EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); |
| client.FinishTestAllowCleanup(); |
| } |
| |
| TEST(DhcpPacFileAdapterFetcher, CancelWhileFetcher) { |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| |
| 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::RunLoop().RunUntilIdle(); |
| } |
| client.fetcher_->Cancel(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(client.fetcher_->DidFinish()); |
| ASSERT_TRUE(client.fetcher_->WasCancelled()); |
| EXPECT_THAT(client.fetcher_->GetResult(), IsError(ERR_ABORTED)); |
| EXPECT_EQ(base::string16(), client.fetcher_->GetPacScript()); |
| // GetPacURL() still returns the URL fetched in this case. |
| EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); |
| client.FinishTestAllowCleanup(); |
| } |
| |
| TEST(DhcpPacFileAdapterFetcher, CancelAtCompletion) { |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| |
| 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_THAT(client.fetcher_->GetResult(), IsOk()); |
| EXPECT_EQ(base::string16(STRING16_LITERAL("bingo")), |
| client.fetcher_->GetPacScript()); |
| EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); |
| client.FinishTestAllowCleanup(); |
| } |
| |
| // Does a real fetch on a mock DHCP configuration. |
| class MockDhcpRealFetchPacFileAdapterFetcher |
| : public MockDhcpPacFileAdapterFetcher { |
| public: |
| explicit MockDhcpRealFetchPacFileAdapterFetcher( |
| URLRequestContext* context, |
| scoped_refptr<base::TaskRunner> task_runner) |
| : MockDhcpPacFileAdapterFetcher(context, task_runner), |
| url_request_context_(context) {} |
| |
| // Returns a real PAC file fetcher. |
| std::unique_ptr<PacFileFetcher> ImplCreateScriptFetcher() override { |
| return PacFileFetcherImpl::Create(url_request_context_); |
| } |
| |
| URLRequestContext* url_request_context_; |
| }; |
| |
| TEST(DhcpPacFileAdapterFetcher, MockDhcpRealFetch) { |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| |
| EmbeddedTestServer test_server; |
| test_server.ServeFilesFromSourceDirectory( |
| "net/data/pac_file_fetcher_unittest"); |
| ASSERT_TRUE(test_server.Start()); |
| |
| GURL configured_url = test_server.GetURL("/downloadable.pac"); |
| |
| FetcherClient client; |
| TestURLRequestContext url_request_context; |
| client.fetcher_.reset(new MockDhcpRealFetchPacFileAdapterFetcher( |
| &url_request_context, |
| base::CreateTaskRunnerWithTraits( |
| {base::MayBlock(), |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}))); |
| client.fetcher_->configured_url_ = configured_url.spec(); |
| client.RunTest(); |
| client.WaitForResult(OK); |
| ASSERT_TRUE(client.fetcher_->DidFinish()); |
| EXPECT_THAT(client.fetcher_->GetResult(), IsOk()); |
| EXPECT_EQ(base::string16(STRING16_LITERAL("-downloadable.pac-\n")), |
| client.fetcher_->GetPacScript()); |
| EXPECT_EQ(configured_url, |
| client.fetcher_->GetPacURL()); |
| } |
| |
| #define BASE_URL "http://corpserver/proxy.pac" |
| |
| TEST(DhcpPacFileAdapterFetcher, SanitizeDhcpApiString) { |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| |
| const size_t kBaseUrlLen = strlen(BASE_URL); |
| |
| // Default case. |
| EXPECT_EQ(BASE_URL, DhcpPacFileAdapterFetcher::SanitizeDhcpApiString( |
| BASE_URL, kBaseUrlLen)); |
| |
| // Trailing \n and no null-termination. |
| EXPECT_EQ(BASE_URL, DhcpPacFileAdapterFetcher::SanitizeDhcpApiString( |
| BASE_URL "\nblablabla", kBaseUrlLen + 1)); |
| |
| // Embedded NULLs. |
| EXPECT_EQ(BASE_URL, DhcpPacFileAdapterFetcher::SanitizeDhcpApiString( |
| BASE_URL "\0foo\0blat", kBaseUrlLen + 9)); |
| } |
| |
| #undef BASE_URL |
| |
| } // namespace |
| |
| } // namespace net |