Browse Source

Merge branch 'trac527'

JINMEI Tatuya 14 years ago
parent
commit
bc2a7f57b5

+ 1 - 1
doc/Doxyfile

@@ -568,7 +568,7 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas
+INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

+ 1 - 0
src/bin/auth/tests/auth_srv_unittest.cc

@@ -32,6 +32,7 @@
 #include <auth/statistics.h>
 
 #include <dns/tests/unittest_util.h>
+#include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 
 using namespace std;

+ 243 - 343
src/bin/auth/tests/query_unittest.cc

@@ -12,8 +12,16 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <sstream>
+#include <vector>
+#include <map>
+
+#include <boost/bind.hpp>
+
+#include <dns/masterload.h>
 #include <dns/message.h>
 #include <dns/name.h>
+#include <dns/opcode.h>
 #include <dns/rcode.h>
 #include <dns/rrttl.h>
 #include <dns/rrtype.h>
@@ -23,162 +31,157 @@
 
 #include <auth/query.h>
 
+#include <testutils/dnsmessage_test.h>
+
 #include <gtest/gtest.h>
 
+using namespace std;
 using namespace isc::dns;
+using namespace isc::dns::rdata;
 using namespace isc::datasrc;
 using namespace isc::auth;
+using namespace isc::testutils;
 
 namespace {
 
-RRsetPtr a_rrset = RRsetPtr(new RRset(Name("www.example.com"),
-                                      RRClass::IN(), RRType::A(),
-                                      RRTTL(3600)));
-RRsetPtr soa_rrset = RRsetPtr(new RRset(Name("example.com"),
-                                        RRClass::IN(), RRType::SOA(),
-                                        RRTTL(3600)));
-RRsetPtr ns_rrset(RRsetPtr(new RRset(Name("ns.example.com"),
-                                     RRClass::IN(), RRType::NS(),
-                                     RRTTL(3600))));
-RRsetPtr glue_a_rrset(RRsetPtr(new RRset(Name("glue.ns.example.com"),
-                                         RRClass::IN(), RRType::A(),
-                                         RRTTL(3600))));
-RRsetPtr glue_aaaa_rrset(RRsetPtr(new RRset(Name("glue.ns.example.com"),
-                                            RRClass::IN(), RRType::AAAA(),
-                                            RRTTL(3600))));
-RRsetPtr noglue_a_rrset(RRsetPtr(new RRset(Name("noglue.example.com"),
-                                           RRClass::IN(), RRType::A(),
-                                           RRTTL(3600))));
-RRsetPtr delegated_mx_a_rrset(RRsetPtr(new RRset(
-    Name("mx.delegation.example.com"), RRClass::IN(), RRType::A(),
-    RRTTL(3600))));
+// This is the content of the mock zone (see below).
+// It's a sequence of textual RRs that is supposed to be parsed by
+// dns::masterLoad().  Some of the RRs are also used as the expected
+// data in specific tests, in which case they are referenced via specific
+// local variables (such as soa_txt).
+const char* const soa_txt = "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
+const char* const zone_ns_txt =
+    "example.com. 3600 IN NS glue.delegation.example.com.\n"
+    "example.com. 3600 IN NS noglue.example.com.\n"
+    "example.com. 3600 IN NS example.net.\n";
+const char* const ns_addrs_txt =
+    "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
+    "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
+    "noglue.example.com. 3600 IN A 192.0.2.53\n";
+const char* const delegation_txt =
+        "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
+        "delegation.example.com. 3600 IN NS noglue.example.com.\n"
+        "delegation.example.com. 3600 IN NS cname.example.com.\n"
+        "delegation.example.com. 3600 IN NS example.org.\n";
+const char* const mx_txt =
+    "mx.example.com. 3600 IN MX 10 www.example.com.\n"
+    "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
+    "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
+const char* const www_a_txt = "www.example.com. 3600 IN A 192.0.2.80\n";
+// The rest of data won't be referenced from the test cases.
+const char* const other_zone_rrs =
+    "cname.example.com. 3600 IN CNAME www.example.com.\n"
+    "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
+    "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
+    "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
 
 // This is a mock Zone class for testing.
-// It is a derived class of Zone, and simply hardcodes the results of find()
-// See the find() implementation if you want to know its content.
+// It is a derived class of Zone for the convenient of tests.
+// Its find() method emulates the common behavior of protocol compliant
+// zone classes, but simplifies some minor cases and also supports broken
+// behavior.
+// For simplicity, most names are assumed to be "in zone"; there's only
+// one zone cut at the point of name "delegation.example.com".
+// It doesn't handle empty non terminal nodes (if we need to test such cases
+// find() should have specialized code for it).
 class MockZone : public Zone {
 public:
-    MockZone(bool has_SOA = true, bool has_apex_NS = true) :
+    MockZone() :
         origin_(Name("example.com")),
-        has_SOA_(has_SOA),
-        has_apex_NS_(has_apex_NS),
-        delegation_rrset(RRsetPtr(new RRset(Name("delegation.example.com"),
-                                            RRClass::IN(), RRType::NS(),
-                                            RRTTL(3600)))),
-        cname_rrset(RRsetPtr(new RRset(Name("cname.example.com"),
-                                       RRClass::IN(), RRType::CNAME(),
-                                       RRTTL(3600)))),
-        auth_ns_rrset(RRsetPtr(new RRset(Name("example.com"),
-                                         RRClass::IN(), RRType::NS(),
-                                         RRTTL(3600)))),
-        mx_cname_rrset_(new RRset(Name("cnamemailer.example.com"),
-            RRClass::IN(), RRType::CNAME(), RRTTL(3600))),
-        mx_rrset_(new RRset(Name("mx.example.com"), RRClass::IN(),
-            RRType::MX(), RRTTL(3600)))
+        delegation_name_("delegation.example.com"),
+        has_SOA_(true),
+        has_apex_NS_(true),
+        rrclass_(RRClass::IN())
     {
-        delegation_rrset->addRdata(rdata::generic::NS(
-                          Name("glue.ns.example.com")));
-        delegation_rrset->addRdata(rdata::generic::NS(
-                          Name("noglue.example.com")));
-        delegation_rrset->addRdata(rdata::generic::NS(
-                          Name("cname.example.com")));
-        delegation_rrset->addRdata(rdata::generic::NS(
-                          Name("example.org")));
-        cname_rrset->addRdata(rdata::generic::CNAME(
-                          Name("www.example.com")));
-        auth_ns_rrset->addRdata(rdata::generic::NS(
-                          Name("glue.ns.example.com")));
-        auth_ns_rrset->addRdata(rdata::generic::NS(
-                          Name("noglue.example.com")));
-        auth_ns_rrset->addRdata(rdata::generic::NS(
-                          Name("example.net")));
-        mx_rrset_->addRdata(isc::dns::rdata::generic::MX(10,
-            Name("www.example.com")));
-        mx_rrset_->addRdata(isc::dns::rdata::generic::MX(20,
-            Name("mailer.example.org")));
-        mx_rrset_->addRdata(isc::dns::rdata::generic::MX(30,
-            Name("mx.delegation.example.com")));
-        mx_cname_rrset_->addRdata(rdata::generic::CNAME(
-            Name("mx.example.com")));
+        stringstream zone_stream;
+        zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
+            delegation_txt << mx_txt << www_a_txt << other_zone_rrs;
+
+        masterLoad(zone_stream, origin_, rrclass_,
+                   boost::bind(&MockZone::loadRRset, this, _1));
     }
-    virtual const isc::dns::Name& getOrigin() const;
-    virtual const isc::dns::RRClass& getClass() const;
+    virtual const isc::dns::Name& getOrigin() const { return (origin_); }
+    virtual const isc::dns::RRClass& getClass() const { return (rrclass_); }
+    virtual FindResult find(const isc::dns::Name& name,
+                            const isc::dns::RRType& type,
+                            const FindOptions options = FIND_DEFAULT) const;
 
-    FindResult find(const isc::dns::Name& name,
-                    const isc::dns::RRType& type,
-                    const FindOptions options = FIND_DEFAULT) const;
+    // If false is passed, it makes the zone broken as if it didn't have the
+    // SOA.
+    void setSOAFlag(bool on) { has_SOA_ = on; }
+
+    // If false is passed, it makes the zone broken as if it didn't have
+    // the apex NS.
+    void setApexNSFlag(bool on) { has_apex_NS_ = on; }
 
 private:
-    Name origin_;
+    typedef map<RRType, ConstRRsetPtr> RRsetStore;
+    typedef map<Name, RRsetStore> Domains;
+    Domains domains_;
+    void loadRRset(ConstRRsetPtr rrset) {
+        domains_[rrset->getName()][rrset->getType()] = rrset;
+        if (rrset->getName() == delegation_name_ &&
+            rrset->getType() == RRType::NS()) {
+            delegation_rrset = rrset;
+        }
+    }
+
+    const Name origin_;
+    const Name delegation_name_;
     bool has_SOA_;
     bool has_apex_NS_;
-    RRsetPtr delegation_rrset;
-    RRsetPtr cname_rrset;
-    RRsetPtr auth_ns_rrset;
-    RRsetPtr mx_cname_rrset_;
-    RRsetPtr mx_rrset_;
+    ConstRRsetPtr delegation_rrset;
+    const RRClass rrclass_;
 };
 
-const Name&
-MockZone::getOrigin() const {
-    return (origin_);
-}
-
-const RRClass&
-MockZone::getClass() const {
-    return (RRClass::IN());
-}
-
 Zone::FindResult
 MockZone::find(const Name& name, const RRType& type,
                const FindOptions options) const
 {
-    // hardcode the find results
-    if (name == Name("www.example.com") && type == RRType::A()) {
-        return (FindResult(SUCCESS, a_rrset));
-    } else if (name == Name("www.example.com")) {
-        return (FindResult(NXRRSET, RRsetPtr()));
-    } else if (name == Name("glue.ns.example.com") && type == RRType::A() &&
-        (options & FIND_GLUE_OK) != 0) {
-        return (FindResult(SUCCESS, glue_a_rrset));
-    } else if (name == Name("noglue.example.com") && (type == RRType::A() ||
-        type == RRType::ANY())) {
-        return (FindResult(SUCCESS, noglue_a_rrset));
-    } else if (name == Name("glue.ns.example.com") && type == RRType::AAAA() &&
-        (options & FIND_GLUE_OK) != 0) {
-        return (FindResult(SUCCESS, glue_aaaa_rrset));
-    } else if (name == Name("example.com") && type == RRType::SOA() &&
-        has_SOA_)
-    {
-        return (FindResult(SUCCESS, soa_rrset));
-    } else if (name == Name("example.com") && type == RRType::NS() &&
-        has_apex_NS_)
-    {
-        return (FindResult(SUCCESS, auth_ns_rrset));
-    } else if (name == Name("mx.delegation.example.com") &&
-        type == RRType::A() && (options & FIND_GLUE_OK) != 0)
-    {
-        return (FindResult(SUCCESS, delegated_mx_a_rrset));
-    } else if (name == Name("delegation.example.com") ||
-        name.compare(Name("delegation.example.com")).getRelation() ==
-        NameComparisonResult::SUBDOMAIN)
-    {
-        return (FindResult(DELEGATION, delegation_rrset));
-    } else if (name == Name("ns.example.com")) {
-        return (FindResult(DELEGATION, ns_rrset));
-    } else if (name == Name("nxdomain.example.com")) {
+    // Emulating a broken zone: mandatory apex RRs are missing if specifically
+    // configured so (which are rare cases).
+    if (name == origin_ && type == RRType::SOA() && !has_SOA_) {
         return (FindResult(NXDOMAIN, RRsetPtr()));
-    } else if (name == Name("nxrrset.example.com")) {
+    } else if (name == origin_ && type == RRType::NS() && !has_apex_NS_) {
+        return (FindResult(NXDOMAIN, RRsetPtr()));
+    }
+
+    // Special case for names on or under a zone cut
+    if ((options & FIND_GLUE_OK) == 0 &&
+        (name == delegation_name_ ||
+         name.compare(delegation_name_).getRelation() ==
+         NameComparisonResult::SUBDOMAIN)) {
+        return (FindResult(DELEGATION, delegation_rrset));
+    }
+
+    // normal cases.  names are searched for only per exact-match basis
+    // for simplicity.
+    const Domains::const_iterator found_domain = domains_.find(name);
+    if (found_domain != domains_.end()) {
+        // First, try exact match.
+        RRsetStore::const_iterator found_rrset =
+            found_domain->second.find(type);
+        if (found_rrset != found_domain->second.end()) {
+            return (FindResult(SUCCESS, found_rrset->second));
+        }
+
+        // If not found but the qtype is ANY, return the first RRset
+        if (type == RRType::ANY()) {
+            return (FindResult(SUCCESS, found_domain->second.begin()->second));
+        }
+
+        // Otherwise, if this domain name has CNAME, return it.
+        found_rrset = found_domain->second.find(RRType::CNAME());
+        if (found_rrset != found_domain->second.end()) {
+            return (FindResult(CNAME, found_rrset->second));
+        }
+
+        // Otherwise it's NXRRSET case.
         return (FindResult(NXRRSET, RRsetPtr()));
-    } else if ((name == Name("cname.example.com"))) {
-        return (FindResult(CNAME, cname_rrset));
-    } else if (name == Name("cnamemailer.example.com")) {
-        return (FindResult(CNAME, mx_cname_rrset_));
-    } else if (name == Name("mx.example.com")) {
-        return (FindResult(SUCCESS, mx_rrset_));
-    } else {
-        return (FindResult(DNAME, RRsetPtr()));
     }
+
+    // query name isn't found in our domains.  returns NXDOMAIN.
+    return (FindResult(NXDOMAIN, RRsetPtr()));
 }
 
 class QueryTest : public ::testing::Test {
@@ -186,195 +189,145 @@ protected:
     QueryTest() :
         qname(Name("www.example.com")), qclass(RRClass::IN()),
         qtype(RRType::A()), response(Message::RENDER),
-        query(memory_datasrc, qname, qtype, response)
+        qid(response.getQid()), query_code(Opcode::QUERY().getCode())
     {
         response.setRcode(Rcode::NOERROR());
+        response.setOpcode(Opcode::QUERY());
+        // create and add a matching zone.
+        mock_zone = new MockZone();
+        memory_datasrc.addZone(ZonePtr(mock_zone));
     }
+    MockZone* mock_zone;
     MemoryDataSrc memory_datasrc;
     const Name qname;
     const RRClass qclass;
     const RRType qtype;
     Message response;
-    Query query;
+    const qid_t qid;
+    const uint16_t query_code;
 };
 
+// A wrapper to check resulting response message commonly used in
+// tests below.
+// check_origin needs to be specified only when the authority section has
+// an SOA RR.  The interface is not generic enough but should be okay
+// for our test cases in practice.
+void
+responseCheck(Message& response, const isc::dns::Rcode& rcode,
+              unsigned int flags, const unsigned int ancount,
+              const unsigned int nscount, const unsigned int arcount,
+              const char* const expected_answer,
+              const char* const expected_authority,
+              const char* const expected_additional,
+              const Name& check_origin = Name::ROOT_NAME())
+{
+    // In our test cases QID, Opcode, and QDCOUNT should be constant, so
+    // we don't bother the test cases specifying these values.
+    headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
+                flags, 0, ancount, nscount, arcount);
+    if (expected_answer != NULL) {
+        rrsetsCheck(expected_answer,
+                    response.beginSection(Message::SECTION_ANSWER),
+                    response.endSection(Message::SECTION_ANSWER));
+    }
+    if (expected_authority != NULL) {
+        rrsetsCheck(expected_authority,
+                    response.beginSection(Message::SECTION_AUTHORITY),
+                    response.endSection(Message::SECTION_AUTHORITY),
+                    check_origin);
+    }
+    if (expected_additional != NULL) {
+        rrsetsCheck(expected_additional,
+                    response.beginSection(Message::SECTION_ADDITIONAL),
+                    response.endSection(Message::SECTION_ADDITIONAL));
+    }
+}
+
 TEST_F(QueryTest, noZone) {
     // There's no zone in the memory datasource.  So the response should have
     // REFUSED.
-    EXPECT_NO_THROW(query.process());
+    MemoryDataSrc empty_memory_datasrc;
+    Query nozone_query(empty_memory_datasrc, qname, qtype, response);
+    EXPECT_NO_THROW(nozone_query.process());
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 
 TEST_F(QueryTest, exactMatch) {
-    // add a matching zone.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
+    Query query(memory_datasrc, qname, qtype, response);
     EXPECT_NO_THROW(query.process());
     // find match rrset
-    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-                                  Name("www.example.com"), RRClass::IN(),
-                                  RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+                  www_a_txt, zone_ns_txt, ns_addrs_txt);
 }
 
 TEST_F(QueryTest, exactAddrMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name noglue_name(Name("noglue.example.com"));
-    Query noglue_query(memory_datasrc, noglue_name, qtype, response);
-    EXPECT_NO_THROW(noglue_query.process());
-    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-                                  Name("noglue.example.com"), RRClass::IN(),
-                                  RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
+                          response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
+                  "noglue.example.com. 3600 IN A 192.0.2.53\n", zone_ns_txt,
+                  "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
+                  "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
 }
 
 TEST_F(QueryTest, apexNSMatch) {
     // find match rrset, omit authority data which has already been provided
     // in the answer section from the authority section.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name apex_name(Name("example.com"));
-    Query apex_ns_query(memory_datasrc, apex_name, RRType::NS(), response);
-    EXPECT_NO_THROW(apex_ns_query.process());
-    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"), RRType::NS(),
+                          response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
+                  zone_ns_txt, NULL, ns_addrs_txt);
 }
 
 TEST_F(QueryTest, exactAnyMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name noglue_name(Name("noglue.example.com"));
-    Query noglue_query(memory_datasrc, noglue_name, RRType::ANY(), response);
-    EXPECT_NO_THROW(noglue_query.process());
-    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-                                  Name("noglue.example.com"), RRClass::IN(),
-                                  RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"),
+                          RRType::ANY(), response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
+                  "noglue.example.com. 3600 IN A 192.0.2.53\n",
+                  zone_ns_txt,
+                  "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
+                  "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
 }
 
 // This tests that when we need to look up Zone's apex NS records for
 // authoritative answer, and there is no apex NS records. It should
 // throw in that case.
 TEST_F(QueryTest, noApexNS) {
-    // Add a zone without apex NS records
-    memory_datasrc.addZone(ZonePtr(new MockZone(true, false)));
-    const Name noglue_name(Name("noglue.example.com"));
-    Query noglue_query(memory_datasrc, noglue_name, qtype, response);
-    EXPECT_THROW(noglue_query.process(), Query::NoApexNS);
-    // We don't look into the response, as it throwed
+    // Disable apex NS record
+    mock_zone->setApexNSFlag(false);
+
+    EXPECT_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
+                       response).process(), Query::NoApexNS);
+    // We don't look into the response, as it threw
 }
 
 TEST_F(QueryTest, delegation) {
-    // add a matching zone.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name delegation_name(Name("delegation.example.com"));
-    Query delegation_query(memory_datasrc, delegation_name, qtype, response);
-    EXPECT_NO_THROW(delegation_query.process());
-    EXPECT_FALSE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("delegation.example.com"),
-                                  RRClass::IN(), RRType::NS()));
-    // glue address records
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    // noglue address records
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    // NS name has a CNAME
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("www.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    // NS name is out of zone
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("example.org"),
-                                  RRClass::IN(), RRType::A()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
+                          qtype, response).process());
+
+    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
+                  NULL, delegation_txt, ns_addrs_txt);
 }
 
 TEST_F(QueryTest, nxdomain) {
-    // add a matching zone.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name nxdomain_name(Name("nxdomain.example.com"));
-    Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
-    EXPECT_NO_THROW(nxdomain_query.process());
-    EXPECT_EQ(Rcode::NXDOMAIN(), response.getRcode());
-    EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
-    EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-        Name("example.com"), RRClass::IN(), RRType::SOA()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"), qtype,
+                          response).process());
+    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
+                  NULL, soa_txt, NULL, mock_zone->getOrigin());
 }
 
 TEST_F(QueryTest, nxrrset) {
-    // add a matching zone.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name nxrrset_name(Name("nxrrset.example.com"));
-    Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
-    EXPECT_NO_THROW(nxrrset_query.process());
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
-    EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-        Name("example.com"), RRClass::IN(), RRType::SOA()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("www.example.com"),
+                          RRType::TXT(), response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
+                  NULL, soa_txt, NULL, mock_zone->getOrigin());
 }
 
 /*
@@ -382,27 +335,23 @@ TEST_F(QueryTest, nxrrset) {
  * throw in that case.
  */
 TEST_F(QueryTest, noSOA) {
-    memory_datasrc.addZone(ZonePtr(new MockZone(false)));
+    // disable zone's SOA RR.
+    mock_zone->setSOAFlag(false);
 
     // The NX Domain
-    const Name nxdomain_name(Name("nxdomain.example.com"));
-    Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
-    EXPECT_THROW(nxdomain_query.process(), Query::NoSOA);
+    EXPECT_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
+                       qtype, response).process(), Query::NoSOA);
     // Of course, we don't look into the response, as it throwed
 
     // NXRRSET
