Browse Source

[2371] Merge branch 'trac2369' into trac2371

JINMEI Tatuya 12 years ago
parent
commit
fadf2a8a91

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

@@ -93,6 +93,7 @@ libb10_dns___la_LDFLAGS = -no-undefined -version-info 2:0:0
 libb10_dns___la_SOURCES =
 libb10_dns___la_SOURCES += edns.h edns.cc
 libb10_dns___la_SOURCES += exceptions.h exceptions.cc
+libb10_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc
 libb10_dns___la_SOURCES += labelsequence.h labelsequence.cc
 libb10_dns___la_SOURCES += masterload.h masterload.cc
 libb10_dns___la_SOURCES += master_lexer.h master_lexer.cc

+ 133 - 0
src/lib/dns/master_lexer_inputsource.cc

@@ -0,0 +1,133 @@
+// 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_lexer_inputsource.h>
+
+namespace isc {
+namespace dns {
+namespace master_lexer_internal {
+
+namespace { // unnamed namespace
+
+std::string
+createStreamName(std::istream& input_stream) {
+     std::stringstream ss;
+     ss << "stream-" << &input_stream;
+     return (ss.str());
+}
+
+} // end of unnamed namespace
+
+InputSource::InputSource(std::istream& input_stream) :
+    at_eof_(false),
+    line_(1),
+    saved_line_(line_),
+    buffer_pos_(0),
+    name_(createStreamName(input_stream)),
+    input_(input_stream)
+{}
+
+InputSource::InputSource(const char* filename) :
+    at_eof_(false),
+    line_(1),
+    saved_line_(line_),
+    buffer_pos_(0),
+    name_(filename),
+    input_(file_stream_)
+{
+    file_stream_.open(filename, std::fstream::in);
+    if (file_stream_.fail()) {
+        isc_throw(OpenError,
+                  "Error opening the input source file: " << filename);
+    }
+}
+
+InputSource::~InputSource()
+{
+    if (file_stream_.is_open()) {
+        file_stream_.close();
+    }
+}
+
+int
+InputSource::getChar() {
+    if (buffer_pos_ == buffer_.size()) {
+        // We may have reached EOF at the last call to
+        // getChar(). at_eof_ will be set then. We then simply return
+        // early.
+        if (at_eof_) {
+            return (END_OF_STREAM);
+        }
+        // We are not yet at EOF. Read from the stream.
+        int c = input_.get();
+        // Have we reached EOF now? If so, set at_eof_ and return early,
+        // but don't modify buffer_pos_ (which should still be equal to
+        // the size of buffer_).
+        if (input_.eof()) {
+            at_eof_ = true;
+            return (END_OF_STREAM);
+        }
+        // This has to come after the .eof() check as some
+        // implementations seem to check the eofbit also in .fail().
+        if (input_.fail()) {
+            isc_throw(ReadError,
+                      "Error reading from the input stream: " << getName());
+        }
+        buffer_.push_back(c);
+    }
+
+    int c = buffer_[buffer_pos_++];
+    if (c == '\n') {
+        line_++;
+    }
+
+    return (c);
+}
+
+void
+InputSource::ungetChar() {
+    if (at_eof_) {
+        at_eof_ = false;
+    } else if (buffer_pos_ == 0) {
+        isc_throw(UngetBeforeBeginning,
+                  "Cannot skip before the start of buffer");
+    } else {
+        buffer_pos_--;
+        if (buffer_[buffer_pos_] == '\n') {
+            line_--;
+        }
+    }
+}
+
+void
+InputSource::ungetAll() {
+    buffer_pos_ = 0;
+    line_ = saved_line_;
+    at_eof_ = false;
+}
+
+void
+InputSource::compact() {
+    if (buffer_pos_ == buffer_.size()) {
+        buffer_.clear();
+    } else {
+        buffer_.erase(buffer_.begin(), buffer_.begin() + buffer_pos_);
+    }
+
+    buffer_pos_ = 0;
+}
+
+} // namespace master_lexer_internal
+} // namespace dns
+} // namespace isc

+ 152 - 0
src/lib/dns/master_lexer_inputsource.h

