OLD | NEW |
| (Empty) |
1 // Copyright (c) 2009 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 "base/string_util.h" | |
6 #include "base/waitable_event.h" | |
7 #include "googleurl/src/gurl.h" | |
8 #include "net/base/net_log.h" | |
9 #include "net/base/net_log_unittest.h" | |
10 #include "net/base/net_errors.h" | |
11 #include "net/base/test_completion_callback.h" | |
12 #include "net/proxy/proxy_info.h" | |
13 #include "net/proxy/single_threaded_proxy_resolver.h" | |
14 #include "testing/gtest/include/gtest/gtest.h" | |
15 | |
16 namespace net { | |
17 namespace { | |
18 | |
19 // A synchronous mock ProxyResolver implementation, which can be used in | |
20 // conjunction with SingleThreadedProxyResolver. | |
21 // - returns a single-item proxy list with the query's host. | |
22 class MockProxyResolver : public ProxyResolver { | |
23 public: | |
24 MockProxyResolver() | |
25 : ProxyResolver(true /*expects_pac_bytes*/), | |
26 wrong_loop_(MessageLoop::current()), | |
27 request_count_(0), | |
28 purge_count_(0), | |
29 resolve_latency_ms_(0) {} | |
30 | |
31 // ProxyResolver implementation: | |
32 virtual int GetProxyForURL(const GURL& query_url, | |
33 ProxyInfo* results, | |
34 CompletionCallback* callback, | |
35 RequestHandle* request, | |
36 const BoundNetLog& net_log) { | |
37 if (resolve_latency_ms_) | |
38 PlatformThread::Sleep(resolve_latency_ms_); | |
39 | |
40 CheckIsOnWorkerThread(); | |
41 | |
42 EXPECT_TRUE(callback == NULL); | |
43 EXPECT_TRUE(request == NULL); | |
44 | |
45 // Write something into |net_log| (doesn't really have any meaning.) | |
46 net_log.BeginEvent(NetLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE, NULL); | |
47 | |
48 results->UseNamedProxy(query_url.host()); | |
49 | |
50 // Return a success code which represents the request's order. | |
51 return request_count_++; | |
52 } | |
53 | |
54 virtual void CancelRequest(RequestHandle request) { | |
55 NOTREACHED(); | |
56 } | |
57 | |
58 virtual int SetPacScript(const GURL& pac_url, | |
59 const string16& text, | |
60 CompletionCallback* callback) { | |
61 CheckIsOnWorkerThread(); | |
62 last_pac_script_ = text; | |
63 return OK; | |
64 } | |
65 | |
66 virtual void PurgeMemory() { | |
67 CheckIsOnWorkerThread(); | |
68 ++purge_count_; | |
69 } | |
70 | |
71 int purge_count() const { return purge_count_; } | |
72 | |
73 const string16& last_pac_script() const { return last_pac_script_; } | |
74 | |
75 void SetResolveLatency(int latency_ms) { | |
76 resolve_latency_ms_ = latency_ms; | |
77 } | |
78 | |
79 private: | |
80 void CheckIsOnWorkerThread() { | |
81 // We should be running on the worker thread -- while we don't know the | |
82 // message loop of SingleThreadedProxyResolver's worker thread, we do | |
83 // know that it is going to be distinct from the loop running the | |
84 // test, so at least make sure it isn't the main loop. | |
85 EXPECT_NE(MessageLoop::current(), wrong_loop_); | |
86 } | |
87 | |
88 MessageLoop* wrong_loop_; | |
89 int request_count_; | |
90 int purge_count_; | |
91 string16 last_pac_script_; | |
92 int resolve_latency_ms_; | |
93 }; | |
94 | |
95 | |
96 // A mock synchronous ProxyResolver which can be set to block upon reaching | |
97 // GetProxyForURL(). | |
98 // TODO(eroman): WaitUntilBlocked() *must* be called before calling Unblock(), | |
99 // otherwise there will be a race on |should_block_| since it is | |
100 // read without any synchronization. | |
101 class BlockableProxyResolver : public MockProxyResolver { | |
102 public: | |
103 BlockableProxyResolver() | |
104 : should_block_(false), | |
105 unblocked_(true, true), | |
106 blocked_(true, false) { | |
107 } | |
108 | |
109 void Block() { | |
110 should_block_ = true; | |
111 unblocked_.Reset(); | |
112 } | |
113 | |
114 void Unblock() { | |
115 should_block_ = false; | |
116 blocked_.Reset(); | |
117 unblocked_.Signal(); | |
118 } | |
119 | |
120 void WaitUntilBlocked() { | |
121 blocked_.Wait(); | |
122 } | |
123 | |
124 virtual int GetProxyForURL(const GURL& query_url, | |
125 ProxyInfo* results, | |
126 CompletionCallback* callback, | |
127 RequestHandle* request, | |
128 const BoundNetLog& net_log) { | |
129 if (should_block_) { | |
130 blocked_.Signal(); | |
131 unblocked_.Wait(); | |
132 } | |
133 | |
134 return MockProxyResolver::GetProxyForURL( | |
135 query_url, results, callback, request, net_log); | |
136 } | |
137 | |
138 private: | |
139 bool should_block_; | |
140 base::WaitableEvent unblocked_; | |
141 base::WaitableEvent blocked_; | |
142 }; | |
143 | |
144 TEST(SingleThreadedProxyResolverTest, Basic) { | |
145 MockProxyResolver* mock = new MockProxyResolver; | |
146 SingleThreadedProxyResolver resolver(mock); | |
147 | |
148 int rv; | |
149 | |
150 EXPECT_TRUE(resolver.expects_pac_bytes()); | |
151 | |
152 // Call SetPacScriptByData() -- verify that it reaches the synchronous | |
153 // resolver. | |
154 TestCompletionCallback set_script_callback; | |
155 rv = resolver.SetPacScriptByData(ASCIIToUTF16("pac script bytes"), | |
156 &set_script_callback); | |
157 EXPECT_EQ(ERR_IO_PENDING, rv); | |
158 EXPECT_EQ(OK, set_script_callback.WaitForResult()); | |
159 EXPECT_EQ(ASCIIToUTF16("pac script bytes"), mock->last_pac_script()); | |
160 | |
161 // Start request 0. | |
162 TestCompletionCallback callback0; | |
163 CapturingBoundNetLog log0(CapturingNetLog::kUnbounded); | |
164 ProxyInfo results0; | |
165 rv = resolver.GetProxyForURL( | |
166 GURL("http://request0"), &results0, &callback0, NULL, log0.bound()); | |
167 EXPECT_EQ(ERR_IO_PENDING, rv); | |
168 | |
169 // Wait for request 0 to finish. | |
170 rv = callback0.WaitForResult(); | |
171 EXPECT_EQ(0, rv); | |
172 EXPECT_EQ("PROXY request0:80", results0.ToPacString()); | |
173 | |
174 // The mock proxy resolver should have written 1 log entry. And | |
175 // on completion, this should have been copied into |log0|. | |
176 EXPECT_EQ(1u, log0.entries().size()); | |
177 | |
178 // Start 3 more requests (request1 to request3). | |
179 | |
180 TestCompletionCallback callback1; | |
181 ProxyInfo results1; | |
182 rv = resolver.GetProxyForURL( | |
183 GURL("http://request1"), &results1, &callback1, NULL, BoundNetLog()); | |
184 EXPECT_EQ(ERR_IO_PENDING, rv); | |
185 | |
186 TestCompletionCallback callback2; | |
187 ProxyInfo results2; | |
188 rv = resolver.GetProxyForURL( | |
189 GURL("http://request2"), &results2, &callback2, NULL, BoundNetLog()); | |
190 EXPECT_EQ(ERR_IO_PENDING, rv); | |
191 | |
192 TestCompletionCallback callback3; | |
193 ProxyInfo results3; | |
194 rv = resolver.GetProxyForURL( | |
195 GURL("http://request3"), &results3, &callback3, NULL, BoundNetLog()); | |
196 EXPECT_EQ(ERR_IO_PENDING, rv); | |
197 | |
198 // Wait for the requests to finish (they must finish in the order they were | |
199 // started, which is what we check for from their magic return value) | |
200 | |
201 rv = callback1.WaitForResult(); | |
202 EXPECT_EQ(1, rv); | |
203 EXPECT_EQ("PROXY request1:80", results1.ToPacString()); | |
204 | |
205 rv = callback2.WaitForResult(); | |
206 EXPECT_EQ(2, rv); | |
207 EXPECT_EQ("PROXY request2:80", results2.ToPacString()); | |
208 | |
209 rv = callback3.WaitForResult(); | |
210 EXPECT_EQ(3, rv); | |
211 EXPECT_EQ("PROXY request3:80", results3.ToPacString()); | |
212 | |
213 // Ensure that PurgeMemory() reaches the wrapped resolver and happens on the | |
214 // right thread. | |
215 EXPECT_EQ(0, mock->purge_count()); | |
216 resolver.PurgeMemory(); | |
217 // There is no way to get a callback directly when PurgeMemory() completes, so | |
218 // we queue up a dummy request after the PurgeMemory() call and wait until it | |
219 // finishes to ensure PurgeMemory() has had a chance to run. | |
220 TestCompletionCallback dummy_callback; | |
221 rv = resolver.SetPacScriptByData(ASCIIToUTF16("dummy"), &dummy_callback); | |
222 EXPECT_EQ(OK, dummy_callback.WaitForResult()); | |
223 EXPECT_EQ(1, mock->purge_count()); | |
224 } | |
225 | |
226 // Tests that the NetLog is updated to include the time the request was waiting | |
227 // to be scheduled to a thread. | |
228 TEST(SingleThreadedProxyResolverTest, UpdatesNetLogWithThreadWait) { | |
229 BlockableProxyResolver* mock = new BlockableProxyResolver; | |
230 SingleThreadedProxyResolver resolver(mock); | |
231 | |
232 int rv; | |
233 | |
234 // Block the proxy resolver, so no request can complete. | |
235 mock->Block(); | |
236 | |
237 // Start request 0. | |
238 ProxyResolver::RequestHandle request0; | |
239 TestCompletionCallback callback0; | |
240 ProxyInfo results0; | |
241 CapturingBoundNetLog log0(CapturingNetLog::kUnbounded); | |
242 rv = resolver.GetProxyForURL( | |
243 GURL("http://request0"), &results0, &callback0, &request0, log0.bound()); | |
244 EXPECT_EQ(ERR_IO_PENDING, rv); | |
245 | |
246 // Start 2 more requests (request1 and request2). | |
247 | |
248 TestCompletionCallback callback1; | |
249 ProxyInfo results1; | |
250 CapturingBoundNetLog log1(CapturingNetLog::kUnbounded); | |
251 rv = resolver.GetProxyForURL( | |
252 GURL("http://request1"), &results1, &callback1, NULL, log1.bound()); | |
253 EXPECT_EQ(ERR_IO_PENDING, rv); | |
254 | |
255 ProxyResolver::RequestHandle request2; | |
256 TestCompletionCallback callback2; | |
257 ProxyInfo results2; | |
258 CapturingBoundNetLog log2(CapturingNetLog::kUnbounded); | |
259 rv = resolver.GetProxyForURL( | |
260 GURL("http://request2"), &results2, &callback2, &request2, log2.bound()); | |
261 EXPECT_EQ(ERR_IO_PENDING, rv); | |
262 | |
263 // Unblock the worker thread so the requests can continue running. | |
264 mock->WaitUntilBlocked(); | |
265 mock->Unblock(); | |
266 | |
267 // Check that request 0 completed as expected. | |
268 // The NetLog only has 1 entry (that came from the mock proxy resolver.) | |
269 EXPECT_EQ(0, callback0.WaitForResult()); | |
270 EXPECT_EQ("PROXY request0:80", results0.ToPacString()); | |
271 ASSERT_EQ(1u, log0.entries().size()); | |
272 | |
273 // Check that request 1 completed as expected. | |
274 EXPECT_EQ(1, callback1.WaitForResult()); | |
275 EXPECT_EQ("PROXY request1:80", results1.ToPacString()); | |
276 ASSERT_EQ(3u, log1.entries().size()); | |
277 EXPECT_TRUE(LogContainsBeginEvent( | |
278 log1.entries(), 0, | |
279 NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD)); | |
280 EXPECT_TRUE(LogContainsEndEvent( | |
281 log1.entries(), 1, | |
282 NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD)); | |
283 | |
284 // Check that request 2 completed as expected. | |
285 EXPECT_EQ(2, callback2.WaitForResult()); | |
286 EXPECT_EQ("PROXY request2:80", results2.ToPacString()); | |
287 ASSERT_EQ(3u, log2.entries().size()); | |
288 EXPECT_TRUE(LogContainsBeginEvent( | |
289 log2.entries(), 0, | |
290 NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD)); | |
291 EXPECT_TRUE(LogContainsEndEvent( | |
292 log2.entries(), 1, | |
293 NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD)); | |
294 } | |
295 | |
296 // Cancel a request which is in progress, and then cancel a request which | |
297 // is pending. | |
298 TEST(SingleThreadedProxyResolverTest, CancelRequest) { | |
299 BlockableProxyResolver* mock = new BlockableProxyResolver; | |
300 SingleThreadedProxyResolver resolver(mock); | |
301 | |
302 int rv; | |
303 | |
304 // Block the proxy resolver, so no request can complete. | |
305 mock->Block(); | |
306 | |
307 // Start request 0. | |
308 ProxyResolver::RequestHandle request0; | |
309 TestCompletionCallback callback0; | |
310 ProxyInfo results0; | |
311 rv = resolver.GetProxyForURL( | |
312 GURL("http://request0"), &results0, &callback0, &request0, BoundNetLog()); | |
313 EXPECT_EQ(ERR_IO_PENDING, rv); | |
314 | |
315 // Wait until requests 0 reaches the worker thread. | |
316 mock->WaitUntilBlocked(); | |
317 | |
318 // Start 3 more requests (request1 : request3). | |
319 | |
320 TestCompletionCallback callback1; | |
321 ProxyInfo results1; | |
322 rv = resolver.GetProxyForURL( | |
323 GURL("http://request1"), &results1, &callback1, NULL, BoundNetLog()); | |
324 EXPECT_EQ(ERR_IO_PENDING, rv); | |
325 | |
326 ProxyResolver::RequestHandle request2; | |
327 TestCompletionCallback callback2; | |
328 ProxyInfo results2; | |
329 rv = resolver.GetProxyForURL( | |
330 GURL("http://request2"), &results2, &callback2, &request2, BoundNetLog()); | |
331 EXPECT_EQ(ERR_IO_PENDING, rv); | |
332 | |
333 TestCompletionCallback callback3; | |
334 ProxyInfo results3; | |
335 rv = resolver.GetProxyForURL( | |
336 GURL("http://request3"), &results3, &callback3, NULL, BoundNetLog()); | |
337 EXPECT_EQ(ERR_IO_PENDING, rv); | |
338 | |
339 // Cancel request0 (inprogress) and request2 (pending). | |
340 resolver.CancelRequest(request0); | |
341 resolver.CancelRequest(request2); | |
342 | |
343 // Unblock the worker thread so the requests can continue running. | |
344 mock->Unblock(); | |
345 | |
346 // Wait for requests 1 and 3 to finish. | |
347 | |
348 rv = callback1.WaitForResult(); | |
349 EXPECT_EQ(1, rv); | |
350 EXPECT_EQ("PROXY request1:80", results1.ToPacString()); | |
351 | |
352 rv = callback3.WaitForResult(); | |
353 // Note that since request2 was cancelled before reaching the resolver, | |
354 // the request count is 2 and not 3 here. | |
355 EXPECT_EQ(2, rv); | |
356 EXPECT_EQ("PROXY request3:80", results3.ToPacString()); | |
357 | |
358 // Requests 0 and 2 which were cancelled, hence their completion callbacks | |
359 // were never summoned. | |
360 EXPECT_FALSE(callback0.have_result()); | |
361 EXPECT_FALSE(callback2.have_result()); | |
362 } | |
363 | |
364 // Test that deleting SingleThreadedProxyResolver while requests are | |
365 // outstanding cancels them (and doesn't leak anything). | |
366 TEST(SingleThreadedProxyResolverTest, CancelRequestByDeleting) { | |
367 BlockableProxyResolver* mock = new BlockableProxyResolver; | |
368 scoped_ptr<SingleThreadedProxyResolver> resolver( | |
369 new SingleThreadedProxyResolver(mock)); | |
370 | |
371 int rv; | |
372 | |
373 // Block the proxy resolver, so no request can complete. | |
374 mock->Block(); | |
375 | |
376 // Start 3 requests. | |
377 | |
378 TestCompletionCallback callback0; | |
379 ProxyInfo results0; | |
380 rv = resolver->GetProxyForURL( | |
381 GURL("http://request0"), &results0, &callback0, NULL, BoundNetLog()); | |
382 EXPECT_EQ(ERR_IO_PENDING, rv); | |
383 | |
384 TestCompletionCallback callback1; | |
385 ProxyInfo results1; | |
386 rv = resolver->GetProxyForURL( | |
387 GURL("http://request1"), &results1, &callback1, NULL, BoundNetLog()); | |
388 EXPECT_EQ(ERR_IO_PENDING, rv); | |
389 | |
390 TestCompletionCallback callback2; | |
391 ProxyInfo results2; | |
392 rv = resolver->GetProxyForURL( | |
393 GURL("http://request2"), &results2, &callback2, NULL, BoundNetLog()); | |
394 EXPECT_EQ(ERR_IO_PENDING, rv); | |
395 | |
396 // Wait until request 0 reaches the worker thread. | |
397 mock->WaitUntilBlocked(); | |
398 | |
399 // Add some latency, to improve the chance that when | |
400 // SingleThreadedProxyResolver is deleted below we are still running inside | |
401 // of the worker thread. The test will pass regardless, so this race doesn't | |
402 // cause flakiness. However the destruction during execution is a more | |
403 // interesting case to test. | |
404 mock->SetResolveLatency(100); | |
405 | |
406 // Unblock the worker thread and delete the underlying | |
407 // SingleThreadedProxyResolver immediately. | |
408 mock->Unblock(); | |
409 resolver.reset(); | |
410 | |
411 // Give any posted tasks a chance to run (in case there is badness). | |
412 MessageLoop::current()->RunAllPending(); | |
413 | |
414 // Check that none of the outstanding requests were completed. | |
415 EXPECT_FALSE(callback0.have_result()); | |
416 EXPECT_FALSE(callback1.have_result()); | |
417 EXPECT_FALSE(callback2.have_result()); | |
418 } | |
419 | |
420 // Cancel an outstanding call to SetPacScriptByData(). | |
421 TEST(SingleThreadedProxyResolverTest, CancelSetPacScript) { | |
422 BlockableProxyResolver* mock = new BlockableProxyResolver; | |
423 SingleThreadedProxyResolver resolver(mock); | |
424 | |
425 int rv; | |
426 | |
427 // Block the proxy resolver, so no request can complete. | |
428 mock->Block(); | |
429 | |
430 // Start request 0. | |
431 ProxyResolver::RequestHandle request0; | |
432 TestCompletionCallback callback0; | |
433 ProxyInfo results0; | |
434 rv = resolver.GetProxyForURL( | |
435 GURL("http://request0"), &results0, &callback0, &request0, BoundNetLog()); | |
436 EXPECT_EQ(ERR_IO_PENDING, rv); | |
437 | |
438 // Wait until requests 0 reaches the worker thread. | |
439 mock->WaitUntilBlocked(); | |
440 | |
441 TestCompletionCallback set_pac_script_callback; | |
442 rv = resolver.SetPacScriptByData(ASCIIToUTF16("data"), | |
443 &set_pac_script_callback); | |
444 EXPECT_EQ(ERR_IO_PENDING, rv); | |
445 | |
446 // Cancel the SetPacScriptByData request (it can't have finished yet, | |
447 // since the single-thread is currently blocked). | |
448 resolver.CancelSetPacScript(); | |
449 | |
450 // Start 1 more request. | |
451 | |
452 TestCompletionCallback callback1; | |
453 ProxyInfo results1; | |
454 rv = resolver.GetProxyForURL( | |
455 GURL("http://request1"), &results1, &callback1, NULL, BoundNetLog()); | |
456 EXPECT_EQ(ERR_IO_PENDING, rv); | |
457 | |
458 // Unblock the worker thread so the requests can continue running. | |
459 mock->Unblock(); | |
460 | |
461 // Wait for requests 0 and 1 to finish. | |
462 | |
463 rv = callback0.WaitForResult(); | |
464 EXPECT_EQ(0, rv); | |
465 EXPECT_EQ("PROXY request0:80", results0.ToPacString()); | |
466 | |
467 rv = callback1.WaitForResult(); | |
468 EXPECT_EQ(1, rv); | |
469 EXPECT_EQ("PROXY request1:80", results1.ToPacString()); | |
470 | |
471 // The SetPacScript callback should never have been completed. | |
472 EXPECT_FALSE(set_pac_script_callback.have_result()); | |
473 } | |
474 | |
475 } // namespace | |
476 } // namespace net | |
OLD | NEW |