master_loader_unittest.cc 9.96 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Copyright (C) 2012  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/master_loader_callbacks.h>
#include <dns/master_loader.h>
17
18
#include <dns/rrtype.h>
#include <dns/rrset.h>
19
20
#include <dns/rrclass.h>
#include <dns/name.h>
21
#include <dns/rdata.h>
22
23
24
25
26

#include <gtest/gtest.h>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>

27
28
29
#include <string>
#include <vector>
#include <list>
30
#include <sstream>
31

32
using namespace isc::dns;
33
34
35
using std::vector;
using std::string;
using std::list;
36
using std::stringstream;
37
using std::endl;
38

39
namespace {
40
41
42
43
class MasterLoaderTest : public ::testing::Test {
public:
    MasterLoaderTest() :
        callbacks_(boost::bind(&MasterLoaderTest::callback, this,
44
                               &errors_, _1, _2, _3),
45
                   boost::bind(&MasterLoaderTest::callback, this,
46
                               &warnings_, _1, _2, _3))
47
48
49
50
    {}

    /// Concatenate file, line, and reason, and add it to either errors
    /// or warnings
51
    void callback(vector<string>* target, const std::string& file, size_t line,
52
                  const std::string& reason)
53
54
    {
        std::stringstream ss;
55
        ss << reason << " [" << file << ":" << line << "]";
56
        target->push_back(ss.str());
57
58
    }

59
60
61
62
63
    void addRRset(const Name& name, const RRClass& rrclass,
                  const RRType& rrtype, const RRTTL& rrttl,
                  const rdata::RdataPtr& data) {
        const RRsetPtr rrset(new BasicRRset(name, rrclass, rrtype, rrttl));
        rrset->addRdata(data);
64
        rrsets_.push_back(rrset);
65
66
    }

67
68
    void setLoader(const char* file, const Name& origin,
                   const RRClass& rrclass, const MasterLoader::Options options)
69
    {
70
        loader_.reset(new MasterLoader(file, origin, rrclass, callbacks_,
71
                                       boost::bind(&MasterLoaderTest::addRRset,
72
73
                                                   this, _1, _2, _3, _4, _5),
                                       options));
74
75
    }

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
    void setLoader(std::istream& stream, const Name& origin,
                   const RRClass& rrclass, const MasterLoader::Options options)
    {
        loader_.reset(new MasterLoader(stream, origin, rrclass, callbacks_,
                                       boost::bind(&MasterLoaderTest::addRRset,
                                                   this, _1, _2, _3, _4, _5),
                                       options));
    }

    string prepareZone(const string& line) {
        string result;
        result += "example.org. 3600 IN SOA ns1.example.org. "
            "admin.example.org. 1234 3600 1800 2419200 7200\n";
        result += line + "\n";
        result += "correct 3600    IN  A 192.0.2.2\n";
        return (result);
92
93
94
95
96
97
98
99
    }

    void clear() {
        warnings_.clear();
        errors_.clear();
        rrsets_.clear();
    }

100
101
102
103
104
105
106
107
108
    // Check the next RR in the ones produced by the loader
    // Other than passed arguments are checked to be the default for the tests
    void checkRR(const string& name, const RRType& type, const string& data) {
        ASSERT_FALSE(rrsets_.empty());
        RRsetPtr current = rrsets_.front();
        rrsets_.pop_front();

        EXPECT_EQ(Name(name), current->getName());
        ASSERT_EQ(type, current->getType());
109
        EXPECT_EQ(RRClass::IN(), current->getClass());
110
        ASSERT_EQ(1, current->getRdataCount());
111
112
        EXPECT_EQ(0, isc::dns::rdata::createRdata(type, RRClass::IN(), data)->
                  compare(current->getRdataIterator()->getCurrent()));
113
114
115
    }

    MasterLoaderCallbacks callbacks_;
116
117
118
119
    boost::scoped_ptr<MasterLoader> loader_;
    vector<string> errors_;
    vector<string> warnings_;
    list<RRsetPtr> rrsets_;
120
121
};

122
123
// Test simple loading. The zone file contains no tricky things, and nothing is
// omitted. No RRset contains more than one RR Also no errors or warnings.
124
TEST_F(MasterLoaderTest, basicLoad) {
125
126
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);
127
128
129
130
131
132

    loader_->load();

    EXPECT_TRUE(errors_.empty());
    EXPECT_TRUE(warnings_.empty());

133
134
    checkRR("example.org", RRType::SOA(),
            "ns1.example.org. admin.example.org. "
135
            "1234 3600 1800 2419200 7200");
136
    checkRR("example.org", RRType::NS(), "ns1.example.org.");
137
    checkRR("www.example.org", RRType::A(), "192.0.2.1");
138
}
139

140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Check it works the same when created based on a stream, not filename
TEST_F(MasterLoaderTest, streamConstructor) {
    stringstream zone_stream(prepareZone(""));
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);

    loader_->load();

    EXPECT_TRUE(errors_.empty());
    EXPECT_TRUE(warnings_.empty());
    checkRR("example.org", RRType::SOA(), "ns1.example.org. "
            "admin.example.org. 1234 3600 1800 2419200 7200");
    checkRR("correct.example.org", RRType::A(), "192.0.2.2");
}

