Chromium Code Reviews| 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 |