@@ -0,0 +1,152 @@
+// 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.
+
+#ifndef DNS_INPUTSOURCE_H
+#define DNS_INPUTSOURCE_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dns {
+namespace master_lexer_internal {
+
+/// \brief An input source that is used internally by MasterLexer.
+///
+/// This is a helper internal class for MasterLexer, and represents
+/// state of a single source of the entire zone data to be
+/// parsed. Normally this means the master zone file, but MasterLexer
+/// can have multiple InputSources if $INCLUDE is used. The source can
+/// also be generic input stream (std::istream).
+///
+/// This class is not meant for public use.
+class InputSource {
+public:
+    /// \brief Returned by getChar() when end of stream is reached.
+    static const int END_OF_STREAM;
+
+    /// \brief Exception thrown when ungetChar() is made to go before
+    /// the start of buffer.
+    struct UngetBeforeBeginning : public OutOfRange {
+        UngetBeforeBeginning(const char* file, size_t line, const char* what) :
+            OutOfRange(file, line, what)
+        {}
+    };
+
+    /// \brief Exception thrown when we fail to read from the input
+    /// stream or file.
+    struct ReadError : public Unexpected {
+        ReadError(const char* file, size_t line, const char* what) :
+            Unexpected(file, line, what)
+        {}
+    };
+
+    /// \brief Exception thrown when we fail to open the input file.
+    struct OpenError : public Unexpected {
+        OpenError(const char* file, size_t line, const char* what) :
+            Unexpected(file, line, what)
+        {}
+    };
+
+    /// \brief Constructor which takes an input stream. The stream is
+    /// read-from, but it is not closed.
+    InputSource(std::istream& input_stream);
+
+    /// \brief Constructor which takes a filename to read from. The
+    /// associated file stream is managed internally.
+    ///
+    /// \throws OpenError when opening the input file fails.
+    InputSource(const char* filename);
+
+    /// \brief Destructor
+    ~InputSource();
+
+    /// \brief Returns a name for the InputSource. Typically this is the
+    /// filename, but if the InputSource was constructed for an
+    /// \c std::istream, it returns a name in the format "stream-%p".
+    const std::string& getName() const {
+        return (name_);
+    }
+
+    /// \brief Returns if the input source is at end of file.
+    bool atEOF() const {
+        return (at_eof_);
+    }
+
+    /// \brief Returns the current line number being read.
+    size_t getCurrentLine() const {
+        return (line_);
+    }
+
+    /// \brief Saves the current line being read. Later, when
+    /// \c ungetAll() is called, it skips back to the last-saved line.
+    void saveLine() {
+        saved_line_ = line_;
+    }
+
+    /// \brief Returns a single character from the input source. If end
+    /// of file is reached, \c END_OF_STREAM is returned.
+    ///
+    /// \throws ReadError when reading from the input stream or file
+    /// fails.
+    int getChar();
+
+    /// \brief Skips backward a single character in the input
+    /// source. The last-read character is unget.
+    ///
+    /// \throws UngetBeforeBeginning if we go backwards past the start
+    /// of reading, or backwards past the last time compact() was
+    /// called.
+    void ungetChar();
+
+    /// Forgets what was read, and skips back to the position where
+    /// \c compact() was last called. If \c compact() was not called, it
+    /// skips back to where reading started. If \c saveLine() was called
+    /// previously, it sets the current line number to the line number
+    /// saved.
+    void ungetAll();
+
+    /// Removes buffered content before the current location in the
+    /// \c InputSource. It's not possible to \c ungetChar() after this,
+    /// unless we read more data using \c getChar().
+    void compact();
+
+private:
+    bool at_eof_;
+    size_t line_;
+    size_t saved_line_;
+
+    std::vector<char> buffer_;
+    size_t buffer_pos_;
+
+    const std::string name_;
+    std::fstream file_stream_;
+    std::istream& input_;
+};
+
+const int InputSource::END_OF_STREAM = -1;
+
+} // namespace master_lexer_internal
+} // namespace dns
+} // namespace isc
+
+#endif  // DNS_INPUTSOURCE_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 0
src/lib/dns/tests/Makefile.am

@@ -22,6 +22,7 @@ if HAVE_GTEST
 TESTS += run_unittests
 run_unittests_SOURCES = unittest_util.h unittest_util.cc
 run_unittests_SOURCES += edns_unittest.cc
+run_unittests_SOURCES += master_lexer_inputsource_unittest.cc
 run_unittests_SOURCES += labelsequence_unittest.cc
 run_unittests_SOURCES += messagerenderer_unittest.cc
 run_unittests_SOURCES += master_lexer_token_unittest.cc

+ 329 - 0
src/lib/dns/tests/master_lexer_inputsource_unittest.cc

