summaryrefslogtreecommitdiffstats
path: root/net/dns/dns_config_service_posix_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'net/dns/dns_config_service_posix_unittest.cc')
-rw-r--r--net/dns/dns_config_service_posix_unittest.cc472
1 files changed, 472 insertions, 0 deletions
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 0000000..9c81e14
--- /dev/null
+++ b/net/dns/dns_config_service_posix_unittest.cc
@@ -0,0 +1,472 @@
+// 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/compiler_specific.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(NS_DEFAULTPORT + i - generation);
+ inet_pton(AF_INET, ip4addr[i], &sa.sin_addr);
+ res->nsaddr_list[i] = sa;
+ }
+ res->nscount = 3;
+
+#if 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(NS_DEFAULTPORT - i);
+ inet_pton(AF_INET6, ip6addr[i], &sa6->sin6_addr);
+ res->_u._ext.nsaddrs[i] = sa6;
+ }
+ res->_u._ext.nscount6 = 2;
+#endif
+}
+
+void CloseResState(res_state res) {
+#if OS_LINUX
+ for (int i = 0; i < res->_u._ext.nscount6; ++i) {
+ ASSERT_TRUE(res->_u._ext.nsaddrs[i] != NULL);
+ free(res->_u._ext.nsaddrs[i]);
+ }
+#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;
+ }
+ virtual void nclose(res_state res) OVERRIDE {
+ CloseResState(res);
+ }
+ 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.
+ CloseResState(&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();
+ CloseResState(&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]);
+ CloseResState(&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
+