#!/usr/bin/env ruby

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

require 'puppettest'
require 'mocha'
require 'puppettest/fileparsing'
require 'puppet/util/filetype'
require 'puppet/provider/parsedfile'
require 'facter'

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

    Puppet::Type.newtype(:testparsedfiletype) do
        ensurable
        newproperty(:one) do
            newvalue(:a)
            newvalue(:b)
        end
        newproperty(:two) do
            newvalue(:c)
            newvalue(:d)
        end

        newparam(:name) do
        end

        # The target should always be a property, not a parameter.
        newproperty(:target) do
            defaultto { @resource.class.defaultprovider.default_target }
        end
    end

    # A simple block to skip the complexity of a full transaction.
    def apply(resource)
        [:one, :two, :ensure, :target].each do |st|
            Puppet.info "Setting %s: %s => %s" %
                [resource[:name], st, resource.should(st)]
            resource.provider.send(st.to_s + "=", resource.should(st))
        end
    end

    def mkresource(name, options = {})
        options[:one] ||= "a"
        options[:two] ||= "c"
        options[:name] ||= name

        resource = @type.create(options)
    end

    def mkprovider(name = :parsed)
        @provider = @type.provide(name, :parent => Puppet::Provider::ParsedFile,
            :filetype => :ram, :default_target => "yayness") do
            record_line name, :fields => %w{name one two}
        end
    end

    def setup
        super
        @type = Puppet::Type.type(:testparsedfiletype)
    end

    def teardown
        if defined? @provider
            @type.unprovide(@provider.name)
            @provider = nil
        end
        super
    end

    def test_create_provider
        assert_nothing_raised do
            mkprovider
        end
    end

    def test_resource_attributes
        prov = nil
        assert_nothing_raised do
            prov = mkprovider
        end

        [:one, :two, :name].each do |attr|
            assert(prov.method_defined?(attr), "Did not define %s" % attr)
        end

        # Now make sure they stay around
        fakeresource = fakeresource(:testparsedfiletype, "yay")

        file = prov.new(fakeresource)
        assert(file, "Could not make provider")

        assert_nothing_raised("Could not set provider name") do
            file.name = :yayness
        end

        # The provider converts to strings
        assert_equal(:yayness, file.name)
    end

    def test_filetype
        prov = mkprovider

        flat = Puppet::Util::FileType.filetype(:flat)
        ram = Puppet::Util::FileType.filetype(:ram)
        assert_nothing_raised do
            prov.filetype = :flat
        end

        assert_equal(flat, prov.filetype)

        assert_nothing_raised do
            prov.filetype = ram
        end
        assert_equal(ram, prov.filetype)
    end

    # Make sure we correctly create a new filetype object, but only when
    # necessary.
    def test_fileobject
        prov = mkprovider

        path = tempfile()
        obj = nil
        assert_nothing_raised do
            obj = prov.target_object(path)
        end

        # The default filetype is 'ram'
        assert_instance_of(Puppet::Util::FileType.filetype(:ram), obj)

        newobj = nil
        assert_nothing_raised do
            newobj = prov.target_object(path)
        end

        assert_equal(obj, newobj, "did not reuse file object")

        # now make sure clear does the right thing
        assert_nothing_raised do
            prov.clear
        end
        assert_nothing_raised do
            newobj = prov.target_object(path)
        end

        assert(obj != newobj, "did not reuse file object")
    end

    def test_retrieve
        prov = mkprovider

        prov.filetype = :ram

        # Override the parse method with our own
        prov.meta_def(:parse) do |text|
            return [text]
        end

        path = :yayness
        file = prov.target_object(path)
        text = "a test"
        file.write(text)

        ret = nil
        assert_nothing_raised do
            ret = prov.retrieve(path)
        end

        assert_equal([text], ret)

        # Now set the text to nil and make sure we get an empty array
        file.write(nil)
        assert_nothing_raised do
            ret = prov.retrieve(path)
        end

        assert_equal([], ret)

        # And the empty string should return an empty array
        file.write("")
        assert_nothing_raised do
            ret = prov.retrieve(path)
        end

        assert_equal([], ret)
    end

    # Verify that prefetch will parse the file, create any necessary instances,
    # and set the 'is' values appropriately.
    def test_prefetch
        prov = mkprovider

        prov.filetype = :ram
        prov.default_target = :default

        # Create a couple of demo files
        prov.target_object(:file1).write "bill b c\njill b d"

        prov.target_object(:default).write "will b d\n"

        # Create some resources for some of those demo files
        bill = mkresource "bill", :target => :file1, :one => "b", :two => "c"
        will = mkresource "will", :target => :default, :one => "b", :two => "d"

        resources = {"bill" => bill, "will" => will}
        prov_ids = {"bill" => bill.provider.object_id, "will" => will.provider.object_id}

        assert_nothing_raised do
            prov.prefetch(resources)
        end

        assert(bill.provider.object_id != prov_ids["bill"], "provider was not replaced in resource")
        assert(will.provider.object_id != prov_ids["will"], "provider was not replaced in resource")

        # Make sure we prefetched our resources.
        assert_equal("b", bill.provider.one, "did not prefetch resource from file1")
        assert_equal("c", bill.provider.two, "did not prefetch resource from file1")
        assert_equal("b", will.provider.one, "did not prefetch resource from default")
        assert_equal("d", will.provider.two, "did not prefetch resource from default")

        # Now modify our resources and write them out, making sure that prefetching
        # hasn't somehow destroyed this ability
        bill[:one] = "a"
        will[:one] = "a"

        assert_apply(bill)
        assert_apply(will)

        prov.prefetch(resources)
        assert_equal("a", bill.provider.one, "did not prefetch resource from file1")
        assert_equal("a", will.provider.one, "did not prefetch resource from default")

        assert_equal("bill a c\njill b d\n", prov.target_object(:file1).read,
            "Did not write changed resource correctly")
        assert_equal("will a d\n", prov.target_object(:default).read,
            "Did not write changed default resource correctly")
    end

    # Make sure we can correctly prefetch on a target.
    def test_prefetch_target
        prov = mkprovider

        prov.filetype = :ram
        target = :yayness
        prov.target_object(target).write "yay b d"

        records = nil
        assert_nothing_raised do
            records = prov.prefetch_target(:yayness)
        end

        # Now make sure we correctly got the hash
        record = records.find { |r| r[:name] == "yay" }
        assert(record, "Did not get record in prefetch_target")
        assert_equal("b", record[:one])
        assert_equal("d", record[:two])
    end

    def test_prefetch_match
        prov = mkprovider

        prov.meta_def(:match) do |record, resources|
            # Look for matches on :one
            if res = resources.find { |name, resource| resource.should(:one).to_s == record[:one].to_s }
                res[1]
            else
                nil
            end
        end

        prov.filetype = :ram
        target = :yayness
        prov.target_object(target).write "foo b d"

        resource = mkresource "yay", :target => :yayness, :one => "b"

        assert_nothing_raised do
            prov.prefetch("yay" => resource)
        end

        # Now make sure we correctly got the hash
        mprov = resource.provider
        assert_equal("yay", resource[:name])
        assert_equal("b", mprov.one)
        assert_equal("d", mprov.two)
    end

    # We need to test that we're retrieving files from all three locations:
    # from any existing target_objects, from the default file location, and
    # from any existing resource instances.
    def test_targets
        prov = mkprovider

        files = {}

        # Set the default target
        default = tempfile()
        files[:default] = default
        prov.default_target = default

        # Create a file object
        inmem = tempfile()
        files[:inmemory] = inmem
        prov.target_object(inmem).write("inmem yay ness")

        # Lastly, create a resource with separate is and should values
        mtarget = tempfile()
        files[:resources] = mtarget
        resource = mkresource "yay", :target => mtarget

        assert(resource.should(:target), "Did not get a value for target")

        list = nil

        # First run it without the resource
        assert_nothing_raised do
            list = prov.targets
        end

        # Make sure it got the first two, but not the resources file
        files.each do |name, file|
            if name == :resources
                assert(! list.include?(file), "Provider somehow found resource target when no resource was passed")
            else
                assert(list.include?(file), "Provider did not find %s file" % name)
            end
        end

        # Now list with the resource passed
        assert_nothing_raised do
            list = prov.targets("yay" => resource)
        end

        # And make sure we get all three files
        files.each do |name, file|
            assert(list.include?(file), "Provider did not find %s file when resource was passed" % name)
        end
    end

    # Make sure that flushing behaves correctly.  This is what actually writes
    # the data out to disk.
    def test_flush
        prov = mkprovider

        prov.filetype = :ram
        prov.default_target = :yayness

        # Create some resources.
        one = mkresource "one", :one => "a", :two => "c", :target => :yayness
        two = mkresource "two", :one => "b", :two => "d", :target => :yayness
        resources = {"one" => one, "two" => two}

        # Write out a file with different data.
        prov.target_object(:yayness).write "one b d\ntwo a c"

        prov.prefetch(resources)

        # Apply and flush the first resource.
        assert_nothing_raised do
            apply(one)
        end
        assert_nothing_raised { one.flush }

        # Make sure it didn't clear out our property hash
        assert_equal(:a, one.provider.one)
        assert_equal(:c, one.provider.two)

        # And make sure it's right on disk
        assert(prov.target_object(:yayness).read.include?("one a c"),
            "Did not write out correct data")

        # Make sure the second resource has not been modified
        assert_equal("a", two.provider.one, "Two was flushed early")
        assert_equal("c", two.provider.two, "Two was flushed early")

        # And on disk
        assert(prov.target_object(:yayness).read.include?("two a c"),
            "Wrote out other resource")

        # Now fetch the data again and make sure we're still right
        assert_nothing_raised { prov.prefetch(resources) }
        assert_equal("a", one.provider.one)
        assert_equal("a", two.provider.one)

        # Now flush the second resource and make sure it goes well
        assert_nothing_raised { apply(two) }
        assert_nothing_raised { two.flush }

        # And make sure it didn't clear our hash
        assert_equal(:b, two.provider.one)
    end

    # Make sure it works even if the file does not currently exist
    def test_creating_file
        prov = mkprovider
        prov.clear

        prov.default_target = :basic

        resource = mkresource "yay", :target => :basic, :one => "a", :two => "c"

        assert_equal(:present, resource.should(:ensure))

        apply(resource)

        assert_nothing_raised do
            resource.flush
        end

        assert_equal("yay a c\n", prov.target_object(:basic).read,
            "Did not create file")

        # Make a change
        resource.provider.one = "b"

        # Flush it
        assert_nothing_raised do
            resource.flush
        end

        # And make sure our resource doesn't appear twice in the file.
        assert_equal("yay b c\n", prov.target_object(:basic).read,
            "Wrote record to file twice")
    end

    # Make sure a record can switch targets.
    def test_switching_targets
        prov = mkprovider

        prov.filetype = :ram
        prov.default_target = :first

        # Make three resources, one for each target and one to switch
        first = mkresource "first", :target => :first
        second = mkresource "second", :target => :second
        mover = mkresource "mover", :target => :first

        [first, second, mover].each do |m|
            assert_nothing_raised("Could not apply %s" % m[:name]) do
                apply(m)
            end
        end

        # Flush.
        [first, second, mover].each do |m|
            assert_nothing_raised do
                m.flush
            end
        end

        check = proc do |target, name|
            assert(prov.target_object(target).read.include?("%s a c" % name),
                "Did not sync %s" % name)
        end
        # Make sure the data is there
        check.call(:first, :first)
        check.call(:second, :second)
        check.call(:first, :mover)

        # Now change the target for the mover
        mover[:target] = :second

        # Apply it
        assert_nothing_raised do
            apply(mover)
        end

        # Flush
        assert_nothing_raised do
            mover.flush
        end

        # Make sure the data is there
        check.call(:first, :first)
        check.call(:second, :second)
        check.call(:second, :mover)

        # And make sure the mover is no longer in the first file
        assert(prov.target_object(:first) !~ /mover/,
            "Mover was not removed from first file")
    end

    # Make sure that 'ensure' correctly calls 'sync' on all properties.
    def test_ensure
        prov = mkprovider

        prov.filetype = :ram
        prov.default_target = :first

        # Make two resources, one that starts on disk and one that doesn't
        ondisk = mkresource "ondisk", :target => :first
        notdisk = mkresource "notdisk", :target => :first

        prov.target_object(:first).write "ondisk a c\n"
        prov.prefetch("ondisk" => ondisk, "notdisk" => notdisk)

        assert_equal(:present, notdisk.should(:ensure),
            "Did not get default ensure value")

        # Try creating the object
        assert_nothing_raised { notdisk.provider.create() }

        # Now make sure all of the data is copied over correctly.
        notdisk.class.validproperties.each do |property|
            assert_equal(notdisk.should(property), notdisk.provider.property_hash[property],
                "%s was not copied over during creation" % property)
        end

        # Flush it to disk and make sure it got copied down
        assert_nothing_raised do
            notdisk.flush
        end

        assert(prov.target_object(:first).read =~ /^notdisk/,
            "Did not write out object to disk")
        assert(prov.target_object(:first).read =~ /^ondisk/,
            "Lost object on disk")

        # Make sure our on-disk resource behaves appropriately.
        assert_equal(:present, ondisk.provider.ensure)

        # Now destroy the object
        assert_nothing_raised { notdisk.provider.destroy() }

        assert_nothing_raised { notdisk.flush }

        # And make sure it's no longer present
        assert(prov.target_object(:first).read !~ /^notdisk/,
            "Did not remove thing from disk")
        assert(prov.target_object(:first).read =~ /^ondisk/,
            "Lost object on disk")
        assert_equal(:present, ondisk.provider.ensure)
    end

    def test_absent_fields
        prov = @type.provide(:record, :parent => Puppet::Provider::ParsedFile) do
            record_line :record, :fields => %w{name one two},
                :separator => "\s"
        end
        cleanup { @type.unprovide(:record) }

        line = prov.parse_line("a  d")

        assert_equal("a", line[:name], "field name was not set")
        assert_equal(:absent, line[:one], "field one was not set to absent")

        # Now use a different provider with a non-blank "absent"
        prov = @type.provide(:cronstyle, :parent => Puppet::Provider::ParsedFile) do
            record_line :cronstyle, :fields => %w{name one two},
                :separator => "\s", :absent => "*"
        end
        cleanup { @type.unprovide(:cronstyle) }
        line = prov.parse_line("a * d")

        assert_equal("a", line[:name], "field name was not set")
        assert_equal(:absent, line[:one], "field one was not set to absent")
    end

    # This test is because in x2puppet I was having problems where multiple
    # retrievals somehow destroyed the 'is' values.
    def test_value_retrieval
        prov = mkprovider
        prov.default_target = :yayness

        prov.target_object(:yayness).write "bill a c\njill b d"

        list = @type.instances

        bill = list.find { |r| r[:name] == "bill" }
        jill = list.find { |r| r[:name] == "jill" }
        assert(bill, "Could not find bill")
        assert(jill, "Could not find jill")

        prov = bill.provider

        4.times do |i|
            assert(prov.one, "Did not get a value for 'one' on try %s" % (i + 1))
        end

        # First make sure we can retrieve values multiple times from the
        # provider
        bills_values = nil
        assert_nothing_raised do
            bills_values = bill.retrieve
        end

        assert(bills_values[bill.property(:one)], 
               "Bill does not have a value for 'one'")
        assert(bills_values[bill.property(:one)], 
               "Bill does not have a value for 'one' on second try")
        assert_nothing_raised do
            bill.retrieve
        end
        assert(bills_values[bill.property(:one)], 
               "bill's value for 'one' disappeared")
    end

    # Make sure that creating a new resource finds existing records in memory
    def test_initialize_finds_records
        prov = mkprovider
        prov.default_target = :yayness

        prov.target_object(:yayness).write "bill a c\njill b d"

        prov.prefetch

        # Now make a resource
        bill = nil
        assert_nothing_raised do
            bill = @type.create :name => "bill"
        end

        assert_equal("a", bill.provider.one,
            "Record was not found in memory")
    end

    # Make sure invalid fields always show up as insync
    def test_invalid_fields
        prov = @type.provide(:test, :parent => Puppet::Provider::ParsedFile,
            :filetype => :ram, :default_target => :yayness) do
            record_line :test, :fields => %w{name two}
        end
        cleanup do @type.unprovide(:test) end

        bill = nil
        assert_nothing_raised do
            bill = @type.create :name => "bill",
                :one => "a", :two => "c"
        end

        assert_apply(bill)

        prov.prefetch
        current_value = nil
        assert_nothing_raised do
            current_value = bill.retrieve
        end

        assert(bill.insync?(current_value),
            "An invalid field marked the record out of sync")
    end

    # Make sure we call the prefetch hook at the right place.
    def test_prefetch_hook
        prov = @type.provide(:test, :parent => Puppet::Provider::ParsedFile,
            :filetype => :ram, :default_target => :yayness) do

            def self.prefetch_hook(records)
                records
            end

            record_line :test, :fields => %w{name two}
        end
        cleanup do @type.unprovide(:test) end

        target = "target"

        records = [{:target => "nope"}]
        targeted = {:target => "target"}
        prov.send(:instance_variable_set, "@records", records)
        prov.expects(:retrieve).with(target).returns([targeted])

        prov.expects(:prefetch_hook).with([targeted]).returns([targeted])

        prov.prefetch_target(target)
    end

    # #529
    def test_keep_content_with_target
        mkprovider
        @provider.filetype = :flat
        dpath = tempfile
        opath = tempfile
        @provider.default_target = dpath

        dtarget = @provider.target_object(dpath)
        otarget = @provider.target_object(opath)

        dtarget.write("dname a c\n")
        otarget.write("oname b d\n")

        # Now make a resource that targets elsewhat.
        res = @type.create(:name => "test", :one => "a", :two => "c",
            :target => opath)

        assert(res.property(:target), "Target is a parameter, not a property")

        assert_apply(res)

        assert_equal("oname b d\ntest a c\n", otarget.read,
            "did not get correct results in specified target")
    end
end

# $Id: parsedfile.rb 2750 2007-08-06 17:59:37Z luke $



syntax highlighted by Code2HTML, v. 0.9.1