@@ -0,0 +1,329 @@
+// 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_lexer_inputsource.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include <string.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::master_lexer_internal;
+
+namespace {
+
+class InputSourceTest : public ::testing::Test {
+protected:
+    InputSourceTest() :
+        str_("Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n"),
+        str_length_(strlen(str_)),
+        iss_(str_),
+        source_(iss_)
+    {}
+
+    const char* str_;
+    const size_t str_length_;
+    stringstream iss_;
+    InputSource source_;
+};
+
+// Test the default return values set during InputSource construction.
+TEST_F(InputSourceTest, defaults) {
+    EXPECT_EQ(1, source_.getCurrentLine());
+    EXPECT_FALSE(source_.atEOF());
+}
+
+// getName() on file and stream sources
+TEST_F(InputSourceTest, getName) {
+    EXPECT_EQ(0, source_.getName().find("stream-"));
+
+    // Use some file; doesn't really matter what.
+    InputSource source2(TEST_DATA_SRCDIR "/masterload.txt");
+    EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", source2.getName());
+}
+
+TEST_F(InputSourceTest, nonExistentFile) {
+    EXPECT_THROW({
+        InputSource source(TEST_DATA_SRCDIR "/videokilledtheradiostar");
+    }, InputSource::OpenError);
+}
+
+// getChar() should return characters from the input stream in
+// sequence. ungetChar() should skip backwards.
+void
+checkGetAndUngetChar(InputSource& source, const char* str, size_t str_length)
+{
+    for (size_t i = 0; i < str_length; i++) {
+        EXPECT_EQ(str[i], source.getChar());
+        EXPECT_FALSE(source.atEOF());
+    }
+
+    // At this point, we still have not reached EOF.
+    EXPECT_FALSE(source.atEOF());
+
+    // This should cause EOF to be set.
+    EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
+
+    // Now, EOF should be set.
+    EXPECT_TRUE(source.atEOF());
+
+    // Now, let's go backwards. This should cause the EOF to be set to
+    // false.
+    source.ungetChar();
+
+    // Now, EOF should be false.
+    EXPECT_FALSE(source.atEOF());
+
+    // This should cause EOF to be set again.
+    EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
+
+    // Now, EOF should be set.
+    EXPECT_TRUE(source.atEOF());
+
+    // Now, let's go backwards in a loop. Start by skipping the EOF.
+    source.ungetChar();
+
+    for (size_t i = 0; i < str_length; i++) {
+        size_t index = str_length - 1 - i;
+        // Skip one character.
+        source.ungetChar();
+        EXPECT_EQ(str[index], source.getChar());
+        // Skip the character we received again.
+        source.ungetChar();
+    }
+
+    // Skipping past the start of buffer should throw.
+    EXPECT_THROW(source.ungetChar(), InputSource::UngetBeforeBeginning);
+}
+
+TEST_F(InputSourceTest, stream) {
+    checkGetAndUngetChar(source_, str_, str_length_);
+}
+
+TEST_F(InputSourceTest, file) {
+    const char* str =
+        ";; a simple (incomplete) zone file\n"
+        "\n"
+        "example.com. 3600 IN TXT \"test data\"\n"
+        "www.example.com. 60 IN A 192.0.2.1\n"
+        "www.example.com. 60 IN A 192.0.2.2\n";
+    size_t str_length = strlen(str);
+
+    InputSource source(TEST_DATA_SRCDIR "/masterload.txt");
+    checkGetAndUngetChar(source, str, str_length);
+}
+
+// ungetAll() should skip back to the place where the InputSource
+// started at construction, or the last saved start of line.
+TEST_F(InputSourceTest, ungetAll) {
+    while (!source_.atEOF()) {
+        source_.getChar();
+    }
+
+    // Now, we are at EOF.
+    EXPECT_TRUE(source_.atEOF());
+    EXPECT_EQ(4, source_.getCurrentLine());
+
+    source_.ungetAll();
+
+    // Now we are back to where we started.
+    EXPECT_EQ(1, source_.getCurrentLine());
+    EXPECT_FALSE(source_.atEOF());
+}
+
+TEST_F(InputSourceTest, compact) {
+    // Compact at the start
+    source_.compact();
+
+    // Ungetting here must throw.
+    EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+    for (size_t i = 0; i < str_length_; i++) {
+        EXPECT_EQ(str_[i], source_.getChar());
+        EXPECT_FALSE(source_.atEOF());
+    }
+
+    // At this point, we still have not reached EOF.
+    EXPECT_FALSE(source_.atEOF());
+
+    // This should cause EOF to be set.
+    EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+
+    // Now, EOF should be set.
+    EXPECT_TRUE(source_.atEOF());
+    EXPECT_EQ(4, source_.getCurrentLine());
+
+    // Compact again
+    source_.compact();
+
+    // We are still at EOF.
+    EXPECT_TRUE(source_.atEOF());
+    EXPECT_EQ(4, source_.getCurrentLine());
+
+    // Skip the EOF.
+    source_.ungetChar();
+
+    // Ungetting here must throw.
+    EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+    EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+    EXPECT_TRUE(source_.atEOF());
+}
+
+TEST_F(InputSourceTest, compactDuring) {
+    // First, skip to line 2.
+    while (!source_.atEOF() &&
+           (source_.getCurrentLine() != 2)) {
+        source_.getChar();
+    }
+    EXPECT_FALSE(source_.atEOF());
+    size_t line = source_.getCurrentLine();
+    EXPECT_EQ(2, line);
+
+    // Now, unget a couple of characters. This should cause the
+    // buffer_pos_ to be not equal to the size of the buffer.
+    source_.ungetChar();
+    source_.ungetChar();
+
+    source_.saveLine();
+    source_.compact();
+
+    // Ungetting here must throw.
+    EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+    for (size_t i = 13; i < str_length_; i++) {
+        EXPECT_EQ(str_[i], source_.getChar());
+        EXPECT_FALSE(source_.atEOF());
+    }
+
+    // At this point, we still have not reached EOF.
+    EXPECT_FALSE(source_.atEOF());
+
+    // This should cause EOF to be set.
+    EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+
+    // Now, EOF should be set.
+    EXPECT_TRUE(source_.atEOF());
+
+    // Now, ungetAll() and check where it goes back.
+    source_.ungetAll();
+
+    // Ungetting here must throw.
+    EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+    for (size_t i = 13; i < str_length_; i++) {
+        EXPECT_EQ(str_[i], source_.getChar());
+        EXPECT_FALSE(source_.atEOF());
+    }
+
+    // At this point, we still have not reached EOF.
+    EXPECT_FALSE(source_.atEOF());
+
+    // This should cause EOF to be set.
+    EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+
+    // Now, EOF should be set.
+    EXPECT_TRUE(source_.atEOF());
+}
+
+// Test line counters.
+TEST_F(InputSourceTest, lines) {
+    size_t line = 1;
+    while (!source_.atEOF()) {
+        if (source_.getChar() == '\n') {
+            line++;
+        }
+        EXPECT_EQ(line, source_.getCurrentLine());
+    }
+
+    // Now, we are at EOF.
+    EXPECT_TRUE(source_.atEOF());
+    EXPECT_EQ(4, source_.getCurrentLine());
+
+    // Go backwards 2 characters, skipping the last EOF and '\n'.
+    source_.ungetChar();
+    source_.ungetChar();
+
+    EXPECT_FALSE(source_.atEOF());
+    EXPECT_EQ(3, source_.getCurrentLine());
+
+    source_.ungetAll();
+
+    // Now we are back to where we started.
+    EXPECT_EQ(1, source_.getCurrentLine());
+    EXPECT_FALSE(source_.atEOF());
+
+    // Now check that line numbers are decremented properly (as much as
+    // possible using the available API).
+    while (!source_.atEOF()) {
+        source_.getChar();
+    }
+    line = source_.getCurrentLine();
+
+    // Now, we are at EOF.
+    EXPECT_TRUE(source_.atEOF());
+    EXPECT_EQ(4, line);
+
+    EXPECT_THROW({
+        while (true) {
+            source_.ungetChar();
+            EXPECT_TRUE(((line == source_.getCurrentLine()) ||
+                         ((line - 1) == source_.getCurrentLine())));
+            line = source_.getCurrentLine();
+        }
+    }, InputSource::UngetBeforeBeginning);
+
+    // Now we are back to where we started.
+    EXPECT_EQ(1, source_.getCurrentLine());
+}
+
+// ungetAll() after saveLine() should skip back to the last-saved place.
+TEST_F(InputSourceTest, saveLine) {
+    // First, skip to line 2.
+    while (!source_.atEOF() &&
+           (source_.getCurrentLine() != 2)) {
+        source_.getChar();
+    }
+    EXPECT_FALSE(source_.atEOF());
+    size_t line = source_.getCurrentLine();
+    EXPECT_EQ(2, line);
+
+    // Now, save the line.
+    source_.saveLine();
+
+    // Now, go to EOF
+    while (!source_.atEOF()) {
+        source_.getChar();
+    }
+    line = source_.getCurrentLine();
+
+    // Now, we are at EOF.
+    EXPECT_TRUE(source_.atEOF());
+    EXPECT_EQ(4, line);
+
+    // Now, ungetAll() and check where it goes back.
+    source_.ungetAll();
+
+    // Now we are back to where we last-saved.
+    EXPECT_EQ(2, source_.getCurrentLine());
+    EXPECT_FALSE(source_.atEOF());
+}
+
+} // end namespace