-    const Name nxrrset_name(Name("nxrrset.example.com"));
-    Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
-    EXPECT_THROW(nxrrset_query.process(), Query::NoSOA);
+    EXPECT_THROW(Query(memory_datasrc, Name("nxrrset.example.com"),
+                       qtype, response).process(), Query::NoSOA);
 }
 
 TEST_F(QueryTest, noMatchZone) {
     // there's a zone in the memory datasource but it doesn't match the qname.
     // should result in REFUSED.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name nomatch_name(Name("example.org"));
-    Query nomatch_query(memory_datasrc, nomatch_name, qtype, response);
-    nomatch_query.process();
+    Query(memory_datasrc, Name("example.org"), qtype, response).process();;
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 
@@ -413,76 +362,27 @@ TEST_F(QueryTest, noMatchZone) {
  * A record, other to unknown out of zone one.
  */
 TEST_F(QueryTest, MX) {
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    Name qname("mx.example.com");
-    Query mx_query(memory_datasrc, qname, RRType::MX(), response);
-    EXPECT_NO_THROW(mx_query.process());
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-        Name("mx.example.com"), RRClass::IN(), RRType::MX()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-        Name("www.example.com"), RRClass::IN(), RRType::A()));
-    // We want to skip the additional ones related to authoritative
-    RRsetPtr ns;
-    for (SectionIterator<RRsetPtr> ai(response.beginSection(
-        Message::SECTION_AUTHORITY)); ai != response.endSection(
-        Message::SECTION_AUTHORITY); ++ai)
-    {
-        if ((*ai)->getName() == Name("example.com") && (*ai)->getType() ==
-            RRType::NS())
-        {
-            ns = *ai;
-            break;
-        }
-    }
-    /*
-     * In fact, the MX RRset mentions three names, but we don't know anything
-     * about one of them and one is under a zone cut, so we should have just
-     * one RRset (A for www.example.com)
-     */
-    // We can't use getRRCount, as it counts RRs, not RRsets
-    unsigned additional_count(0);
-    for (SectionIterator<RRsetPtr> ai(response.beginSection(
-        Message::SECTION_ADDITIONAL)); ai != response.endSection(
-        Message::SECTION_ADDITIONAL); ++ai)
-    {
-        // Skip the ones for the NS record
-        if (ns) {
-            for (RdataIteratorPtr nsi(ns->getRdataIterator()); !nsi->isLast();
-                nsi->next())
-            {
-                if ((*ai)->getName() ==
-                    dynamic_cast<const isc::dns::rdata::generic::NS&>(
-                    nsi->getCurrent()).getNSName())
-                {
-                    goto NS_ADDITIONAL_DATA;
-                }
-            }
-        }
-        // It is not related to the NS, then it must be related to the MX
-        ++additional_count;
-        EXPECT_EQ(Name("www.example.com"), (*ai)->getName());
-        EXPECT_EQ(RRType::A(), (*ai)->getType());
-        NS_ADDITIONAL_DATA:;
-    }
-    EXPECT_EQ(1, additional_count);
+    Query(memory_datasrc, Name("mx.example.com"), RRType::MX(),
+          response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
+                  mx_txt, NULL,
+                  (string(ns_addrs_txt) + string(www_a_txt)).c_str());
 }
 
 /*
- * Test when we ask for MX and encounter an alias (CNAME in this case).
+ * Test when we ask for MX whose exchange is an alias (CNAME in this case).
  *
- * This should not trigger the additional processing.
+ * This should not trigger the additional processing for the exchange.
  */
 TEST_F(QueryTest, MXAlias) {
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    Name qname("cnamemailer.example.com");
-    Query mx_query(memory_datasrc, qname, RRType::MX(), response);
-    EXPECT_NO_THROW(mx_query.process());
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    // We should not have the IP address in additional section
-    // Currently, the section should be completely empty
-    EXPECT_TRUE(response.beginSection(Message::SECTION_ADDITIONAL) ==
-        response.endSection(Message::SECTION_ADDITIONAL));
-}
+    Query(memory_datasrc, Name("cnamemx.example.com"), RRType::MX(),
+          response).process();
 