155
156
// Try loading data incrementally.
TEST_F(MasterLoaderTest, incrementalLoad) {
157
158
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);
159
160
161
162
163
164

    EXPECT_FALSE(loader_->loadIncremental(2));

    EXPECT_TRUE(errors_.empty());
    EXPECT_TRUE(warnings_.empty());

165
166
    checkRR("example.org", RRType::SOA(),
            "ns1.example.org. admin.example.org. "
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
            "1234 3600 1800 2419200 7200");
    checkRR("example.org", RRType::NS(), "ns1.example.org.");

    // The third one is not loaded yet
    EXPECT_TRUE(rrsets_.empty());

    // Load the rest.
    EXPECT_TRUE(loader_->loadIncremental(20));

    EXPECT_TRUE(errors_.empty());
    EXPECT_TRUE(warnings_.empty());

    checkRR("www.example.org", RRType::A(), "192.0.2.1");
}

182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Try loading from file that doesn't exist. There should be single error
// saying so.
TEST_F(MasterLoaderTest, invalidFile) {
    setLoader("This file doesn't exist at all",
              Name("exmaple.org."), RRClass::IN(), MasterLoader::MANY_ERRORS);

    // Nothing yet. The loader is dormant until invoked.
    // Is it really what we want?
    EXPECT_TRUE(errors_.empty());

    loader_->load();

    EXPECT_TRUE(warnings_.empty());
    EXPECT_TRUE(rrsets_.empty());
    ASSERT_EQ(1, errors_.size());
    EXPECT_EQ(0, errors_[0].find("Error opening the input source file: ")) <<
        "Different error: " << errors_[0];
}
200
201

struct ErrorCase {
202
203
    const char* const line;    // The broken line in master file
    const char* const problem; // Description of the problem for SCOPED_TRACE
204
} const error_cases[] = {
205
206
207
208
209
210
211
212
213
214
215
216
217
218
    { "www...   3600    IN  A   192.0.2.1", "Invalid name" },
    { "www      FORTNIGHT   IN  A   192.0.2.1", "Invalid TTL" },
    { "www      3600    XX  A   192.0.2.1", "Invalid class" },
    { "www      3600    IN  A   bad_ip", "Invalid Rdata" },
    { "www      3600    IN", "Unexpected EOLN" },
    { "www      3600    CH  TXT nothing", "Class mismatch" },
    { NULL, NULL }
};

// Test a broken zone is handled properly. We test several problems,
// both in strict and lenient mode.
TEST_F(MasterLoaderTest, brokenZone) {
    for (const ErrorCase* ec = error_cases; ec->line != NULL; ++ec) {
        SCOPED_TRACE(ec->problem);
219
        const string zone(prepareZone(ec->line));
220
221
222
223

        {
            SCOPED_TRACE("Strict mode");
            clear();
224
225
            stringstream zone_stream(zone);
            setLoader(zone_stream, Name("example.org."), RRClass::IN(),
226
                      MasterLoader::DEFAULT);
227
228
            EXPECT_THROW(loader_->load(), MasterLoaderError);
            EXPECT_EQ(1, errors_.size()) << errors_[0];
229
230
231
232
233
234
235
236
237
238
239
240
            EXPECT_TRUE(warnings_.empty());

            checkRR("example.org", RRType::SOA(), "ns1.example.org. "
                    "admin.example.org. 1234 3600 1800 2419200 7200");
            // In the strict mode, it is aborted. The last RR is not
            // even attempted.
            EXPECT_TRUE(rrsets_.empty());
        }

        {
            SCOPED_TRACE("Lenient mode");
            clear();
241
242
            stringstream zone_stream(zone);
            setLoader(zone_stream, Name("example.org."), RRClass::IN(),
243
                      MasterLoader::MANY_ERRORS);
244
            EXPECT_NO_THROW(loader_->load());
245
246
247
248
249
250
251
252
253
254
            EXPECT_EQ(1, errors_.size());
            EXPECT_TRUE(warnings_.empty());
            checkRR("example.org", RRType::SOA(), "ns1.example.org. "
                    "admin.example.org. 1234 3600 1800 2419200 7200");
            // This one is below the error one.
            checkRR("correct.example.org", RRType::A(), "192.0.2.2");
            EXPECT_TRUE(rrsets_.empty());
        }
    }
}
255
256
257
258
259
260

// Test the constructor rejects empty add callback.
TEST_F(MasterLoaderTest, emptyCallback) {
    EXPECT_THROW(MasterLoader(TEST_DATA_SRCDIR "/example.org",
                              Name("example.org"), RRClass::IN(), callbacks_,
                              AddRRCallback()), isc::InvalidParameter);
261
262
263
264
265
    // And the same with the second constructor
    stringstream ss("");
    EXPECT_THROW(MasterLoader(ss, Name("example.org"), RRClass::IN(),
                              callbacks_, AddRRCallback()),
                 isc::InvalidParameter);
266
267
268
269
270
271
272
273
274
275
}

// Check it throws when we try to load after loading was complete.
TEST_F(MasterLoaderTest, loadTwice) {
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);

    loader_->load();
    EXPECT_THROW(loader_->load(), isc::InvalidOperation);
}
276
}