#!/usr/bin/env ruby

$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/

require 'puppettest'
require 'puppettest/fileparsing'
require 'puppet'
require 'puppet/util/fileparsing'

class TestUtilFileParsing < Test::Unit::TestCase
	include PuppetTest
	include PuppetTest::FileParsing

    class FParser
        include Puppet::Util::FileParsing
    end

    def setup
        super
        @parser = FParser.new
    end

    def test_lines
        assert_equal("\n", @parser.line_separator,
            "Default separator was incorrect")

        {"\n" => ["one two\nthree four", "one two\nthree four\n"],
         "\t" => ["one two\tthree four", "one two\tthree four\t"],
        }.each do |sep, tests|
            assert_nothing_raised do
                @parser.line_separator = sep
            end
            assert_equal(sep, @parser.line_separator,
                "Did not set separator")

            tests.each do |test|
                assert_equal(["one two", "three four"], @parser.lines(test),
                    "Incorrectly parsed %s" % test.inspect)
            end
        end
    end

    # Make sure parse calls the appropriate methods or errors out
    def test_parse
        @parser.meta_def(:parse_line) do |line|
            line.split(/\s+/)
        end

        text = "one line\ntwo line"
        should = [%w{one line}, %w{two line}]
        ret = nil
        assert_nothing_raised do
            ret = @parser.parse(text)
        end

        assert_equal(should, ret)
    end

    # Make sure we correctly handle different kinds of text lines.
    def test_text_line
        comment = "# this is a comment"

        # Make sure it fails if no regex is passed
        assert_raise(ArgumentError) do
            @parser.text_line :comment
        end

        # define a text matching comment record
        assert_nothing_raised do
            @parser.text_line :comment, :match => /^#/
        end

        # Make sure it matches
        assert_nothing_raised do
            assert_equal({:record_type => :comment, :line => comment}, 
                 @parser.parse_line(comment))
        end

        # But not something else
        assert_nothing_raised do
            assert_nil(@parser.parse_line("some other text"))
        end

        # Now define another type and make sure we get the right one back
        assert_nothing_raised do
            @parser.text_line :blank, :match => /^\s*$/
        end

        # The comment should still match
        assert_nothing_raised do
            assert_equal({:record_type => :comment, :line => comment}, 
                 @parser.parse_line(comment))
        end

        # As should our new line type
        assert_nothing_raised do
            assert_equal({:record_type => :blank, :line => ""}, 
                 @parser.parse_line(""))
        end

    end

    def test_parse_line
        Puppet[:trace] = false

        comment = "# this is a comment"

        # Make sure it fails if we don't have any record types defined
        assert_raise(Puppet::DevError) do
            @parser.parse_line(comment)
        end

        # Now define a text matching comment record
        assert_nothing_raised do
            @parser.text_line :comment, :match => /^#/
        end

        # And make sure we can't define another one with the same name
        assert_raise(ArgumentError) do
            @parser.text_line :comment, :match => /^"/
        end

        result = nil
        assert_nothing_raised("Did not parse text line") do
            result = @parser.parse_line comment
        end

        assert_equal({:record_type => :comment, :line => comment}, result)

        # Make sure we just return nil on unmatched lines.
        assert_nothing_raised("Did not parse text line") do
            result = @parser.parse_line "No match for this"
        end

        assert_nil(result, "Somehow matched an empty line")

        # Now define another type of comment, and make sure both types get
        # correctly returned as comments
        assert_nothing_raised do
            @parser.text_line :comment2, :match => /^"/
        end

        assert_nothing_raised("Did not parse old comment") do
            assert_equal({:record_type => :comment, :line => comment}, 
                 @parser.parse_line(comment))
        end
        comment = '" another type of comment'
        assert_nothing_raised("Did not parse new comment") do
            assert_equal({:record_type => :comment2, :line => comment}, 
                 @parser.parse_line(comment))
        end

        # Now define two overlapping record types and make sure we keep the
        # correct order.  We do first match, not longest match.
        assert_nothing_raised do
            @parser.text_line :one, :match => /^y/
            @parser.text_line :two, :match => /^yay/
        end

        assert_nothing_raised do
            assert_equal({:record_type => :one, :line => "yayness"},
                @parser.parse_line("yayness"))
        end

    end

    def test_record_line
        tabrecord = "tab	separated	content"
        spacerecord = "space separated content"

        # Make sure we always require an appropriate set of options
        [{:separator => "\t"}, {}, {:fields => %w{record_type}}].each do |opts|
            assert_raise(ArgumentError, "Accepted %s" % opts.inspect) do
                @parser.record_line :record, opts
            end
        end

        # Verify that our default separator is tabs
        tabs = nil
        assert_nothing_raised do
            tabs = @parser.record_line :tabs, :fields => [:name, :first, :second]
        end

        # Make sure out tab line gets matched
        tabshould = {:record_type => :tabs, :name => "tab", :first => "separated",
                            :second => "content"}
        assert_nothing_raised do
            assert_equal(tabshould, @parser.handle_record_line(tabrecord, tabs))
        end

        # Now add our space-separated record type
        spaces = nil
        assert_nothing_raised do
            spaces = @parser.record_line :spaces, :fields => [:name, :first, :second]
        end

        # Now make sure both lines parse correctly
        spaceshould = {:record_type => :spaces, :name => "space",
            :first => "separated", :second => "content"}

        assert_nothing_raised do
            assert_equal(tabshould, @parser.handle_record_line(tabrecord, tabs))
            assert_equal(spaceshould, @parser.handle_record_line(spacerecord, spaces))
        end
    end

    def test_to_line
        @parser.text_line :comment, :match => /^#/
        @parser.text_line :blank, :match => /^\s*$/
        @parser.record_line :record, :fields => %w{name one two}, :joiner => "\t"

        johnny = {:record_type => :record, :name => "johnny", :one => "home",
            :two => "yay"}
        bill = {:record_type => :record, :name => "bill", :one => "work",
            :two => "boo"}

        records = {
            :comment => {:record_type => :comment, :line => "# This is a file"},
            :blank => {:record_type => :blank, :line => ""},
            :johnny => johnny,
            :bill => bill
        }

        lines = {
            :comment => "# This is a file",
            :blank => "",
            :johnny => "johnny	home	yay",
            :bill => "bill	work	boo"
        }

        records.each do |name, details|
            result = nil
            assert_nothing_raised do
                result = @parser.to_line(details)
            end

            assert_equal(lines[name], result)
        end
        order = [:comment, :blank, :johnny, :bill]

        file = order.collect { |name| lines[name] }.join("\n")

        ordered_records = order.collect { |name| records[name] }

        # Make sure we default to a trailing separator
        assert_equal(true, @parser.trailing_separator,
            "Did not default to a trailing separtor")

        # Start without a trailing separator
        @parser.trailing_separator = false
        assert_nothing_raised do
            assert_equal(file, @parser.to_file(ordered_records))
        end

        # Now with a trailing separator
        file += "\n"
        @parser.trailing_separator = true
        assert_nothing_raised do
            assert_equal(file, @parser.to_file(ordered_records))
        end

        # Now try it with a different separator, so we're not just catching
        # defaults
        file.gsub!("\n", "\t")
        @parser.line_separator = "\t"
        assert_nothing_raised do
            assert_equal(file, @parser.to_file(ordered_records))
        end
    end

    # Make sure fields that are marked absent get replaced with the appropriate
    # string.
    def test_absent_fields
        record = nil
        assert_nothing_raised do
            record = @parser.record_line :record, :fields => %w{one two three},
                :optional => %w{two three}
        end
        assert_equal("", record.absent, "Did not set a default absent string")

        result = nil
        assert_nothing_raised do
            result = @parser.to_line(:record_type => :record,
                :one => "a", :two => :absent, :three => "b")
        end

        assert_equal("a  b", result, "Absent was not correctly replaced")

        # Now try using a different replacement character
        record.absent = "*" # Because cron is a pain in my ass
        assert_nothing_raised do
            result = @parser.to_line(:record_type => :record,
                :one => "a", :two => :absent, :three => "b")
        end

        assert_equal("a * b", result, "Absent was not correctly replaced")

        # Make sure we deal correctly with the string 'absent'
        assert_nothing_raised do
            result = @parser.to_line(:record_type => :record,
                :one => "a", :two => "b", :three => 'absent')
        end

        assert_equal("a b absent", result, "Replaced string 'absent'")

        # And, of course, make sure we can swap things around.
        assert_nothing_raised do
            result = @parser.to_line(:record_type => :record,
                :one => "a", :two => "b", :three => :absent)
        end

        assert_equal("a b *", result, "Absent was not correctly replaced")
    end

    # Make sure we can specify a different join character than split character
    def test_split_join_record_line
        check = proc do |start, record, final|
            # Check parsing first
            result = @parser.parse_line(start)
            [:one, :two].each do |param|
                assert_equal(record[param], result[param], 
                    "Did not correctly parse %s" % start.inspect)
            end

            # And generating
            assert_equal(final, @parser.to_line(result),
                "Did not correctly generate %s from %s" %
                [final.inspect, record.inspect])
        end

        # First try it with symmetric characters
        @parser.record_line :symmetric, :fields => %w{one two},
            :separator => " "

        check.call "a b", {:one => "a", :two => "b"}, "a b"
        @parser.clear_records

        # Now assymetric but both strings
        @parser.record_line :asymmetric, :fields => %w{one two},
            :separator => "\t", :joiner => " "

        check.call "a\tb", {:one => "a", :two => "b"}, "a b"
        @parser.clear_records

        # And assymmetric with a regex
        @parser.record_line :asymmetric2, :fields => %w{one two},
            :separator => /\s+/, :joiner => " "

        check.call "a\tb", {:one => "a", :two => "b"}, "a b"
        check.call "a b", {:one => "a", :two => "b"}, "a b"
    end

    # Make sure we correctly regenerate files.
    def test_to_file
        @parser.text_line :comment, :match => /^#/
        @parser.text_line :blank, :match => /^\s*$/
        @parser.record_line :record, :fields => %w{name one two}

        text = "# This is a comment