+    // there shouldn't be no additional RRs for the exchanges (we have 3
+    // RRs for the NS).  The normal MX case is tested separately so we don't
+    // bother to examine the answer (and authority) sections.
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+                  NULL, NULL, ns_addrs_txt);
+}
 }

+ 1 - 0
src/bin/resolver/tests/resolver_unittest.cc

@@ -16,6 +16,7 @@
 
 #include <resolver/resolver.h>
 #include <dns/tests/unittest_util.h>
+#include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 
 using namespace isc::dns;

+ 1 - 0
src/lib/testutils/Makefile.am

@@ -8,6 +8,7 @@ if HAVE_GTEST
 lib_LTLIBRARIES = libtestutils.la
 
 libtestutils_la_SOURCES = srv_test.h srv_test.cc
+libtestutils_la_SOURCES += dnsmessage_test.h dnsmessage_test.cc
 libtestutils_la_SOURCES += mockups.h
 libtestutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 endif

+ 115 - 0
src/lib/testutils/dnsmessage_test.cc

@@ -0,0 +1,115 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/message.h>
+#include <dns/opcode.h>
+#include <dns/rdata.h>
+#include <dns/rcode.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+
+#include <gtest/gtest.h>
+
+#include <testutils/dnsmessage_test.h>
+
+using namespace isc::dns;
+
+namespace isc {
+namespace testutils {
+const unsigned int QR_FLAG = 0x1;
+const unsigned int AA_FLAG = 0x2;
+const unsigned int TC_FLAG = 0x4;
+const unsigned int RD_FLAG = 0x8;
+const unsigned int RA_FLAG = 0x10;
+const unsigned int AD_FLAG = 0x20;
+const unsigned int CD_FLAG = 0x40;
+
+void
+headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
+            const uint16_t opcodeval, const unsigned int flags,
+            const unsigned int qdcount,
+            const unsigned int ancount, const unsigned int nscount,
+            const unsigned int arcount)
+{
+    EXPECT_EQ(qid, message.getQid());
+    EXPECT_EQ(rcode, message.getRcode());
+    EXPECT_EQ(opcodeval, message.getOpcode().getCode());
+    EXPECT_EQ((flags & QR_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_QR));
+    EXPECT_EQ((flags & AA_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_AA));
+    EXPECT_EQ((flags & TC_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_TC));
+    EXPECT_EQ((flags & RA_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_RA));
+    EXPECT_EQ((flags & RD_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_RD));
+    EXPECT_EQ((flags & AD_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_AD));
+    EXPECT_EQ((flags & CD_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_CD));
+
+    EXPECT_EQ(qdcount, message.getRRCount(Message::SECTION_QUESTION));
+    EXPECT_EQ(ancount, message.getRRCount(Message::SECTION_ANSWER));
+    EXPECT_EQ(nscount, message.getRRCount(Message::SECTION_AUTHORITY));
+    EXPECT_EQ(arcount, message.getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+namespace {
+::testing::AssertionResult
+matchRdata(const char*, const char*,
+           const rdata::Rdata& expected, const rdata::Rdata& actual)
+{
+    if (expected.compare(actual) != 0) {
+        ::testing::Message msg;
+        msg << "Two RDATAs are expected to be equal but not:\n"
+            << "  Actual: " << actual.toText() << "\n"
+            << "Expected: " << expected.toText();
+        return (::testing::AssertionFailure(msg));
+    }
+    return (::testing::AssertionSuccess());
+}
+}
+
+void
+rrsetCheck(isc::dns::ConstRRsetPtr expected_rrset,
+           isc::dns::ConstRRsetPtr actual_rrset)
+{
+    EXPECT_EQ(expected_rrset->getName(), actual_rrset->getName());
+    EXPECT_EQ(expected_rrset->getClass(), actual_rrset->getClass());
+    EXPECT_EQ(expected_rrset->getType(), actual_rrset->getType());
+    EXPECT_EQ(expected_rrset->getTTL(), actual_rrset->getTTL());
+
+    isc::dns::RdataIteratorPtr rdata_it = actual_rrset->getRdataIterator();
+    isc::dns::RdataIteratorPtr expected_rdata_it =
+        expected_rrset->getRdataIterator();
+    while (!expected_rdata_it->isLast()) {
+        EXPECT_FALSE(rdata_it->isLast());
+        if (rdata_it->isLast()) {
+            // buggy case, should stop here
+            break;
+        }
+
+        EXPECT_PRED_FORMAT2(matchRdata, expected_rdata_it->getCurrent(),
+                            rdata_it->getCurrent());
+
+        expected_rdata_it->next();
+        rdata_it->next();
+    }
+
+    // Make sure we have examined all sets of rrset RDATA
+    EXPECT_TRUE(rdata_it->isLast());
+}
+} // end of namespace testutils
+} // end of namespace isc

+ 304 - 0
src/lib/testutils/dnsmessage_test.h

@@ -0,0 +1,304 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <algorithm>
+#include <functional>
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/masterload.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace testutils {
+///
+/// \name Header flags
+///
+/// These are flags to indicate whether the corresponding flag bit of the
+/// DNS header is to be set in the test cases using \c headerCheck().
+/// (The flag values is irrelevant to their wire-format values).
+/// The meaning of the flags should be obvious from the variable names.
+//@{
+extern const unsigned int QR_FLAG;
+extern const unsigned int AA_FLAG;
+extern const unsigned int TC_FLAG;
+extern const unsigned int RD_FLAG;
+extern const unsigned int RA_FLAG;
+extern const unsigned int AD_FLAG;
+extern const unsigned int CD_FLAG;
+//@}
+
+/// Set of unit tests to examine a DNS message header.
+///
+/// This function takes a dns::Message object and performs various tests
+/// to confirm if the header fields of the message have the given specified
+/// value.  The \c message parameter is the Message object to be tested,
+/// and the remaining parameters specify the expected values of the fields.
+///
+/// If all fields have the expected values the test will be considered
+/// successful.  Otherwise, some of the tests will indicate a failure, which
+/// will make the test case that calls this function fail.
+///
+/// The meaning of the parameters should be obvious, but here are some notes
+/// that may not be so trivial:
+/// - \c opcode is an integer, not an \c dns::Opcode object.  This is because
+///   we can easily iterate over all possible OPCODEs in a test.
+/// - \c flags is a bitmask so that we can specify a set of header flags
+///   via a single parameter.  For example, when we expect the message has
+///   QR and AA flags are on and others are off, we'd set this parameter to
+///   <code>(QR_FLAG | AA_FLAG)</code>.
+///
+/// \param message The DNS message to be tested.
+/// \param qid The expected QID
+/// \param rcode The expected RCODE
+/// \param opcodeval The code value of the expected OPCODE
+/// \param flags Bit flags specifying header flags that are expected to be set
+/// \param qdcount The expected value of QDCOUNT
+/// \param ancount The expected value of ANCOUNT
+/// \param nscount The expected value of NSCOUNT
+/// \param arcount The expected value of ARCOUNT
+void
+headerCheck(const isc::dns::Message& message, const isc::dns::qid_t qid,
+            const isc::dns::Rcode& rcode,
+            const uint16_t opcodeval, const unsigned int flags,
+            const unsigned int qdcount,
+            const unsigned int ancount, const unsigned int nscount,
+            const unsigned int arcount);
+
+/// Set of unit tests to check equality of two RRsets
+///
+/// This function takes two RRset objects and performs detailed tests to
+/// check if these two are "equal", where equal means:
+/// - The owner name, RR class, RR type and TTL are all equal.  Names are
+///   compared in case-insensitive manner.
+/// - The number of RRs (more accurately RDATAs) is the same.
+/// - RDATAs are equal as a sequence.  That is, the first RDATA of
+///   \c expected_rrset is equal to the first RDATA of \c actual_rrset,
+///   the second RDATA of \c expected_rrset is equal to the second RDATA
+///   of \c actual_rrset, and so on.  Two RDATAs are equal iff they have
+///   the same DNSSEC sorting order as defined in RFC4034.
+///
+/// Some of the tests will fail if any of the above isn't met.
+///
+/// \note In future we may want to allow more flexible matching for RDATAs.
+/// For example, we may want to allow comparison as "sets", i.e., comparing
+/// RDATAs regardless of the ordering; we may also want to support suppressing
+/// duplicate RDATA.  For now, it's caller's responsibility to match the
+/// ordering (and any duplicates) between the expected and actual sets.
+/// Even if and when we support the flexible behavior, this "strict mode"
+/// will still be useful.
+///
+/// \param expected_rrset The expected RRset
+/// \param actual_rrset The RRset to be tested
+void rrsetCheck(isc::dns::ConstRRsetPtr expected_rrset,
+                isc::dns::ConstRRsetPtr actual_rrset);
+
+/// The definitions in this name space are not supposed to be used publicly,
+/// but are given here because they are used in templated functions.
+namespace detail {
+// Helper matching class used in rrsetsCheck()
+struct RRsetMatch : public std::unary_function<isc::dns::ConstRRsetPtr, bool> {
+    RRsetMatch(isc::dns::ConstRRsetPtr target) : target_(target) {}
+    bool operator()(isc::dns::ConstRRsetPtr rrset) const {
+        return (rrset->getType() == target_->getType() &&
+                rrset->getClass() == target_->getClass() &&
+                rrset->getName() == target_->getName());
+    }
+    const isc::dns::ConstRRsetPtr target_;
+};
+
+// Helper callback functor for masterLoad() used in rrsetsCheck (stream
+// version)
+class RRsetInserter {
+public:
+    RRsetInserter(std::vector<isc::dns::ConstRRsetPtr>& rrsets) :
+        rrsets_(rrsets)
+    {}
+    void operator()(isc::dns::ConstRRsetPtr rrset) const {
+        rrsets_.push_back(rrset);
+    }
+private:
+    std::vector<isc::dns::ConstRRsetPtr>& rrsets_;
+};
+}
+
+/// Set of unit tests to check if two sets of RRsets are identical.
+///
+/// This templated function takes two sets of sequences, each defined by
+/// two input iterators pointing to \c ConstRRsetPtr (begin and end).
+/// This function compares these two sets of RRsets as "sets", and considers
+/// they are equal when:
+/// - The number of RRsets are the same.
+/// - For any RRset in one set, there is an equivalent RRset in the other set,
+///   and vice versa, where the equivalence of two RRsets is tested using
+///   \c rrsetCheck().
+///
+/// Note that the sets of RRsets are compared as "sets", i.e, they don't have
+/// to be listed in the same order.
+///
+/// The entire tests will pass if the two sets are identical.  Otherwise
+/// some of the tests will indicate a failure.
+///
+/// \note
+/// - There is one known restriction: each set of RRsets must not have more
+///   than one RRsets for the same name, RR type and RR class.  If this
+///   condition isn't met, some of the tests will fail either against an
+///   explicit duplication check or as a result of counter mismatch.
+/// - This function uses linear searches on the expected and actual sequences,
+///   and won't be scalable for large input.  For the purpose of testing it
+///   should be acceptable, but be aware of the size of test data.
+///
+/// \param expected_begin The beginning of the expected set of RRsets
+/// \param expected_end The end of the expected set of RRsets
+/// \param actual_begin The beginning of the set of RRsets to be tested
+/// \param actual_end The end of the set of RRsets to be tested
+template<typename EXPECTED_ITERATOR, typename ACTUAL_ITERATOR>
+void
+rrsetsCheck(EXPECTED_ITERATOR expected_begin, EXPECTED_ITERATOR expected_end,
+            ACTUAL_ITERATOR actual_begin, ACTUAL_ITERATOR actual_end)
+{
+    std::vector<isc::dns::ConstRRsetPtr> checked_rrsets; // for duplicate check
+    unsigned int rrset_matched = 0;
+    ACTUAL_ITERATOR it;
+    for (it = actual_begin; it != actual_end; ++it) {
+        // Make sure there's no duplicate RRset in actual (using a naive
+        // search).  Since the actual set is guaranteed to be unique, we can
+        // detect it if the expected data has a duplicate by the match/size
+        // checks at the end of the function.
+        // Note: we cannot use EXPECT_EQ for iterators
+        EXPECT_TRUE(checked_rrsets.end() ==
+                    std::find_if(checked_rrsets.begin(), checked_rrsets.end(),
+                                 detail::RRsetMatch(*it)));
+        checked_rrsets.push_back(*it);
+
+        EXPECTED_ITERATOR found_rrset_it =
+            std::find_if(expected_begin, expected_end,
+                         detail::RRsetMatch(*it));
+        if (found_rrset_it != expected_end) {
+            rrsetCheck(*found_rrset_it, *it);
+            ++rrset_matched;
+        }
+    }
+
+    // make sure all expected RRsets are in actual sets
+    EXPECT_EQ(std::distance(expected_begin, expected_end), rrset_matched);
+    // make sure rrsets only contains expected RRsets
+    EXPECT_EQ(std::distance(expected_begin, expected_end),
+              std::distance(actual_begin, actual_end));
+}
+
+/// Set of unit tests to check if two sets of RRsets are identical using
+/// streamed expected data.
+///
+/// This templated function takes a standard input stream that produces
+/// a sequence of textural RRs and compares the entire set of RRsets
+/// with the range of RRsets specified by two input iterators.
+///
+/// This function is actually a convenient wrapper for the other version
+/// of function; it internally builds a standard vector of RRsets
+/// from the input stream and uses iterators of the vector as the expected
+/// input iterators for the backend function.
+/// Expected data in the form of input stream would be useful for testing
+/// as it can be easily hardcoded in test cases using string streams or
+/// given from a data source file.
+///
+/// One common use case of this function is to test whether a particular
+/// section of a DNS message contains an expected set of RRsets.
+/// For example, when \c message is an \c dns::Message object, the following
+/// test code will check if the additional section of \c message contains
+/// the hardcoded two RRsets (2 A RRs and 1 AAAA RR) and only contains these
+/// RRsets:
+/// \code std::stringstream expected;
+/// expected << "foo.example.com. 3600 IN A 192.0.2.1\n"
+///          << "foo.example.com. 3600 IN A 192.0.2.2\n"
+///          << "foo.example.com. 7200 IN AAAA 2001:db8::1\n"
+/// rrsetsCheck(expected, message.beginSection(Message::SECTION_ADDITIONAL),
+///                       message.endSection(Message::SECTION_ADDITIONAL));
+/// \endcode
+///
+/// The input stream is parsed using the \c dns::masterLoad() function,
+/// and notes and restrictions of that function apply.
+/// This is also the reason why this function takes \c origin and \c rrclass
+/// parameters.  The default values of these parameters should just work
+/// in many cases for usual tests, but due to a validity check on the SOA RR
+/// in \c dns::masterLoad(), if the input stream contains an SOA RR, the
+/// \c origin parameter will have to be set to the owner name of the SOA
+/// explicitly.  Likewise, all RRsets must have the same RR class.
+/// (We may have to modify \c dns::masterLoad() so that it can
+/// have an option to be more generous about these points if it turns out
+/// to be too restrictive).
+///
+/// \param expected_stream An input stream object that is to emit expected set
+/// of RRsets
+/// \param actual_begin The beginning of the set of RRsets to be tested
+/// \param actual_end The end of the set of RRsets to be tested
+/// \param origin A domain name that is a super domain of the owner name
+/// of all RRsets contained in the stream.
+/// \param rrclass The RR class of the RRsets contained in the stream.
+template<typename ACTUAL_ITERATOR>
+void
+rrsetsCheck(std::istream& expected_stream,
+            ACTUAL_ITERATOR actual_begin, ACTUAL_ITERATOR actual_end,
+            const isc::dns::Name& origin = isc::dns::Name::ROOT_NAME(),
+            const isc::dns::RRClass& rrclass = isc::dns::RRClass::IN())
+{
+    std::vector<isc::dns::ConstRRsetPtr> expected;
+    isc::dns::masterLoad(expected_stream, origin, rrclass,
+                         detail::RRsetInserter(expected));
+    rrsetsCheck(expected.begin(), expected.end(), actual_begin, actual_end);
+}
+
+/// Set of unit tests to check if two sets of RRsets are identical using
+/// expected data as string.
+///
+/// This function is a wrapper for the input stream version:
+/// \c rrsetsCheck(std::istream&, ACTUAL_ITERATOR, ACTUAL_ITERATOR, const isc::dns::Name&, const isc::dns::RRClass&)(),
+/// and takes a string object instead of a stream.
+/// While the stream version is more generic, this version would be more
+/// convenient for tests using hardcoded expected data.  Using this version,
+/// the example test case shown for the stream version would look as follows:
+/// \code
+/// rrsetsCheck("foo.example.com. 3600 IN A 192.0.2.1\n"
+///             "foo.example.com. 3600 IN A 192.0.2.2\n"
+///             "foo.example.com. 7200 IN AAAA 2001:db8::1\n",
+///             message.beginSection(Message::SECTION_ADDITIONAL),
+///             message.endSection(Message::SECTION_ADDITIONAL));
+/// \endcode
+///
+/// The semantics of parameters is the same as that of the stream version
+/// except that \c expected is a string of expected sets of RRsets.
+template<typename ACTUAL_ITERATOR>
+void
+rrsetsCheck(const std::string& expected,
+            ACTUAL_ITERATOR actual_begin, ACTUAL_ITERATOR actual_end,
+            const isc::dns::Name& origin = isc::dns::Name::ROOT_NAME(),
+            const isc::dns::RRClass& rrclass = isc::dns::RRClass::IN())
+{
+    std::stringstream expected_stream(expected);
+    rrsetsCheck(expected_stream, actual_begin, actual_end, origin,
+                rrclass);
+}
+
+} // end of namespace testutils
+} // end of namespace isc
+
+// Local Variables:
+// mode: c++
+// End:

