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 <arpa/inet.h> | |
6 #include <resolv.h> | |
7 | |
8 #include "base/bind.h" | |
9 #include "base/message_loop.h" | |
10 #include "base/message_loop_proxy.h" | |
11 #include "base/synchronization/waitable_event.h" | |
12 #include "base/threading/thread.h" | |
13 #include "net/base/ip_endpoint.h" | |
14 #include "net/dns/dns_config_service_posix.h" | |
15 #include "testing/gtest/include/gtest/gtest.h" | |
16 | |
17 namespace net { | |
18 | |
19 namespace { | |
20 | |
21 void CompareConfig(const struct __res_state &res, const DnsConfig& config) { | |
22 EXPECT_EQ(config.ndots, static_cast<int>(res.ndots)); | |
23 EXPECT_EQ(config.edns0, (res.options & RES_USE_EDNS0) != 0); | |
24 EXPECT_EQ(config.rotate, (res.options & RES_ROTATE) != 0); | |
25 EXPECT_EQ(config.timeout.InSeconds(), res.retrans); | |
26 EXPECT_EQ(config.attempts, res.retry); | |
27 | |
28 // Compare nameservers. IPv6 precede IPv4. | |
29 #if OS_LINUX | |
30 size_t nscount6 = res._u._ext.nscount6; | |
31 #else | |
32 size_t nscount6 = 0; | |
33 #endif | |
34 size_t nscount4 = res.nscount; | |
35 ASSERT_EQ(config.nameservers.size(), nscount6 + nscount4); | |
36 #if OS_LINUX | |
37 for (size_t i = 0; i < nscount6; ++i) { | |
38 IPEndPoint ipe; | |
39 EXPECT_TRUE(ipe.FromSockAddr( | |
40 reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]), | |
41 sizeof *res._u._ext.nsaddrs[i])); | |
42 EXPECT_EQ(config.nameservers[i], ipe); | |
43 } | |
44 #endif | |
45 for (size_t i = 0; i < nscount4; ++i) { | |
46 IPEndPoint ipe; | |
47 EXPECT_TRUE(ipe.FromSockAddr( | |
48 reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]), | |
49 sizeof res.nsaddr_list[i])); | |
50 EXPECT_EQ(config.nameservers[nscount6 + i], ipe); | |
51 } | |
52 | |
53 ASSERT_TRUE(config.search.size() <= MAXDNSRCH); | |
54 EXPECT_TRUE(res.dnsrch[config.search.size()] == NULL); | |
55 for (size_t i = 0; i < config.search.size(); ++i) { | |
56 EXPECT_EQ(config.search[i], res.dnsrch[i]); | |
57 } | |
58 } | |
59 | |
60 // Fills in |res| with sane configuration. Change |generation| to add diversity. | |
61 void InitializeResState(res_state res, int generation) { | |
62 memset(res, 0, sizeof(*res)); | |
63 res->options = RES_INIT | RES_ROTATE; | |
64 res->ndots = 2; | |
65 res->retrans = 8; | |
66 res->retry = 7; | |
67 | |
68 const char kDnsrch[] = "chromium.org" "\0" "example.com"; | |
69 memcpy(res->defdname, kDnsrch, sizeof(kDnsrch)); | |
70 memset(res->dnsrch, 0, sizeof(res->dnsrch)); | |
71 res->dnsrch[0] = res->defdname; | |
72 res->dnsrch[1] = res->defdname + sizeof("chromium.org"); | |
73 | |
74 const char* ip4addr[3] = { | |
75 "8.8.8.8", | |
76 "192.168.1.1", | |
77 "63.1.2.4", | |
78 }; | |
79 | |
80 for (int i = 0; i < 3; ++i) { | |
81 struct sockaddr_in sa; | |
82 sa.sin_family = AF_INET; | |
83 sa.sin_port = htons(NAMESERVER_PORT + i - generation); | |
84 inet_pton(AF_INET, ip4addr[i], &sa.sin_addr); | |
85 res->nsaddr_list[i] = sa; | |
86 } | |
87 res->nscount = 3; | |
88 | |
89 #ifdef OS_LINUX | |
90 const char* ip6addr[2] = { | |
91 "2001:db8:0::42", | |
92 "::FFFF:129.144.52.38", | |
93 }; | |
94 | |
95 for (int i = 0; i < 2; ++i) { | |
96 // Must use malloc to mimick res_ninit. | |
97 struct sockaddr_in6 *sa6; | |
98 sa6 = (struct sockaddr_in6 *)malloc(sizeof(*sa6)); | |
99 sa6->sin6_family = AF_INET6; | |
100 sa6->sin6_port = htons(NAMESERVER_PORT - i); | |
101 inet_pton(AF_INET6, ip6addr[i], &sa6->sin6_addr); | |
102 res->_u._ext.nsaddrs[i] = sa6; | |
103 } | |
104 res->_u._ext.nscount6 = 2; | |
105 #endif | |
106 } | |
107 | |
108 | |
cbentzel
2011/08/15 18:16:34
Nit: extra line.
szym
2011/08/15 22:02:01
Done.
| |
109 class DnsConfigServiceTest : public testing::Test, | |
110 public DnsConfigService::Observer { | |
111 public: | |
112 // Mocks | |
113 | |
114 // DnsConfigService owns the instances of ResolverLib and | |
115 // FilePathWatcherFactory that it gets, so use simple proxies to call | |
116 // DnsConfigServiceTest. | |
117 | |
118 // ResolverLib is owned by WatcherDelegate which is posted to WorkerPool so | |
119 // it must be cancelled before the test is over. | |
120 class MockResolverLib : public ResolverLib { | |
121 public: | |
122 explicit MockResolverLib(DnsConfigServiceTest *test) : test_(test) {} | |
123 virtual ~MockResolverLib() { | |
124 base::AutoLock lock(lock_); | |
125 if (test_) { | |
126 EXPECT_TRUE(test_->IsComplete()); | |
127 } | |
128 } | |
129 virtual int ninit(res_state res) OVERRIDE { | |
cbentzel
2011/08/15 18:16:34
Does this need an nclose()?
szym
2011/08/15 22:02:01
Done. There were a few missing nclose calls.
| |
130 base::AutoLock lock(lock_); | |
131 if (test_) | |
132 return test_->OnNinit(res); | |
133 else | |
134 return -1; | |
135 } | |
136 void Cancel() { | |
137 base::AutoLock lock(lock_); | |
138 test_ = NULL; | |
139 } | |
140 private: | |
141 base::Lock lock_; | |
142 DnsConfigServiceTest *test_; | |
143 }; | |
144 | |
145 class MockFilePathWatcherShim : public FilePathWatcherShim { | |
146 public: | |
147 typedef base::files::FilePathWatcher::Delegate Delegate; | |
148 | |
149 explicit MockFilePathWatcherShim(DnsConfigServiceTest* t) : test_(t) {} | |
150 virtual ~MockFilePathWatcherShim() { | |
151 test_->OnShimDestroyed(this); | |
152 } | |
153 | |
154 // Enforce one-Watch-per-lifetime as the original FilePathWatcher | |
155 virtual bool Watch(const FilePath& path, | |
156 Delegate* delegate) OVERRIDE { | |
157 EXPECT_TRUE(path_.empty()) << "Only one-Watch-per-lifetime allowed."; | |
158 EXPECT_TRUE(!delegate_.get()); | |
159 path_ = path; | |
160 delegate_ = delegate; | |
161 return test_->OnWatch(); | |
162 } | |
163 | |
164 void PathChanged() { | |
165 delegate_->OnFilePathChanged(path_); | |
166 } | |
167 | |
168 void PathError() { | |
169 delegate_->OnFilePathError(path_); | |
170 } | |
171 | |
172 private: | |
173 FilePath path_; | |
174 scoped_refptr<Delegate> delegate_; | |
175 DnsConfigServiceTest* test_; | |
176 }; | |
177 | |
178 struct MockFilePathWatcherFactory : public FilePathWatcherFactory { | |
179 explicit MockFilePathWatcherFactory(DnsConfigServiceTest* t) : test(t) {} | |
180 virtual ~MockFilePathWatcherFactory() { | |
181 EXPECT_TRUE(test->IsComplete()); | |
182 } | |
183 virtual FilePathWatcherShim* CreateFilePathWatcher() OVERRIDE { | |
184 return test->CreateFilePathWatcher(); | |
185 } | |
186 DnsConfigServiceTest* test; | |
187 }; | |
188 | |
189 // Helpers for mocks. | |
190 | |
191 FilePathWatcherShim* CreateFilePathWatcher() { | |
192 watcher_shim_ = new MockFilePathWatcherShim(this); | |
193 return watcher_shim_; | |
194 } | |
195 | |
196 void OnShimDestroyed(MockFilePathWatcherShim* destroyed_shim) { | |
197 // Precaution to avoid segfault. | |
198 if (watcher_shim_ == destroyed_shim) | |
199 watcher_shim_ = NULL; | |
200 } | |
201 | |
202 // On each event, post QuitTask to allow use of MessageLoop::Run() to | |
203 // synchronize the threads. | |
204 | |
205 bool OnWatch() { | |
206 EXPECT_TRUE(message_loop_ == MessageLoop::current()); | |
207 watch_called_ = true; | |
208 message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); | |
209 return !fail_on_watch_; | |
210 } | |
211 | |
212 int OnNinit(res_state res) { | |
213 { // Check that res_ninit is executed serially. | |
214 base::AutoLock lock(ninit_lock_); | |
215 EXPECT_FALSE(ninit_running_) << "res_ninit is not called serially!"; | |
216 ninit_running_ = true; | |
217 } | |
218 | |
219 message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); | |
220 | |
221 ninit_allowed_.Wait(); | |
222 // Calling from another thread is a bit dirty, but it works. | |
223 int rv = OnNinitNonThreadSafe(res); | |
224 | |
225 // This lock might be destroyed after ninit_called_ is signalled. | |
226 { | |
227 base::AutoLock lock(ninit_lock_); | |
228 ninit_running_ = false; | |
229 } | |
230 ninit_called_.Signal(); | |
231 return rv; | |
232 } | |
233 | |
234 virtual void OnConfigChanged(const DnsConfig& new_config) OVERRIDE { | |
235 EXPECT_TRUE(message_loop_ == MessageLoop::current()); | |
236 CompareConfig(res_, new_config); | |
237 EXPECT_FALSE(new_config.Equals(last_config_)) << | |
238 "Config must be different from last call."; | |
239 last_config_ = new_config; | |
240 got_config_ = true; | |
241 message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); | |
242 } | |
243 | |
244 bool IsComplete() { | |
245 return complete_; | |
246 } | |
247 | |
248 protected: | |
249 DnsConfigServiceTest() | |
250 : res_lib_(new MockResolverLib(this)), | |
cbentzel
2011/08/15 18:16:34
Does this get leaked in favor of the one created i
szym
2011/08/15 22:02:01
Good catch. Done.
| |
251 watcher_shim_(NULL), | |
252 res_generation_(1), | |
253 watch_called_(false), | |
254 got_config_(false), | |
255 fail_on_watch_(false), | |
256 fail_on_ninit_(false), | |
257 complete_(false), | |
258 ninit_allowed_(false, false), | |
259 ninit_called_(false, false), | |
260 ninit_running_(false) { | |
261 } | |
262 | |
263 // This is on WorkerPool, but protected by ninit_allowed_. | |
264 int OnNinitNonThreadSafe(res_state res) { | |
265 if (fail_on_ninit_) | |
266 return -1; | |
267 InitializeResState(res, res_generation_); | |
268 // Store a (deep) copy in the fixture to later verify correctness. | |
269 InitializeResState(&res_, res_generation_); | |
270 return 0; | |
271 } | |
272 | |
273 // Helpers for tests. | |
274 | |
275 // Lets OnNinit run OnNinitNonThreadSafe and waits for it to complete. | |
276 // Might get OnConfigChanged scheduled on the loop but we have no certainty. | |
277 void WaitForNinit() { | |
278 message_loop_->Run(); // Until OnNinit | |
279 ninit_allowed_.Signal(); | |
280 ninit_called_.Wait(); | |
281 } | |
282 | |
283 // test::Test methods | |
284 virtual void SetUp() OVERRIDE { | |
285 message_loop_ = MessageLoop::current(); | |
286 service_.reset(new DnsConfigServicePosix()); | |
287 res_lib_ = new MockResolverLib(this); | |
288 service_->set_resolver_lib(res_lib_); | |
289 service_->set_watcher_factory(new MockFilePathWatcherFactory(this)); | |
290 } | |
291 | |
292 virtual void TearDown() OVERRIDE { | |
293 // res_lib_ could outlive the test, so make sure it doesn't call it. | |
294 res_lib_->Cancel(); | |
295 // Allow service_ to clean up ResolverLib and FilePathWatcherFactory. | |
296 complete_ = true; | |
297 } | |
298 | |
299 MockResolverLib* res_lib_; | |
300 MockFilePathWatcherShim* watcher_shim_; | |
301 // Adds variety to the content of res_state. | |
302 int res_generation_; | |
303 struct __res_state res_; | |
304 DnsConfig last_config_; | |
305 | |
306 bool watch_called_; | |
307 bool got_config_; | |
308 bool fail_on_watch_; | |
309 bool fail_on_ninit_; | |
310 bool complete_; | |
311 | |
312 // Ninit is called on WorkerPool so we need to synchronize with it. | |
313 base::WaitableEvent ninit_allowed_; | |
314 base::WaitableEvent ninit_called_; | |
315 | |
316 // Protected by ninit_lock_. Used to verify that Ninit calls are serialized. | |
317 bool ninit_running_; | |
318 base::Lock ninit_lock_; | |
319 | |
320 // Loop for this thread. | |
321 MessageLoop* message_loop_; | |
322 | |
323 // Service under test. | |
324 scoped_ptr<DnsConfigServicePosix> service_; | |
325 }; | |
326 | |
327 TEST(DnsConfigTest, ResolverConfigConvertAndEquals) { | |
328 struct __res_state res[2]; | |
329 DnsConfig config[2]; | |
330 for (int i = 0; i < 2; ++i) { | |
331 InitializeResState(&res[i], i); | |
332 ASSERT_TRUE(ConvertResToConfig(res[i], &config[i])); | |
333 } | |
334 for (int i = 0; i < 2; ++i) { | |
335 CompareConfig(res[0], config[0]); | |
336 } | |
337 EXPECT_TRUE(config[0].Equals(config[0])); | |
338 EXPECT_FALSE(config[0].Equals(config[1])); | |
339 EXPECT_FALSE(config[1].Equals(config[0])); | |
340 } | |
341 | |
342 TEST_F(DnsConfigServiceTest, FilePathWatcherFailures) { | |
343 // For this tests, disable ninit. | |
344 res_lib_->Cancel(); | |
345 | |
346 fail_on_watch_ = true; | |
347 service_->Watch(); | |
348 message_loop_->Run(); // until Watch | |
349 EXPECT_TRUE(watch_called_) << "Must call FilePathWatcher::Watch()."; | |
cbentzel
2011/08/15 18:16:34
I need to think through this some more, but I'm wo
| |
350 | |
351 fail_on_watch_ = false; | |
352 watch_called_ = false; | |
353 message_loop_->Run(); // until Watch, takes 100ms | |
cbentzel
2011/08/15 18:16:34
You may want to make it clear that there isn't a s
szym
2011/08/15 22:02:01
Done.
| |
354 EXPECT_TRUE(watch_called_) << | |
355 "Must restart on FilePathWatcher::Watch() failure."; | |
356 | |
357 watch_called_ = false; | |
358 ASSERT_TRUE(watcher_shim_); | |
359 watcher_shim_->PathError(); | |
360 message_loop_->Run(); // until Watch | |
361 EXPECT_TRUE(watch_called_) << | |
362 "Must restart on FilePathWatcher::Delegate::OnFilePathError()."; | |
363 | |
364 // Worker thread could still be posting OnResultAvailable to the message loop | |
365 } | |
366 | |
367 TEST_F(DnsConfigServiceTest, NotifyOnValidAndDistinctConfig) { | |
368 service_->AddObserver(this); | |
369 service_->Watch(); | |
370 watch_called_ = false; | |
371 fail_on_ninit_ = true; | |
372 WaitForNinit(); | |
373 | |
374 fail_on_ninit_ = false; | |
375 ASSERT_TRUE(watcher_shim_); | |
376 watcher_shim_->PathChanged(); | |
377 WaitForNinit(); | |
378 message_loop_->Run(); // until OnConfigChanged | |
379 EXPECT_TRUE(got_config_); | |
380 | |
381 got_config_ = false; | |
382 // Forget about the config to test if we get it again on AddObserver. | |
383 last_config_ = DnsConfig(); | |
384 service_->RemoveObserver(this); | |
385 service_->AddObserver(this); | |
386 EXPECT_TRUE(got_config_) << "Did not get config after AddObserver."; | |
387 | |
388 // OnConfigChanged will catch that the config did not actually change. | |
389 got_config_ = false; | |
390 ASSERT_TRUE(watcher_shim_); | |
391 watcher_shim_->PathChanged(); | |
392 WaitForNinit(); | |
393 | |
394 got_config_ = false; | |
395 ++res_generation_; | |
396 ASSERT_TRUE(watcher_shim_); | |
397 watcher_shim_->PathChanged(); | |
398 WaitForNinit(); | |
399 message_loop_->Run(); // until OnConfigchanged | |
400 EXPECT_TRUE(got_config_) << "Did not get config after change"; | |
401 | |
402 message_loop_->AssertIdle(); | |
403 | |
404 // Schedule two calls. OnNinit checks if it is called serially. | |
405 ++res_generation_; | |
406 ASSERT_TRUE(watcher_shim_); | |
407 watcher_shim_->PathChanged(); | |
408 watcher_shim_->PathChanged(); | |
409 WaitForNinit(); | |
410 ++res_generation_; | |
411 WaitForNinit(); | |
412 message_loop_->Run(); // until OnConfigchanged | |
413 EXPECT_TRUE(got_config_) << "Did not get config after change"; | |
414 | |
415 // This check might be too strict. | |
416 EXPECT_FALSE(watch_called_) << "Unexpected Watch() without failures."; | |
417 | |
418 // We should be done with all tasks. | |
419 message_loop_->AssertIdle(); | |
420 } | |
421 | |
422 } // namespace | |
423 | |
424 } // namespace net | |
425 | |
OLD | NEW |