johnny one two
billy three four\n"

        # Just parse and generate, to make sure it's isomorphic.
        assert_nothing_raised do
            assert_equal(text, @parser.to_file(@parser.parse(text)),
                "parsing was not isomorphic")
        end
    end

    def test_valid_attrs
        @parser.record_line :record, :fields => %w{one two three}

        assert(@parser.valid_attr?(:record, :one),
            "one was considered invalid")

        assert(@parser.valid_attr?(:record, :ensure),
            "ensure was considered invalid")

        assert(! @parser.valid_attr?(:record, :four),
            "four was considered valid")
    end

    def test_record_blocks
        options = nil
        assert_nothing_raised do
            # Just do a simple test
            options = @parser.record_line :record,
                :fields => %w{name alias info} do |line|
                line = line.dup
                ret = {}
                if line.sub!(/(\w+)\s*/, '')
                    ret[:name] = $1
                else
                    return nil
                end

                if line.sub!(/(#.+)/, '')
                    desc = $1.sub(/^#\s*/, '')
                    ret[:description] = desc unless desc == ""
                end

                if line != ""
                    ret[:alias] = line.split(/\s+/)
                end

                return ret
            end
        end

        values = {
            :name => "tcpmux",
            :description => "TCP port service multiplexer",
            :alias => ["sink"]
        }

        {

            "tcpmux      " => [:name],
            "tcpmux" => [:name],
            "tcpmux      sink" => [:name, :port, :protocols, :alias],
            "tcpmux      # TCP port service multiplexer" =>
                [:name, :description, :port, :protocols],
            "tcpmux      sink         # TCP port service multiplexer" =>
                [:name, :description, :port, :alias, :protocols],
            "tcpmux      sink null    # TCP port service multiplexer" =>
                [:name, :description, :port, :alias, :protocols],
        }.each do |line, should|
            result = nil
            assert_nothing_raised do
                result = @parser.handle_record_line(line, options)
            end
            assert(result, "Did not get a result back for '%s'" % line)
            should.each do |field|
                if field == :alias and line =~ /null/
                    assert_equal(%w{sink null}, result[field],
                        "Field %s was not right in '%s'" % [field, line])
                else
                    assert_equal(values[field], result[field],
                        "Field %s was not right in '%s'" % [field, line])
                end
            end
        end


    end

    # Make sure we correctly handle optional fields.  We'll skip this
    # functionality until we really know we need it.
    def test_optional_fields
        assert_nothing_raised do
            @parser.record_line :record,
                :fields => %w{one two three four},
                :optional => %w{three four},
                :absent => "*",
                :separator => " " # A single space
        end

        ["a b c d", "a b * d", "a b * *", "a b c *"].each do |line|
            record = nil
            assert_nothing_raised do
                record = @parser.parse_line(line)
            end

            # Now regenerate the line
            newline = nil
            assert_nothing_raised do
                newline = @parser.to_line(record)
            end

            # And make sure they're equal
            assert_equal(line, newline)
        end

        # Now make sure it pukes if we don't provide the required fields
        assert_raise(ArgumentError) do
            @parser.to_line(:record_type => :record, :one => "yay")
        end
    end

    def test_record_rts
        # Start with the default
        assert_nothing_raised do
            @parser.record_line :record,
                :fields => %w{one two three four},
                :optional => %w{three four}
        end

        assert_equal("a b  ",
            @parser.to_line(:record_type => :record, :one => "a", :two => "b")
        )

        # Now say yes to removing
        @parser.clear_records
        assert_nothing_raised do
            @parser.record_line :record,
                :fields => %w{one two three four},
                :optional => %w{three four},
                :rts => true
        end

        assert_equal("a b",
            @parser.to_line(:record_type => :record, :one => "a", :two => "b")
        )

        # Lastly, try a regex
        @parser.clear_records
        assert_nothing_raised do
            @parser.record_line :record,
                :fields => %w{one two three four},
                :optional => %w{three four},
                :absent => "*",
                :rts => /[ *]+$/
        end

        assert_equal("a b",
            @parser.to_line(:record_type => :record, :one => "a", :two => "b")
        )
    end

    # Make sure the last field can contain the separator, as crontabs do, and
    # that we roll them all up by default.
    def test_field_rollups
        @parser.record_line :yes, :fields => %w{name one two}

        result = nil
        assert_nothing_raised do
            result = @parser.send(:parse_line, "Name One Two Three")
        end
        assert_equal("Two Three", result[:two],
            "Did not roll up last fields by default")

        @parser = FParser.new
        assert_nothing_raised("Could not create record that rolls up fields") do
            @parser.record_line :no, :fields => %w{name one two}, :rollup => false
        end

        result = nil
        assert_nothing_raised do
            result = @parser.send(:parse_line, "Name One Two Three")
        end
        assert_equal("Two", result[:two],
            "Rolled up last fields when rollup => false")
    end

    def test_text_blocks
        record = nil
        assert_nothing_raised do
            record = @parser.text_line :name, :match => %r{^#} do |line|
                {:line => line.upcase}
            end
        end

        assert(record.respond_to?(:process),
            "Block was not used with text line")

        assert_equal("YAYNESS", record.process("yayness")[:line],
            "Did not call process method")
    end

    def test_hooks
        record = nil
        # First try it with a normal record
        assert_nothing_raised("Could not set hooks") do
            record = @parser.record_line :yay, :fields => %w{one two},
                :post_parse => proc { |hash| hash[:posted] = true },
                :pre_gen => proc { |hash| hash[:one] = hash[:one].upcase },
                :to_line => proc { |hash| "# Line\n" + join(hash) }
        end

        assert(record.respond_to?(:post_parse), "did not create method for post-hook")
        assert(record.respond_to?(:pre_gen), "did not create method for pre-hook")

        result = nil
        assert_nothing_raised("Could not process line with hooks") do
            result = @parser.parse_line("one two")
        end

        assert(result[:posted], "Did not run post-hook")
        old_result = result

        # Now make sure our pre-gen hook is called
        assert_nothing_raised("Could not generate line with hooks") do
            result = @parser.to_line(result)
        end
        assert_equal("# Line\nONE two", result, "did not call pre-gen hook")
        assert_equal("one", old_result[:one], "passed original hash to pre hook")
    end
end

class TestUtilFileRecord < Test::Unit::TestCase
	include PuppetTest
	include PuppetTest::FileParsing

    Record = Puppet::Util::FileParsing::FileRecord
    def test_new_filerecord
        [   [:fake, {}],
            [nil, ]
        ].each do |args|
            assert_raise(ArgumentError, "Did not fail on %s" % args.inspect) do
                Record.new(*args)
            end
        end

        # Make sure the fields get turned into symbols
        record = nil
        assert_nothing_raised do
            record = Record.new(:record, :fields => %w{one two})
        end
        assert_equal([:one, :two], record.fields,
            "Did not symbolize fields")

        # Make sure we fail on invalid fields
        [:record_type, :target, :on_disk].each do |field|
            assert_raise(ArgumentError, "Did not fail on invalid field %s" % field) {
                Record.new(:record, :fields => [field])
            }
        end
    end

    def test_defaults
        record = Record.new(:text, :match => %r{^#})
        [:absent, :separator, :joiner, :optional].each do |field|
            assert_nil(record.send(field), "%s was not nil" % field)
        end

        record = Record.new(:record, :fields => %w{fields})
        {:absent => "", :separator => /\s+/, :joiner => " ",
            :optional => []}.each do |field, default|
                assert_equal(default, record.send(field), "%s was not default" % field)
        end
    end

    def test_block
        record = nil
        assert_nothing_raised("Could not pass a block when creating record") do
            record = Record.new(:record, :fields => %w{one}) do |line|
                return line.upcase
            end
        end

        line = "This is a line"

        assert(record.respond_to?(:process),
            "Record did not define :process method")

        assert_equal(line.upcase, record.process(line),
            "Record did not process line correctly")
    end

    # Make sure we can declare that we want the block to be instance-eval'ed instead of
    # defining the 'process' method.
    def test_instance_block
        record = nil
        assert_nothing_raised("Could not pass a block when creating record") do
            record = Record.new(:record, :block_eval => :instance, :fields => %w{one}) do
                def process(line)
                    line.upcase
                end

                def to_line(details)
                    details.upcase
                end
            end
        end

        assert(record.respond_to?(:process), "Block was not instance-eval'ed and process was not defined")
        assert(record.respond_to?(:to_line), "Block was not instance-eval'ed and to_line was not defined")

        line = "some text"
        assert_equal(line.upcase, record.process(line), "Instance-eval'ed record did not call :process correctly")
        assert_equal(line.upcase, record.to_line(line), "Instance-eval'ed record did not call :to_line correctly")
    end
end

# $Id: fileparsing.rb 2407 2007-04-23 18:01:36Z luke $



syntax highlighted by Code2HTML, v. 0.9.1