#!/usr/bin/env ruby

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

require 'puppettest'
require 'facter'

class TestProvider < Test::Unit::TestCase
	include PuppetTest

    def echo
        echo = Puppet::Util.binary("echo")

        unless echo
            raise "Could not find 'echo' binary; cannot complete test"
        end

        return echo
    end

    def newprovider
        # Create our provider
        provider = Class.new(Puppet::Provider) do
            @name = :fakeprovider
        end
        provider.initvars

        return provider
    end

    def test_confine
        provider = newprovider

        assert(provider.suitable?,
            "Marked unsuitable with no confines")

        {
            {:true => true} => true,
            {:true => false} => false,
            {:false => false} => true,
            {:false => true} => false,
            {:operatingsystem => Facter.value(:operatingsystem)} => true,
            {:operatingsystem => :yayness} => false,
            {:nothing => :yayness} => false,
            {:exists => echo} => true,
            {:exists => "/this/file/does/not/exist"} => false,
        }.each do |hash, result|
            # First test :true
            hash.each do |test, val|
                assert_nothing_raised do
                    provider.confine test => val
                end
            end

            assert_equal(result, provider.suitable?,
                "Failed for %s" % [hash.inspect])

            provider.initvars
        end

        # Make sure multiple confines don't overwrite each other
        provider.confine :true => false
        assert(! provider.suitable?)
        provider.confine :true => true
        assert(! provider.suitable?)

        provider.initvars

        # Make sure we test multiple of them, and that a single false wins
        provider.confine :true => true, :false => false
        assert(provider.suitable?)
        provider.confine :true => false
        assert(! provider.suitable?)
    end

    def test_command
        {:echo => "echo", :echo_with_path => echo, :missing => "nosuchcommand", :missing_qualified => "/path/to/nosuchcommand"}.each do |name, command|
            provider = newprovider
            assert_nothing_raised("Could not define command %s with argument %s for provider" % [name, command]) do
                provider.commands(name => command)
            end

            if name.to_s =~ /missing/
                assert_nil(provider.command(name), "Somehow got a response for missing commands")
                assert(! provider.suitable?, "Provider was considered suitable with missing command")
                next # skip, since we don't do any validity checking here.
            end

            assert_equal(echo, provider.command(name), "Did not get correct path for echo")
            assert(provider.suitable?, "Provider was not considered suitable with 'echo'")

            # Now make sure they both work
            inst = provider.new(nil)
            [provider, inst].each do |thing|
                assert_nothing_raised("Could not call %s on %s" % [command, thing]) do
                    out = thing.send(name, "some", "text")
                    assert_equal("some text\n", out)
                end
            end

            assert(provider.suitable?, "Provider considered unsuitable")

            # Now add an invalid command
            assert_nothing_raised do
                provider.commands :fake => "nosuchcommanddefinitely"
            end
            assert(! provider.suitable?, "Provider considered suitable")

            assert_nil(provider.command(:fake), "Got a value for missing command")
            assert_raise(Puppet::Error) do
                provider.fake
            end

            Puppet[:trace] = false
            assert_raise(Puppet::DevError) do
                provider.command(:nosuchcmd)
            end

            # Lastly, verify that we can find our superclass commands
            newprov = Class.new(provider)
            newprov.initvars

            assert_equal(echo, newprov.command(name))
        end
    end

    def test_default?
        provider = newprovider

        assert(! provider.default?, "Was considered default with no settings")

        assert_nothing_raised do
            provider.defaultfor :operatingsystem => Facter.value(:operatingsystem)
        end

        assert(provider.default?, "Was not considered default")

        # Make sure any true value is sufficient.
        assert_nothing_raised do
            provider.defaultfor :operatingsystem => [
                :yayness, :rahness,
                Facter.value(:operatingsystem)
            ]
        end

        assert(provider.default?, "Was not considered default")

        # Now make sure that a random setting returns false.
        assert_nothing_raised do
            provider.defaultfor :operatingsystem => :yayness
        end

        assert(! provider.default?, "Was considered default")
    end

    # Make sure that failed commands get their output in the error.
    def test_outputonfailure
        provider = newprovider

        dir = tstdir()
        file = File.join(dir, "mycmd")
        sh = Puppet::Util.binary("sh")
        File.open(file, "w") { |f|
            f.puts %{#!#{sh}
            echo A Failure >&2
            exit 2
            }
        }
        File.chmod(0755, file)

        provider.commands :cmd => file

        inst = provider.new(nil)

        assert_raise(Puppet::ExecutionFailure) do
            inst.cmd "some", "arguments"
        end

        out = nil
        begin
            inst.cmd "some", "arguments"
        rescue Puppet::ExecutionFailure => detail
            out = detail.to_s
        end

        assert(out =~ /A Failure/,
               "Did not receive command output on failure")

        assert(out =~ /Execution of/,
               "Did not receive info wrapper on failure")
    end

    def test_mkmodelmethods
        prov = newprovider
        modeltype = Struct.new(:validproperties, :parameters)
        m = modeltype.new([:prop1, :prop2], [:param1, :param2])
        prov.model = m

        assert_nothing_raised("could not call mkmodelmethods") do
            prov.mkmodelmethods
        end

        obj = prov.new(nil)

        %w{prop1 prop2 param1 param2}.each do |param|
            assert(prov.public_method_defined?(param), "no getter for %s" % param)
            assert(prov.public_method_defined?(param + "="), "no setter for %s" % param)

            assert_equal(:absent, obj.send(param),
                "%s did not default to :absent")
            val = "testing %s" % param
            assert_nothing_raised("Could not call setter for %s" % param) do
                obj.send(param + "=", val)
            end
            assert_equal(val, obj.send(param),
                "did not get correct value for %s" % param)
        end
    end

    # Make sure optional commands get looked up but don't affect suitability.
    def test_optional_commands
        type = Puppet::Type.newtype(:optional_commands) {}

        cleanup { Puppet::Type.rmtype(:optional_commands) }

        # Define a provider with mandatory commands
        required = type.provide(:required) {
            commands :missing => "/no/such/binary/definitely"
        }

        # And another with optional commands
        optional = type.provide(:optional) {
            optional_commands :missing => "/no/such/binary/definitely"
        }

        assert(! required.suitable?, "Provider with missing commands considered suitable")
        assert_nil(required.command(:missing), "Provider returned non-nil from missing command")

        assert(optional.suitable?, "Provider with optional commands considered unsuitable")
        assert_nil(optional.command(:missing), "Provider returned non-nil from missing command")

        assert_raise(Puppet::Error, "Provider did not fail when missing command was called") do
            optional.missing
        end
    end
