diff options
Diffstat (limited to 'net/dns/dns_config_service_posix_unittest.cc')
-rw-r--r-- | net/dns/dns_config_service_posix_unittest.cc | 472 |
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 + |