test: Run mock tests over both TCP and UDP

With the exception of a few tests that make use of the timed
retry aspect of UDP.
pull/34/head
David Drysdale 9 years ago
parent 0c423efd78
commit 83839fde7a
  1. 2
      test/ares-test-live.cc
  2. 15
      test/ares-test-mock.cc
  3. 177
      test/ares-test.cc
  4. 32
      test/ares-test.h

@ -533,7 +533,7 @@ TEST_F(LibraryTest, GetTCPSock) {
bitmask = ares_getsock(channel, nullptr, 0); bitmask = ares_getsock(channel, nullptr, 0);
EXPECT_EQ(0, bitmask); EXPECT_EQ(0, bitmask);
ProcessWork(channel, -1, nullptr); ProcessWork(channel, NoExtraFDs, nullptr);
ares_destroy(channel); ares_destroy(channel);
} }

@ -359,7 +359,8 @@ TEST_P(MockChannelTest, SortListV6) {
} }
} }
TEST_P(MockChannelTest, SearchDomainsAllocFail) { // Relies on retries so is UDP-only
TEST_P(MockUDPChannelTest, SearchDomainsAllocFail) {
DNSPacket nofirst; DNSPacket nofirst;
nofirst.set_response().set_aa().set_rcode(ns_r_nxdomain) nofirst.set_response().set_aa().set_rcode(ns_r_nxdomain)
.add_question(new DNSQuestion("www.first.com", ns_t_a)); .add_question(new DNSQuestion("www.first.com", ns_t_a));
@ -403,7 +404,8 @@ TEST_P(MockChannelTest, SearchDomainsAllocFail) {
channel_ = nullptr; channel_ = nullptr;
} }
TEST_P(MockChannelTest, Resend) { // Relies on retries so is UDP-only
TEST_P(MockUDPChannelTest, Resend) {
std::vector<byte> nothing; std::vector<byte> nothing;
DNSPacket reply; DNSPacket reply;
reply.set_response().set_aa() reply.set_response().set_aa()
@ -434,7 +436,8 @@ TEST_P(MockChannelTest, CancelImmediate) {
EXPECT_EQ(0, result.timeouts_); EXPECT_EQ(0, result.timeouts_);
} }
TEST_P(MockChannelTest, CancelLater) { // Relies on retries so is UDP-only
TEST_P(MockUDPChannelTest, CancelLater) {
std::vector<byte> nothing; std::vector<byte> nothing;
// On second request, cancel the channel. // On second request, cancel the channel.
@ -557,6 +560,12 @@ TEST_P(MockChannelTest, HostAliasUnreadable) {
} }
INSTANTIATE_TEST_CASE_P(AddressFamilies, MockChannelTest, INSTANTIATE_TEST_CASE_P(AddressFamilies, MockChannelTest,
::testing::Values(std::make_pair<int, bool>(AF_INET, false),
std::make_pair<int, bool>(AF_INET, true),
std::make_pair<int, bool>(AF_INET6, false),
std::make_pair<int, bool>(AF_INET6, true)));
INSTANTIATE_TEST_CASE_P(AddressFamilies, MockUDPChannelTest,
::testing::Values(AF_INET, AF_INET6)); ::testing::Values(AF_INET, AF_INET6));
} // namespace test } // namespace test

@ -15,31 +15,39 @@
namespace ares { namespace ares {
namespace test { namespace test {
bool verbose = false;
int mock_port = 5300;
unsigned long LibraryTest::fails_ = 0;
std::map<size_t, int> LibraryTest::size_fails_;
void ProcessWork(ares_channel channel, void ProcessWork(ares_channel channel,
int extrafd, std::function<void(int)> process_extra) { std::function<std::set<int>()> get_extrafds,
std::function<void(int)> process_extra) {
int nfds, count; int nfds, count;
fd_set readers, writers; fd_set readers, writers;
struct timeval tv; struct timeval tv;
struct timeval* tvp;
while (true) { while (true) {
// Retrieve the set of file descriptors that the library wants us to monitor. // Retrieve the set of file descriptors that the library wants us to monitor.
FD_ZERO(&readers); FD_ZERO(&readers);
FD_ZERO(&writers); FD_ZERO(&writers);
nfds = ares_fds(channel, &readers, &writers); nfds = ares_fds(channel, &readers, &writers);
if (nfds == 0) // no work left to do in the library
// Add in the extra FD if present
if (extrafd >= 0)
FD_SET(extrafd, &readers);
if (nfds == 0) // no work left to do
return; return;
// Also retrieve the timeout value that the library wants us to use. // Add in the extra FDs if present.
tvp = ares_timeout(channel, nullptr, &tv); std::set<int> extrafds = get_extrafds();
EXPECT_EQ(tvp, &tv); for (int extrafd : extrafds) {
FD_SET(extrafd, &readers);
if (extrafd >= nfds) {
nfds = extrafd + 1;
}
}
// Wait for activity or timeout. // Wait for activity or timeout.
count = select(nfds, &readers, &writers, nullptr, tvp); tv.tv_sec = 0;
tv.tv_usec = 100000; // 100ms
count = select(nfds, &readers, &writers, nullptr, &tv);
if (count < 0) { if (count < 0) {
fprintf(stderr, "select() failed, errno %d\n", errno); fprintf(stderr, "select() failed, errno %d\n", errno);
return; return;
@ -47,18 +55,16 @@ void ProcessWork(ares_channel channel,
// Let the library process any activity. // Let the library process any activity.
ares_process(channel, &readers, &writers); ares_process(channel, &readers, &writers);
// Let the provided callback process any activity on the extra FD. // Let the provided callback process any activity on the extra FD.
if (extrafd > 0 && FD_ISSET(extrafd, &readers)) for (int extrafd : extrafds) {
process_extra(extrafd); if (FD_ISSET(extrafd, &readers)) {
process_extra(extrafd);
}
}
} }
} }
bool verbose = false;
int mock_port = 5300;
unsigned long LibraryTest::fails_ = 0;
std::map<size_t, int> LibraryTest::size_fails_;
// static // static
void LibraryTest::SetAllocFail(int nth) { void LibraryTest::SetAllocFail(int nth) {
assert(nth > 0); assert(nth > 0);
@ -113,18 +119,31 @@ void LibraryTest::afree(void *ptr) {
free(ptr); free(ptr);
} }
std::set<int> NoExtraFDs() {
return std::set<int>();
}
void DefaultChannelTest::Process() { void DefaultChannelTest::Process() {
ProcessWork(channel_, -1, nullptr); ProcessWork(channel_, NoExtraFDs, nullptr);
} }
void DefaultChannelModeTest::Process() { void DefaultChannelModeTest::Process() {
ProcessWork(channel_, -1, nullptr); ProcessWork(channel_, NoExtraFDs, nullptr);
} }
MockServer::MockServer(int family, int port) : port_(port) { MockServer::MockServer(int family, bool tcp, int port) : tcp_(tcp), port_(port) {
// Create a UDP socket to receive data on. if (tcp_) {
sockfd_ = socket(family, SOCK_DGRAM, 0); // Create a TCP socket to receive data on.
EXPECT_NE(-1, sockfd_); sockfd_ = socket(family, SOCK_STREAM, 0);
EXPECT_NE(-1, sockfd_);
int optval = 1;
setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int));
} else {
// Create a UDP socket to receive data on.
sockfd_ = socket(family, SOCK_DGRAM, 0);
EXPECT_NE(-1, sockfd_);
}
// Bind it to the given port. // Bind it to the given port.
if (family == AF_INET) { if (family == AF_INET) {
@ -145,20 +164,61 @@ MockServer::MockServer(int family, int port) : port_(port) {
int rc = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr)); int rc = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));
EXPECT_EQ(0, rc) << "Failed to bind AF_INET6 to port " << port_; EXPECT_EQ(0, rc) << "Failed to bind AF_INET6 to port " << port_;
} }
// For TCP, also need to listen for connections.
if (tcp_) {
EXPECT_EQ(0, listen(sockfd_, 5)) << "Failed to listen for TCP connections";
}
} }
MockServer::~MockServer() { MockServer::~MockServer() {
for (int fd : connfds_) {
close(fd);
}
close(sockfd_); close(sockfd_);
sockfd_ = -1; sockfd_ = -1;
} }
void MockServer::Process(int fd) { void MockServer::Process(int fd) {
if (fd != sockfd_) return; if (fd != sockfd_ && connfds_.find(fd) == connfds_.end()) {
std::cerr << "Asked to process unknown fd " << fd << std::endl;
return;
}
if (tcp_ && fd == sockfd_) {
int connfd = accept(sockfd_, NULL, NULL);
if (connfd < 0) {
std::cerr << "Error accepting connection on fd " << fd << std::endl;
} else {
connfds_.insert(connfd);
}
return;
}
// Activity on a data-bearing file descriptor.
struct sockaddr_storage addr; struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr); socklen_t addrlen = sizeof(addr);
byte buffer[2048]; byte buffer[2048];
int len = recvfrom(fd, buffer, sizeof(buffer), 0, int len = recvfrom(fd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&addr, &addrlen); (struct sockaddr *)&addr, &addrlen);
byte* data = buffer;
if (tcp_) {
if (len == 0) {
connfds_.erase(std::find(connfds_.begin(), connfds_.end(), fd));
close(fd);
return;
}
if (len < 2) {
std::cerr << "Packet too short (" << len << ")" << std::endl;
return;
}
int tcplen = (data[0] << 8) + data[1];
data += 2;
if (tcplen + 2 != len) {
std::cerr << "Warning: TCP length " << tcplen
<< " doesn't match data length (" << len
<< " - 2)" << std::endl;
}
}
// Assume the packet is a well-formed DNS request and extract the request // Assume the packet is a well-formed DNS request and extract the request
// details. // details.
@ -166,27 +226,27 @@ void MockServer::Process(int fd) {
std::cerr << "Packet too short (" << len << ")" << std::endl; std::cerr << "Packet too short (" << len << ")" << std::endl;
return; return;
} }
int qid = DNS_HEADER_QID(buffer); int qid = DNS_HEADER_QID(data);
if (DNS_HEADER_QR(buffer) != 0) { if (DNS_HEADER_QR(data) != 0) {
std::cerr << "Not a request" << std::endl; std::cerr << "Not a request" << std::endl;
return; return;
} }
if (DNS_HEADER_OPCODE(buffer) != ns_o_query) { if (DNS_HEADER_OPCODE(data) != ns_o_query) {
std::cerr << "Not a query (opcode " << DNS_HEADER_OPCODE(buffer) std::cerr << "Not a query (opcode " << DNS_HEADER_OPCODE(data)
<< ")" << std::endl; << ")" << std::endl;
return; return;
} }
if (DNS_HEADER_QDCOUNT(buffer) != 1) { if (DNS_HEADER_QDCOUNT(data) != 1) {
std::cerr << "Unexpected question count (" << DNS_HEADER_QDCOUNT(buffer) std::cerr << "Unexpected question count (" << DNS_HEADER_QDCOUNT(data)
<< ")" << std::endl; << ")" << std::endl;
return; return;
} }
byte* question = buffer + 12; byte* question = data + 12;
int qlen = len - 12; int qlen = len - 12;
char *name = nullptr; char *name = nullptr;
long enclen; long enclen;
ares_expand_name(question, buffer, len, &name, &enclen); ares_expand_name(question, data, len, &name, &enclen);
if (!name) { if (!name) {
std::cerr << "Failed to retrieve name" << std::endl; std::cerr << "Failed to retrieve name" << std::endl;
return; return;
@ -210,34 +270,54 @@ void MockServer::Process(int fd) {
if (verbose) std::cerr << "ProcessRequest(" << qid << ", '" << namestr if (verbose) std::cerr << "ProcessRequest(" << qid << ", '" << namestr
<< "', " << RRTypeToString(rrtype) << ")" << std::endl; << "', " << RRTypeToString(rrtype) << ")" << std::endl;
ProcessRequest(&addr, addrlen, qid, namestr, rrtype); ProcessRequest(fd, &addr, addrlen, qid, namestr, rrtype);
}
std::set<int> MockServer::fds() const {
std::set<int> result = connfds_;
result.insert(sockfd_);
return result;
} }
void MockServer::ProcessRequest(struct sockaddr_storage* addr, int addrlen, void MockServer::ProcessRequest(int fd, struct sockaddr_storage* addr, int addrlen,
int qid, const std::string& name, int rrtype) { int qid, const std::string& name, int rrtype) {
// Before processing, let gMock know the request is happening. // Before processing, let gMock know the request is happening.
OnRequest(name, rrtype); OnRequest(name, rrtype);
// Send the current pending reply. First overwrite the qid with the value // Make a local copy of the current pending reply.
// from the argument.
if (reply_.size() < 2) { if (reply_.size() < 2) {
if (verbose) std::cerr << "Skipping reply as not-present/too-short" << std::endl; if (verbose) std::cerr << "Skipping reply as not-present/too-short" << std::endl;
return; return;
} }
reply_[0] = (byte)((qid >> 8) & 0xff); std::vector<byte> reply = reply_;
reply_[1] = (byte)(qid & 0xff);
if (verbose) std::cerr << "sending reply " << PacketToString(reply_) << std::endl; // Overwrite the query ID with the value from the argument.
int rc = sendto(sockfd_, reply_.data(), reply_.size(), 0, reply[0] = (byte)((qid >> 8) & 0xff);
reply[1] = (byte)(qid & 0xff);
if (verbose) std::cerr << "sending reply " << PacketToString(reply) << std::endl;
// Prefix with 2-byte length if TCP.
if (tcp_) {
int len = reply.size();
std::vector<byte> vlen = {(byte)((len & 0xFF00) >> 8), (byte)(len & 0xFF)};
reply.insert(reply.begin(), vlen.begin(), vlen.end());
// Also, don't bother with the destination address.
addr = nullptr;
addrlen = 0;
}
int rc = sendto(fd, reply.data(), reply.size(), 0,
(struct sockaddr *)addr, addrlen); (struct sockaddr *)addr, addrlen);
if (rc < static_cast<int>(reply_.size())) { if (rc < static_cast<int>(reply.size())) {
std::cerr << "Failed to send full reply, rc=" << rc << std::endl; std::cerr << "Failed to send full reply, rc=" << rc << std::endl;
} }
} }
MockChannelOptsTest::MockChannelOptsTest(int family, MockChannelOptsTest::MockChannelOptsTest(int family,
bool force_tcp,
struct ares_options* givenopts, struct ares_options* givenopts,
int optmask) int optmask)
: server_(family, mock_port), channel_(nullptr) { : server_(family, force_tcp, mock_port), channel_(nullptr) {
// Set up channel options. // Set up channel options.
struct ares_options opts; struct ares_options opts;
if (givenopts) { if (givenopts) {
@ -278,6 +358,10 @@ MockChannelOptsTest::MockChannelOptsTest(int family,
opts.domains = (char**)domains; opts.domains = (char**)domains;
optmask |= ARES_OPT_DOMAINS; optmask |= ARES_OPT_DOMAINS;
} }
if (force_tcp) {
opts.flags = ARES_FLAG_USEVC;
optmask |= ARES_OPT_FLAGS;
}
EXPECT_EQ(ARES_SUCCESS, ares_init_options(&channel_, &opts, optmask)); EXPECT_EQ(ARES_SUCCESS, ares_init_options(&channel_, &opts, optmask));
EXPECT_NE(nullptr, channel_); EXPECT_NE(nullptr, channel_);
@ -301,7 +385,8 @@ MockChannelOptsTest::~MockChannelOptsTest() {
void MockChannelOptsTest::Process() { void MockChannelOptsTest::Process() {
using namespace std::placeholders; using namespace std::placeholders;
ProcessWork(channel_, server_.sockfd(), ProcessWork(channel_,
std::bind(&MockServer::fds, &server_),
std::bind(&MockServer::Process, &server_, _1)); std::bind(&MockServer::Process, &server_, _1));
} }

@ -14,6 +14,8 @@
#include <functional> #include <functional>
#include <map> #include <map>
#include <set>
#include <utility>
namespace ares { namespace ares {
@ -25,9 +27,11 @@ extern bool verbose;
extern int mock_port; extern int mock_port;
// Process all pending work on ares-owned file descriptors, plus // Process all pending work on ares-owned file descriptors, plus
// optionally the given FD + work function. // optionally the given set-of-FDs + work function.
void ProcessWork(ares_channel channel, void ProcessWork(ares_channel channel,
int extrafd, std::function<void(int)> process_extra); std::function<std::set<int>()> get_extrafds,
std::function<void(int)> process_extra);
std::set<int> NoExtraFDs();
// Test fixture that ensures library initialization, and allows // Test fixture that ensures library initialization, and allows
// memory allocations to be failed. // memory allocations to be failed.
@ -110,7 +114,7 @@ class DefaultChannelModeTest
// Mock DNS server to allow responses to be scripted by tests. // Mock DNS server to allow responses to be scripted by tests.
class MockServer { class MockServer {
public: public:
MockServer(int family, int port); MockServer(int family, bool tcp, int port);
~MockServer(); ~MockServer();
// Mock method indicating the processing of a particular <name, RRtype> // Mock method indicating the processing of a particular <name, RRtype>
@ -122,25 +126,30 @@ class MockServer {
void SetReplyData(const std::vector<byte>& reply) { reply_ = reply; } void SetReplyData(const std::vector<byte>& reply) { reply_ = reply; }
void SetReply(const DNSPacket* reply) { SetReplyData(reply->data()); } void SetReply(const DNSPacket* reply) { SetReplyData(reply->data()); }
// Process activity on the mock server's socket FD. // The set of file descriptors that the server handles.
std::set<int> fds() const;
// Process activity on the mock server's socket FDs.
void Process(int fd); void Process(int fd);
// Port the server is responding to
int port() const { return port_; } int port() const { return port_; }
int sockfd() const { return sockfd_; }
private: private:
void ProcessRequest(struct sockaddr_storage* addr, int addrlen, void ProcessRequest(int fd, struct sockaddr_storage* addr, int addrlen,
int qid, const std::string& name, int rrtype); int qid, const std::string& name, int rrtype);
bool tcp_;
int port_; int port_;
int sockfd_; int sockfd_;
std::set<int> connfds_;
std::vector<byte> reply_; std::vector<byte> reply_;
}; };
// Test fixture that uses a mock DNS server. // Test fixture that uses a mock DNS server.
class MockChannelOptsTest : public LibraryTest { class MockChannelOptsTest : public LibraryTest {
public: public:
MockChannelOptsTest(int family, struct ares_options* givenopts, int optmask); MockChannelOptsTest(int family, bool force_tcp, struct ares_options* givenopts, int optmask);
~MockChannelOptsTest(); ~MockChannelOptsTest();
// Process all pending work on ares-owned and mock-server-owned file descriptors. // Process all pending work on ares-owned and mock-server-owned file descriptors.
@ -152,10 +161,17 @@ class MockChannelOptsTest : public LibraryTest {
}; };
class MockChannelTest class MockChannelTest
: public MockChannelOptsTest,
public ::testing::WithParamInterface< std::pair<int, bool> > {
public:
MockChannelTest() : MockChannelOptsTest(GetParam().first, GetParam().second, nullptr, 0) {}
};
class MockUDPChannelTest
: public MockChannelOptsTest, : public MockChannelOptsTest,
public ::testing::WithParamInterface<int> { public ::testing::WithParamInterface<int> {
public: public:
MockChannelTest() : MockChannelOptsTest(GetParam(), nullptr, 0) {} MockUDPChannelTest() : MockChannelOptsTest(GetParam(), false, nullptr, 0) {}
}; };
// gMock action to set the reply for a mock server. // gMock action to set the reply for a mock server.

Loading…
Cancel
Save