Index: net/dns/dns_config_service_posix_unittest.cc |
diff --git a/net/dns/dns_config_service_posix_unittest.cc b/net/dns/dns_config_service_posix_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..22638f68942a44832f5029a08af9782df7e1df21 |
--- /dev/null |
+++ b/net/dns/dns_config_service_posix_unittest.cc |
@@ -0,0 +1,460 @@ |
+// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <arpa/inet.h> |
+#include <resolv.h> |
+ |
+#include "base/bind.h" |
+#include "base/message_loop.h" |
+#include "base/message_loop_proxy.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/threading/thread.h" |
+#include "net/base/ip_endpoint.h" |
+#include "net/dns/dns_config_service_posix.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace net { |
+ |
+namespace { |
+ |
+void CompareConfig(const struct __res_state &res, const DnsConfig& config) { |
+ EXPECT_EQ(config.ndots, static_cast<int>(res.ndots)); |
+ EXPECT_EQ(config.edns0, (res.options & RES_USE_EDNS0) != 0); |
+ EXPECT_EQ(config.rotate, (res.options & RES_ROTATE) != 0); |
+ EXPECT_EQ(config.timeout.InSeconds(), res.retrans); |
+ EXPECT_EQ(config.attempts, res.retry); |
+ |
+ // Compare nameservers. IPv6 precede IPv4. |
+#if OS_LINUX |
+ size_t nscount6 = res._u._ext.nscount6; |
+#else |
+ size_t nscount6 = 0; |
+#endif |
+ size_t nscount4 = res.nscount; |
+ ASSERT_EQ(config.nameservers.size(), nscount6 + nscount4); |
+#if OS_LINUX |
+ for (size_t i = 0; i < nscount6; ++i) { |
+ IPEndPoint ipe; |
+ EXPECT_TRUE(ipe.FromSockAddr( |
+ reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]), |
+ sizeof *res._u._ext.nsaddrs[i])); |
+ EXPECT_EQ(config.nameservers[i], ipe); |
+ } |
+#endif |
+ for (size_t i = 0; i < nscount4; ++i) { |
+ IPEndPoint ipe; |
+ EXPECT_TRUE(ipe.FromSockAddr( |
+ reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]), |
+ sizeof res.nsaddr_list[i])); |
+ EXPECT_EQ(config.nameservers[nscount6 + i], ipe); |
+ } |
+ |
+ ASSERT_TRUE(config.search.size() <= MAXDNSRCH); |
+ EXPECT_TRUE(res.dnsrch[config.search.size()] == NULL); |
+ for (size_t i = 0; i < config.search.size(); ++i) { |
+ EXPECT_EQ(config.search[i], res.dnsrch[i]); |
+ } |
+} |
+ |
+// Fills in |res| with sane configuration. Change |generation| to add diversity. |
+void InitializeResState(res_state res, int generation) { |
+ memset(res, 0, sizeof(*res)); |
+ res->options = RES_INIT | RES_ROTATE; |
+ res->ndots = 2; |
+ res->retrans = 8; |
+ res->retry = 7; |
+ |
+ const char kDnsrch[] = "chromium.org" "\0" "example.com"; |
+ memcpy(res->defdname, kDnsrch, sizeof(kDnsrch)); |
+ res->dnsrch[0] = res->defdname; |
+ res->dnsrch[1] = res->defdname + sizeof("chromium.org"); |
+ |
+ const char* ip4addr[3] = { |
+ "8.8.8.8", |
+ "192.168.1.1", |
+ "63.1.2.4", |
+ }; |
+ |
+ for (int i = 0; i < 3; ++i) { |
+ struct sockaddr_in sa; |
+ sa.sin_family = AF_INET; |
+ sa.sin_port = htons(NAMESERVER_PORT + i - generation); |
+ inet_pton(AF_INET, ip4addr[i], &sa.sin_addr); |
+ res->nsaddr_list[i] = sa; |
+ } |
+ res->nscount = 3; |
+ |
+#ifdef OS_LINUX |
+ const char* ip6addr[2] = { |
+ "2001:db8:0::42", |
+ "::FFFF:129.144.52.38", |
+ }; |
+ |
+ for (int i = 0; i < 2; ++i) { |
+ // Must use malloc to mimick res_ninit. |
+ struct sockaddr_in6 *sa6; |
+ sa6 = (struct sockaddr_in6 *)malloc(sizeof(*sa6)); |
+ sa6->sin6_family = AF_INET6; |
+ sa6->sin6_port = htons(NAMESERVER_PORT - i); |
+ inet_pton(AF_INET6, ip6addr[i], &sa6->sin6_addr); |
+ res->_u._ext.nsaddrs[i] = sa6; |
+ } |
+ res->_u._ext.nscount6 = 2; |
+#endif |
+} |
+ |
+ |
+class DnsConfigServiceTest : public testing::Test, |
+ public DnsConfigService::Observer { |
+ public: |
+ // Mocks |
+ |
+ // DnsConfigService owns the instances of ResolverLib and |
+ // FilePathWatcherFactory that it gets, so use simple proxies to call |
+ // DnsConfigServiceTest. |
+ |
+ // ResolverLib is owned by WatcherDelegate which is posted to WorkerPool so |
+ // it must be canceled before the test is over. |
+ class MockResolverLib : public DnsConfigServicePosix::ResolverLib { |
+ public: |
+ explicit MockResolverLib(DnsConfigServiceTest *test) : test_(test) {} |
+ virtual ~MockResolverLib() { |
+ base::AutoLock lock(lock_); |
+ if (test_) { |
+ EXPECT_TRUE(test_->IsComplete()); |
+ } |
+ } |
+ virtual int ninit(res_state res) OVERRIDE { |
+ base::AutoLock lock(lock_); |
+ if (test_) |
+ return test_->OnNinit(res); |
+ else |
+ return -1; |
+ } |
+ void Cancel() { |
+ base::AutoLock lock(lock_); |
+ test_ = NULL; |
+ } |
+ private: |
+ base::Lock lock_; |
+ DnsConfigServiceTest *test_; |
+ }; |
+ |
+ class MockFilePathWatcherShim : |
+ public DnsConfigServicePosix::FilePathWatcherShim { |
+ public: |
+ typedef base::files::FilePathWatcher::Delegate Delegate; |
+ |
+ explicit MockFilePathWatcherShim(DnsConfigServiceTest* t) : test_(t) {} |
+ virtual ~MockFilePathWatcherShim() { |
+ test_->OnShimDestroyed(this); |
+ } |
+ |
+ // Enforce one-Watch-per-lifetime as the original FilePathWatcher |
+ virtual bool Watch(const FilePath& path, |
+ Delegate* delegate) OVERRIDE { |
+ EXPECT_TRUE(path_.empty()) << "Only one-Watch-per-lifetime allowed."; |
+ EXPECT_TRUE(!delegate_.get()); |
+ path_ = path; |
+ delegate_ = delegate; |
+ return test_->OnWatch(); |
+ } |
+ |
+ void PathChanged() { |
+ delegate_->OnFilePathChanged(path_); |
+ } |
+ |
+ void PathError() { |
+ delegate_->OnFilePathError(path_); |
+ } |
+ |
+ private: |
+ FilePath path_; |
+ scoped_refptr<Delegate> delegate_; |
+ DnsConfigServiceTest* test_; |
+ }; |
+ |
+ class MockFilePathWatcherFactory : |
+ public DnsConfigServicePosix::FilePathWatcherFactory { |
+ public: |
+ explicit MockFilePathWatcherFactory(DnsConfigServiceTest* t) : test(t) {} |
+ virtual ~MockFilePathWatcherFactory() { |
+ EXPECT_TRUE(test->IsComplete()); |
+ } |
+ virtual DnsConfigServicePosix::FilePathWatcherShim* |
+ CreateFilePathWatcher() OVERRIDE { |
+ return test->CreateFilePathWatcher(); |
+ } |
+ DnsConfigServiceTest* test; |
+ }; |
+ |
+ // Helpers for mocks. |
+ |
+ DnsConfigServicePosix::FilePathWatcherShim* CreateFilePathWatcher() { |
+ watcher_shim_ = new MockFilePathWatcherShim(this); |
+ return watcher_shim_; |
+ } |
+ |
+ void OnShimDestroyed(MockFilePathWatcherShim* destroyed_shim) { |
+ // Precaution to avoid segfault. |
+ if (watcher_shim_ == destroyed_shim) |
+ watcher_shim_ = NULL; |
+ } |
+ |
+ // On each event, post QuitTask to allow use of MessageLoop::Run() to |
+ // synchronize the threads. |
+ |
+ bool OnWatch() { |
+ EXPECT_TRUE(message_loop_ == MessageLoop::current()); |
+ watch_called_ = true; |
+ BreakNow("OnWatch"); |
+ return !fail_on_watch_; |
+ } |
+ |
+ int OnNinit(res_state res) { |
+ { // Check that res_ninit is executed serially. |
+ base::AutoLock lock(ninit_lock_); |
+ EXPECT_FALSE(ninit_running_) << "res_ninit is not called serially!"; |
+ ninit_running_ = true; |
+ } |
+ BreakNow("OnNinit"); |
+ ninit_allowed_.Wait(); |
+ // Calling from another thread is a bit dirty, but it's protected. |
+ int rv = OnNinitNonThreadSafe(res); |
+ // This lock might be destroyed after ninit_called_ is signalled. |
+ { |
+ base::AutoLock lock(ninit_lock_); |
+ ninit_running_ = false; |
+ } |
+ ninit_called_.Signal(); |
+ return rv; |
+ } |
+ |
+ virtual void OnConfigChanged(const DnsConfig& new_config) OVERRIDE { |
+ EXPECT_TRUE(message_loop_ == MessageLoop::current()); |
+ CompareConfig(res_, new_config); |
+ EXPECT_FALSE(new_config.Equals(last_config_)) << |
+ "Config must be different from last call."; |
+ last_config_ = new_config; |
+ got_config_ = true; |
+ BreakNow("OnConfigChanged"); |
+ } |
+ |
+ bool IsComplete() { |
+ return complete_; |
+ } |
+ |
+ protected: |
+ friend class BreakTask; |
+ class BreakTask : public Task { |
+ public: |
+ BreakTask(DnsConfigServiceTest* test, std::string breakpoint) |
+ : test_(test), breakpoint_(breakpoint) {} |
+ virtual ~BreakTask() {} |
+ virtual void Run() OVERRIDE { |
+ test_->breakpoint_ = breakpoint_; |
+ MessageLoop::current()->QuitNow(); |
+ } |
+ private: |
+ DnsConfigServiceTest* test_; |
+ std::string breakpoint_; |
+ }; |
+ |
+ void BreakNow(std::string b) { |
+ message_loop_->PostTask(FROM_HERE, new BreakTask(this, b)); |
+ } |
+ |
+ void RunUntilBreak(std::string b) { |
+ message_loop_->Run(); |
+ ASSERT_EQ(breakpoint_, b); |
+ } |
+ |
+ DnsConfigServiceTest() |
+ : res_lib_(new MockResolverLib(this)), |
+ watcher_shim_(NULL), |
+ res_generation_(1), |
+ watch_called_(false), |
+ got_config_(false), |
+ fail_on_watch_(false), |
+ fail_on_ninit_(false), |
+ complete_(false), |
+ ninit_allowed_(false, false), |
+ ninit_called_(false, false), |
+ ninit_running_(false) { |
+ } |
+ |
+ // This is on WorkerPool, but protected by ninit_allowed_. |
+ int OnNinitNonThreadSafe(res_state res) { |
+ if (fail_on_ninit_) |
+ return -1; |
+ InitializeResState(res, res_generation_); |
+ // Store a (deep) copy in the fixture to later verify correctness. |
+ res_nclose(&res_); |
+ InitializeResState(&res_, res_generation_); |
+ return 0; |
+ } |
+ |
+ // Helpers for tests. |
+ |
+ // Lets OnNinit run OnNinitNonThreadSafe and waits for it to complete. |
+ // Might get OnConfigChanged scheduled on the loop but we have no certainty. |
+ void WaitForNinit() { |
+ RunUntilBreak("OnNinit"); |
+ ninit_allowed_.Signal(); |
+ ninit_called_.Wait(); |
+ } |
+ |
+ // test::Test methods |
+ virtual void SetUp() OVERRIDE { |
+ message_loop_ = MessageLoop::current(); |
+ service_.reset(new DnsConfigServicePosix()); |
+ service_->set_resolver_lib(res_lib_); |
+ service_->set_watcher_factory(new MockFilePathWatcherFactory(this)); |
+ memset(&res_, 0, sizeof(res_)); |
+ } |
+ |
+ virtual void TearDown() OVERRIDE { |
+ // res_lib_ could outlive the test, so make sure it doesn't call it. |
+ res_lib_->Cancel(); |
+ res_nclose(&res_); |
+ // Allow service_ to clean up ResolverLib and FilePathWatcherFactory. |
+ complete_ = true; |
+ } |
+ |
+ MockResolverLib* res_lib_; |
+ MockFilePathWatcherShim* watcher_shim_; |
+ // Adds variety to the content of res_state. |
+ int res_generation_; |
+ struct __res_state res_; |
+ DnsConfig last_config_; |
+ |
+ bool watch_called_; |
+ bool got_config_; |
+ bool fail_on_watch_; |
+ bool fail_on_ninit_; |
+ bool complete_; |
+ |
+ // Ninit is called on WorkerPool so we need to synchronize with it. |
+ base::WaitableEvent ninit_allowed_; |
+ base::WaitableEvent ninit_called_; |
+ |
+ // Protected by ninit_lock_. Used to verify that Ninit calls are serialized. |
+ bool ninit_running_; |
+ base::Lock ninit_lock_; |
+ |
+ // Loop for this thread. |
+ MessageLoop* message_loop_; |
+ |
+ // Service under test. |
+ scoped_ptr<DnsConfigServicePosix> service_; |
+ |
+ std::string breakpoint_; |
+}; |
+ |
+TEST(DnsConfigTest, ResolverConfigConvertAndEquals) { |
+ struct __res_state res[2]; |
+ DnsConfig config[2]; |
+ for (int i = 0; i < 2; ++i) { |
+ InitializeResState(&res[i], i); |
+ ASSERT_TRUE(ConvertResToConfig(res[i], &config[i])); |
+ } |
+ for (int i = 0; i < 2; ++i) { |
+ CompareConfig(res[i], config[i]); |
+ res_nclose(&res[i]); |
+ } |
+ EXPECT_TRUE(config[0].Equals(config[0])); |
+ EXPECT_FALSE(config[0].Equals(config[1])); |
+ EXPECT_FALSE(config[1].Equals(config[0])); |
+} |
+ |
+TEST_F(DnsConfigServiceTest, FilePathWatcherFailures) { |
+ // For these tests, disable ninit. |
+ res_lib_->Cancel(); |
+ |
+ fail_on_watch_ = true; |
+ service_->Watch(); |
+ RunUntilBreak("OnWatch"); |
+ EXPECT_TRUE(watch_called_) << "Must call FilePathWatcher::Watch()."; |
+ |
+ fail_on_watch_ = false; |
+ watch_called_ = false; |
+ RunUntilBreak("OnWatch"); // Due to backoff this will take 100ms. |
+ EXPECT_TRUE(watch_called_) << |
+ "Must restart on FilePathWatcher::Watch() failure."; |
+ |
+ watch_called_ = false; |
+ ASSERT_TRUE(watcher_shim_); |
+ watcher_shim_->PathError(); |
+ RunUntilBreak("OnWatch"); |
+ EXPECT_TRUE(watch_called_) << |
+ "Must restart on FilePathWatcher::Delegate::OnFilePathError()."; |
+ |
+ // Worker thread could still be posting OnResultAvailable to the message loop |
+} |
+ |
+TEST_F(DnsConfigServiceTest, NotifyOnValidAndDistinctConfig) { |
+ service_->AddObserver(this); |
+ service_->Watch(); |
+ RunUntilBreak("OnWatch"); |
+ fail_on_ninit_ = true; |
+ WaitForNinit(); |
+ |
+ // If OnNinit posts OnResultAvailable before the next call, then this test |
+ // verifies that failure on ninit should not cause OnConfigChanged. |
+ // Otherwise, this only verifies that ninit calls are serialized. |
+ |
+ fail_on_ninit_ = false; |
+ ASSERT_TRUE(watcher_shim_); |
+ watcher_shim_->PathChanged(); |
+ WaitForNinit(); |
+ |
+ RunUntilBreak("OnConfigChanged"); |
+ EXPECT_TRUE(got_config_); |
+ |
+ message_loop_->AssertIdle(); |
+ |
+ got_config_ = false; |
+ // Forget about the config to test if we get it again on AddObserver. |
+ last_config_ = DnsConfig(); |
+ service_->RemoveObserver(this); |
+ service_->AddObserver(this); |
+ RunUntilBreak("OnConfigChanged"); |
+ EXPECT_TRUE(got_config_) << "Did not get config after AddObserver."; |
+ |
+ // Simulate spurious FilePathChanged. |
+ ASSERT_TRUE(watcher_shim_); |
+ watcher_shim_->PathChanged(); |
+ WaitForNinit(); |
+ |
+ // OnConfigChanged will catch that the config did not actually change. |
+ |
+ got_config_ = false; |
+ ++res_generation_; |
+ ASSERT_TRUE(watcher_shim_); |
+ watcher_shim_->PathChanged(); |
+ WaitForNinit(); |
+ RunUntilBreak("OnConfigChanged"); |
+ EXPECT_TRUE(got_config_) << "Did not get config after change"; |
+ |
+ message_loop_->AssertIdle(); |
+ |
+ // Schedule two calls. OnNinit checks if it is called serially. |
+ ++res_generation_; |
+ ASSERT_TRUE(watcher_shim_); |
+ watcher_shim_->PathChanged(); |
+ // ninit is blocked, so this will have to induce read_pending |
+ watcher_shim_->PathChanged(); |
+ WaitForNinit(); |
+ WaitForNinit(); |
+ RunUntilBreak("OnConfigChanged"); |
+ EXPECT_TRUE(got_config_) << "Did not get config after change"; |
+ |
+ // We should be done with all tasks. |
+ message_loop_->AssertIdle(); |
+} |
+ |
+} // namespace |
+ |
+} // namespace net |
+ |