master_loader_unittest.cc 13.3 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
    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));
    }

85
    static string prepareZone(const string& line, bool include_last) {
86
87
88
        string result;
        result += "example.org. 3600 IN SOA ns1.example.org. "
            "admin.example.org. 1234 3600 1800 2419200 7200\n";
89
90
91
92
93
        result += line;
        if (include_last) {
            result += "\n";
            result += "correct 3600    IN  A 192.0.2.2\n";
        }
94
        return (result);
95
96
97
98
99
100
101
102
    }

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

103
104
105
106
107
108
109
110
    // 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());
111
        EXPECT_EQ(type, current->getType());
112
        EXPECT_EQ(RRClass::IN(), current->getClass());
113
        ASSERT_EQ(1, current->getRdataCount());
114
115
        EXPECT_EQ(0, isc::dns::rdata::createRdata(type, RRClass::IN(), data)->
                  compare(current->getRdataIterator()->getCurrent()));
116
117
    }

118
119
120
121
122
123
124
125
    void checkBasicRRs() {
        checkRR("example.org", RRType::SOA(),
                "ns1.example.org. admin.example.org. "
                "1234 3600 1800 2419200 7200");
        checkRR("example.org", RRType::NS(), "ns1.example.org.");
        checkRR("www.example.org", RRType::A(), "192.0.2.1");
    }

126
    MasterLoaderCallbacks callbacks_;
127
128
129
130
    boost::scoped_ptr<MasterLoader> loader_;
    vector<string> errors_;
    vector<string> warnings_;
    list<RRsetPtr> rrsets_;
131
132
};

133
134
// 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.
135
TEST_F(MasterLoaderTest, basicLoad) {
136
137
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);
138

139
    EXPECT_FALSE(loader_->loadedSucessfully());
140
    loader_->load();
141
    EXPECT_TRUE(loader_->loadedSucessfully());
142
143
144
145

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

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    checkBasicRRs();
}

// Test the $INCLUDE directive
TEST_F(MasterLoaderTest, include) {
    // Test various cases of include
    const char* includes[] = {
        "include",
        "INCLUDE",
        "Include",
        "InCluDe",
        NULL
    };
    for (const char** include = includes; *include != NULL; ++include) {
        SCOPED_TRACE(*include);
161
162
        // Prepare input source that has the include and some more data
        // below (to see it returns back to the original source).
163
        const string include_str = "$" + string(*include) + " " +
164
            TEST_DATA_SRCDIR + "/example.org\nwww 3600 IN AAAA 2001:db8::1\n";
165
166
167
168
169
170
        stringstream ss(include_str);
        setLoader(ss, Name("example.org."), RRClass::IN(),
                  MasterLoader::MANY_ERRORS);

        loader_->load();
        EXPECT_TRUE(loader_->loadedSucessfully());
171
        EXPECT_TRUE(errors_.empty());
172
173
174
        EXPECT_TRUE(warnings_.empty());

        checkBasicRRs();
175
        checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
176
    }
177
}
178

179
180
// Check it works the same when created based on a stream, not filename
TEST_F(MasterLoaderTest, streamConstructor) {
181
    stringstream zone_stream(prepareZone("", true));
182
183
184
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);

185
    EXPECT_FALSE(loader_->loadedSucessfully());
186
    loader_->load();
187
    EXPECT_TRUE(loader_->loadedSucessfully());
188
189
190
191
192
193
194
195

    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");
}

196
197
// Try loading data incrementally.
TEST_F(MasterLoaderTest, incrementalLoad) {
198
199
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);
200

201
    EXPECT_FALSE(loader_->loadedSucessfully());
202
    EXPECT_FALSE(loader_->loadIncremental(2));
203
    EXPECT_FALSE(loader_->loadedSucessfully());
204
205
206
207

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

