1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h" 6 7#include "base/synchronization/waitable_event.h" 8#include "base/test/test_timeouts.h" 9#include "base/threading/sequenced_worker_pool.h" 10#include "base/timer/elapsed_timer.h" 11#include "base/timer/timer.h" 12#include "net/base/net_errors.h" 13#include "net/base/test_completion_callback.h" 14#include "net/proxy/mock_proxy_script_fetcher.h" 15#include "net/proxy/proxy_script_fetcher_impl.h" 16#include "net/test/spawned_test_server/spawned_test_server.h" 17#include "net/url_request/url_request_test_util.h" 18#include "testing/gtest/include/gtest/gtest.h" 19 20namespace net { 21 22namespace { 23 24const char* const kPacUrl = "http://pacserver/script.pac"; 25 26// In net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc there are a few 27// tests that exercise DhcpProxyScriptAdapterFetcher end-to-end along with 28// DhcpProxyScriptFetcherWin, i.e. it tests the end-to-end usage of Win32 29// APIs and the network. In this file we test only by stubbing out 30// functionality. 31 32// Version of DhcpProxyScriptAdapterFetcher that mocks out dependencies 33// to allow unit testing. 34class MockDhcpProxyScriptAdapterFetcher 35 : public DhcpProxyScriptAdapterFetcher { 36 public: 37 explicit MockDhcpProxyScriptAdapterFetcher( 38 URLRequestContext* context, 39 scoped_refptr<base::TaskRunner> task_runner) 40 : DhcpProxyScriptAdapterFetcher(context, task_runner), 41 dhcp_delay_(base::TimeDelta::FromMilliseconds(1)), 42 timeout_(TestTimeouts::action_timeout()), 43 configured_url_(kPacUrl), 44 fetcher_delay_ms_(1), 45 fetcher_result_(OK), 46 pac_script_("bingo") { 47 } 48 49 void Cancel() { 50 DhcpProxyScriptAdapterFetcher::Cancel(); 51 fetcher_ = NULL; 52 } 53 54 virtual ProxyScriptFetcher* ImplCreateScriptFetcher() OVERRIDE { 55 // We don't maintain ownership of the fetcher, it is transferred to 56 // the caller. 57 fetcher_ = new MockProxyScriptFetcher(); 58 if (fetcher_delay_ms_ != -1) { 59 fetcher_timer_.Start(FROM_HERE, 60 base::TimeDelta::FromMilliseconds(fetcher_delay_ms_), 61 this, &MockDhcpProxyScriptAdapterFetcher::OnFetcherTimer); 62 } 63 return fetcher_; 64 } 65 66 class DelayingDhcpQuery : public DhcpQuery { 67 public: 68 explicit DelayingDhcpQuery() 69 : DhcpQuery(), 70 test_finished_event_(true, false) { 71 } 72 73 std::string ImplGetPacURLFromDhcp( 74 const std::string& adapter_name) OVERRIDE { 75 base::ElapsedTimer timer; 76 test_finished_event_.TimedWait(dhcp_delay_); 77 return configured_url_; 78 } 79 80 base::WaitableEvent test_finished_event_; 81 base::TimeDelta dhcp_delay_; 82 std::string configured_url_; 83 }; 84 85 virtual DhcpQuery* ImplCreateDhcpQuery() OVERRIDE { 86 dhcp_query_ = new DelayingDhcpQuery(); 87 dhcp_query_->dhcp_delay_ = dhcp_delay_; 88 dhcp_query_->configured_url_ = configured_url_; 89 return dhcp_query_; 90 } 91 92 // Use a shorter timeout so tests can finish more quickly. 93 virtual base::TimeDelta ImplGetTimeout() const OVERRIDE { 94 return timeout_; 95 } 96 97 void OnFetcherTimer() { 98 // Note that there is an assumption by this mock implementation that 99 // DhcpProxyScriptAdapterFetcher::Fetch will call ImplCreateScriptFetcher 100 // and call Fetch on the fetcher before the message loop is re-entered. 101 // This holds true today, but if you hit this DCHECK the problem can 102 // possibly be resolved by having a separate subclass of 103 // MockProxyScriptFetcher that adds the delay internally (instead of 104 // the simple approach currently used in ImplCreateScriptFetcher above). 105 DCHECK(fetcher_ && fetcher_->has_pending_request()); 106 fetcher_->NotifyFetchCompletion(fetcher_result_, pac_script_); 107 fetcher_ = NULL; 108 } 109 110 bool IsWaitingForFetcher() const { 111 return state() == STATE_WAIT_URL; 112 } 113 114 bool WasCancelled() const { 115 return state() == STATE_CANCEL; 116 } 117 118 void FinishTest() { 119 DCHECK(dhcp_query_); 120 dhcp_query_->test_finished_event_.Signal(); 121 } 122 123 base::TimeDelta dhcp_delay_; 124 base::TimeDelta timeout_; 125 std::string configured_url_; 126 int fetcher_delay_ms_; 127 int fetcher_result_; 128 std::string pac_script_; 129 MockProxyScriptFetcher* fetcher_; 130 base::OneShotTimer<MockDhcpProxyScriptAdapterFetcher> fetcher_timer_; 131 scoped_refptr<DelayingDhcpQuery> dhcp_query_; 132}; 133 134class FetcherClient { 135 public: 136 FetcherClient() 137 : url_request_context_(new TestURLRequestContext()), 138 worker_pool_( 139 new base::SequencedWorkerPool(4, "DhcpAdapterFetcherTest")), 140 fetcher_(new MockDhcpProxyScriptAdapterFetcher( 141 url_request_context_.get(), 142 worker_pool_->GetTaskRunnerWithShutdownBehavior( 143 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN))) { 144 } 145 146 ~FetcherClient() { 147 worker_pool_->Shutdown(); 148 } 149 150 void WaitForResult(int expected_error) { 151 EXPECT_EQ(expected_error, callback_.WaitForResult()); 152 } 153 154 void RunTest() { 155 fetcher_->Fetch("adapter name", callback_.callback()); 156 } 157 158 void FinishTestAllowCleanup() { 159 fetcher_->FinishTest(); 160 base::MessageLoop::current()->RunUntilIdle(); 161 } 162 163 TestCompletionCallback callback_; 164 scoped_ptr<URLRequestContext> url_request_context_; 165 scoped_refptr<base::SequencedWorkerPool> worker_pool_; 166 scoped_ptr<MockDhcpProxyScriptAdapterFetcher> fetcher_; 167 base::string16 pac_text_; 168}; 169 170TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLNotInDhcp) { 171 FetcherClient client; 172 client.fetcher_->configured_url_ = ""; 173 client.RunTest(); 174 client.WaitForResult(ERR_PAC_NOT_IN_DHCP); 175 ASSERT_TRUE(client.fetcher_->DidFinish()); 176 EXPECT_EQ(ERR_PAC_NOT_IN_DHCP, client.fetcher_->GetResult()); 177 EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); 178} 179 180TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLInDhcp) { 181 FetcherClient client; 182 client.RunTest(); 183 client.WaitForResult(OK); 184 ASSERT_TRUE(client.fetcher_->DidFinish()); 185 EXPECT_EQ(OK, client.fetcher_->GetResult()); 186 EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript()); 187 EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); 188} 189 190TEST(DhcpProxyScriptAdapterFetcher, TimeoutDuringDhcp) { 191 // Does a Fetch() with a long enough delay on accessing DHCP that the 192 // fetcher should time out. This is to test a case manual testing found, 193 // where under certain circumstances (e.g. adapter enabled for DHCP and 194 // needs to retrieve its configuration from DHCP, but no DHCP server 195 // present on the network) accessing DHCP can take on the order of tens 196 // of seconds. 197 FetcherClient client; 198 client.fetcher_->dhcp_delay_ = TestTimeouts::action_max_timeout(); 199 client.fetcher_->timeout_ = base::TimeDelta::FromMilliseconds(25); 200 201 base::ElapsedTimer timer; 202 client.RunTest(); 203 // An error different from this would be received if the timeout didn't 204 // kick in. 205 client.WaitForResult(ERR_TIMED_OUT); 206 207 ASSERT_TRUE(client.fetcher_->DidFinish()); 208 EXPECT_EQ(ERR_TIMED_OUT, client.fetcher_->GetResult()); 209 EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); 210 EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); 211 client.FinishTestAllowCleanup(); 212} 213 214TEST(DhcpProxyScriptAdapterFetcher, CancelWhileDhcp) { 215 FetcherClient client; 216 client.RunTest(); 217 client.fetcher_->Cancel(); 218 base::MessageLoop::current()->RunUntilIdle(); 219 ASSERT_FALSE(client.fetcher_->DidFinish()); 220 ASSERT_TRUE(client.fetcher_->WasCancelled()); 221 EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult()); 222 EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); 223 EXPECT_EQ(GURL(), client.fetcher_->GetPacURL()); 224 client.FinishTestAllowCleanup(); 225} 226 227TEST(DhcpProxyScriptAdapterFetcher, CancelWhileFetcher) { 228 FetcherClient client; 229 // This causes the mock fetcher not to pretend the 230 // fetcher finishes after a timeout. 231 client.fetcher_->fetcher_delay_ms_ = -1; 232 client.RunTest(); 233 int max_loops = 4; 234 while (!client.fetcher_->IsWaitingForFetcher() && max_loops--) { 235 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); 236 base::MessageLoop::current()->RunUntilIdle(); 237 } 238 client.fetcher_->Cancel(); 239 base::MessageLoop::current()->RunUntilIdle(); 240 ASSERT_FALSE(client.fetcher_->DidFinish()); 241 ASSERT_TRUE(client.fetcher_->WasCancelled()); 242 EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult()); 243 EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript()); 244 // GetPacURL() still returns the URL fetched in this case. 245 EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); 246 client.FinishTestAllowCleanup(); 247} 248 249TEST(DhcpProxyScriptAdapterFetcher, CancelAtCompletion) { 250 FetcherClient client; 251 client.RunTest(); 252 client.WaitForResult(OK); 253 client.fetcher_->Cancel(); 254 // Canceling after you're done should have no effect, so these 255 // are identical expectations to the NormalCaseURLInDhcp test. 256 ASSERT_TRUE(client.fetcher_->DidFinish()); 257 EXPECT_EQ(OK, client.fetcher_->GetResult()); 258 EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript()); 259 EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL()); 260 client.FinishTestAllowCleanup(); 261} 262 263// Does a real fetch on a mock DHCP configuration. 264class MockDhcpRealFetchProxyScriptAdapterFetcher 265 : public MockDhcpProxyScriptAdapterFetcher { 266 public: 267 explicit MockDhcpRealFetchProxyScriptAdapterFetcher( 268 URLRequestContext* context, 269 scoped_refptr<base::TaskRunner> task_runner) 270 : MockDhcpProxyScriptAdapterFetcher(context, task_runner), 271 url_request_context_(context) { 272 } 273 274 // Returns a real proxy script fetcher. 275 ProxyScriptFetcher* ImplCreateScriptFetcher() OVERRIDE { 276 ProxyScriptFetcher* fetcher = 277 new ProxyScriptFetcherImpl(url_request_context_); 278 return fetcher; 279 } 280 281 URLRequestContext* url_request_context_; 282}; 283 284TEST(DhcpProxyScriptAdapterFetcher, MockDhcpRealFetch) { 285 SpawnedTestServer test_server( 286 SpawnedTestServer::TYPE_HTTP, 287 SpawnedTestServer::kLocalhost, 288 base::FilePath( 289 FILE_PATH_LITERAL("net/data/proxy_script_fetcher_unittest"))); 290 ASSERT_TRUE(test_server.Start()); 291 292 GURL configured_url = test_server.GetURL("files/downloadable.pac"); 293 294 FetcherClient client; 295 TestURLRequestContext url_request_context; 296 scoped_refptr<base::TaskRunner> runner = 297 client.worker_pool_->GetTaskRunnerWithShutdownBehavior( 298 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); 299 client.fetcher_.reset( 300 new MockDhcpRealFetchProxyScriptAdapterFetcher( 301 &url_request_context, runner)); 302 client.fetcher_->configured_url_ = configured_url.spec(); 303 client.RunTest(); 304 client.WaitForResult(OK); 305 ASSERT_TRUE(client.fetcher_->DidFinish()); 306 EXPECT_EQ(OK, client.fetcher_->GetResult()); 307 EXPECT_EQ(base::string16(L"-downloadable.pac-\n"), 308 client.fetcher_->GetPacScript()); 309 EXPECT_EQ(configured_url, 310 client.fetcher_->GetPacURL()); 311} 312 313#define BASE_URL "http://corpserver/proxy.pac" 314 315TEST(DhcpProxyScriptAdapterFetcher, SanitizeDhcpApiString) { 316 const size_t kBaseUrlLen = strlen(BASE_URL); 317 318 // Default case. 319 EXPECT_EQ(BASE_URL, 320 DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( 321 BASE_URL, kBaseUrlLen)); 322 323 // Trailing \n and no null-termination. 324 EXPECT_EQ(BASE_URL, 325 DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( 326 BASE_URL "\nblablabla", kBaseUrlLen + 1)); 327 328 // Embedded NULLs. 329 EXPECT_EQ(BASE_URL, 330 DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( 331 BASE_URL "\0foo\0blat", kBaseUrlLen + 9)); 332} 333 334#undef BASE_URL 335 336} // namespace 337 338} // namespace net 339