end

class TestProviderFeatures < Test::Unit::TestCase
	include PuppetTest

    def setup
        super
        @type = Puppet::Type.newtype(:feature_test) do
            newparam(:name) {}
            ensurable
        end
        cleanup { Puppet::Type.rmtype(:feature_test) }

        @features = {:numeric => [:one, :two], :alpha => [:a, :b]}

        @features.each do |name, methods|
            assert_nothing_raised("Could not define features") do
                @type.feature(name, "boo", :methods => methods)
            end
        end
    end

    # Give them the basic run-through.
    def test_method_features
        @providers = {:numbers => @features[:numeric], :letters => @features[:alpha]}
        @providers[:both] = @features[:numeric] + @features[:alpha]
        @providers[:mixed] = [:one, :b]
        @providers[:neither] = [:something, :else]

        @providers.each do |name, methods|
            assert_nothing_raised("Could not create provider %s" % name) do
                @type.provide(name) do
                    methods.each do |name|
                        define_method(name) {}
                    end
                end
            end
        end

        model = @type.create(:name => "foo")
        {:numbers => [:numeric], :letters => [:alpha], :both => [:numeric, :alpha],
            :mixed => [], :neither => []}.each do |name, should|
                should.sort! { |a,b| a.to_s <=> b.to_s }
                provider = @type.provider(name)
                assert(provider, "Could not find provider %s" % name)
                assert_equal(should, provider.features,
                    "Provider %s has incorrect features" % name)

                inst = provider.new(model)
                # Make sure the boolean methods work on both the provider and
                # instance.
                @features.keys.each do |feature|
                    method = feature.to_s + "?"
                    assert(inst.respond_to?(method),
                        "No boolean instance method for %s on %s" %
                        [name, feature])
                    assert(provider.respond_to?(method),
                        "No boolean class method for %s on %s" % [name, feature])

                    if should.include?(feature)
                        assert(provider.feature?(feature),
                            "class missing feature? %s" % feature)
                        assert(inst.feature?(feature),
                            "instance missing feature? %s" % feature)
                        assert(provider.send(method),
                            "class missing feature %s" % feature)
                        assert(inst.send(method),
                            "instance missing feature %s" % feature)
                        assert(inst.satisfies?(feature),
                            "instance.satisfy %s returned false" % feature)
                    else
                        assert(! provider.feature?(feature),
                            "class has feature? %s" % feature)
                        assert(! inst.feature?(feature),
                            "instance has feature? %s" % feature)
                        assert(! provider.send(method),
                            "class has feature %s" % feature)
                        assert(! inst.send(method),
                            "instance has feature %s" % feature)
                        assert(! inst.satisfies?(feature),
                            "instance.satisfy %s returned true" % feature)
                    end
                end

            end

        Puppet[:trace] = true
        Puppet::Type.loadall
        Puppet::Type.eachtype do |type|
            assert(type.respond_to?(:feature),
                "No features method defined for %s" % type.name)
        end
    end

    def test_has_feature
        # Define a new feature that has no methods
        @type.feature(:nomeths, "desc")

        # Define a provider with nothing
        provider = @type.provide(:nothing) {}

        assert(provider.respond_to?(:has_features),
            "Provider did not get 'has_features' method added")

        # One with the numeric methods and nothing else
        @type.provide(:numbers) do
            define_method(:one) {}
            define_method(:two) {}
        end
        
        # Another with the numbers and a declaration
        @type.provide(:both) do
            define_method(:one) {}
            define_method(:two) {}

            has_features :alpha
        end
        
        # And just the declaration
        @type.provide(:letters) do
            has_features :alpha
        end

        # And a provider that declares it has our methodless feature.
        @type.provide(:none) do
            has_features :nomeths
        end

        should = {:nothing => [], :both => [:numeric, :alpha],
            :letters => [:alpha], :numbers => [:numeric],
            :none => [:nomeths]}

        should.each do |name, features|
            provider = @type.provider(name)
            assert(provider, "did not get provider named %s" % name)
            features.sort! { |a,b| a.to_s <=> b.to_s }
            assert_equal(features, provider.features,
                "Got incorrect feature list for %s" % name)
        end
    end

    def test_supports_parameter?
        # Make some parameters for each setting
        @type.newparam(:neither) {}
        @type.newparam(:some, :required_features => :alpha)
        @type.newparam(:both, :required_features => [:alpha, :numeric])

        # and appropriate providers
        nope = @type.provide(:nope) {}
        maybe = @type.provide(:maybe) { has_features(:alpha) }
        yep = @type.provide(:yep) { has_features(:alpha, :numeric) }

        # Now make sure our providers answer correctly.
        [nope, maybe, yep].each do |prov|
            assert(prov.respond_to?(:supports_parameter?), "%s does not respond to :supports_parameter?" % prov.name)
            case prov.name
            when :nope:
                supported = [:neither]
                un = [:some, :both]
            when :maybe:
                supported = [:neither, :some]
                un = [:both]
            when :yep:
                supported = [:neither, :some, :both]
                un = []
            end

            supported.each do |param|
                assert(prov.supports_parameter?(param), "%s was not supported by %s" % [param, prov.name])
            end
            un.each do |param|
                assert(! prov.supports_parameter?(param), "%s was incorrectly supported by %s" % [param, prov.name])
            end
        end
    end
end

# $Id: provider.rb 2444 2007-05-01 03:14:09Z luke $


syntax highlighted by Code2HTML, v. 0.9.1