+ 2 - 0
src/lib/testutils/mockups.h

@@ -19,6 +19,8 @@
 
 #include <xfr/xfrout_client.h>
 
+#include <asiolink/asiolink.h>
+
 // A minimal mock configuration session.  Most the methods are
 // stubbed out, except for a very basic group_sendmsg() and
 // group_recvmsg().  hasQueuedMessages() always returns false.

+ 1 - 39
src/lib/testutils/srv_test.cc

@@ -21,6 +21,7 @@
 
 #include <dns/tests/unittest_util.h>
 
+#include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 
 using namespace isc::dns;
@@ -30,14 +31,6 @@ namespace isc {
 namespace testutils {
 const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
 
-const unsigned int QR_FLAG = 0x1;
-const unsigned int AA_FLAG = 0x2;
-const unsigned int TC_FLAG = 0x4;
-const unsigned int RD_FLAG = 0x8;
-const unsigned int RA_FLAG = 0x10;
-const unsigned int AD_FLAG = 0x20;
-const unsigned int CD_FLAG = 0x40;
-
 SrvTestBase::SrvTestBase() : request_message(Message::RENDER),
                              parse_message(new Message(Message::PARSE)),
                              default_qid(0x1035),
@@ -232,37 +225,6 @@ SrvTestBase::axfrOverUDP() {
     headerCheck(*parse_message, default_qid, isc::dns::Rcode::FORMERR(),
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
-
-void
-headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
-            const uint16_t opcodeval, const unsigned int flags,
-            const unsigned int qdcount,
-            const unsigned int ancount, const unsigned int nscount,
-            const unsigned int arcount)
-{
-    EXPECT_EQ(qid, message.getQid());
-    EXPECT_EQ(rcode, message.getRcode());
-    EXPECT_EQ(opcodeval, message.getOpcode().getCode());
-    EXPECT_EQ((flags & QR_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_QR));
-    EXPECT_EQ((flags & AA_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ((flags & TC_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_TC));
-    EXPECT_EQ((flags & RA_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_RA));
-    EXPECT_EQ((flags & RD_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_RD));
-    EXPECT_EQ((flags & AD_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_AD));
-    EXPECT_EQ((flags & CD_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_CD));
-
-    EXPECT_EQ(qdcount, message.getRRCount(Message::SECTION_QUESTION));
-    EXPECT_EQ(ancount, message.getRRCount(Message::SECTION_ANSWER));
-    EXPECT_EQ(nscount, message.getRRCount(Message::SECTION_AUTHORITY));
-    EXPECT_EQ(arcount, message.getRRCount(Message::SECTION_ADDITIONAL));
-}
 } // end of namespace testutils
 } // end of namespace isc
 

+ 0 - 10
src/lib/testutils/srv_test.h

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <gtest/gtest.h>
-
 #include <dns/buffer.h>
 #include <dns/name.h>
 #include <dns/message.h>
@@ -46,14 +44,6 @@ extern const unsigned int RA_FLAG;
 extern const unsigned int AD_FLAG;
 extern const unsigned int CD_FLAG;
 
-void
-headerCheck(const isc::dns::Message& message, const isc::dns::qid_t qid,
-            const isc::dns::Rcode& rcode,
-            const uint16_t opcodeval, const unsigned int flags,
-            const unsigned int qdcount,
-            const unsigned int ancount, const unsigned int nscount,
-            const unsigned int arcount);
-
 // The base class for Auth and Recurse test case
 class SrvTestBase : public ::testing::Test {
 protected: