OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2011 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_fetcher_win.h" | |
6 | |
7 #include <vector> | |
8 | |
9 #include "base/message_loop.h" | |
10 #include "base/perftimer.h" | |
11 #include "base/rand_util.h" | |
12 #include "base/threading/platform_thread.h" | |
13 #include "net/base/completion_callback.h" | |
14 #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h" | |
15 #include "net/url_request/url_request_test_util.h" | |
16 #include "testing/gtest/include/gtest/gtest.h" | |
17 | |
18 namespace net { | |
19 | |
20 // Allows private access to WindowsDhcpProxyScriptFetcher for unit tests. | |
21 class WindowsDhcpProxyScriptFetcherTestFriend { | |
22 public: | |
23 static bool HasCompleted(WindowsDhcpProxyScriptFetcher* fetcher) { | |
24 return fetcher->num_pending_fetchers_ == 0; | |
25 } | |
26 }; | |
27 | |
28 namespace { | |
29 | |
30 TEST(WindowsDhcpProxyScriptFetcher, AdapterNamesAndPacURLFromDhcp) { | |
31 // This tests our core Win32 implementation without any of the wrappers | |
32 // we layer on top to achieve asynchronous and parallel operations. | |
33 // | |
34 // We don't make assumptions about the environment this unit test is | |
35 // running in, so it just exercises the code to make sure there | |
36 // is no crash and no error returned, but does not assert on the number | |
37 // of interfaces or the information returned via DHCP. | |
38 std::set<std::string> adapter_names; | |
39 WindowsDhcpProxyScriptFetcher::GetCandidateAdapterNames(&adapter_names); | |
40 for (std::set<std::string>::iterator it = adapter_names.begin(); | |
41 it != adapter_names.end(); | |
42 ++it) { | |
43 const std::string& adapter_name = *it; | |
44 std::string pac_url = | |
45 DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); | |
46 printf("Adapter '%s' has PAC URL '%s' configured in DHCP.\n", | |
47 adapter_name.c_str(), | |
48 pac_url.c_str()); | |
49 } | |
50 } | |
51 | |
52 // Helper for RealFetch* tests below. | |
53 class RealFetchTester { | |
54 public: | |
55 RealFetchTester() | |
56 : context_((new TestURLRequestContext())), | |
57 fetcher_(new WindowsDhcpProxyScriptFetcher(context_.get())), | |
58 finished_(false), | |
59 ALLOW_THIS_IN_INITIALIZER_LIST( | |
60 completion_callback_(this, &RealFetchTester::OnCompletion)), | |
61 on_completion_is_error_(false) { | |
62 // Make sure the test ends. | |
63 timeout_.Start( | |
64 base::TimeDelta::FromSeconds(5), this, &RealFetchTester::OnTimeout); | |
65 } | |
66 | |
67 void RunTest() { | |
68 fetcher_->Fetch(&pac_text_, &completion_callback_); | |
eroman
2011/05/13 05:03:32
Same comment as earlier, please use a TestCompleti
Jói
2011/05/13 20:19:09
This would make it difficult to implement the canc
| |
69 } | |
70 | |
71 void RunTestWithCancel() { | |
72 RunTest(); | |
73 fetcher_->Cancel(); | |
74 } | |
75 | |
76 void RunTestWithDeferredCancel() { | |
77 RunTest(); | |
78 cancel_timer_.Start(base::TimeDelta::FromMilliseconds(1), | |
79 this, &RealFetchTester::OnCancelTimer); | |
80 } | |
81 | |
82 void OnCompletion(int result) { | |
83 if (on_completion_is_error_) { | |
84 FAIL() << "Received completion for test in which this is error."; | |
85 } | |
86 finished_ = true; | |
87 printf("Result code %d PAC data length %d\n", result, pac_text_.size()); | |
88 } | |
89 | |
90 void OnTimeout() { | |
91 printf("Timeout!"); | |
92 OnCompletion(0); | |
93 } | |
94 | |
95 void OnCancelTimer() { | |
96 fetcher_->Cancel(); | |
97 finished_ = true; | |
98 } | |
99 | |
100 // Attempts to give worker threads time to finish. This is currently | |
101 // very simplistic as completion (via completion callback or cancellation) | |
102 // immediately "detaches" any worker threads, so the best we can do is give | |
103 // them a little time. If we start running into Valgrind leaks, we can | |
104 // do something a bit more clever to track worker threads even when the | |
105 // WindowsDhcpProxyScriptFetcher state machine has finished. | |
106 void FinishTestAllowCleanup() { | |
107 base::PlatformThread::Sleep(30); | |
108 } | |
109 | |
110 scoped_refptr<URLRequestContext> context_; | |
111 scoped_ptr<WindowsDhcpProxyScriptFetcher> fetcher_; | |
112 bool finished_; | |
113 string16 pac_text_; | |
114 CompletionCallbackImpl<RealFetchTester> completion_callback_; | |
115 base::OneShotTimer<RealFetchTester> timeout_; | |
116 base::OneShotTimer<RealFetchTester> cancel_timer_; | |
117 bool on_completion_is_error_; | |
118 }; | |
119 | |
120 TEST(WindowsDhcpProxyScriptFetcher, RealFetch) { | |
121 // This tests a call to Fetch() with no stubbing out of dependencies. | |
122 // | |
123 // We don't make assumptions about the environment this unit test is | |
124 // running in, so it just exercises the code to make sure there | |
125 // is no crash and no unexpected error returned, but does not assert on | |
126 // results beyond that. | |
127 RealFetchTester fetcher; | |
128 fetcher.RunTest(); | |
129 | |
130 while (!fetcher.finished_) { | |
131 MessageLoop::current()->RunAllPending(); | |
132 } | |
133 printf("PAC URL was %s\n", | |
134 fetcher.fetcher_->GetPacURL().possibly_invalid_spec().c_str()); | |
135 | |
136 fetcher.FinishTestAllowCleanup(); | |
137 } | |
138 | |
139 TEST(WindowsDhcpProxyScriptFetcher, RealFetchWithCancel) { | |
140 // Does a Fetch() with an immediate cancel. As before, just | |
141 // exercises the code without stubbing out dependencies. | |
142 RealFetchTester fetcher; | |
143 fetcher.RunTestWithCancel(); | |
144 MessageLoop::current()->RunAllPending(); | |
145 | |
146 // Attempt to avoid Valgrind leak reports in case worker thread is | |
147 // still running. | |
148 base::PlatformThread::Sleep(10); | |
149 | |
150 fetcher.FinishTestAllowCleanup(); | |
151 } | |
152 | |
153 // For RealFetchWithDeferredCancel, below. | |
154 class DelayingDhcpProxyScriptAdapterFetcher | |
155 : public DhcpProxyScriptAdapterFetcher { | |
156 public: | |
157 explicit DelayingDhcpProxyScriptAdapterFetcher( | |
158 URLRequestContext* url_request_context) | |
159 : DhcpProxyScriptAdapterFetcher(url_request_context) { | |
160 } | |
161 | |
162 std::string ImplGetPacURLFromDhcp(const std::string& adapter_name) OVERRIDE { | |
163 base::PlatformThread::Sleep(20); | |
164 return DhcpProxyScriptAdapterFetcher::ImplGetPacURLFromDhcp(adapter_name); | |
165 } | |
166 }; | |
167 | |
168 // For RealFetchWithDeferredCancel, below. | |
169 class DelayingWindowsDhcpProxyScriptFetcher | |
170 : public WindowsDhcpProxyScriptFetcher { | |
171 public: | |
172 explicit DelayingWindowsDhcpProxyScriptFetcher( | |
173 URLRequestContext* context) | |
174 : WindowsDhcpProxyScriptFetcher(context) { | |
175 } | |
176 | |
177 DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher() OVERRIDE { | |
178 return new DelayingDhcpProxyScriptAdapterFetcher(url_request_context_); | |
179 } | |
180 }; | |
181 | |
182 TEST(WindowsDhcpProxyScriptFetcher, RealFetchWithDeferredCancel) { | |
183 // Does a Fetch() with a slightly delayed cancel. As before, just | |
184 // exercises the code without stubbing out dependencies, but | |
185 // introduces a guaranteed 20 ms delay on the worker threads so that | |
186 // the cancel is called before they complete. | |
187 RealFetchTester fetcher; | |
188 fetcher.fetcher_.reset( | |
189 new DelayingWindowsDhcpProxyScriptFetcher(fetcher.context_)); | |
190 fetcher.on_completion_is_error_ = true; | |
191 fetcher.RunTestWithDeferredCancel(); | |
192 while (!fetcher.finished_) { | |
193 MessageLoop::current()->RunAllPending(); | |
194 } | |
195 MessageLoop::current()->RunAllPending(); | |
196 } | |
197 | |
198 // The remaining tests are to exercise our state machine in various | |
199 // situations, with actual network access fully stubbed out. | |
200 | |
201 class DummyDhcpProxyScriptAdapterFetcher | |
202 : public DhcpProxyScriptAdapterFetcher { | |
203 public: | |
204 DummyDhcpProxyScriptAdapterFetcher() | |
205 : DhcpProxyScriptAdapterFetcher(new TestURLRequestContext()), | |
206 did_finish_(false), | |
207 result_(OK), | |
208 pac_script_(L"bingo"), | |
209 fetch_delay_ms_(1), | |
210 client_callback_(NULL) { | |
211 } | |
212 | |
213 void Fetch(const std::string& adapter_name, | |
214 CompletionCallback* callback) OVERRIDE { | |
215 client_callback_ = callback; | |
216 timer_.Start(base::TimeDelta::FromMilliseconds(fetch_delay_ms_), | |
217 this, &DummyDhcpProxyScriptAdapterFetcher::OnTimer); | |
218 } | |
219 | |
220 void Cancel() OVERRIDE { | |
221 timer_.Stop(); | |
222 } | |
223 | |
224 bool DidFinish() const OVERRIDE { | |
225 return did_finish_; | |
226 } | |
227 | |
228 int result() const OVERRIDE { | |
229 return result_; | |
230 } | |
231 | |
232 string16 GetPacScript() const OVERRIDE { | |
233 return pac_script_; | |
234 } | |
235 | |
236 void OnTimer() { | |
237 client_callback_->Run(result_); | |
238 } | |
239 | |
240 void Configure( | |
241 bool did_finish, int result, string16 pac_script, int fetch_delay_ms) { | |
242 did_finish_ = did_finish; | |
243 result_ = result; | |
244 pac_script_ = pac_script; | |
245 fetch_delay_ms_ = fetch_delay_ms; | |
246 } | |
247 | |
248 private: | |
249 bool did_finish_; | |
250 int result_; | |
251 string16 pac_script_; | |
252 int fetch_delay_ms_; | |
253 CompletionCallback* client_callback_; | |
254 base::OneShotTimer<DummyDhcpProxyScriptAdapterFetcher> timer_; | |
255 }; | |
256 | |
257 class MockWindowsDhcpProxyScriptFetcher : public WindowsDhcpProxyScriptFetcher { | |
258 public: | |
259 MockWindowsDhcpProxyScriptFetcher() | |
260 : WindowsDhcpProxyScriptFetcher(new TestURLRequestContext()), | |
261 next_adapter_fetcher_index_(0) { | |
262 } | |
263 | |
264 // Adds a fetcher object to the queue of fetchers used by | |
265 // |ImplCreateAdapterFetcher()|, and its name to the list of adapters | |
266 // returned by ImplGetCandidateAdapterNames. | |
267 void PushBackAdapter(const std::string& adapter_name, | |
268 DhcpProxyScriptAdapterFetcher* fetcher) { | |
269 adapter_names_.push_back(adapter_name); | |
270 adapter_fetchers_.push_back(fetcher); | |
271 } | |
272 | |
273 void ConfigureAndPushBackAdapter(const std::string& adapter_name, | |
274 bool did_finish, | |
275 int result, | |
276 string16 pac_script, | |
277 int fetch_delay_ms) { | |
278 scoped_refptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher = | |
279 new DummyDhcpProxyScriptAdapterFetcher(); | |
280 adapter_fetcher->Configure(did_finish, result, pac_script, fetch_delay_ms); | |
281 PushBackAdapter(adapter_name, adapter_fetcher.get()); | |
282 } | |
283 | |
284 DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher() OVERRIDE { | |
285 return adapter_fetchers_[next_adapter_fetcher_index_++].get(); | |
286 } | |
287 | |
288 bool ImplGetCandidateAdapterNames( | |
289 std::set<std::string>* adapter_names) OVERRIDE { | |
290 adapter_names->insert(adapter_names_.begin(), adapter_names_.end()); | |
291 return true; | |
292 } | |
293 | |
294 int ImplGetMaxWaitMs() OVERRIDE { | |
295 return 25; | |
296 } | |
297 | |
298 void ResetTestState() { | |
299 next_adapter_fetcher_index_ = 0; | |
300 adapter_fetchers_.clear(); | |
301 // String pointers contained herein will have been freed during test. | |
302 adapter_names_.clear(); | |
303 } | |
304 | |
305 int next_adapter_fetcher_index_; | |
306 std::vector<scoped_refptr<DhcpProxyScriptAdapterFetcher>> adapter_fetchers_; | |
307 std::vector<std::string> adapter_names_; | |
308 }; | |
309 | |
310 class FetcherClient { | |
311 public: | |
312 FetcherClient() | |
313 : finished_(false), | |
314 result_(ERR_UNEXPECTED), | |
315 ALLOW_THIS_IN_INITIALIZER_LIST( | |
316 completion_callback_(this, &FetcherClient::OnCompletion)) { | |
317 } | |
318 | |
319 void RunTest() { | |
320 int result = fetcher_.Fetch(&pac_text_, &completion_callback_); | |
321 ASSERT_EQ(ERR_IO_PENDING, result); | |
322 } | |
323 | |
324 void RunImmediateReturnTest() { | |
325 int result = fetcher_.Fetch(&pac_text_, &completion_callback_); | |
326 ASSERT_EQ(ERR_PAC_NOT_IN_DHCP, result); | |
327 } | |
328 | |
329 void RunMessageLoopUntilComplete() { | |
330 while (!finished_) { | |
331 MessageLoop::current()->RunAllPending(); | |
332 } | |
333 MessageLoop::current()->RunAllPending(); | |
334 } | |
335 | |
336 void OnCompletion(int result) { | |
337 finished_ = true; | |
338 result_ = result; | |
339 } | |
340 | |
341 void ResetTestState() { | |
342 finished_ = false; | |
343 result_ = ERR_UNEXPECTED; | |
344 pac_text_ = L""; | |
345 fetcher_.ResetTestState(); | |
346 } | |
347 | |
348 MockWindowsDhcpProxyScriptFetcher fetcher_; | |
349 bool finished_; | |
350 int result_; | |
351 string16 pac_text_; | |
352 CompletionCallbackImpl<FetcherClient> completion_callback_; | |
353 }; | |
354 | |
355 // We separate out each test's logic so that we can easily implement | |
356 // the ReuseFetcher test at the bottom. | |
357 void TestNormalCaseURLConfiguredOneAdapter(FetcherClient* client) { | |
358 scoped_refptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher = | |
359 new DummyDhcpProxyScriptAdapterFetcher(); | |
360 adapter_fetcher->Configure(true, OK, L"bingo", 1); | |
361 client->fetcher_.PushBackAdapter("a", adapter_fetcher.get()); | |
362 client->RunTest(); | |
363 client->RunMessageLoopUntilComplete(); | |
364 ASSERT_EQ(OK, client->result_); | |
365 ASSERT_EQ(L"bingo", client->pac_text_); | |
366 } | |
367 | |
368 TEST(WindowsDhcpProxyScriptFetcher, NormalCaseURLConfiguredOneAdapter) { | |
369 FetcherClient client; | |
370 TestNormalCaseURLConfiguredOneAdapter(&client); | |
371 } | |
372 | |
373 void TestNormalCaseURLConfiguredMultipleAdapters(FetcherClient* client) { | |
374 client->fetcher_.ConfigureAndPushBackAdapter( | |
375 "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"", 1); | |
376 client->fetcher_.ConfigureAndPushBackAdapter( | |
377 "second", true, OK, L"bingo", 50); | |
378 client->fetcher_.ConfigureAndPushBackAdapter( | |
379 "third", true, OK, L"rocko", 1); | |
380 client->RunTest(); | |
381 client->RunMessageLoopUntilComplete(); | |
382 ASSERT_EQ(OK, client->result_); | |
383 ASSERT_EQ(L"bingo", client->pac_text_); | |
384 } | |
385 | |
386 TEST(WindowsDhcpProxyScriptFetcher, NormalCaseURLConfiguredMultipleAdapters) { | |
387 FetcherClient client; | |
388 TestNormalCaseURLConfiguredMultipleAdapters(&client); | |
389 } | |
390 | |
391 void TestNormalCaseURLConfiguredMultipleAdaptersWithTimeout( | |
392 FetcherClient* client) { | |
393 client->fetcher_.ConfigureAndPushBackAdapter( | |
394 "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"", 1); | |
395 // This will time out. | |
396 client->fetcher_.ConfigureAndPushBackAdapter( | |
397 "second", false, ERR_IO_PENDING, L"bingo", 1000); | |
398 client->fetcher_.ConfigureAndPushBackAdapter( | |
399 "third", true, OK, L"rocko", 1); | |
400 client->RunTest(); | |
401 client->RunMessageLoopUntilComplete(); | |
402 ASSERT_EQ(OK, client->result_); | |
403 ASSERT_EQ(L"rocko", client->pac_text_); | |
404 } | |
405 | |
406 TEST(WindowsDhcpProxyScriptFetcher, | |
407 NormalCaseURLConfiguredMultipleAdaptersWithTimeout) { | |
408 FetcherClient client; | |
409 TestNormalCaseURLConfiguredMultipleAdaptersWithTimeout(&client); | |
410 } | |
411 | |
412 void TestFailureCaseURLConfiguredMultipleAdaptersWithTimeout( | |
413 FetcherClient* client) { | |
414 client->fetcher_.ConfigureAndPushBackAdapter( | |
415 "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"", 1); | |
416 // This will time out. | |
417 client->fetcher_.ConfigureAndPushBackAdapter( | |
418 "second", false, ERR_IO_PENDING, L"bingo", 1000); | |
419 // This is the first non-ERR_PAC_NOT_IN_DHCP error and as such | |
420 // should be chosen. | |
421 client->fetcher_.ConfigureAndPushBackAdapter( | |
422 "third", true, ERR_PAC_STATUS_NOT_OK, L"", 1); | |
423 client->fetcher_.ConfigureAndPushBackAdapter( | |
424 "fourth", true, ERR_NOT_IMPLEMENTED, L"", 1); | |
425 client->RunTest(); | |
426 client->RunMessageLoopUntilComplete(); | |
427 ASSERT_EQ(ERR_PAC_STATUS_NOT_OK, client->result_); | |
428 ASSERT_EQ(L"", client->pac_text_); | |
429 } | |
430 | |
431 TEST(WindowsDhcpProxyScriptFetcher, | |
432 FailureCaseURLConfiguredMultipleAdaptersWithTimeout) { | |
433 FetcherClient client; | |
434 TestFailureCaseURLConfiguredMultipleAdaptersWithTimeout(&client); | |
435 } | |
436 | |
437 void TestFailureCaseNoURLConfigured(FetcherClient* client) { | |
438 client->fetcher_.ConfigureAndPushBackAdapter( | |
439 "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"", 1); | |
440 // This will time out. | |
441 client->fetcher_.ConfigureAndPushBackAdapter( | |
442 "second", false, ERR_IO_PENDING, L"bingo", 1000); | |
443 // This is the first non-ERR_PAC_NOT_IN_DHCP error and as such | |
444 // should be chosen. | |
445 client->fetcher_.ConfigureAndPushBackAdapter( | |
446 "third", true, ERR_PAC_NOT_IN_DHCP, L"", 1); | |
447 client->RunTest(); | |
448 client->RunMessageLoopUntilComplete(); | |
449 ASSERT_EQ(ERR_PAC_NOT_IN_DHCP, client->result_); | |
450 ASSERT_EQ(L"", client->pac_text_); | |
451 } | |
452 | |
453 TEST(WindowsDhcpProxyScriptFetcher, FailureCaseNoURLConfigured) { | |
454 FetcherClient client; | |
455 TestFailureCaseNoURLConfigured(&client); | |
456 } | |
457 | |
458 void TestFailureCaseNoDhcpAdapters(FetcherClient* client) { | |
459 client->RunImmediateReturnTest(); | |
460 // In case there are any pending messages that get us in a bad state | |
461 // (there shouldn't be). | |
462 MessageLoop::current()->RunAllPending(); | |
463 } | |
464 | |
465 TEST(WindowsDhcpProxyScriptFetcher, FailureCaseNoDhcpAdapters) { | |
466 FetcherClient client; | |
467 TestFailureCaseNoDhcpAdapters(&client); | |
468 } | |
469 | |
470 void TestShortCircuitLessPreferredAdapters(FetcherClient* client) { | |
471 // Here we have a bunch of adapters; the first reports no PAC in DHCP, | |
472 // the second responds quickly with a PAC file, the rest take a long | |
473 // time. Verify that we complete quickly and do not wait for the slow | |
474 // adapters, i.e. we finish before timeout. | |
475 client->fetcher_.ConfigureAndPushBackAdapter( | |
476 "1", true, ERR_PAC_NOT_IN_DHCP, L"", 1); | |
477 client->fetcher_.ConfigureAndPushBackAdapter( | |
478 "2", true, OK, L"bingo", 1); | |
479 client->fetcher_.ConfigureAndPushBackAdapter( | |
480 "3", true, OK, L"wrongo", 1000); | |
481 | |
482 PerfTimer timer; | |
483 client->RunTest(); | |
484 client->RunMessageLoopUntilComplete(); | |
485 // Assert that the time passed is just less than the wait timer | |
486 // timeout (which we have mocked out above to be 25 ms), to avoid | |
487 // flakiness but still get a strong signal that it was the shortcut | |
488 // mechanism (in OnFetcherDone) that kicked in. | |
489 ASSERT_GT(TimeDelta::FromMilliseconds(23), timer.Elapsed()); | |
490 } | |
491 | |
492 TEST(WindowsDhcpProxyScriptFetcher, ShortCircuitLessPreferredAdapters) { | |
493 FetcherClient client; | |
494 TestShortCircuitLessPreferredAdapters(&client); | |
495 } | |
496 | |
497 TEST(WindowsDhcpProxyScriptFetcher, ReuseFetcher) { | |
498 FetcherClient client; | |
499 | |
500 // The ProxyScriptFetcher interface stipulates that only a single | |
501 // |Fetch()| may be in flight at once, but allows reuse, so test | |
502 // that the state transitions correctly from done to start in all | |
503 // cases we're testing. | |
504 | |
505 typedef void (*FetcherClientTestFunction)(FetcherClient*); | |
506 typedef std::vector<FetcherClientTestFunction> TestVector; | |
507 TestVector test_functions; | |
508 test_functions.push_back(TestNormalCaseURLConfiguredOneAdapter); | |
509 test_functions.push_back(TestNormalCaseURLConfiguredMultipleAdapters); | |
510 test_functions.push_back( | |
511 TestNormalCaseURLConfiguredMultipleAdaptersWithTimeout); | |
512 test_functions.push_back( | |
513 TestFailureCaseURLConfiguredMultipleAdaptersWithTimeout); | |
514 test_functions.push_back(TestFailureCaseNoURLConfigured); | |
515 test_functions.push_back(TestFailureCaseNoDhcpAdapters); | |
516 test_functions.push_back(TestShortCircuitLessPreferredAdapters); | |
517 | |
518 std::random_shuffle(test_functions.begin(), | |
519 test_functions.end(), | |
520 base::RandGenerator); | |
521 for (TestVector::iterator it = test_functions.begin(); | |
522 it != test_functions.end(); | |
523 ++it) { | |
524 (*it)(&client); | |
525 client.ResetTestState(); | |
526 } | |
527 | |
528 // Re-do the first test to make sure the last test that was run did | |
529 // not leave things in a bad state. | |
530 (*test_functions.begin())(&client); | |
531 } | |
532 | |
533 } // namespace | |
534 } // namespace net | |
OLD | NEW |