208
209
    checkRR("example.org", RRType::SOA(),
            "ns1.example.org. admin.example.org. "
210
211
212
213
214
215
216
217
            "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));
218
    EXPECT_TRUE(loader_->loadedSucessfully());
219
220
221
222
223
224
225

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

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

226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// 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];
}
244
245

struct ErrorCase {
246
247
    const char* const line;    // The broken line in master file
    const char* const problem; // Description of the problem for SCOPED_TRACE
248
} const error_cases[] = {
249
250
251
252
253
254
    { "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" },
255
256
257
258
259
    { "www      \"3600\"  IN  A   192.0.2.1", "Quoted TTL" },
    { "www      3600    \"IN\"  A   192.0.2.1", "Quoted class" },
    { "www      3600    IN  \"A\"   192.0.2.1", "Quoted type" },
    { "unbalanced)paren 3600    IN  A   192.0.2.1", "Token error 1" },
    { "www  3600    unbalanced)paren    A   192.0.2.1", "Token error 2" },
260
261
262
    // Check the unknown directive. The rest looks like ordinary RR,
    // so we see the $ is actually special.
    { "$UNKNOWN 3600    IN  A   192.0.2.1", "Unknown $ directive" },
263
264
265
266
267
268
269
270
    { 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);
271
        const string zone(prepareZone(ec->line, true));
272
273
274
275

        {
            SCOPED_TRACE("Strict mode");
            clear();
276
277
            stringstream zone_stream(zone);
            setLoader(zone_stream, Name("example.org."), RRClass::IN(),
278
                      MasterLoader::DEFAULT);
279
            EXPECT_FALSE(loader_->loadedSucessfully());
280
            EXPECT_THROW(loader_->load(), MasterLoaderError);
281
            EXPECT_FALSE(loader_->loadedSucessfully());
282
            EXPECT_EQ(1, errors_.size());
283
284
285
286
287
288
289
290
291
292
293
294
            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();
295
296
            stringstream zone_stream(zone);
            setLoader(zone_stream, Name("example.org."), RRClass::IN(),
297
                      MasterLoader::MANY_ERRORS);
298
            EXPECT_FALSE(loader_->loadedSucessfully());
299
            EXPECT_NO_THROW(loader_->load());
300
            EXPECT_FALSE(loader_->loadedSucessfully());
301
302
303
304
305
306
307
308
            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());
        }
309
310
311
312
313
314
315
316
317

        {
            SCOPED_TRACE("Error at EOF");
            // This case is interesting only in the lenient mode.
            const string zoneEOF(prepareZone(ec->line, false));
            clear();
            stringstream zone_stream(zoneEOF);
            setLoader(zone_stream, Name("example.org."), RRClass::IN(),
                      MasterLoader::MANY_ERRORS);
318
            EXPECT_FALSE(loader_->loadedSucessfully());
319
            EXPECT_NO_THROW(loader_->load());
320
            EXPECT_FALSE(loader_->loadedSucessfully());
321
            EXPECT_EQ(1, errors_.size());
322
323
            // The unexpected EOF warning
            EXPECT_EQ(1, warnings_.size());
324
325
326
327
            checkRR("example.org", RRType::SOA(), "ns1.example.org. "
                    "admin.example.org. 1234 3600 1800 2419200 7200");
            EXPECT_TRUE(rrsets_.empty());
        }
328
329
    }
}
330
331
332
333
334
335

// 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);
336
337
338
339
340
    // And the same with the second constructor
    stringstream ss("");
    EXPECT_THROW(MasterLoader(ss, Name("example.org"), RRClass::IN(),
                              callbacks_, AddRRCallback()),
                 isc::InvalidParameter);
341
342
343
344
345
346
347
348
349
350
}

// 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);
}
351
352
353
354
355
356
357
358

// Load 0 items should be rejected
TEST_F(MasterLoaderTest, loadZero) {
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);
    EXPECT_THROW(loader_->loadIncremental(0), isc::InvalidParameter);
}

359
}